diff --git a/Makefile b/Makefile index 1dd91ae..46db916 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ build: sudo tar -xvf /home/djonker/data/latest.tar.gz sudo mv wordpress /home/djonker/data/wordpress sudo cp srcs/requirements/wordpress/srcs/wordpress.conf /home/djonker/data/wordpress/wp-config.php + sudo curl -f https://downloads.wordpress.org/plugin/redis-cache.2.4.4.zip --output /home/djonker/data/rediscache.zip + sudo unzip /home/djonker/data/rediscache.zip + sudo mv redis-cache /home/djonker/data/wordpress/wp-content/plugins/redis-cache sudo chown -R 1000:1000 /home/djonker/data/wordpress sudo curl -L https://github.com/vrana/adminer/releases/download/v4.8.1/adminer-4.8.1.php --output /home/djonker/data/adminer/adminer.php diff --git a/redis-cache/LICENSE.md b/redis-cache/LICENSE.md new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/redis-cache/LICENSE.md @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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. But first, please read +. diff --git a/redis-cache/assets/css/admin.css b/redis-cache/assets/css/admin.css new file mode 100644 index 0000000..a5088fe --- /dev/null +++ b/redis-cache/assets/css/admin.css @@ -0,0 +1,196 @@ +#rediscache .content-column { + margin-bottom: 3em; +} + +#rediscache .sidebar-column h6 { + margin: 5px 0; + padding: 10px 0; + font-size: 13px; + line-height: 19px; + border-bottom: 1px solid #ccc; +} + +#rediscache .tab-pane { + display: none; +} + +#rediscache .tab-pane.active { + display: block; +} + +#rediscache .form-table th, +#rediscache .form-table td { + padding-top: 7px; + padding-bottom: 7px; +} + +#rediscache .form-table .success { + color: #008a20; +} + +#rediscache .form-table .warning { + color: #dba617; +} + +#rediscache .form-table .error { + color: #d63638; +} + +#rediscache .form-table td ul { + margin: 0; +} + +#rediscache .form-table .description.is-notice { + max-width: 25rem; + color: #d54e21; +} + +#rediscache .form-table .description.is-notice a { + color: #d54e21; +} + +#rediscache .card ul { + list-style: circle; + padding-left: 25px; +} + +#rediscache .tab-pane-metrics .card { + min-width: auto; + max-width: 100%; + padding: 0; +} + +#rediscache .nav-tab-wrapper .nav-tab-disabled { + cursor: not-allowed; + background-color: #e5e5e5 !important; +} + +#rediscache .tab-pane-diagnostics p { + margin-top: 20px; +} + +#rediscache #diagnostics-pane .card { + width: 100%; + max-width: 100%; +} + +#rediscache #diagnostics-pane pre { + height: 400px; + overflow: scroll; +} + +#rediscache .compatibility { + display: flex; + max-width: 520px; + margin-bottom: 5px; + color: #666; +} + +#rediscache .compatibility + ul { + margin-top: 0; + padding-left: 50px; + list-style: circle; + max-width: 470px; + color: #666; +} + +#rediscache .compatibility .dashicons { + margin-right: 5px; +} + +@media screen and (min-width: 1200px) { + #rediscache .columns { + display: flex; + } + + #rediscache .content-column { + flex-grow: 1; + } + + #rediscache .sidebar-column { + max-width: 400px; + margin-left: 20px; + } +} + +#dashboard_rediscache .inside { + margin: 0; + padding: 0; +} + +#widget-redis-stats ul { + margin: 11px 11px 0; + display: flex; +} + +#widget-redis-stats ul li { + margin-bottom: 0; +} + +#metrics-pane #widget-redis-stats ul { + display: block; + list-style-type: none; + text-align: center; + font-size: 14px; +} + +#metrics-pane #widget-redis-stats ul li { + display: inline; + list-style-type: none; +} + +#widget-redis-stats ul a { + padding: 0 7px; + text-decoration: none; +} + +#widget-redis-stats ul a:hover { + color: #135e96; +} + +#widget-redis-stats ul a.active { + color: #1d2327; +} + +#redis-stats-chart .apexcharts-annotations { + display: none; + opacity: 0.75; +} + +#redis-stats-chart .apexcharts-tooltip, +#redis-stats-chart .apexcharts-tooltip-title { + font-size: 13px; +} + +#redis-stats-chart .apexcharts-tooltip { + display: flex; + flex-direction: row; + width: 100%; + justify-content: center; + border: none; + box-shadow: none; + background: #fff; + margin-top: 1px; +} + +#redis-stats-chart .apexcharts-tooltip-title { + margin-bottom: 0; + padding: 0 15px; + border-bottom: none; + background-color: transparent; +} + +#redis-stats-chart .apexcharts-tooltip-series-group { + display: flex; + padding-bottom: 0; + padding: 0 15px; +} + +#redis-stats-chart .apexcharts-tooltip-y-group { + padding: 0; +} + +#redis-stats-chart .apexcharts-tooltip-text-value { + margin-left: 0; + font-variant-numeric: tabular-nums; +} diff --git a/redis-cache/assets/js/admin.js b/redis-cache/assets/js/admin.js new file mode 100644 index 0000000..6b04242 --- /dev/null +++ b/redis-cache/assets/js/admin.js @@ -0,0 +1,488 @@ +( function ( $, root, undefined ) { + root.rediscache = root.rediscache || {}; + var rediscache = root.rediscache; + + $.extend( rediscache, { + metrics: { + computed: null, + }, + chart: null, + chart_defaults: { + noData: { + text: root.rediscache_metrics + ? rediscache.l10n.no_data + : rediscache.l10n.no_cache, + align: 'center', + verticalAlign: 'middle', + offsetY: -25, + style: { + color: '#72777c', + fontSize: '14px', + fontFamily: 'inherit', + } + }, + stroke: { + width: [2, 2], + curve: 'smooth', + dashArray: [0, 8], + }, + colors: [ + '#0096dd', + '#72777c', + ], + annotations: { + texts: [{ + x: '15%', + y: '30%', + fontSize: '20px', + fontWeight: 600, + fontFamily: 'inherit', + foreColor: '#72777c', + }], + }, + chart: { + type: 'line', + height: $( '#metrics-pane #widget-redis-stats' ).length ? '300px' : '100%', + toolbar: { show: false }, + zoom: { enabled: false }, + animations: { enabled: false }, + }, + dataLabels: { + enabled: false, + }, + legend: { + show: false, + }, + fill: { + opacity: [0.25, 1], + }, + xaxis: { + type: 'datetime', + labels: { + format: 'HH:mm', + datetimeUTC: false, + style: { colors: '#72777c', fontSize: '13px', fontFamily: 'inherit' }, + }, + tooltip: { enabled: false }, + }, + yaxis: { + type: 'numeric', + tickAmount: 4, + min: 0, + labels: { + style: { colors: '#72777c', fontSize: '13px', fontFamily: 'inherit' }, + formatter: function ( value ) { + return Math.round( value ); + }, + }, + }, + tooltip: { + fixed: { + enabled: true, + position: 'bottomLeft', + offsetY: 15, + offsetX: 0, + }, + } + }, + templates: { + tooltip_title: _.template( + '
<%- title %>
' + ), + series_group: _.template( + '
' + + ' ' + + '
' + + '
' + + ' <%- name %>:' + + ' <%- value %>' + + '
' + + '
' + + '
' + ), + series_pro: _.template( + '
' + + ' ' + + '
' + + '
' + + ' <%- name %>' + + '
' + + '
' + + '
' + ), + } + } ); + + // Build the charts by deep extending the chart defaults + $.extend( rediscache, { + charts: { + time: $.extend( true, {}, rediscache.chart_defaults, { + yaxis: { + labels: { + formatter: function ( value ) { + return Math.round( value ) + ' ms'; + }, + }, + }, + tooltip: { + custom: function ({ series, seriesIndex, dataPointIndex, w }) { + return [ + rediscache.templates.tooltip_title({ + title: new Date( w.globals.seriesX[ seriesIndex ][ dataPointIndex ] ) + .toTimeString().slice( 0, 5 ), + }), + rediscache.templates.series_group({ + color: rediscache.chart_defaults.colors[0], + name: w.globals.seriesNames[0], + value: series[0][ dataPointIndex ].toFixed(2) + ' ms', + }), + rediscache.templates.series_pro({ + color: rediscache.chart_defaults.colors[1], + name: rediscache.l10n.pro, + }), + ].join(''); + }, + }, + } ), + bytes: $.extend( true, {}, rediscache.chart_defaults, { + yaxis: { + labels: { + formatter: function ( value ) { + var i = value === 0 ? 0 : Math.floor( Math.log( value ) / Math.log( 1024 ) ); + + return parseFloat( (value / Math.pow( 1024, i ) ).toFixed( i ? 2 : 0 ) ) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i]; + }, + }, + }, + tooltip: { + custom: function ({ series, seriesIndex, dataPointIndex, w }) { + var value = series[0][ dataPointIndex ]; + var i = value === 0 ? 0 : Math.floor( Math.log( value ) / Math.log( 1024 ) ); + var bytes = parseFloat( (value / Math.pow( 1024, i ) ).toFixed( i ? 2 : 0 ) ) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i]; + + return [ + rediscache.templates.tooltip_title({ + title: new Date( w.globals.seriesX[ seriesIndex ][ dataPointIndex ] ).toTimeString().slice( 0, 5 ), + }), + rediscache.templates.series_group({ + color: rediscache.chart_defaults.colors[0], + name: w.globals.seriesNames[0], + value: bytes, + }), + rediscache.templates.series_pro({ + color: rediscache.chart_defaults.colors[1], + name: rediscache.l10n.pro, + }), + ].join(''); + }, + }, + } ), + ratio: $.extend( true, {}, rediscache.chart_defaults, { + yaxis: { + max: 100, + labels: { + formatter: function ( value ) { + return Math.round( value ) + '%'; + }, + }, + }, + tooltip: { + custom: function ({ series, seriesIndex, dataPointIndex, w }) { + return [ + rediscache.templates.tooltip_title({ + title: new Date( w.globals.seriesX[ seriesIndex ][ dataPointIndex ] ) + .toTimeString().slice( 0, 5 ), + }), + rediscache.templates.series_group({ + color: rediscache.chart_defaults.colors[0], + name: w.globals.seriesNames[0], + value: Math.round( series[0][ dataPointIndex ] * 100 ) / 100 + '%', + }), + ].join(''); + }, + }, + } ), + calls: $.extend( true, {}, rediscache.chart_defaults, { + tooltip: { + custom: function ({ series, seriesIndex, dataPointIndex, w }) { + return [ + rediscache.templates.tooltip_title({ + title: new Date( w.globals.seriesX[ seriesIndex ][ dataPointIndex ] ) + .toTimeString().slice( 0, 5 ), + }), + rediscache.templates.series_group({ + color: rediscache.chart_defaults.colors[0], + name: w.globals.seriesNames[0], + value: Math.round( series[0][ dataPointIndex ] ), + }), + rediscache.templates.series_pro({ + color: rediscache.chart_defaults.colors[1], + name: rediscache.l10n.pro, + }), + ].join(''); + }, + }, + } ), + }, + } ); + + var compute_metrics = function ( raw_metrics ) { + var metrics = {}; + + // parse raw metrics in blocks of minutes + for ( var entry in raw_metrics ) { + var values = {}; + var timestamp = raw_metrics[ entry ].timestamp; + var minute = ( timestamp - timestamp % 60 ) * 1000; + + for ( var key in raw_metrics[ entry ] ) { + if ( raw_metrics[ entry ].hasOwnProperty( key ) ) { + values[ key ] = Number( raw_metrics[ entry ][ key ] ); + } + } + + if ( ! metrics[ minute ] ) { + metrics[ minute ] = []; + } + + metrics[ minute ].push( values ); + } + + // calculate median value for each block + for ( var entry in metrics ) { + if ( metrics[ entry ].length === 1 ) { + metrics[ entry ] = metrics[ entry ].shift(); + continue; + } + + var medians = {}; + + for ( var key in metrics[ entry ][0] ) { + medians[ key ] = compute_median( + metrics[ entry ].map( + function ( metric ) { + return metric[ key ]; + } + ) + ); + } + + metrics[ entry ] = medians; + } + + var computed = []; + + for ( var timestamp in metrics ) { + var entry = metrics[ timestamp ]; + + entry.date = Number( timestamp ); + entry.time = entry.time * 1000; + + computed.push( entry ); + } + + computed.sort( + function( a, b ) { + return a.date - b.date; + } + ); + + return computed.length < 2 ? [] : computed; + }; + + var compute_median = function ( numbers ) { + var median = 0; + var numsLen = numbers.length; + + numbers.sort(); + + if ( numsLen % 2 === 0 ) { + median = ( numbers[ numsLen / 2 - 1 ] + numbers[ numsLen / 2 ] ) / 2; + } else { + median = numbers[ ( numsLen - 1 ) / 2 ]; + } + + return median; + }; + + var render_chart = function ( id ) { + if ( rediscache.chart ) { + rediscache.chart.updateOptions( rediscache.charts[ id ] ); + return; + } + + var chart = new ApexCharts( + document.querySelector( '#redis-stats-chart' ), + rediscache.charts[ id ] + ); + + chart.render(); + root.rediscache.chart = chart; + }; + + var setup_charts = function () { + var metrics = {}; + + for ( var type in rediscache.charts ) { + if ( ! rediscache.charts.hasOwnProperty( type ) ) { + continue; + } + + metrics[type] = rediscache.metrics.computed.map( + function ( entry ) { + return [ entry.date, entry[type] ]; + } + ); + + rediscache.charts[type].series = [{ + name: rediscache.l10n[type], + type: 'area', + data: metrics[type], + }]; + } + + if ( ! rediscache.disable_pro ) { + var pro_charts = { + time: function ( entry ) { + return [ entry[0], entry[1] * 0.5 ] + }, + bytes: function ( entry ) { + return [ entry[0], entry[1] * 0.3 ] + }, + calls: function ( entry ) { + return [ entry[0], Math.round( entry[1] / 50 ) + 5 ] + }, + }; + + for ( var type in pro_charts ) { + if ( ! rediscache.charts[type] ) { + continue; + } + + rediscache.charts[type].series.push({ + name: rediscache.l10n.pro, + type: 'line', + data: metrics[type].map( pro_charts[type] ), + }); + } + + } + }; + + // executed on page load + $(function () { + var $tabs = $( '#rediscache .nav-tab-wrapper' ); + var $panes = $( '#rediscache .content-column .tab-content' ); + + $tabs.find( 'a' ).on( + 'click.redis-cache', + function ( event ) { + var toggle = $( this ).data( 'toggle' ); + + $( this ).blur(); + + show_tab( toggle ); + + if ( history.pushState ) { + history.pushState( null, null, '#' + toggle ); + } + + return false; + } + ); + + var firstRender = window.location.hash.indexOf('metrics') === -1; + + var show_tab = function ( name ) { + $tabs.find( '.nav-tab-active' ).removeClass( 'nav-tab-active' ); + $panes.find( '.tab-pane.active' ).removeClass( 'active' ); + + $( '#' + name + '-tab' ).addClass( 'nav-tab-active' ); + $( '#' + name + '-pane' ).addClass( 'active' ); + + if (name === 'metrics' && firstRender) { + firstRender = false; + render_chart( 'time' ); + } + }; + + var show_current_tab = function () { + var tabHash = window.location.hash.replace( '#', '' ); + + if ( tabHash !== '' && $( '#' + tabHash + '-tab' ) ) { + show_tab( tabHash ); + } + }; + + show_current_tab(); + + $( window ).on( 'hashchange', show_current_tab ); + + if ( $( '#widget-redis-stats' ).length ) { + rediscache.metrics.computed = compute_metrics( root.rediscache_metrics ); + + setup_charts(); + render_chart( 'time' ); + } + + $( '#widget-redis-stats ul a[data-chart]' ).on( + 'click.redis-cache', + function ( event ) { + event.preventDefault(); + + $( '#widget-redis-stats .active' ).removeClass( 'active' ); + $( this ).blur().addClass( 'active' ); + + render_chart( + $( event.target ).data( 'chart' ) + ); + } + ); + + $( '.notice.is-dismissible[data-dismissible]' ).on( + 'click.roc-dismiss-notice', + '.notice-dismiss', + function ( event ) { + event.preventDefault(); + + var $parent = $( this ).parent(); + + $.post( ajaxurl, { + notice: $parent.data( 'dismissible' ), + action: 'roc_dismiss_notice', + _ajax_nonce: $parent.data( 'nonce' ), + } ); + } + ); + + if ( $( '#redis-cache-copy-button' ).length ) { + if ( typeof ClipboardJS === 'undefined' ) { + $( '#redis-cache-copy-button' ).remove(); + } else { + var successTimeout; + var clipboard = new ClipboardJS( '#redis-cache-copy-button .copy-button' ); + + clipboard.on( 'success', function( e ) { + var triggerElement = $( e.trigger ), + successElement = $( '.success', triggerElement.closest( 'div' ) ); + + e.clearSelection(); + triggerElement.trigger( 'focus' ); + + clearTimeout( successTimeout ); + successElement.removeClass( 'hidden' ); + + successTimeout = setTimeout( function() { + successElement.addClass( 'hidden' ); + + if ( clipboard.clipboardAction.fakeElem && clipboard.clipboardAction.removeFake ) { + clipboard.clipboardAction.removeFake(); + } + }, 3000 ); + + } ); + } + } + }); + +} ( window[ rediscache.jQuery ], window ) ); diff --git a/redis-cache/assets/js/apexcharts.min.js b/redis-cache/assets/js/apexcharts.min.js new file mode 100644 index 0000000..fccb4d9 --- /dev/null +++ b/redis-cache/assets/js/apexcharts.min.js @@ -0,0 +1,14 @@ +/*! + * ApexCharts v3.31.0 + * (c) 2018-2021 ApexCharts + * Released under the MIT License. + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).ApexCharts=e()}(this,(function(){"use strict";function t(t,e){var i=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),i.push.apply(i,a)}return i}function e(e){for(var i=1;it.length)&&(e=t.length);for(var i=0,a=new Array(e);i>16,o=i>>8&255,n=255&i;return"#"+(16777216+65536*(Math.round((a-r)*s)+r)+256*(Math.round((a-o)*s)+o)+(Math.round((a-n)*s)+n)).toString(16).slice(1)}},{key:"shadeColor",value:function(e,i){return t.isColorHex(i)?this.shadeHexColor(e,i):this.shadeRGBColor(e,i)}}],[{key:"bind",value:function(t,e){return function(){return t.apply(e,arguments)}}},{key:"isObject",value:function(t){return t&&"object"===i(t)&&!Array.isArray(t)&&null!=t}},{key:"is",value:function(t,e){return Object.prototype.toString.call(e)==="[object "+t+"]"}},{key:"listToArray",value:function(t){var e,i=[];for(e=0;ee.length?t:e}))),t.length>e.length?t:e}),0)}},{key:"hexToRgba",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"#999999",e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:.6;"#"!==t.substring(0,1)&&(t="#999999");var i=t.replace("#","");i=i.match(new RegExp("(.{"+i.length/3+"})","g"));for(var a=0;a1&&void 0!==arguments[1]?arguments[1]:"x",i=t.toString().slice();return i=i.replace(/[` ~!@#$%^&*()_|+\-=?;:'",.<>{}[\]\\/]/gi,e)}},{key:"negToZero",value:function(t){return t<0?0:t}},{key:"moveIndexInArray",value:function(t,e,i){if(i>=t.length)for(var a=i-t.length+1;a--;)t.push(void 0);return t.splice(i,0,t.splice(e,1)[0]),t}},{key:"extractNumber",value:function(t){return parseFloat(t.replace(/[^\d.]*/g,""))}},{key:"findAncestor",value:function(t,e){for(;(t=t.parentElement)&&!t.classList.contains(e););return t}},{key:"setELstyles",value:function(t,e){for(var i in e)e.hasOwnProperty(i)&&(t.style.key=e[i])}},{key:"isNumber",value:function(t){return!isNaN(t)&&parseFloat(Number(t))===t&&!isNaN(parseInt(t,10))}},{key:"isFloat",value:function(t){return Number(t)===t&&t%1!=0}},{key:"isSafari",value:function(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}},{key:"isFirefox",value:function(){return navigator.userAgent.toLowerCase().indexOf("firefox")>-1}},{key:"isIE11",value:function(){if(-1!==window.navigator.userAgent.indexOf("MSIE")||window.navigator.appVersion.indexOf("Trident/")>-1)return!0}},{key:"isIE",value:function(){var t=window.navigator.userAgent,e=t.indexOf("MSIE ");if(e>0)return parseInt(t.substring(e+5,t.indexOf(".",e)),10);if(t.indexOf("Trident/")>0){var i=t.indexOf("rv:");return parseInt(t.substring(i+3,t.indexOf(".",i)),10)}var a=t.indexOf("Edge/");return a>0&&parseInt(t.substring(a+5,t.indexOf(".",a)),10)}}]),t}(),f=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.setEasingFunctions()}return r(t,[{key:"setEasingFunctions",value:function(){var t;if(!this.w.globals.easing){switch(this.w.config.chart.animations.easing){case"linear":t="-";break;case"easein":t="<";break;case"easeout":t=">";break;case"easeinout":t="<>";break;case"swing":t=function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1};break;case"bounce":t=function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375};break;case"elastic":t=function(t){return t===!!t?t:Math.pow(2,-10*t)*Math.sin((t-.075)*(2*Math.PI)/.3)+1};break;default:t="<>"}this.w.globals.easing=t}}},{key:"animateLine",value:function(t,e,i,a){t.attr(e).animate(a).attr(i)}},{key:"animateMarker",value:function(t,e,i,a,s,r){e||(e=0),t.attr({r:e,width:e,height:e}).animate(a,s).attr({r:i,width:i.width,height:i.height}).afterAll((function(){r()}))}},{key:"animateCircle",value:function(t,e,i,a,s){t.attr({r:e.r,cx:e.cx,cy:e.cy}).animate(a,s).attr({r:i.r,cx:i.cx,cy:i.cy})}},{key:"animateRect",value:function(t,e,i,a,s){t.attr(e).animate(a).attr(i).afterAll((function(){return s()}))}},{key:"animatePathsGradually",value:function(t){var e=t.el,i=t.realIndex,a=t.j,s=t.fill,r=t.pathFrom,o=t.pathTo,n=t.speed,l=t.delay,h=this.w,c=0;h.config.chart.animations.animateGradually.enabled&&(c=h.config.chart.animations.animateGradually.delay),h.config.chart.animations.dynamicAnimation.enabled&&h.globals.dataChanged&&"bar"!==h.config.chart.type&&(c=0),this.morphSVG(e,i,a,"line"!==h.config.chart.type||h.globals.comboCharts?s:"stroke",r,o,n,l*c)}},{key:"showDelayedElements",value:function(){this.w.globals.delayedElements.forEach((function(t){t.el.classList.remove("apexcharts-element-hidden")}))}},{key:"animationCompleted",value:function(t){var e=this.w;e.globals.animationEnded||(e.globals.animationEnded=!0,this.showDelayedElements(),"function"==typeof e.config.chart.events.animationEnd&&e.config.chart.events.animationEnd(this.ctx,{el:t,w:e}))}},{key:"morphSVG",value:function(t,e,i,a,s,r,o,n){var l=this,h=this.w;s||(s=t.attr("pathFrom")),r||(r=t.attr("pathTo"));var c=function(t){return"radar"===h.config.chart.type&&(o=1),"M 0 ".concat(h.globals.gridHeight)};(!s||s.indexOf("undefined")>-1||s.indexOf("NaN")>-1)&&(s=c()),(!r||r.indexOf("undefined")>-1||r.indexOf("NaN")>-1)&&(r=c()),h.globals.shouldAnimate||(o=1),t.plot(s).animate(1,h.globals.easing,n).plot(s).animate(o,h.globals.easing,n).plot(r).afterAll((function(){p.isNumber(i)?i===h.globals.series[h.globals.maxValsInArrayIndex].length-2&&h.globals.shouldAnimate&&l.animationCompleted(t):"none"!==a&&h.globals.shouldAnimate&&(!h.globals.comboCharts&&e===h.globals.series.length-1||h.globals.comboCharts)&&l.animationCompleted(t),l.showDelayedElements()}))}}]),t}(),x=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"getDefaultFilter",value:function(t,e){var i=this.w;t.unfilter(!0),(new window.SVG.Filter).size("120%","180%","-5%","-40%"),"none"!==i.config.states.normal.filter?this.applyFilter(t,e,i.config.states.normal.filter.type,i.config.states.normal.filter.value):i.config.chart.dropShadow.enabled&&this.dropShadow(t,i.config.chart.dropShadow,e)}},{key:"addNormalFilter",value:function(t,e){var i=this.w;i.config.chart.dropShadow.enabled&&!t.node.classList.contains("apexcharts-marker")&&this.dropShadow(t,i.config.chart.dropShadow,e)}},{key:"addLightenFilter",value:function(t,e,i){var a=this,s=this.w,r=i.intensity;t.unfilter(!0);new window.SVG.Filter;t.filter((function(t){var i=s.config.chart.dropShadow;(i.enabled?a.addShadow(t,e,i):t).componentTransfer({rgb:{type:"linear",slope:1.5,intercept:r}})})),t.filterer.node.setAttribute("filterUnits","userSpaceOnUse"),this._scaleFilterSize(t.filterer.node)}},{key:"addDarkenFilter",value:function(t,e,i){var a=this,s=this.w,r=i.intensity;t.unfilter(!0);new window.SVG.Filter;t.filter((function(t){var i=s.config.chart.dropShadow;(i.enabled?a.addShadow(t,e,i):t).componentTransfer({rgb:{type:"linear",slope:r}})})),t.filterer.node.setAttribute("filterUnits","userSpaceOnUse"),this._scaleFilterSize(t.filterer.node)}},{key:"applyFilter",value:function(t,e,i){var a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:.5;switch(i){case"none":this.addNormalFilter(t,e);break;case"lighten":this.addLightenFilter(t,e,{intensity:a});break;case"darken":this.addDarkenFilter(t,e,{intensity:a})}}},{key:"addShadow",value:function(t,e,i){var a=i.blur,s=i.top,r=i.left,o=i.color,n=i.opacity,l=t.flood(Array.isArray(o)?o[e]:o,n).composite(t.sourceAlpha,"in").offset(r,s).gaussianBlur(a).merge(t.source);return t.blend(t.source,l)}},{key:"dropShadow",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,a=e.top,s=e.left,r=e.blur,o=e.color,n=e.opacity,l=e.noUserSpaceOnUse,h=this.w;return t.unfilter(!0),p.isIE()&&"radialBar"===h.config.chart.type||(o=Array.isArray(o)?o[i]:o,t.filter((function(t){var e=null;e=p.isSafari()||p.isFirefox()||p.isIE()?t.flood(o,n).composite(t.sourceAlpha,"in").offset(s,a).gaussianBlur(r):t.flood(o,n).composite(t.sourceAlpha,"in").offset(s,a).gaussianBlur(r).merge(t.source),t.blend(t.source,e)})),l||t.filterer.node.setAttribute("filterUnits","userSpaceOnUse"),this._scaleFilterSize(t.filterer.node)),t}},{key:"setSelectionFilter",value:function(t,e,i){var a=this.w;if(void 0!==a.globals.selectedDataPoints[e]&&a.globals.selectedDataPoints[e].indexOf(i)>-1){t.node.setAttribute("selected",!0);var s=a.config.states.active.filter;"none"!==s&&this.applyFilter(t,e,s.type,s.value)}}},{key:"_scaleFilterSize",value:function(t){!function(e){for(var i in e)e.hasOwnProperty(i)&&t.setAttribute(i,e[i])}({width:"200%",height:"200%",x:"-50%",y:"-50%"})}}]),t}(),b=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"drawLine",value:function(t,e,i,a){var s=arguments.length>4&&void 0!==arguments[4]?arguments[4]:"#a8a8a8",r=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0,o=arguments.length>6&&void 0!==arguments[6]?arguments[6]:null,n=arguments.length>7&&void 0!==arguments[7]?arguments[7]:"butt",l=this.w,h=l.globals.dom.Paper.line().attr({x1:t,y1:e,x2:i,y2:a,stroke:s,"stroke-dasharray":r,"stroke-width":o,"stroke-linecap":n});return h}},{key:"drawRect",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,s=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0,r=arguments.length>5&&void 0!==arguments[5]?arguments[5]:"#fefefe",o=arguments.length>6&&void 0!==arguments[6]?arguments[6]:1,n=arguments.length>7&&void 0!==arguments[7]?arguments[7]:null,l=arguments.length>8&&void 0!==arguments[8]?arguments[8]:null,h=arguments.length>9&&void 0!==arguments[9]?arguments[9]:0,c=this.w,d=c.globals.dom.Paper.rect();return d.attr({x:t,y:e,width:i>0?i:0,height:a>0?a:0,rx:s,ry:s,opacity:o,"stroke-width":null!==n?n:0,stroke:null!==l?l:"none","stroke-dasharray":h}),d.node.setAttribute("fill",r),d}},{key:"drawPolygon",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"#e1e1e1",i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"none",s=this.w,r=s.globals.dom.Paper.polygon(t).attr({fill:a,stroke:e,"stroke-width":i});return r}},{key:"drawCircle",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,i=this.w;t<0&&(t=0);var a=i.globals.dom.Paper.circle(2*t);return null!==e&&a.attr(e),a}},{key:"drawPath",value:function(t){var e=t.d,i=void 0===e?"":e,a=t.stroke,s=void 0===a?"#a8a8a8":a,r=t.strokeWidth,o=void 0===r?1:r,n=t.fill,l=t.fillOpacity,h=void 0===l?1:l,c=t.strokeOpacity,d=void 0===c?1:c,g=t.classes,u=t.strokeLinecap,p=void 0===u?null:u,f=t.strokeDashArray,x=void 0===f?0:f,b=this.w;return null===p&&(p=b.config.stroke.lineCap),(i.indexOf("undefined")>-1||i.indexOf("NaN")>-1)&&(i="M 0 ".concat(b.globals.gridHeight)),b.globals.dom.Paper.path(i).attr({fill:n,"fill-opacity":h,stroke:s,"stroke-opacity":d,"stroke-linecap":p,"stroke-width":o,"stroke-dasharray":x,class:g})}},{key:"group",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,e=this.w,i=e.globals.dom.Paper.group();return null!==t&&i.attr(t),i}},{key:"move",value:function(t,e){var i=["M",t,e].join(" ");return i}},{key:"line",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,a=null;return null===i?a=["L",t,e].join(" "):"H"===i?a=["H",t].join(" "):"V"===i&&(a=["V",e].join(" ")),a}},{key:"curve",value:function(t,e,i,a,s,r){var o=["C",t,e,i,a,s,r].join(" ");return o}},{key:"quadraticCurve",value:function(t,e,i,a){return["Q",t,e,i,a].join(" ")}},{key:"arc",value:function(t,e,i,a,s,r,o){var n=arguments.length>7&&void 0!==arguments[7]&&arguments[7],l="A";n&&(l="a");var h=[l,t,e,i,a,s,r,o].join(" ");return h}},{key:"renderPaths",value:function(t){var i,a=t.j,s=t.realIndex,r=t.pathFrom,o=t.pathTo,n=t.stroke,l=t.strokeWidth,h=t.strokeLinecap,c=t.fill,d=t.animationDelay,g=t.initialSpeed,u=t.dataChangeSpeed,p=t.className,b=t.shouldClipToGrid,v=void 0===b||b,m=t.bindEventsOnPaths,y=void 0===m||m,w=t.drawShadow,k=void 0===w||w,A=this.w,S=new x(this.ctx),C=new f(this.ctx),L=this.w.config.chart.animations.enabled,P=L&&this.w.config.chart.animations.dynamicAnimation.enabled,T=!!(L&&!A.globals.resized||P&&A.globals.dataChanged&&A.globals.shouldAnimate);T?i=r:(i=o,A.globals.animationEnded=!0);var M=A.config.stroke.dashArray,I=0;I=Array.isArray(M)?M[s]:A.config.stroke.dashArray;var z=this.drawPath({d:i,stroke:n,strokeWidth:l,fill:c,fillOpacity:1,classes:p,strokeLinecap:h,strokeDashArray:I});if(z.attr("index",s),v&&z.attr({"clip-path":"url(#gridRectMask".concat(A.globals.cuid,")")}),"none"!==A.config.states.normal.filter.type)S.getDefaultFilter(z,s);else if(A.config.chart.dropShadow.enabled&&k&&(!A.config.chart.dropShadow.enabledOnSeries||A.config.chart.dropShadow.enabledOnSeries&&-1!==A.config.chart.dropShadow.enabledOnSeries.indexOf(s))){var X=A.config.chart.dropShadow;S.dropShadow(z,X,s)}y&&(z.node.addEventListener("mouseenter",this.pathMouseEnter.bind(this,z)),z.node.addEventListener("mouseleave",this.pathMouseLeave.bind(this,z)),z.node.addEventListener("mousedown",this.pathMouseDown.bind(this,z))),z.attr({pathTo:o,pathFrom:r});var E={el:z,j:a,realIndex:s,pathFrom:r,pathTo:o,fill:c,strokeWidth:l,delay:d};return!L||A.globals.resized||A.globals.dataChanged?!A.globals.resized&&A.globals.dataChanged||C.showDelayedElements():C.animatePathsGradually(e(e({},E),{},{speed:g})),A.globals.dataChanged&&P&&T&&C.animatePathsGradually(e(e({},E),{},{speed:u})),z}},{key:"drawPattern",value:function(t,e,i){var a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"#a8a8a8",s=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0,r=this.w,o=r.globals.dom.Paper.pattern(e,i,(function(r){"horizontalLines"===t?r.line(0,0,i,0).stroke({color:a,width:s+1}):"verticalLines"===t?r.line(0,0,0,e).stroke({color:a,width:s+1}):"slantedLines"===t?r.line(0,0,e,i).stroke({color:a,width:s}):"squares"===t?r.rect(e,i).fill("none").stroke({color:a,width:s}):"circles"===t&&r.circle(e).fill("none").stroke({color:a,width:s})}));return o}},{key:"drawGradient",value:function(t,e,i,a,s){var r,o=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null,n=arguments.length>6&&void 0!==arguments[6]?arguments[6]:null,l=arguments.length>7&&void 0!==arguments[7]?arguments[7]:null,h=arguments.length>8&&void 0!==arguments[8]?arguments[8]:0,c=this.w;e.length<9&&0===e.indexOf("#")&&(e=p.hexToRgba(e,a)),i.length<9&&0===i.indexOf("#")&&(i=p.hexToRgba(i,s));var d=0,g=1,u=1,f=null;null!==n&&(d=void 0!==n[0]?n[0]/100:0,g=void 0!==n[1]?n[1]/100:1,u=void 0!==n[2]?n[2]/100:1,f=void 0!==n[3]?n[3]/100:null);var x=!("donut"!==c.config.chart.type&&"pie"!==c.config.chart.type&&"polarArea"!==c.config.chart.type&&"bubble"!==c.config.chart.type);if(r=null===l||0===l.length?c.globals.dom.Paper.gradient(x?"radial":"linear",(function(t){t.at(d,e,a),t.at(g,i,s),t.at(u,i,s),null!==f&&t.at(f,e,a)})):c.globals.dom.Paper.gradient(x?"radial":"linear",(function(t){(Array.isArray(l[h])?l[h]:l).forEach((function(e){t.at(e.offset/100,e.color,e.opacity)}))})),x){var b=c.globals.gridWidth/2,v=c.globals.gridHeight/2;"bubble"!==c.config.chart.type?r.attr({gradientUnits:"userSpaceOnUse",cx:b,cy:v,r:o}):r.attr({cx:.5,cy:.5,r:.8,fx:.2,fy:.2})}else"vertical"===t?r.from(0,0).to(0,1):"diagonal"===t?r.from(0,0).to(1,1):"horizontal"===t?r.from(0,1).to(1,1):"diagonal2"===t&&r.from(1,0).to(0,1);return r}},{key:"drawText",value:function(t){var e,i=t.x,a=t.y,s=t.text,r=t.textAnchor,o=t.fontSize,n=t.fontFamily,l=t.fontWeight,h=t.foreColor,c=t.opacity,d=t.cssClass,g=void 0===d?"":d,u=t.isPlainText,p=void 0===u||u,f=this.w;return void 0===s&&(s=""),r||(r="start"),h&&h.length||(h=f.config.chart.foreColor),n=n||f.config.chart.fontFamily,l=l||"regular",(e=Array.isArray(s)?f.globals.dom.Paper.text((function(t){for(var e=0;e-1){var n=i.globals.selectedDataPoints[s].indexOf(r);i.globals.selectedDataPoints[s].splice(n,1)}}else{if(!i.config.states.active.allowMultipleDataPointsSelection&&i.globals.selectedDataPoints.length>0){i.globals.selectedDataPoints=[];var l=i.globals.dom.Paper.select(".apexcharts-series path").members,h=i.globals.dom.Paper.select(".apexcharts-series circle, .apexcharts-series rect").members,c=function(t){Array.prototype.forEach.call(t,(function(t){t.node.setAttribute("selected","false"),a.getDefaultFilter(t,s)}))};c(l),c(h)}t.node.setAttribute("selected","true"),o="true",void 0===i.globals.selectedDataPoints[s]&&(i.globals.selectedDataPoints[s]=[]),i.globals.selectedDataPoints[s].push(r)}if("true"===o){var d=i.config.states.active.filter;"none"!==d&&a.applyFilter(t,s,d.type,d.value)}else"none"!==i.config.states.active.filter.type&&a.getDefaultFilter(t,s);"function"==typeof i.config.chart.events.dataPointSelection&&i.config.chart.events.dataPointSelection(e,this.ctx,{selectedDataPoints:i.globals.selectedDataPoints,seriesIndex:s,dataPointIndex:r,w:i}),e&&this.ctx.events.fireEvent("dataPointSelection",[e,this.ctx,{selectedDataPoints:i.globals.selectedDataPoints,seriesIndex:s,dataPointIndex:r,w:i}])}},{key:"rotateAroundCenter",value:function(t){var e={};return t&&"function"==typeof t.getBBox&&(e=t.getBBox()),{x:e.x+e.width/2,y:e.y+e.height/2}}},{key:"getTextRects",value:function(t,e,i,a){var s=!(arguments.length>4&&void 0!==arguments[4])||arguments[4],r=this.w,o=this.drawText({x:-200,y:-200,text:t,textAnchor:"start",fontSize:e,fontFamily:i,foreColor:"#fff",opacity:0});a&&o.attr("transform",a),r.globals.dom.Paper.add(o);var n=o.bbox();return s||(n=o.node.getBoundingClientRect()),o.remove(),{width:n.width,height:n.height}}},{key:"placeTextWithEllipsis",value:function(t,e,i){if("function"==typeof t.getComputedTextLength&&(t.textContent=e,e.length>0&&t.getComputedTextLength()>=i/1.1)){for(var a=e.length-3;a>0;a-=3)if(t.getSubStringLength(0,a)<=i/1.1)return void(t.textContent=e.substring(0,a)+"...");t.textContent="."}}}],[{key:"setAttrs",value:function(t,e){for(var i in e)e.hasOwnProperty(i)&&t.setAttribute(i,e[i])}}]),t}(),v=function(){function t(e){a(this,t),this.w=e.w,this.annoCtx=e}return r(t,[{key:"setOrientations",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,i=this.w;if("vertical"===t.label.orientation){var a=null!==e?e:0,s=i.globals.dom.baseEl.querySelector(".apexcharts-xaxis-annotations .apexcharts-xaxis-annotation-label[rel='".concat(a,"']"));if(null!==s){var r=s.getBoundingClientRect();s.setAttribute("x",parseFloat(s.getAttribute("x"))-r.height+4),"top"===t.label.position?s.setAttribute("y",parseFloat(s.getAttribute("y"))+r.width):s.setAttribute("y",parseFloat(s.getAttribute("y"))-r.width);var o=this.annoCtx.graphics.rotateAroundCenter(s),n=o.x,l=o.y;s.setAttribute("transform","rotate(-90 ".concat(n," ").concat(l,")"))}}}},{key:"addBackgroundToAnno",value:function(t,e){var i=this.w;if(!t||void 0===e.label.text||void 0!==e.label.text&&!String(e.label.text).trim())return null;var a=i.globals.dom.baseEl.querySelector(".apexcharts-grid").getBoundingClientRect(),s=t.getBoundingClientRect(),r=e.label.style.padding.left,o=e.label.style.padding.right,n=e.label.style.padding.top,l=e.label.style.padding.bottom;"vertical"===e.label.orientation&&(n=e.label.style.padding.left,l=e.label.style.padding.right,r=e.label.style.padding.top,o=e.label.style.padding.bottom);var h=s.left-a.left-r,c=s.top-a.top-n,d=this.annoCtx.graphics.drawRect(h-i.globals.barPadForNumericAxis,c,s.width+r+o,s.height+n+l,e.label.borderRadius,e.label.style.background,1,e.label.borderWidth,e.label.borderColor,0);return e.id&&d.node.classList.add(p.escapeString(e.id)),d}},{key:"annotationsBackground",value:function(){var t=this,e=this.w,i=function(i,a,s){var r=e.globals.dom.baseEl.querySelector(".apexcharts-".concat(s,"-annotations .apexcharts-").concat(s,"-annotation-label[rel='").concat(a,"']"));if(r){var o=r.parentNode,n=t.addBackgroundToAnno(r,i);n&&(o.insertBefore(n.node,r),i.label.mouseEnter&&n.node.addEventListener("mouseenter",i.label.mouseEnter.bind(t,i)),i.label.mouseLeave&&n.node.addEventListener("mouseleave",i.label.mouseLeave.bind(t,i)))}};e.config.annotations.xaxis.map((function(t,e){i(t,e,"xaxis")})),e.config.annotations.yaxis.map((function(t,e){i(t,e,"yaxis")})),e.config.annotations.points.map((function(t,e){i(t,e,"point")}))}},{key:"getStringX",value:function(t){var e=this.w,i=t;e.config.xaxis.convertedCatToNumeric&&e.globals.categoryLabels.length&&(t=e.globals.categoryLabels.indexOf(t)+1);var a=e.globals.labels.indexOf(t),s=e.globals.dom.baseEl.querySelector(".apexcharts-xaxis-texts-g text:nth-child("+(a+1)+")");return s&&(i=parseFloat(s.getAttribute("x"))),i}}]),t}(),m=function(){function t(e){a(this,t),this.w=e.w,this.annoCtx=e,this.invertAxis=this.annoCtx.invertAxis}return r(t,[{key:"addXaxisAnnotation",value:function(t,e,i){var a=this.w,s=this.invertAxis?a.globals.minY:a.globals.minX,r=this.invertAxis?a.globals.maxY:a.globals.maxX,o=this.invertAxis?a.globals.yRange[0]:a.globals.xRange,n=(t.x-s)/(o/a.globals.gridWidth);this.annoCtx.inversedReversedAxis&&(n=(r-t.x)/(o/a.globals.gridWidth));var l=t.label.text;"category"!==a.config.xaxis.type&&!a.config.xaxis.convertedCatToNumeric||this.invertAxis||a.globals.dataFormatXNumeric||(n=this.annoCtx.helpers.getStringX(t.x));var h=t.strokeDashArray;if(p.isNumber(n)){if(null===t.x2||void 0===t.x2){var c=this.annoCtx.graphics.drawLine(n+t.offsetX,0+t.offsetY,n+t.offsetX,a.globals.gridHeight+t.offsetY,t.borderColor,h,t.borderWidth);e.appendChild(c.node),t.id&&c.node.classList.add(t.id)}else{var d=(t.x2-s)/(o/a.globals.gridWidth);if(this.annoCtx.inversedReversedAxis&&(d=(r-t.x2)/(o/a.globals.gridWidth)),"category"!==a.config.xaxis.type&&!a.config.xaxis.convertedCatToNumeric||this.invertAxis||a.globals.dataFormatXNumeric||(d=this.annoCtx.helpers.getStringX(t.x2)),d0&&void 0!==arguments[0]?arguments[0]:null;return null===t?this.w.config.series.reduce((function(t,e){return t+e}),0):this.w.globals.series[t].reduce((function(t,e){return t+e}),0)}},{key:"isSeriesNull",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;return 0===(null===t?this.w.config.series.filter((function(t){return null!==t})):this.w.config.series[t].data.filter((function(t){return null!==t}))).length}},{key:"seriesHaveSameValues",value:function(t){return this.w.globals.series[t].every((function(t,e,i){return t===i[0]}))}},{key:"getCategoryLabels",value:function(t){var e=this.w,i=t.slice();return e.config.xaxis.convertedCatToNumeric&&(i=t.map((function(t,i){return e.config.xaxis.labels.formatter(t-e.globals.minX+1)}))),i}},{key:"getLargestSeries",value:function(){var t=this.w;t.globals.maxValsInArrayIndex=t.globals.series.map((function(t){return t.length})).indexOf(Math.max.apply(Math,t.globals.series.map((function(t){return t.length}))))}},{key:"getLargestMarkerSize",value:function(){var t=this.w,e=0;return t.globals.markers.size.forEach((function(t){e=Math.max(e,t)})),t.globals.markers.largestSize=e,e}},{key:"getSeriesTotals",value:function(){var t=this.w;t.globals.seriesTotals=t.globals.series.map((function(t,e){var i=0;if(Array.isArray(t))for(var a=0;at&&i.globals.seriesX[s][o]0&&(e=!0),{comboBarCount:i,comboCharts:e}}},{key:"extendArrayProps",value:function(t,e,i){return e.yaxis&&(e=t.extendYAxis(e,i)),e.annotations&&(e.annotations.yaxis&&(e=t.extendYAxisAnnotations(e)),e.annotations.xaxis&&(e=t.extendXAxisAnnotations(e)),e.annotations.points&&(e=t.extendPointAnnotations(e))),e}}]),t}(),w=function(){function t(e){a(this,t),this.w=e.w,this.annoCtx=e}return r(t,[{key:"addYaxisAnnotation",value:function(t,e,i){var a,s=this.w,r=t.strokeDashArray,o=this._getY1Y2("y1",t),n=t.label.text;if(null===t.y2||void 0===t.y2){var l=this.annoCtx.graphics.drawLine(0+t.offsetX,o+t.offsetY,this._getYAxisAnnotationWidth(t),o+t.offsetY,t.borderColor,r,t.borderWidth);e.appendChild(l.node),t.id&&l.node.classList.add(t.id)}else{if((a=this._getY1Y2("y2",t))>o){var h=o;o=a,a=h}var c=this.annoCtx.graphics.drawRect(0+t.offsetX,a+t.offsetY,this._getYAxisAnnotationWidth(t),o-a,0,t.fillColor,t.opacity,1,t.borderColor,r);c.node.classList.add("apexcharts-annotation-rect"),c.attr("clip-path","url(#gridRectMask".concat(s.globals.cuid,")")),e.appendChild(c.node),t.id&&c.node.classList.add(t.id)}var d="right"===t.label.position?s.globals.gridWidth:0,g=this.annoCtx.graphics.drawText({x:d+t.label.offsetX,y:(null!=a?a:o)+t.label.offsetY-3,text:n,textAnchor:t.label.textAnchor,fontSize:t.label.style.fontSize,fontFamily:t.label.style.fontFamily,fontWeight:t.label.style.fontWeight,foreColor:t.label.style.color,cssClass:"apexcharts-yaxis-annotation-label ".concat(t.label.style.cssClass," ").concat(t.id?t.id:"")});g.attr({rel:i}),e.appendChild(g.node)}},{key:"_getY1Y2",value:function(t,e){var i,a="y1"===t?e.y:e.y2,s=this.w;if(this.annoCtx.invertAxis){var r=s.globals.labels.indexOf(a);s.config.xaxis.convertedCatToNumeric&&(r=s.globals.categoryLabels.indexOf(a));var o=s.globals.dom.baseEl.querySelector(".apexcharts-yaxis-texts-g text:nth-child("+(r+1)+")");o&&(i=parseFloat(o.getAttribute("y")))}else{var n;if(s.config.yaxis[e.yAxisIndex].logarithmic)n=(a=new y(this.annoCtx.ctx).getLogVal(a,e.yAxisIndex))/s.globals.yLogRatio[e.yAxisIndex];else n=(a-s.globals.minYArr[e.yAxisIndex])/(s.globals.yRange[e.yAxisIndex]/s.globals.gridHeight);i=s.globals.gridHeight-n,s.config.yaxis[e.yAxisIndex]&&s.config.yaxis[e.yAxisIndex].reversed&&(i=n)}return i}},{key:"_getYAxisAnnotationWidth",value:function(t){var e=this.w;e.globals.gridWidth;return(t.width.indexOf("%")>-1?e.globals.gridWidth*parseInt(t.width,10)/100:parseInt(t.width,10))+t.offsetX}},{key:"drawYAxisAnnotations",value:function(){var t=this,e=this.w,i=this.annoCtx.graphics.group({class:"apexcharts-yaxis-annotations"});return e.config.annotations.yaxis.map((function(e,a){t.addYaxisAnnotation(e,i.node,a)})),i}}]),t}(),k=function(){function t(e){a(this,t),this.w=e.w,this.annoCtx=e}return r(t,[{key:"addPointAnnotation",value:function(t,e,i){var a=this.w,s=0,r=0,o=0;this.annoCtx.invertAxis&&console.warn("Point annotation is not supported in horizontal bar charts.");var n=parseFloat(t.y);if("string"==typeof t.x||"category"===a.config.xaxis.type||a.config.xaxis.convertedCatToNumeric){var l=a.globals.labels.indexOf(t.x);a.config.xaxis.convertedCatToNumeric&&(l=a.globals.categoryLabels.indexOf(t.x)),s=this.annoCtx.helpers.getStringX(t.x),null===t.y&&(n=a.globals.series[t.seriesIndex][l])}else s=(t.x-a.globals.minX)/(a.globals.xRange/a.globals.gridWidth);for(var h,c=[],d=0,g=0;g<=t.seriesIndex;g++){var u=a.config.yaxis[g].seriesName;if(u)for(var f=g+1;f<=t.seriesIndex;f++)a.config.yaxis[f].seriesName===u&&-1===c.indexOf(u)&&(d++,c.push(u))}if(a.config.yaxis[t.yAxisIndex].logarithmic){h=(n=new y(this.annoCtx.ctx).getLogVal(n,t.yAxisIndex))/a.globals.yLogRatio[t.yAxisIndex]}else{var x=t.yAxisIndex+d;h=(n-a.globals.minYArr[x])/(a.globals.yRange[x]/a.globals.gridHeight)}if(r=a.globals.gridHeight-h-parseFloat(t.label.style.fontSize)-t.marker.size,o=a.globals.gridHeight-h,a.config.yaxis[t.yAxisIndex]&&a.config.yaxis[t.yAxisIndex].reversed&&(r=h+parseFloat(t.label.style.fontSize)+t.marker.size,o=h),p.isNumber(s)){var b={pSize:t.marker.size,pointStrokeWidth:t.marker.strokeWidth,pointFillColor:t.marker.fillColor,pointStrokeColor:t.marker.strokeColor,shape:t.marker.shape,pRadius:t.marker.radius,class:"apexcharts-point-annotation-marker ".concat(t.marker.cssClass," ").concat(t.id?t.id:"")},v=this.annoCtx.graphics.drawMarker(s+t.marker.offsetX,o+t.marker.offsetY,b);e.appendChild(v.node);var m=t.label.text?t.label.text:"",w=this.annoCtx.graphics.drawText({x:s+t.label.offsetX,y:r+t.label.offsetY,text:m,textAnchor:t.label.textAnchor,fontSize:t.label.style.fontSize,fontFamily:t.label.style.fontFamily,fontWeight:t.label.style.fontWeight,foreColor:t.label.style.color,cssClass:"apexcharts-point-annotation-label ".concat(t.label.style.cssClass," ").concat(t.id?t.id:"")});if(w.attr({rel:i}),e.appendChild(w.node),t.customSVG.SVG){var k=this.annoCtx.graphics.group({class:"apexcharts-point-annotations-custom-svg "+t.customSVG.cssClass});k.attr({transform:"translate(".concat(s+t.customSVG.offsetX,", ").concat(r+t.customSVG.offsetY,")")}),k.node.innerHTML=t.customSVG.SVG,e.appendChild(k.node)}if(t.image.path){var A=t.image.width?t.image.width:20,S=t.image.height?t.image.height:20;v=this.annoCtx.addImage({x:s+t.image.offsetX-A/2,y:r+t.image.offsetY-S/2,width:A,height:S,path:t.image.path,appendTo:".apexcharts-point-annotations"})}t.mouseEnter&&v.node.addEventListener("mouseenter",t.mouseEnter.bind(this,t)),t.mouseLeave&&v.node.addEventListener("mouseleave",t.mouseLeave.bind(this,t))}}},{key:"drawPointAnnotations",value:function(){var t=this,e=this.w,i=this.annoCtx.graphics.group({class:"apexcharts-point-annotations"});return e.config.annotations.points.map((function(e,a){t.addPointAnnotation(e,i.node,a)})),i}}]),t}();var A={name:"en",options:{months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],toolbar:{exportToSVG:"Download SVG",exportToPNG:"Download PNG",exportToCSV:"Download CSV",menu:"Menu",selection:"Selection",selectionZoom:"Selection Zoom",zoomIn:"Zoom In",zoomOut:"Zoom Out",pan:"Panning",reset:"Reset Zoom"}}},S=function(){function t(){a(this,t),this.yAxis={show:!0,showAlways:!1,showForNullSeries:!0,seriesName:void 0,opposite:!1,reversed:!1,logarithmic:!1,logBase:10,tickAmount:void 0,forceNiceScale:!1,max:void 0,min:void 0,floating:!1,decimalsInFloat:void 0,labels:{show:!0,minWidth:0,maxWidth:160,offsetX:0,offsetY:0,align:void 0,rotate:0,padding:20,style:{colors:[],fontSize:"11px",fontWeight:400,fontFamily:void 0,cssClass:""},formatter:void 0},axisBorder:{show:!1,color:"#e0e0e0",width:1,offsetX:0,offsetY:0},axisTicks:{show:!1,color:"#e0e0e0",width:6,offsetX:0,offsetY:0},title:{text:void 0,rotate:-90,offsetY:0,offsetX:0,style:{color:void 0,fontSize:"11px",fontWeight:900,fontFamily:void 0,cssClass:""}},tooltip:{enabled:!1,offsetX:0},crosshairs:{show:!0,position:"front",stroke:{color:"#b6b6b6",width:1,dashArray:0}}},this.pointAnnotation={id:void 0,x:0,y:null,yAxisIndex:0,seriesIndex:0,mouseEnter:void 0,mouseLeave:void 0,marker:{size:4,fillColor:"#fff",strokeWidth:2,strokeColor:"#333",shape:"circle",offsetX:0,offsetY:0,radius:2,cssClass:""},label:{borderColor:"#c2c2c2",borderWidth:1,borderRadius:2,text:void 0,textAnchor:"middle",offsetX:0,offsetY:0,mouseEnter:void 0,mouseLeave:void 0,style:{background:"#fff",color:void 0,fontSize:"11px",fontFamily:void 0,fontWeight:400,cssClass:"",padding:{left:5,right:5,top:2,bottom:2}}},customSVG:{SVG:void 0,cssClass:void 0,offsetX:0,offsetY:0},image:{path:void 0,width:20,height:20,offsetX:0,offsetY:0}},this.yAxisAnnotation={id:void 0,y:0,y2:null,strokeDashArray:1,fillColor:"#c2c2c2",borderColor:"#c2c2c2",borderWidth:1,opacity:.3,offsetX:0,offsetY:0,width:"100%",yAxisIndex:0,label:{borderColor:"#c2c2c2",borderWidth:1,borderRadius:2,text:void 0,textAnchor:"end",position:"right",offsetX:0,offsetY:-3,mouseEnter:void 0,mouseLeave:void 0,style:{background:"#fff",color:void 0,fontSize:"11px",fontFamily:void 0,fontWeight:400,cssClass:"",padding:{left:5,right:5,top:2,bottom:2}}}},this.xAxisAnnotation={id:void 0,x:0,x2:null,strokeDashArray:1,fillColor:"#c2c2c2",borderColor:"#c2c2c2",borderWidth:1,opacity:.3,offsetX:0,offsetY:0,label:{borderColor:"#c2c2c2",borderWidth:1,borderRadius:2,text:void 0,textAnchor:"middle",orientation:"vertical",position:"top",offsetX:0,offsetY:0,mouseEnter:void 0,mouseLeave:void 0,style:{background:"#fff",color:void 0,fontSize:"11px",fontFamily:void 0,fontWeight:400,cssClass:"",padding:{left:5,right:5,top:2,bottom:2}}}},this.text={x:0,y:0,text:"",textAnchor:"start",foreColor:void 0,fontSize:"13px",fontFamily:void 0,fontWeight:400,appendTo:".apexcharts-annotations",backgroundColor:"transparent",borderColor:"#c2c2c2",borderRadius:0,borderWidth:0,paddingLeft:4,paddingRight:4,paddingTop:2,paddingBottom:2}}return r(t,[{key:"init",value:function(){return{annotations:{position:"front",yaxis:[this.yAxisAnnotation],xaxis:[this.xAxisAnnotation],points:[this.pointAnnotation],texts:[],images:[],shapes:[]},chart:{animations:{enabled:!0,easing:"easeinout",speed:800,animateGradually:{delay:150,enabled:!0},dynamicAnimation:{enabled:!0,speed:350}},background:"transparent",locales:[A],defaultLocale:"en",dropShadow:{enabled:!1,enabledOnSeries:void 0,top:2,left:2,blur:4,color:"#000",opacity:.35},events:{animationEnd:void 0,beforeMount:void 0,mounted:void 0,updated:void 0,click:void 0,mouseMove:void 0,mouseLeave:void 0,legendClick:void 0,markerClick:void 0,selection:void 0,dataPointSelection:void 0,dataPointMouseEnter:void 0,dataPointMouseLeave:void 0,beforeZoom:void 0,beforeResetZoom:void 0,zoomed:void 0,scrolled:void 0,brushScrolled:void 0},foreColor:"#373d3f",fontFamily:"Helvetica, Arial, sans-serif",height:"auto",parentHeightOffset:15,redrawOnParentResize:!0,redrawOnWindowResize:!0,id:void 0,group:void 0,offsetX:0,offsetY:0,selection:{enabled:!1,type:"x",fill:{color:"#24292e",opacity:.1},stroke:{width:1,color:"#24292e",opacity:.4,dashArray:3},xaxis:{min:void 0,max:void 0},yaxis:{min:void 0,max:void 0}},sparkline:{enabled:!1},brush:{enabled:!1,autoScaleYaxis:!0,target:void 0},stacked:!1,stackType:"normal",toolbar:{show:!0,offsetX:0,offsetY:0,tools:{download:!0,selection:!0,zoom:!0,zoomin:!0,zoomout:!0,pan:!0,reset:!0,customIcons:[]},export:{csv:{filename:void 0,columnDelimiter:",",headerCategory:"category",headerValue:"value",dateFormatter:function(t){return new Date(t).toDateString()}},png:{filename:void 0},svg:{filename:void 0}},autoSelected:"zoom"},type:"line",width:"100%",zoom:{enabled:!0,type:"x",autoScaleYaxis:!1,zoomedArea:{fill:{color:"#90CAF9",opacity:.4},stroke:{color:"#0D47A1",opacity:.4,width:1}}}},plotOptions:{area:{fillTo:"origin"},bar:{horizontal:!1,columnWidth:"70%",barHeight:"70%",distributed:!1,borderRadius:0,rangeBarOverlap:!0,rangeBarGroupRows:!1,colors:{ranges:[],backgroundBarColors:[],backgroundBarOpacity:1,backgroundBarRadius:0},dataLabels:{position:"top",maxItems:100,hideOverflowingLabels:!0,orientation:"horizontal"}},bubble:{minBubbleRadius:void 0,maxBubbleRadius:void 0},candlestick:{colors:{upward:"#00B746",downward:"#EF403C"},wick:{useFillColor:!0}},boxPlot:{colors:{upper:"#00E396",lower:"#008FFB"}},heatmap:{radius:2,enableShades:!0,shadeIntensity:.5,reverseNegativeShade:!1,distributed:!1,useFillColorAsStroke:!1,colorScale:{inverse:!1,ranges:[],min:void 0,max:void 0}},treemap:{enableShades:!0,shadeIntensity:.5,distributed:!1,reverseNegativeShade:!1,useFillColorAsStroke:!1,colorScale:{inverse:!1,ranges:[],min:void 0,max:void 0}},radialBar:{inverseOrder:!1,startAngle:0,endAngle:360,offsetX:0,offsetY:0,hollow:{margin:5,size:"50%",background:"transparent",image:void 0,imageWidth:150,imageHeight:150,imageOffsetX:0,imageOffsetY:0,imageClipped:!0,position:"front",dropShadow:{enabled:!1,top:0,left:0,blur:3,color:"#000",opacity:.5}},track:{show:!0,startAngle:void 0,endAngle:void 0,background:"#f2f2f2",strokeWidth:"97%",opacity:1,margin:5,dropShadow:{enabled:!1,top:0,left:0,blur:3,color:"#000",opacity:.5}},dataLabels:{show:!0,name:{show:!0,fontSize:"16px",fontFamily:void 0,fontWeight:600,color:void 0,offsetY:0,formatter:function(t){return t}},value:{show:!0,fontSize:"14px",fontFamily:void 0,fontWeight:400,color:void 0,offsetY:16,formatter:function(t){return t+"%"}},total:{show:!1,label:"Total",fontSize:"16px",fontWeight:600,fontFamily:void 0,color:void 0,formatter:function(t){return t.globals.seriesTotals.reduce((function(t,e){return t+e}),0)/t.globals.series.length+"%"}}}},pie:{customScale:1,offsetX:0,offsetY:0,startAngle:0,endAngle:360,expandOnClick:!0,dataLabels:{offset:0,minAngleToShowLabel:10},donut:{size:"65%",background:"transparent",labels:{show:!1,name:{show:!0,fontSize:"16px",fontFamily:void 0,fontWeight:600,color:void 0,offsetY:-10,formatter:function(t){return t}},value:{show:!0,fontSize:"20px",fontFamily:void 0,fontWeight:400,color:void 0,offsetY:10,formatter:function(t){return t}},total:{show:!1,showAlways:!1,label:"Total",fontSize:"16px",fontWeight:400,fontFamily:void 0,color:void 0,formatter:function(t){return t.globals.seriesTotals.reduce((function(t,e){return t+e}),0)}}}}},polarArea:{rings:{strokeWidth:1,strokeColor:"#e8e8e8"},spokes:{strokeWidth:1,connectorColors:"#e8e8e8"}},radar:{size:void 0,offsetX:0,offsetY:0,polygons:{strokeWidth:1,strokeColors:"#e8e8e8",connectorColors:"#e8e8e8",fill:{colors:void 0}}}},colors:void 0,dataLabels:{enabled:!0,enabledOnSeries:void 0,formatter:function(t){return null!==t?t:""},textAnchor:"middle",distributed:!1,offsetX:0,offsetY:0,style:{fontSize:"12px",fontFamily:void 0,fontWeight:600,colors:void 0},background:{enabled:!0,foreColor:"#fff",borderRadius:2,padding:4,opacity:.9,borderWidth:1,borderColor:"#fff",dropShadow:{enabled:!1,top:1,left:1,blur:1,color:"#000",opacity:.45}},dropShadow:{enabled:!1,top:1,left:1,blur:1,color:"#000",opacity:.45}},fill:{type:"solid",colors:void 0,opacity:.85,gradient:{shade:"dark",type:"horizontal",shadeIntensity:.5,gradientToColors:void 0,inverseColors:!0,opacityFrom:1,opacityTo:1,stops:[0,50,100],colorStops:[]},image:{src:[],width:void 0,height:void 0},pattern:{style:"squares",width:6,height:6,strokeWidth:2}},forecastDataPoints:{count:0,fillOpacity:.5,strokeWidth:void 0,dashArray:4},grid:{show:!0,borderColor:"#e0e0e0",strokeDashArray:0,position:"back",xaxis:{lines:{show:!1}},yaxis:{lines:{show:!0}},row:{colors:void 0,opacity:.5},column:{colors:void 0,opacity:.5},padding:{top:0,right:10,bottom:0,left:12}},labels:[],legend:{show:!0,showForSingleSeries:!1,showForNullSeries:!0,showForZeroSeries:!0,floating:!1,position:"bottom",horizontalAlign:"center",inverseOrder:!1,fontSize:"12px",fontFamily:void 0,fontWeight:400,width:void 0,height:void 0,formatter:void 0,tooltipHoverFormatter:void 0,offsetX:-20,offsetY:4,customLegendItems:[],labels:{colors:void 0,useSeriesColors:!1},markers:{width:12,height:12,strokeWidth:0,fillColors:void 0,strokeColor:"#fff",radius:12,customHTML:void 0,offsetX:0,offsetY:0,onClick:void 0},itemMargin:{horizontal:5,vertical:2},onItemClick:{toggleDataSeries:!0},onItemHover:{highlightDataSeries:!0}},markers:{discrete:[],size:0,colors:void 0,strokeColors:"#fff",strokeWidth:2,strokeOpacity:.9,strokeDashArray:0,fillOpacity:1,shape:"circle",width:8,height:8,radius:2,offsetX:0,offsetY:0,onClick:void 0,onDblClick:void 0,showNullDataPoints:!0,hover:{size:void 0,sizeOffset:3}},noData:{text:void 0,align:"center",verticalAlign:"middle",offsetX:0,offsetY:0,style:{color:void 0,fontSize:"14px",fontFamily:void 0}},responsive:[],series:void 0,states:{normal:{filter:{type:"none",value:0}},hover:{filter:{type:"lighten",value:.1}},active:{allowMultipleDataPointsSelection:!1,filter:{type:"darken",value:.5}}},title:{text:void 0,align:"left",margin:5,offsetX:0,offsetY:0,floating:!1,style:{fontSize:"14px",fontWeight:900,fontFamily:void 0,color:void 0}},subtitle:{text:void 0,align:"left",margin:5,offsetX:0,offsetY:30,floating:!1,style:{fontSize:"12px",fontWeight:400,fontFamily:void 0,color:void 0}},stroke:{show:!0,curve:"smooth",lineCap:"butt",width:2,colors:void 0,dashArray:0},tooltip:{enabled:!0,enabledOnSeries:void 0,shared:!0,followCursor:!1,intersect:!1,inverseOrder:!1,custom:void 0,fillSeriesColor:!1,theme:"light",style:{fontSize:"12px",fontFamily:void 0},onDatasetHover:{highlightDataSeries:!1},x:{show:!0,format:"dd MMM",formatter:void 0},y:{formatter:void 0,title:{formatter:function(t){return t?t+": ":""}}},z:{formatter:void 0,title:"Size: "},marker:{show:!0,fillColors:void 0},items:{display:"flex"},fixed:{enabled:!1,position:"topRight",offsetX:0,offsetY:0}},xaxis:{type:"category",categories:[],convertedCatToNumeric:!1,offsetX:0,offsetY:0,overwriteCategories:void 0,labels:{show:!0,rotate:-45,rotateAlways:!1,hideOverlappingLabels:!0,trim:!1,minHeight:void 0,maxHeight:120,showDuplicates:!0,style:{colors:[],fontSize:"12px",fontWeight:400,fontFamily:void 0,cssClass:""},offsetX:0,offsetY:0,format:void 0,formatter:void 0,datetimeUTC:!0,datetimeFormatter:{year:"yyyy",month:"MMM 'yy",day:"dd MMM",hour:"HH:mm",minute:"HH:mm:ss",second:"HH:mm:ss"}},axisBorder:{show:!0,color:"#e0e0e0",width:"100%",height:1,offsetX:0,offsetY:0},axisTicks:{show:!0,color:"#e0e0e0",height:6,offsetX:0,offsetY:0},tickAmount:void 0,tickPlacement:"on",min:void 0,max:void 0,range:void 0,floating:!1,decimalsInFloat:void 0,position:"bottom",title:{text:void 0,offsetX:0,offsetY:0,style:{color:void 0,fontSize:"12px",fontWeight:900,fontFamily:void 0,cssClass:""}},crosshairs:{show:!0,width:1,position:"back",opacity:.9,stroke:{color:"#b6b6b6",width:1,dashArray:3},fill:{type:"solid",color:"#B1B9C4",gradient:{colorFrom:"#D8E3F0",colorTo:"#BED1E6",stops:[0,100],opacityFrom:.4,opacityTo:.5}},dropShadow:{enabled:!1,left:0,top:0,blur:1,opacity:.4}},tooltip:{enabled:!0,offsetY:0,formatter:void 0,style:{fontSize:"12px",fontFamily:void 0}}},yaxis:this.yAxis,theme:{mode:"light",palette:"palette1",monochrome:{enabled:!1,color:"#008FFB",shadeTo:"light",shadeIntensity:.65}}}}}]),t}(),C=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.graphics=new b(this.ctx),this.w.globals.isBarHorizontal&&(this.invertAxis=!0),this.helpers=new v(this),this.xAxisAnnotations=new m(this),this.yAxisAnnotations=new w(this),this.pointsAnnotations=new k(this),this.w.globals.isBarHorizontal&&this.w.config.yaxis[0].reversed&&(this.inversedReversedAxis=!0),this.xDivision=this.w.globals.gridWidth/this.w.globals.dataPoints}return r(t,[{key:"drawAxesAnnotations",value:function(){var t=this.w;if(t.globals.axisCharts){for(var e=this.yAxisAnnotations.drawYAxisAnnotations(),i=this.xAxisAnnotations.drawXAxisAnnotations(),a=this.pointsAnnotations.drawPointAnnotations(),s=t.config.chart.animations.enabled,r=[e,i,a],o=[i.node,e.node,a.node],n=0;n<3;n++)t.globals.dom.elGraphical.add(r[n]),!s||t.globals.resized||t.globals.dataChanged||"scatter"!==t.config.chart.type&&"bubble"!==t.config.chart.type&&t.globals.dataPoints>1&&o[n].classList.add("apexcharts-element-hidden"),t.globals.delayedElements.push({el:o[n],index:0});this.helpers.annotationsBackground()}}},{key:"drawImageAnnos",value:function(){var t=this;this.w.config.annotations.images.map((function(e,i){t.addImage(e,i)}))}},{key:"drawTextAnnos",value:function(){var t=this;this.w.config.annotations.texts.map((function(e,i){t.addText(e,i)}))}},{key:"addXaxisAnnotation",value:function(t,e,i){this.xAxisAnnotations.addXaxisAnnotation(t,e,i)}},{key:"addYaxisAnnotation",value:function(t,e,i){this.yAxisAnnotations.addYaxisAnnotation(t,e,i)}},{key:"addPointAnnotation",value:function(t,e,i){this.pointsAnnotations.addPointAnnotation(t,e,i)}},{key:"addText",value:function(t,e){var i=t.x,a=t.y,s=t.text,r=t.textAnchor,o=t.foreColor,n=t.fontSize,l=t.fontFamily,h=t.fontWeight,c=t.cssClass,d=t.backgroundColor,g=t.borderWidth,u=t.strokeDashArray,p=t.borderRadius,f=t.borderColor,x=t.appendTo,b=void 0===x?".apexcharts-annotations":x,v=t.paddingLeft,m=void 0===v?4:v,y=t.paddingRight,w=void 0===y?4:y,k=t.paddingBottom,A=void 0===k?2:k,S=t.paddingTop,C=void 0===S?2:S,L=this.w,P=this.graphics.drawText({x:i,y:a,text:s,textAnchor:r||"start",fontSize:n||"12px",fontWeight:h||"regular",fontFamily:l||L.config.chart.fontFamily,foreColor:o||L.config.chart.foreColor,cssClass:c}),T=L.globals.dom.baseEl.querySelector(b);T&&T.appendChild(P.node);var M=P.bbox();if(s){var I=this.graphics.drawRect(M.x-m,M.y-C,M.width+m+w,M.height+A+C,p,d||"transparent",1,g,f,u);T.insertBefore(I.node,P.node)}}},{key:"addImage",value:function(t,e){var i=this.w,a=t.path,s=t.x,r=void 0===s?0:s,o=t.y,n=void 0===o?0:o,l=t.width,h=void 0===l?20:l,c=t.height,d=void 0===c?20:c,g=t.appendTo,u=void 0===g?".apexcharts-annotations":g,p=i.globals.dom.Paper.image(a);p.size(h,d).move(r,n);var f=i.globals.dom.baseEl.querySelector(u);return f&&f.appendChild(p.node),p}},{key:"addXaxisAnnotationExternal",value:function(t,e,i){return this.addAnnotationExternal({params:t,pushToMemory:e,context:i,type:"xaxis",contextMethod:i.addXaxisAnnotation}),i}},{key:"addYaxisAnnotationExternal",value:function(t,e,i){return this.addAnnotationExternal({params:t,pushToMemory:e,context:i,type:"yaxis",contextMethod:i.addYaxisAnnotation}),i}},{key:"addPointAnnotationExternal",value:function(t,e,i){return void 0===this.invertAxis&&(this.invertAxis=i.w.globals.isBarHorizontal),this.addAnnotationExternal({params:t,pushToMemory:e,context:i,type:"point",contextMethod:i.addPointAnnotation}),i}},{key:"addAnnotationExternal",value:function(t){var e=t.params,i=t.pushToMemory,a=t.context,s=t.type,r=t.contextMethod,o=a,n=o.w,l=n.globals.dom.baseEl.querySelector(".apexcharts-".concat(s,"-annotations")),h=l.childNodes.length+1,c=new S,d=Object.assign({},"xaxis"===s?c.xAxisAnnotation:"yaxis"===s?c.yAxisAnnotation:c.pointAnnotation),g=p.extend(d,e);switch(s){case"xaxis":this.addXaxisAnnotation(g,l,h);break;case"yaxis":this.addYaxisAnnotation(g,l,h);break;case"point":this.addPointAnnotation(g,l,h)}var u=n.globals.dom.baseEl.querySelector(".apexcharts-".concat(s,"-annotations .apexcharts-").concat(s,"-annotation-label[rel='").concat(h,"']")),f=this.helpers.addBackgroundToAnno(u,g);return f&&l.insertBefore(f.node,u),i&&n.globals.memory.methodsToExec.push({context:o,id:g.id?g.id:p.randomId(),method:r,label:"addAnnotation",params:e}),a}},{key:"clearAnnotations",value:function(t){var e=t.w,i=e.globals.dom.baseEl.querySelectorAll(".apexcharts-yaxis-annotations, .apexcharts-xaxis-annotations, .apexcharts-point-annotations");e.globals.memory.methodsToExec.map((function(t,i){"addText"!==t.label&&"addAnnotation"!==t.label||e.globals.memory.methodsToExec.splice(i,1)})),i=p.listToArray(i),Array.prototype.forEach.call(i,(function(t){for(;t.firstChild;)t.removeChild(t.firstChild)}))}},{key:"removeAnnotation",value:function(t,e){var i=t.w,a=i.globals.dom.baseEl.querySelectorAll(".".concat(e));a&&(i.globals.memory.methodsToExec.map((function(t,a){t.id===e&&i.globals.memory.methodsToExec.splice(a,1)})),Array.prototype.forEach.call(a,(function(t){t.parentElement.removeChild(t)})))}}]),t}(),L=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.opts=null,this.seriesIndex=0}return r(t,[{key:"clippedImgArea",value:function(t){var e=this.w,i=e.config,a=parseInt(e.globals.gridWidth,10),s=parseInt(e.globals.gridHeight,10),r=a>s?a:s,o=t.image,n=0,l=0;void 0===t.width&&void 0===t.height?void 0!==i.fill.image.width&&void 0!==i.fill.image.height?(n=i.fill.image.width+1,l=i.fill.image.height):(n=r+1,l=r):(n=t.width,l=t.height);var h=document.createElementNS(e.globals.SVGNS,"pattern");b.setAttrs(h,{id:t.patternID,patternUnits:t.patternUnits?t.patternUnits:"userSpaceOnUse",width:n+"px",height:l+"px"});var c=document.createElementNS(e.globals.SVGNS,"image");h.appendChild(c),c.setAttributeNS(window.SVG.xlink,"href",o),b.setAttrs(c,{x:0,y:0,preserveAspectRatio:"none",width:n+"px",height:l+"px"}),c.style.opacity=t.opacity,e.globals.dom.elDefs.node.appendChild(h)}},{key:"getSeriesIndex",value:function(t){var e=this.w;return("bar"===e.config.chart.type||"rangeBar"===e.config.chart.type)&&e.config.plotOptions.bar.distributed||"heatmap"===e.config.chart.type||"treemap"===e.config.chart.type?this.seriesIndex=t.seriesNumber:this.seriesIndex=t.seriesNumber%e.globals.series.length,this.seriesIndex}},{key:"fillPath",value:function(t){var e=this.w;this.opts=t;var i,a,s,r=this.w.config;this.seriesIndex=this.getSeriesIndex(t);var o=this.getFillColors()[this.seriesIndex];void 0!==e.globals.seriesColors[this.seriesIndex]&&(o=e.globals.seriesColors[this.seriesIndex]),"function"==typeof o&&(o=o({seriesIndex:this.seriesIndex,dataPointIndex:t.dataPointIndex,value:t.value,w:e}));var n=this.getFillType(this.seriesIndex),l=Array.isArray(r.fill.opacity)?r.fill.opacity[this.seriesIndex]:r.fill.opacity;t.color&&(o=t.color);var h=o;if(-1===o.indexOf("rgb")?o.length<9&&(h=p.hexToRgba(o,l)):o.indexOf("rgba")>-1&&(l=p.getOpacityFromRGBA(o)),t.opacity&&(l=t.opacity),"pattern"===n&&(a=this.handlePatternFill(a,o,l,h)),"gradient"===n&&(s=this.handleGradientFill(o,l,this.seriesIndex)),"image"===n){var c=r.fill.image.src,d=t.patternID?t.patternID:"";this.clippedImgArea({opacity:l,image:Array.isArray(c)?t.seriesNumber-1&&(c=p.getOpacityFromRGBA(h));var d=void 0===s.fill.gradient.opacityTo?e:Array.isArray(s.fill.gradient.opacityTo)?s.fill.gradient.opacityTo[i]:s.fill.gradient.opacityTo;if(void 0===s.fill.gradient.gradientToColors||0===s.fill.gradient.gradientToColors.length)a="dark"===s.fill.gradient.shade?n.shadeColor(-1*parseFloat(s.fill.gradient.shadeIntensity),t.indexOf("rgb")>-1?p.rgb2hex(t):t):n.shadeColor(parseFloat(s.fill.gradient.shadeIntensity),t.indexOf("rgb")>-1?p.rgb2hex(t):t);else if(s.fill.gradient.gradientToColors[r.seriesNumber]){var g=s.fill.gradient.gradientToColors[r.seriesNumber];a=g,g.indexOf("rgba")>-1&&(d=p.getOpacityFromRGBA(g))}else a=t;if(s.fill.gradient.inverseColors){var u=h;h=a,a=u}return h.indexOf("rgb")>-1&&(h=p.rgb2hex(h)),a.indexOf("rgb")>-1&&(a=p.rgb2hex(a)),o.drawGradient(l,h,a,c,d,r.size,s.fill.gradient.stops,s.fill.gradient.colorStops,i)}}]),t}(),P=function(){function t(e,i){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"setGlobalMarkerSize",value:function(){var t=this.w;if(t.globals.markers.size=Array.isArray(t.config.markers.size)?t.config.markers.size:[t.config.markers.size],t.globals.markers.size.length>0){if(t.globals.markers.size.length4&&void 0!==arguments[4]&&arguments[4],o=this.w,n=e,l=t,h=null,c=new b(this.ctx);if((o.globals.markers.size[e]>0||r)&&(h=c.group({class:r?"":"apexcharts-series-markers"})).attr("clip-path","url(#gridRectMarkerMask".concat(o.globals.cuid,")")),Array.isArray(l.x))for(var d=0;d0:o.config.markers.size>0;if(f||r){p.isNumber(l.y[d])?u+=" w".concat(p.randomId()):u="apexcharts-nullpoint";var v=this.getMarkerConfig({cssClass:u,seriesIndex:e,dataPointIndex:g});o.config.series[n].data[g]&&(o.config.series[n].data[g].fillColor&&(v.pointFillColor=o.config.series[n].data[g].fillColor),o.config.series[n].data[g].strokeColor&&(v.pointStrokeColor=o.config.series[n].data[g].strokeColor)),a&&(v.pSize=a),(s=c.drawMarker(l.x[d],l.y[d],v)).attr("rel",g),s.attr("j",g),s.attr("index",e),s.node.setAttribute("default-marker-size",v.pSize);var m=new x(this.ctx);m.setSelectionFilter(s,e,g),this.addEvents(s),h&&h.add(s)}else void 0===o.globals.pointsArray[e]&&(o.globals.pointsArray[e]=[]),o.globals.pointsArray[e].push([l.x[d],l.y[d]])}return h}},{key:"getMarkerConfig",value:function(t){var e=t.cssClass,i=t.seriesIndex,a=t.dataPointIndex,s=void 0===a?null:a,r=t.finishRadius,o=void 0===r?null:r,n=this.w,l=this.getMarkerStyle(i),h=n.globals.markers.size[i],c=n.config.markers;return null!==s&&c.discrete.length&&c.discrete.map((function(t){t.seriesIndex===i&&t.dataPointIndex===s&&(l.pointStrokeColor=t.strokeColor,l.pointFillColor=t.fillColor,h=t.size,l.pointShape=t.shape)})),{pSize:null===o?h:o,pRadius:c.radius,width:Array.isArray(c.width)?c.width[i]:c.width,height:Array.isArray(c.height)?c.height[i]:c.height,pointStrokeWidth:Array.isArray(c.strokeWidth)?c.strokeWidth[i]:c.strokeWidth,pointStrokeColor:l.pointStrokeColor,pointFillColor:l.pointFillColor,shape:l.pointShape||(Array.isArray(c.shape)?c.shape[i]:c.shape),class:e,pointStrokeOpacity:Array.isArray(c.strokeOpacity)?c.strokeOpacity[i]:c.strokeOpacity,pointStrokeDashArray:Array.isArray(c.strokeDashArray)?c.strokeDashArray[i]:c.strokeDashArray,pointFillOpacity:Array.isArray(c.fillOpacity)?c.fillOpacity[i]:c.fillOpacity,seriesIndex:i}}},{key:"addEvents",value:function(t){var e=this.w,i=new b(this.ctx);t.node.addEventListener("mouseenter",i.pathMouseEnter.bind(this.ctx,t)),t.node.addEventListener("mouseleave",i.pathMouseLeave.bind(this.ctx,t)),t.node.addEventListener("mousedown",i.pathMouseDown.bind(this.ctx,t)),t.node.addEventListener("click",e.config.markers.onClick),t.node.addEventListener("dblclick",e.config.markers.onDblClick),t.node.addEventListener("touchstart",i.pathMouseDown.bind(this.ctx,t),{passive:!0})}},{key:"getMarkerStyle",value:function(t){var e=this.w,i=e.globals.markers.colors,a=e.config.markers.strokeColor||e.config.markers.strokeColors;return{pointStrokeColor:Array.isArray(a)?a[t]:a,pointFillColor:Array.isArray(i)?i[t]:i}}}]),t}(),T=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.initialAnim=this.w.config.chart.animations.enabled,this.dynamicAnim=this.initialAnim&&this.w.config.chart.animations.dynamicAnimation.enabled}return r(t,[{key:"draw",value:function(t,e,i){var a=this.w,s=new b(this.ctx),r=i.realIndex,o=i.pointsPos,n=i.zRatio,l=i.elParent,h=s.group({class:"apexcharts-series-markers apexcharts-series-".concat(a.config.chart.type)});if(h.attr("clip-path","url(#gridRectMarkerMask".concat(a.globals.cuid,")")),Array.isArray(o.x))for(var c=0;cf.maxBubbleRadius&&(p=f.maxBubbleRadius)}a.config.chart.animations.enabled||(u=p);var x=o.x[c],v=o.y[c];if(u=u||0,null!==v&&void 0!==a.globals.series[r][d]||(g=!1),g){var m=this.drawPoint(x,v,u,p,r,d,e);h.add(m)}l.add(h)}}},{key:"drawPoint",value:function(t,e,i,a,s,r,o){var n=this.w,l=s,h=new f(this.ctx),c=new x(this.ctx),d=new L(this.ctx),g=new P(this.ctx),u=new b(this.ctx),p=g.getMarkerConfig({cssClass:"apexcharts-marker",seriesIndex:l,dataPointIndex:r,finishRadius:"bubble"===n.config.chart.type||n.globals.comboCharts&&n.config.series[s]&&"bubble"===n.config.series[s].type?a:null});a=p.pSize;var v,m=d.fillPath({seriesNumber:s,dataPointIndex:r,color:p.pointFillColor,patternUnits:"objectBoundingBox",value:n.globals.series[s][o]});if("circle"===p.shape?v=u.drawCircle(i):"square"!==p.shape&&"rect"!==p.shape||(v=u.drawRect(0,0,p.width-p.pointStrokeWidth/2,p.height-p.pointStrokeWidth/2,p.pRadius)),n.config.series[l].data[r]&&n.config.series[l].data[r].fillColor&&(m=n.config.series[l].data[r].fillColor),v.attr({x:t-p.width/2-p.pointStrokeWidth/2,y:e-p.height/2-p.pointStrokeWidth/2,cx:t,cy:e,fill:m,"fill-opacity":p.pointFillOpacity,stroke:p.pointStrokeColor,r:a,"stroke-width":p.pointStrokeWidth,"stroke-dasharray":p.pointStrokeDashArray,"stroke-opacity":p.pointStrokeOpacity}),n.config.chart.dropShadow.enabled){var y=n.config.chart.dropShadow;c.dropShadow(v,y,s)}if(!this.initialAnim||n.globals.dataChanged||n.globals.resized)n.globals.animationEnded=!0;else{var w=n.config.chart.animations.speed;h.animateMarker(v,0,"circle"===p.shape?a:{width:p.width,height:p.height},w,n.globals.easing,(function(){window.setTimeout((function(){h.animationCompleted(v)}),100)}))}if(n.globals.dataChanged&&"circle"===p.shape)if(this.dynamicAnim){var k,A,S,C,T=n.config.chart.animations.dynamicAnimation.speed;null!=(C=n.globals.previousPaths[s]&&n.globals.previousPaths[s][o])&&(k=C.x,A=C.y,S=void 0!==C.r?C.r:a);for(var M=0;Mn.globals.gridHeight+d&&(e=n.globals.gridHeight+d/2),void 0===n.globals.dataLabelsRects[a]&&(n.globals.dataLabelsRects[a]=[]),n.globals.dataLabelsRects[a].push({x:t,y:e,width:c,height:d});var g=n.globals.dataLabelsRects[a].length-2,u=void 0!==n.globals.lastDrawnDataLabelsIndexes[a]?n.globals.lastDrawnDataLabelsIndexes[a][n.globals.lastDrawnDataLabelsIndexes[a].length-1]:0;if(void 0!==n.globals.dataLabelsRects[a][g]){var p=n.globals.dataLabelsRects[a][u];(t>p.x+p.width+2||e>p.y+p.height+2||t+c4&&void 0!==arguments[4]?arguments[4]:2,r=this.w,o=new b(this.ctx),n=r.config.dataLabels,l=0,h=0,c=i,d=null;if(!n.enabled||!Array.isArray(t.x))return d;d=o.group({class:"apexcharts-data-labels"});for(var g=0;ge.globals.gridWidth+f.textRects.width+10)&&(n="");var v=e.globals.dataLabels.style.colors[r];(("bar"===e.config.chart.type||"rangeBar"===e.config.chart.type)&&e.config.plotOptions.bar.distributed||e.config.dataLabels.distributed)&&(v=e.globals.dataLabels.style.colors[o]),"function"==typeof v&&(v=v({series:e.globals.series,seriesIndex:r,dataPointIndex:o,w:e})),g&&(v=g);var m=d.offsetX,y=d.offsetY;if("bar"!==e.config.chart.type&&"rangeBar"!==e.config.chart.type||(m=0,y=0),f.drawnextLabel&&void 0!==n&&String(n).trim().length){var w=i.drawText({width:100,height:parseInt(d.style.fontSize,10),x:a+m,y:s+y,foreColor:v,textAnchor:l||d.textAnchor,text:n,fontSize:h||d.style.fontSize,fontFamily:d.style.fontFamily,fontWeight:d.style.fontWeight||"normal"});if(w.attr({class:"apexcharts-datalabel",cx:a,cy:s}),d.dropShadow.enabled){var k=d.dropShadow;new x(this.ctx).dropShadow(w,k)}c.add(w),void 0===e.globals.lastDrawnDataLabelsIndexes[r]&&(e.globals.lastDrawnDataLabelsIndexes[r]=[]),e.globals.lastDrawnDataLabelsIndexes[r].push(o)}}}},{key:"addBackgroundToDataLabel",value:function(t,e){var i=this.w,a=i.config.dataLabels.background,s=a.padding,r=a.padding/2,o=e.width,n=e.height,l=new b(this.ctx).drawRect(e.x-s,e.y-r/2,o+2*s,n+r,a.borderRadius,"transparent"===i.config.chart.background?"#fff":i.config.chart.background,a.opacity,a.borderWidth,a.borderColor);a.dropShadow.enabled&&new x(this.ctx).dropShadow(l,a.dropShadow);return l}},{key:"dataLabelsBackground",value:function(){var t=this.w;if("bubble"!==t.config.chart.type)for(var e=t.globals.dom.baseEl.querySelectorAll(".apexcharts-datalabels text"),i=0;ii.globals.gridHeight&&(c=i.globals.gridHeight-g)),{bcx:o,bcy:r,dataLabelsX:e,dataLabelsY:c}}},{key:"calculateBarsDataLabelsPosition",value:function(t){var e=this.w,i=t.x,a=t.i,s=t.j,r=t.bcy,o=t.barHeight,n=t.barWidth,l=t.textRects,h=t.dataLabelsX,c=t.strokeWidth,d=t.barDataLabelsConfig,g=t.offX,u=t.offY,p=e.globals.gridHeight/e.globals.dataPoints;n=Math.abs(n);var f=r-(this.barCtx.isRangeBar?0:p)+o/2+l.height/2+u-3,x=this.barCtx.series[a][s]<0,b=i;switch(this.barCtx.isReversed&&(b=i+n-(x?2*n:0),i=e.globals.gridWidth-n),d.position){case"center":h=x?b+n/2-g:Math.max(l.width/2,b-n/2)+g;break;case"bottom":h=x?b+n-c-Math.round(l.width/2)-g:b-n+c+Math.round(l.width/2)+g;break;case"top":h=x?b-c+Math.round(l.width/2)-g:b-c-Math.round(l.width/2)+g}return e.config.chart.stacked||(h<0?h=h+l.width+c:h+l.width/2>e.globals.gridWidth&&(h=e.globals.gridWidth-l.width-c)),{bcx:i,bcy:r,dataLabelsX:h,dataLabelsY:f}}},{key:"drawCalculatedDataLabels",value:function(t){var i=t.x,a=t.y,s=t.val,r=t.i,o=t.j,n=t.textRects,l=t.barHeight,h=t.barWidth,c=t.dataLabelsConfig,d=this.w,g="rotate(0)";"vertical"===d.config.plotOptions.bar.dataLabels.orientation&&(g="rotate(-90, ".concat(i,", ").concat(a,")"));var u=new M(this.barCtx.ctx),p=new b(this.barCtx.ctx),f=c.formatter,x=null,v=d.globals.collapsedSeriesIndices.indexOf(r)>-1;if(c.enabled&&!v){x=p.group({class:"apexcharts-data-labels",transform:g});var m="";void 0!==s&&(m=f(s,{seriesIndex:r,dataPointIndex:o,w:d}));var y=d.globals.series[r][o]<0,w=d.config.plotOptions.bar.dataLabels.position;if("vertical"===d.config.plotOptions.bar.dataLabels.orientation&&("top"===w&&(c.textAnchor=y?"end":"start"),"center"===w&&(c.textAnchor="middle"),"bottom"===w&&(c.textAnchor=y?"end":"start")),this.barCtx.isRangeBar&&this.barCtx.barOptions.dataLabels.hideOverflowingLabels)hMath.abs(h)&&(m=""):n.height/1.6>Math.abs(l)&&(m=""));var k=e({},c);this.barCtx.isHorizontal&&s<0&&("start"===c.textAnchor?k.textAnchor="end":"end"===c.textAnchor&&(k.textAnchor="start")),u.plotDataLabelsText({x:i,y:a,text:m,i:r,j:o,parent:x,dataLabelsConfig:k,alwaysDrawDataLabel:!0,offsetCorrection:!0})}return x}}]),t}(),z=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.legendInactiveClass="legend-mouseover-inactive"}return r(t,[{key:"getAllSeriesEls",value:function(){return this.w.globals.dom.baseEl.getElementsByClassName("apexcharts-series")}},{key:"getSeriesByName",value:function(t){return this.w.globals.dom.baseEl.querySelector(".apexcharts-inner .apexcharts-series[seriesName='".concat(p.escapeString(t),"']"))}},{key:"isSeriesHidden",value:function(t){var e=this.getSeriesByName(t),i=parseInt(e.getAttribute("data:realIndex"),10);return{isHidden:e.classList.contains("apexcharts-series-collapsed"),realIndex:i}}},{key:"addCollapsedClassToSeries",value:function(t,e){var i=this.w;function a(i){for(var a=0;a0&&void 0!==arguments[0])||arguments[0],e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],a=this.w,s=p.clone(a.globals.initialSeries);a.globals.previousPaths=[],i?(a.globals.collapsedSeries=[],a.globals.ancillaryCollapsedSeries=[],a.globals.collapsedSeriesIndices=[],a.globals.ancillaryCollapsedSeriesIndices=[]):s=this.emptyCollapsedSeries(s),a.config.series=s,t&&(e&&(a.globals.zoomed=!1,this.ctx.updateHelpers.revertDefaultAxisMinMax()),this.ctx.updateHelpers._updateSeries(s,a.config.chart.animations.dynamicAnimation.enabled))}},{key:"emptyCollapsedSeries",value:function(t){for(var e=this.w,i=0;i-1&&(t[i].data=[]);return t}},{key:"toggleSeriesOnHover",value:function(t,e){var i=this.w;e||(e=t.target);var a=i.globals.dom.baseEl.querySelectorAll(".apexcharts-series, .apexcharts-datalabels");if("mousemove"===t.type){var s=parseInt(e.getAttribute("rel"),10)-1,r=null,o=null;i.globals.axisCharts||"radialBar"===i.config.chart.type?i.globals.axisCharts?(r=i.globals.dom.baseEl.querySelector(".apexcharts-series[data\\:realIndex='".concat(s,"']")),o=i.globals.dom.baseEl.querySelector(".apexcharts-datalabels[data\\:realIndex='".concat(s,"']"))):r=i.globals.dom.baseEl.querySelector(".apexcharts-series[rel='".concat(s+1,"']")):r=i.globals.dom.baseEl.querySelector(".apexcharts-series[rel='".concat(s+1,"'] path"));for(var n=0;n=t.from&&a<=t.to&&s[e].classList.remove(i.legendInactiveClass)}}(a.config.plotOptions.heatmap.colorScale.ranges[o])}else"mouseout"===t.type&&r("remove")}},{key:"getActiveConfigSeriesIndex",value:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"asc",i=this.w,a=0;if(i.config.series.length>1)for(var s=i.config.series.map((function(e,a){var s=!1;return t&&(s="bar"===i.config.series[a].type||"column"===i.config.series[a].type),e.data&&e.data.length>0&&!s?a:-1})),r="asc"===e?0:s.length-1;"asc"===e?r=0;"asc"===e?r++:r--)if(-1!==s[r]){a=s[r];break}return a}},{key:"getPreviousPaths",value:function(){var t=this.w;function e(e,i,a){for(var s=e[i].childNodes,r={type:a,paths:[],realIndex:e[i].getAttribute("data:realIndex")},o=0;o0)for(var a=function(e){for(var i=t.globals.dom.baseEl.querySelectorAll(".apexcharts-".concat(t.config.chart.type," .apexcharts-series[data\\:realIndex='").concat(e,"'] rect")),a=[],s=function(t){var e=function(e){return i[t].getAttribute(e)},s={x:parseFloat(e("x")),y:parseFloat(e("y")),width:parseFloat(e("width")),height:parseFloat(e("height"))};a.push({rect:s,color:i[t].getAttribute("color")})},r=0;r0)for(var a=0;a0?t:[]}));return t}}]),t}(),X=function(){function t(e){a(this,t),this.w=e.w,this.barCtx=e}return r(t,[{key:"initVariables",value:function(t){var e=this.w;this.barCtx.series=t,this.barCtx.totalItems=0,this.barCtx.seriesLen=0,this.barCtx.visibleI=-1,this.barCtx.visibleItems=1;for(var i=0;i0&&(this.barCtx.seriesLen=this.barCtx.seriesLen+1,this.barCtx.totalItems+=t[i].length),e.globals.isXNumeric)for(var a=0;ae.globals.minX&&e.globals.seriesX[i][a]0&&(a=l.globals.minXDiff/d),(r=a/this.barCtx.seriesLen*parseInt(this.barCtx.barOptions.columnWidth,10)/100)<1&&(r=1)}o=l.globals.gridHeight-this.barCtx.baseLineY[this.barCtx.yaxisIndex]-(this.barCtx.isReversed?l.globals.gridHeight:0)+(this.barCtx.isReversed?2*this.barCtx.baseLineY[this.barCtx.yaxisIndex]:0),t=l.globals.padHorizontal+(a-r*this.barCtx.seriesLen)/2}return{x:t,y:e,yDivision:i,xDivision:a,barHeight:s,barWidth:r,zeroH:o,zeroW:n}}},{key:"getPathFillColor",value:function(t,e,i,a){var s=this.w,r=new L(this.barCtx.ctx),o=null,n=this.barCtx.barOptions.distributed?i:e;this.barCtx.barOptions.colors.ranges.length>0&&this.barCtx.barOptions.colors.ranges.map((function(a){t[e][i]>=a.from&&t[e][i]<=a.to&&(o=a.color)}));return s.config.series[e].data[i]&&s.config.series[e].data[i].fillColor&&(o=s.config.series[e].data[i].fillColor),r.fillPath({seriesNumber:this.barCtx.barOptions.distributed?n:a,dataPointIndex:i,color:o,value:t[e][i]})}},{key:"getStrokeWidth",value:function(t,e,i){var a=0,s=this.w;return void 0===this.barCtx.series[t][e]||null===this.barCtx.series[t][e]?this.barCtx.isNullValue=!0:this.barCtx.isNullValue=!1,s.config.stroke.show&&(this.barCtx.isNullValue||(a=Array.isArray(this.barCtx.strokeWidth)?this.barCtx.strokeWidth[i]:this.barCtx.strokeWidth)),a}},{key:"barBackground",value:function(t){var e=t.j,i=t.i,a=t.x1,s=t.x2,r=t.y1,o=t.y2,n=t.elSeries,l=this.w,h=new b(this.barCtx.ctx),c=new z(this.barCtx.ctx).getActiveConfigSeriesIndex();if(this.barCtx.barOptions.colors.backgroundBarColors.length>0&&c===i){e>=this.barCtx.barOptions.colors.backgroundBarColors.length&&(e-=this.barCtx.barOptions.colors.backgroundBarColors.length);var d=this.barCtx.barOptions.colors.backgroundBarColors[e],g=h.drawRect(void 0!==a?a:0,void 0!==r?r:0,void 0!==s?s:l.globals.gridWidth,void 0!==o?o:l.globals.gridHeight,this.barCtx.barOptions.colors.backgroundBarRadius,d,this.barCtx.barOptions.colors.backgroundBarOpacity);n.add(g),g.node.classList.add("apexcharts-backgroundBar")}}},{key:"getColumnPaths",value:function(t){var e=t.barWidth,i=t.barXPosition,a=t.yRatio,s=t.y1,r=t.y2,o=t.strokeWidth,n=t.series,l=t.realIndex,h=t.i,c=t.j,d=t.w,g=new b(this.barCtx.ctx);(o=Array.isArray(o)?o[l]:o)||(o=0);var u={barWidth:e,strokeWidth:o,yRatio:a,barXPosition:i,y1:s,y2:r},p=this.getRoundedBars(d,u,n,h,c),f=i,x=i+e,v=g.move(f,s),m=g.move(f,s),y=g.line(x-o,s);return d.globals.previousPaths.length>0&&(m=this.barCtx.getPreviousPath(l,c,!1)),v=v+g.line(f,p.y2)+p.pathWithRadius+g.line(x-o,p.y2)+y+y+"z",m=m+g.line(f,s)+y+y+y+y+y+g.line(f,s),d.config.chart.stacked&&(this.barCtx.yArrj.push(p.y2),this.barCtx.yArrjF.push(Math.abs(s-p.y2)),this.barCtx.yArrjVal.push(this.barCtx.series[h][c])),{pathTo:v,pathFrom:m}}},{key:"getBarpaths",value:function(t){var e=t.barYPosition,i=t.barHeight,a=t.x1,s=t.x2,r=t.strokeWidth,o=t.series,n=t.realIndex,l=t.i,h=t.j,c=t.w,d=new b(this.barCtx.ctx);(r=Array.isArray(r)?r[n]:r)||(r=0);var g={barHeight:i,strokeWidth:r,barYPosition:e,x2:s,x1:a},u=this.getRoundedBars(c,g,o,l,h),p=d.move(a,e),f=d.move(a,e);c.globals.previousPaths.length>0&&(f=this.barCtx.getPreviousPath(n,h,!1));var x=e,v=e+i,m=d.line(a,v-r);return p=p+d.line(u.x2,x)+u.pathWithRadius+d.line(u.x2,v-r)+m+m+"z",f=f+d.line(a,x)+m+m+m+m+m+d.line(a,x),c.config.chart.stacked&&(this.barCtx.xArrj.push(u.x2),this.barCtx.xArrjF.push(Math.abs(a-u.x2)),this.barCtx.xArrjVal.push(this.barCtx.series[l][h])),{pathTo:p,pathFrom:f}}},{key:"getRoundedBars",value:function(t,e,i,a,s){var r=new b(this.barCtx.ctx),o=0,n=t.config.plotOptions.bar.borderRadius,l=Array.isArray(n);l?o=n[a>n.length-1?n.length-1:a]:o=n;if(t.config.chart.stacked&&i.length>1&&a!==this.barCtx.radiusOnSeriesNumber&&!l&&(o=0),this.barCtx.isHorizontal){var h="",c=e.x2;if(Math.abs(e.x1-e.x2)0:i[a][s]<0;d&&(o*=-1),c-=o,h=r.quadraticCurve(c+o,e.barYPosition,c+o,e.barYPosition+(d?-1*o:o))+r.line(c+o,e.barYPosition+e.barHeight-e.strokeWidth-(d?-1*o:o))+r.quadraticCurve(c+o,e.barYPosition+e.barHeight-e.strokeWidth,c,e.barYPosition+e.barHeight-e.strokeWidth)}return{pathWithRadius:h,x2:c}}var g="",u=e.y2;if(Math.abs(e.y1-e.y2)=0;o--)this.barCtx.zeroSerieses.indexOf(o)>-1&&o===this.radiusOnSeriesNumber&&(this.barCtx.radiusOnSeriesNumber-=1);for(var n=e.length-1;n>=0;n--)i.globals.collapsedSeriesIndices.indexOf(this.barCtx.radiusOnSeriesNumber)>-1&&(this.barCtx.radiusOnSeriesNumber-=1)}},{key:"getXForValue",value:function(t,e){var i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],a=i?e:null;return null!=t&&(a=e+t/this.barCtx.invertedYRatio-2*(this.barCtx.isReversed?t/this.barCtx.invertedYRatio:0)),a}},{key:"getYForValue",value:function(t,e){var i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],a=i?e:null;return null!=t&&(a=e-t/this.barCtx.yRatio[this.barCtx.yaxisIndex]+2*(this.barCtx.isReversed?t/this.barCtx.yRatio[this.barCtx.yaxisIndex]:0)),a}},{key:"getGoalValues",value:function(t,e,i,a,s){var r=this,n=this.w,l=[];return n.globals.seriesGoals[a]&&n.globals.seriesGoals[a][s]&&Array.isArray(n.globals.seriesGoals[a][s])&&n.globals.seriesGoals[a][s].forEach((function(a){var s;l.push((o(s={},t,"x"===t?r.getXForValue(a.value,e,!1):r.getYForValue(a.value,i,!1)),o(s,"attrs",a),s))})),l}},{key:"drawGoalLine",value:function(t){var e=t.barXPosition,i=t.barYPosition,a=t.goalX,s=t.goalY,r=t.barWidth,o=t.barHeight,n=new b(this.barCtx.ctx),l=n.group({className:"apexcharts-bar-goals-groups"}),h=null;return this.barCtx.isHorizontal?Array.isArray(a)&&a.forEach((function(t){var e=void 0!==t.attrs.strokeHeight?t.attrs.strokeHeight:o/2,a=i+e+o/2;h=n.drawLine(t.x,a-2*e,t.x,a,t.attrs.strokeColor?t.attrs.strokeColor:void 0,t.attrs.strokeDashArray,t.attrs.strokeWidth?t.attrs.strokeWidth:2,t.attrs.strokeLineCap),l.add(h)})):Array.isArray(s)&&s.forEach((function(t){var i=void 0!==t.attrs.strokeWidth?t.attrs.strokeWidth:r/2,a=e+i+r/2;h=n.drawLine(a-2*i,t.y,a,t.y,t.attrs.strokeColor?t.attrs.strokeColor:void 0,t.attrs.strokeDashArray,t.attrs.strokeHeight?t.attrs.strokeHeight:2,t.attrs.strokeLineCap),l.add(h)})),l}}]),t}(),E=function(){function t(e,i){a(this,t),this.ctx=e,this.w=e.w;var s=this.w;this.barOptions=s.config.plotOptions.bar,this.isHorizontal=this.barOptions.horizontal,this.strokeWidth=s.config.stroke.width,this.isNullValue=!1,this.isRangeBar=s.globals.seriesRangeBar.length&&this.isHorizontal,this.xyRatios=i,null!==this.xyRatios&&(this.xRatio=i.xRatio,this.initialXRatio=i.initialXRatio,this.yRatio=i.yRatio,this.invertedXRatio=i.invertedXRatio,this.invertedYRatio=i.invertedYRatio,this.baseLineY=i.baseLineY,this.baseLineInvertedY=i.baseLineInvertedY),this.yaxisIndex=0,this.seriesLen=0,this.barHelpers=new X(this)}return r(t,[{key:"draw",value:function(t,i){var a=this.w,s=new b(this.ctx),r=new y(this.ctx,a);t=r.getLogSeries(t),this.series=t,this.yRatio=r.getLogYRatios(this.yRatio),this.barHelpers.initVariables(t);var o=s.group({class:"apexcharts-bar-series apexcharts-plot-series"});a.config.dataLabels.enabled&&this.totalItems>this.barOptions.dataLabels.maxItems&&console.warn("WARNING: DataLabels are enabled but there are too many to display. This may cause performance issue when rendering.");for(var n=0,l=0;n0&&(this.visibleI=this.visibleI+1);var k=0,A=0;this.yRatio.length>1&&(this.yaxisIndex=m),this.isReversed=a.config.yaxis[this.yaxisIndex]&&a.config.yaxis[this.yaxisIndex].reversed;var S=this.barHelpers.initialPositions();f=S.y,k=S.barHeight,c=S.yDivision,g=S.zeroW,u=S.x,A=S.barWidth,h=S.xDivision,d=S.zeroH,this.horizontal||v.push(u+A/2);for(var C=s.group({class:"apexcharts-datalabels","data:realIndex":m}),L=s.group({class:"apexcharts-bar-goals-markers",style:"pointer-events: none"}),P=0;P0&&v.push(u+A/2),x.push(f);var X=this.barHelpers.getPathFillColor(t,n,P,m);this.renderSeries({realIndex:m,pathFill:X,j:P,i:n,pathFrom:M.pathFrom,pathTo:M.pathTo,strokeWidth:T,elSeries:w,x:u,y:f,series:t,barHeight:k,barWidth:A,elDataLabelsWrap:C,elGoalsMarkers:L,visibleSeries:this.visibleI,type:"bar"})}a.globals.seriesXvalues[m]=v,a.globals.seriesYvalues[m]=x,o.add(w)}return o}},{key:"renderSeries",value:function(t){var e=t.realIndex,i=t.pathFill,a=t.lineFill,s=t.j,r=t.i,o=t.pathFrom,n=t.pathTo,l=t.strokeWidth,h=t.elSeries,c=t.x,d=t.y,g=t.y1,u=t.y2,p=t.series,f=t.barHeight,v=t.barWidth,m=t.barYPosition,y=t.elDataLabelsWrap,w=t.elGoalsMarkers,k=t.visibleSeries,A=t.type,S=this.w,C=new b(this.ctx);a||(a=this.barOptions.distributed?S.globals.stroke.colors[s]:S.globals.stroke.colors[e]),S.config.series[r].data[s]&&S.config.series[r].data[s].strokeColor&&(a=S.config.series[r].data[s].strokeColor),this.isNullValue&&(i="none");var L=s/S.config.chart.animations.animateGradually.delay*(S.config.chart.animations.speed/S.globals.dataPoints)/2.4,P=C.renderPaths({i:r,j:s,realIndex:e,pathFrom:o,pathTo:n,stroke:a,strokeWidth:l,strokeLineCap:S.config.stroke.lineCap,fill:i,animationDelay:L,initialSpeed:S.config.chart.animations.speed,dataChangeSpeed:S.config.chart.animations.dynamicAnimation.speed,className:"apexcharts-".concat(A,"-area")});P.attr("clip-path","url(#gridRectMask".concat(S.globals.cuid,")"));var T=S.config.forecastDataPoints;T.count>0&&s>=S.globals.dataPoints-T.count&&(P.node.setAttribute("stroke-dasharray",T.dashArray),P.node.setAttribute("stroke-width",T.strokeWidth),P.node.setAttribute("fill-opacity",T.fillOpacity)),void 0!==g&&void 0!==u&&(P.attr("data-range-y1",g),P.attr("data-range-y2",u)),new x(this.ctx).setSelectionFilter(P,e,s),h.add(P);var M=new I(this).handleBarDataLabels({x:c,y:d,y1:g,y2:u,i:r,j:s,series:p,realIndex:e,barHeight:f,barWidth:v,barYPosition:m,renderedPath:P,visibleSeries:k});return null!==M&&y.add(M),h.add(y),w&&h.add(w),h}},{key:"drawBarPaths",value:function(t){var e=t.indexes,i=t.barHeight,a=t.strokeWidth,s=t.zeroW,r=t.x,o=t.y,n=t.yDivision,l=t.elSeries,h=this.w,c=e.i,d=e.j;h.globals.isXNumeric&&(o=(h.globals.seriesX[c][d]-h.globals.minX)/this.invertedXRatio-i);var g=o+i*this.visibleI;r=this.barHelpers.getXForValue(this.series[c][d],s);var u=this.barHelpers.getBarpaths({barYPosition:g,barHeight:i,x1:s,x2:r,strokeWidth:a,series:this.series,realIndex:e.realIndex,i:c,j:d,w:h});return h.globals.isXNumeric||(o+=n),this.barHelpers.barBackground({j:d,i:c,y1:g-i*this.visibleI,y2:i*this.seriesLen,elSeries:l}),{pathTo:u.pathTo,pathFrom:u.pathFrom,x:r,y:o,goalX:this.barHelpers.getGoalValues("x",s,null,c,d),barYPosition:g}}},{key:"drawColumnPaths",value:function(t){var e=t.indexes,i=t.x,a=t.y,s=t.xDivision,r=t.barWidth,o=t.zeroH,n=t.strokeWidth,l=t.elSeries,h=this.w,c=e.realIndex,d=e.i,g=e.j,u=e.bc;if(h.globals.isXNumeric){var p=c;h.globals.seriesX[c].length||(p=h.globals.maxValsInArrayIndex),i=(h.globals.seriesX[p][g]-h.globals.minX)/this.xRatio-r*this.seriesLen/2}var f=i+r*this.visibleI;a=this.barHelpers.getYForValue(this.series[d][g],o);var x=this.barHelpers.getColumnPaths({barXPosition:f,barWidth:r,y1:o,y2:a,strokeWidth:n,series:this.series,realIndex:e.realIndex,i:d,j:g,w:h});return h.globals.isXNumeric||(i+=s),this.barHelpers.barBackground({bc:u,j:g,i:d,x1:f-n/2-r*this.visibleI,x2:r*this.seriesLen+n/2,elSeries:l}),{pathTo:x.pathTo,pathFrom:x.pathFrom,x:i,y:a,goalY:this.barHelpers.getGoalValues("y",null,o,d,g),barXPosition:f}}},{key:"getPreviousPath",value:function(t,e){for(var i,a=this.w,s=0;s0&&parseInt(r.realIndex,10)===parseInt(t,10)&&void 0!==a.globals.previousPaths[s].paths[e]&&(i=a.globals.previousPaths[s].paths[e].d)}return i}}]),t}(),Y=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.months31=[1,3,5,7,8,10,12],this.months30=[2,4,6,9,11],this.daysCntOfYear=[0,31,59,90,120,151,181,212,243,273,304,334]}return r(t,[{key:"isValidDate",value:function(t){return!isNaN(this.parseDate(t))}},{key:"getTimeStamp",value:function(t){return Date.parse(t)?this.w.config.xaxis.labels.datetimeUTC?new Date(new Date(t).toISOString().substr(0,25)).getTime():new Date(t).getTime():t}},{key:"getDate",value:function(t){return this.w.config.xaxis.labels.datetimeUTC?new Date(new Date(t).toUTCString()):new Date(t)}},{key:"parseDate",value:function(t){var e=Date.parse(t);if(!isNaN(e))return this.getTimeStamp(t);var i=Date.parse(t.replace(/-/g,"/").replace(/[a-z]+/gi," "));return i=this.getTimeStamp(i)}},{key:"parseDateWithTimezone",value:function(t){return Date.parse(t.replace(/-/g,"/").replace(/[a-z]+/gi," "))}},{key:"formatDate",value:function(t,e){var i=this.w.globals.locale,a=this.w.config.xaxis.labels.datetimeUTC,s=["\0"].concat(g(i.months)),r=["\x01"].concat(g(i.shortMonths)),o=["\x02"].concat(g(i.days)),n=["\x03"].concat(g(i.shortDays));function l(t,e){var i=t+"";for(e=e||2;i.length12?u-12:0===u?12:u;e=(e=(e=(e=e.replace(/(^|[^\\])HH+/g,"$1"+l(u))).replace(/(^|[^\\])H/g,"$1"+u)).replace(/(^|[^\\])hh+/g,"$1"+l(p))).replace(/(^|[^\\])h/g,"$1"+p);var f=a?t.getUTCMinutes():t.getMinutes();e=(e=e.replace(/(^|[^\\])mm+/g,"$1"+l(f))).replace(/(^|[^\\])m/g,"$1"+f);var x=a?t.getUTCSeconds():t.getSeconds();e=(e=e.replace(/(^|[^\\])ss+/g,"$1"+l(x))).replace(/(^|[^\\])s/g,"$1"+x);var b=a?t.getUTCMilliseconds():t.getMilliseconds();e=e.replace(/(^|[^\\])fff+/g,"$1"+l(b,3)),b=Math.round(b/10),e=e.replace(/(^|[^\\])ff/g,"$1"+l(b)),b=Math.round(b/10);var v=u<12?"AM":"PM";e=(e=(e=e.replace(/(^|[^\\])f/g,"$1"+b)).replace(/(^|[^\\])TT+/g,"$1"+v)).replace(/(^|[^\\])T/g,"$1"+v.charAt(0));var m=v.toLowerCase();e=(e=e.replace(/(^|[^\\])tt+/g,"$1"+m)).replace(/(^|[^\\])t/g,"$1"+m.charAt(0));var y=-t.getTimezoneOffset(),w=a||!y?"Z":y>0?"+":"-";if(!a){var k=(y=Math.abs(y))%60;w+=l(Math.floor(y/60))+":"+l(k)}e=e.replace(/(^|[^\\])K/g,"$1"+w);var A=(a?t.getUTCDay():t.getDay())+1;return e=(e=(e=(e=(e=e.replace(new RegExp(o[0],"g"),o[A])).replace(new RegExp(n[0],"g"),n[A])).replace(new RegExp(s[0],"g"),s[c])).replace(new RegExp(r[0],"g"),r[c])).replace(/\\(.)/g,"$1")}},{key:"getTimeUnitsfromTimestamp",value:function(t,e,i){var a=this.w;void 0!==a.config.xaxis.min&&(t=a.config.xaxis.min),void 0!==a.config.xaxis.max&&(e=a.config.xaxis.max);var s=this.getDate(t),r=this.getDate(e),o=this.formatDate(s,"yyyy MM dd HH mm ss fff").split(" "),n=this.formatDate(r,"yyyy MM dd HH mm ss fff").split(" ");return{minMillisecond:parseInt(o[6],10),maxMillisecond:parseInt(n[6],10),minSecond:parseInt(o[5],10),maxSecond:parseInt(n[5],10),minMinute:parseInt(o[4],10),maxMinute:parseInt(n[4],10),minHour:parseInt(o[3],10),maxHour:parseInt(n[3],10),minDate:parseInt(o[2],10),maxDate:parseInt(n[2],10),minMonth:parseInt(o[1],10)-1,maxMonth:parseInt(n[1],10)-1,minYear:parseInt(o[0],10),maxYear:parseInt(n[0],10)}}},{key:"isLeapYear",value:function(t){return t%4==0&&t%100!=0||t%400==0}},{key:"calculcateLastDaysOfMonth",value:function(t,e,i){return this.determineDaysOfMonths(t,e)-i}},{key:"determineDaysOfYear",value:function(t){var e=365;return this.isLeapYear(t)&&(e=366),e}},{key:"determineRemainingDaysOfYear",value:function(t,e,i){var a=this.daysCntOfYear[e]+i;return e>1&&this.isLeapYear()&&a++,a}},{key:"determineDaysOfMonths",value:function(t,e){var i=30;switch(t=p.monthMod(t),!0){case this.months30.indexOf(t)>-1:2===t&&(i=this.isLeapYear(e)?29:28);break;case this.months31.indexOf(t)>-1:default:i=31}return i}}]),t}(),F=function(t){n(s,t);var i=d(s);function s(){return a(this,s),i.apply(this,arguments)}return r(s,[{key:"draw",value:function(t,i){var a=this.w,s=new b(this.ctx);this.rangeBarOptions=this.w.config.plotOptions.rangeBar,this.series=t,this.seriesRangeStart=a.globals.seriesRangeStart,this.seriesRangeEnd=a.globals.seriesRangeEnd,this.barHelpers.initVariables(t);for(var r=s.group({class:"apexcharts-rangebar-series apexcharts-plot-series"}),o=0;o0&&(this.visibleI=this.visibleI+1);var x=0,v=0;this.yRatio.length>1&&(this.yaxisIndex=u);var m=this.barHelpers.initialPositions();d=m.y,h=m.zeroW,c=m.x,v=m.barWidth,n=m.xDivision,l=m.zeroH;for(var y=s.group({class:"apexcharts-datalabels","data:realIndex":u}),w=s.group({class:"apexcharts-rangebar-goals-markers",style:"pointer-events: none"}),k=0;k0}));return a=l.config.plotOptions.bar.rangeBarGroupRows?s+o*g:s+r*this.visibleI+o*g,u>-1&&!l.config.plotOptions.bar.rangeBarOverlap&&(h=l.globals.seriesRangeBar[e][u].overlaps).indexOf(c)>-1&&(a=(r=n.barHeight/h.length)*this.visibleI+o*(100-parseInt(this.barOptions.barHeight,10))/100/2+r*(this.visibleI+h.indexOf(c))+o*g),{barYPosition:a,barHeight:r}}},{key:"drawRangeColumnPaths",value:function(t){var e=t.indexes,i=t.x;t.strokeWidth;var a=t.xDivision,s=t.barWidth,r=t.zeroH,o=this.w,n=e.i,l=e.j,h=this.yRatio[this.yaxisIndex],c=e.realIndex,d=this.getRangeValue(c,l),g=Math.min(d.start,d.end),u=Math.max(d.start,d.end);o.globals.isXNumeric&&(i=(o.globals.seriesX[n][l]-o.globals.minX)/this.xRatio-s/2);var p=i+s*this.visibleI;void 0===this.series[n][l]||null===this.series[n][l]?g=r:(g=r-g/h,u=r-u/h);var f=Math.abs(u-g),x=this.barHelpers.getColumnPaths({barXPosition:p,barWidth:s,y1:g,y2:u,strokeWidth:this.strokeWidth,series:this.seriesRangeEnd,realIndex:e.realIndex,i:c,j:l,w:o});return o.globals.isXNumeric||(i+=a),{pathTo:x.pathTo,pathFrom:x.pathFrom,barHeight:f,x:i,y:u,goalY:this.barHelpers.getGoalValues("y",null,r,n,l),barXPosition:p}}},{key:"drawRangeBarPaths",value:function(t){var e=t.indexes,i=t.y,a=t.y1,s=t.y2,r=t.yDivision,o=t.barHeight,n=t.barYPosition,l=t.zeroW,h=this.w,c=l+a/this.invertedYRatio,d=l+s/this.invertedYRatio,g=Math.abs(d-c),u=this.barHelpers.getBarpaths({barYPosition:n,barHeight:o,x1:c,x2:d,strokeWidth:this.strokeWidth,series:this.seriesRangeEnd,i:e.realIndex,realIndex:e.realIndex,j:e.j,w:h});return h.globals.isXNumeric||(i+=r),{pathTo:u.pathTo,pathFrom:u.pathFrom,barWidth:g,x:d,goalX:this.barHelpers.getGoalValues("x",l,null,e.realIndex,e.j),y:i}}},{key:"getRangeValue",value:function(t,e){var i=this.w;return{start:i.globals.seriesRangeStart[t][e],end:i.globals.seriesRangeEnd[t][e]}}},{key:"getTooltipValues",value:function(t){var e=t.ctx,i=t.seriesIndex,a=t.dataPointIndex,s=t.y1,r=t.y2,o=t.w,n=o.globals.seriesRangeStart[i][a],l=o.globals.seriesRangeEnd[i][a],h=o.globals.labels[a],c=o.config.series[i].name?o.config.series[i].name:"",d=o.config.tooltip.y.formatter,g=o.config.tooltip.y.title.formatter,u={w:o,seriesIndex:i,dataPointIndex:a,start:n,end:l};"function"==typeof g&&(c=g(c,u)),Number.isFinite(s)&&Number.isFinite(r)&&(n=s,l=r,o.config.series[i].data[a].x&&(h=o.config.series[i].data[a].x+":"),"function"==typeof d&&(h=d(h,u)));var p="",f="",x=o.globals.colors[i];if(void 0===o.config.tooltip.x.formatter)if("datetime"===o.config.xaxis.type){var b=new Y(e);p=b.formatDate(b.getDate(n),o.config.tooltip.x.format),f=b.formatDate(b.getDate(l),o.config.tooltip.x.format)}else p=n,f=l;else p=o.config.tooltip.x.formatter(n),f=o.config.tooltip.x.formatter(l);return{start:n,end:l,startVal:p,endVal:f,ylabel:h,color:x,seriesName:c}}},{key:"buildCustomTooltipHTML",value:function(t){var e=t.color,i=t.seriesName;return'
'+(i||"")+'
'+t.ylabel+' '+t.start+' - '+t.end+"
"}}]),s}(E),R=function(){function t(e){a(this,t),this.opts=e}return r(t,[{key:"line",value:function(){return{chart:{animations:{easing:"swing"}},dataLabels:{enabled:!1},stroke:{width:5,curve:"straight"},markers:{size:0,hover:{sizeOffset:6}},xaxis:{crosshairs:{width:1}}}}},{key:"sparkline",value:function(t){this.opts.yaxis[0].show=!1,this.opts.yaxis[0].title.text="",this.opts.yaxis[0].axisBorder.show=!1,this.opts.yaxis[0].axisTicks.show=!1,this.opts.yaxis[0].floating=!0;return p.extend(t,{grid:{show:!1,padding:{left:0,right:0,top:0,bottom:0}},legend:{show:!1},xaxis:{labels:{show:!1},tooltip:{enabled:!1},axisBorder:{show:!1},axisTicks:{show:!1}},chart:{toolbar:{show:!1},zoom:{enabled:!1}},dataLabels:{enabled:!1}})}},{key:"bar",value:function(){return{chart:{stacked:!1,animations:{easing:"swing"}},plotOptions:{bar:{dataLabels:{position:"center"}}},dataLabels:{style:{colors:["#fff"]},background:{enabled:!1}},stroke:{width:0,lineCap:"round"},fill:{opacity:.85},legend:{markers:{shape:"square",radius:2,size:8}},tooltip:{shared:!1,intersect:!0},xaxis:{tooltip:{enabled:!1},tickPlacement:"between",crosshairs:{width:"barWidth",position:"back",fill:{type:"gradient"},dropShadow:{enabled:!1},stroke:{width:0}}}}}},{key:"candlestick",value:function(){var t=this;return{stroke:{width:1,colors:["#333"]},fill:{opacity:1},dataLabels:{enabled:!1},tooltip:{shared:!0,custom:function(e){var i=e.seriesIndex,a=e.dataPointIndex,s=e.w;return t._getBoxTooltip(s,i,a,["Open","High","","Low","Close"],"candlestick")}},states:{active:{filter:{type:"none"}}},xaxis:{crosshairs:{width:1}}}}},{key:"boxPlot",value:function(){var t=this;return{chart:{animations:{dynamicAnimation:{enabled:!1}}},stroke:{width:1,colors:["#24292e"]},dataLabels:{enabled:!1},tooltip:{shared:!0,custom:function(e){var i=e.seriesIndex,a=e.dataPointIndex,s=e.w;return t._getBoxTooltip(s,i,a,["Minimum","Q1","Median","Q3","Maximum"],"boxPlot")}},markers:{size:5,strokeWidth:1,strokeColors:"#111"},xaxis:{crosshairs:{width:1}}}}},{key:"rangeBar",value:function(){return{stroke:{width:0,lineCap:"square"},plotOptions:{bar:{borderRadius:0,dataLabels:{position:"center"}}},dataLabels:{enabled:!1,formatter:function(t,e){e.ctx;var i=e.seriesIndex,a=e.dataPointIndex,s=e.w,r=s.globals.seriesRangeStart[i][a];return s.globals.seriesRangeEnd[i][a]-r},background:{enabled:!1},style:{colors:["#fff"]}},tooltip:{shared:!1,followCursor:!0,custom:function(t){return t.w.config.plotOptions&&t.w.config.plotOptions.bar&&t.w.config.plotOptions.bar.horizontal?function(t){var e=new F(t.ctx,null),i=e.getTooltipValues(t),a=i.color,s=i.seriesName,r=i.ylabel,o=i.startVal,n=i.endVal;return e.buildCustomTooltipHTML({color:a,seriesName:s,ylabel:r,start:o,end:n})}(t):function(t){var e=new F(t.ctx,null),i=e.getTooltipValues(t),a=i.color,s=i.seriesName,r=i.ylabel,o=i.start,n=i.end;return e.buildCustomTooltipHTML({color:a,seriesName:s,ylabel:r,start:o,end:n})}(t)}},xaxis:{tickPlacement:"between",tooltip:{enabled:!1},crosshairs:{stroke:{width:0}}}}}},{key:"area",value:function(){return{stroke:{width:4},fill:{type:"gradient",gradient:{inverseColors:!1,shade:"light",type:"vertical",opacityFrom:.65,opacityTo:.5,stops:[0,100,100]}},markers:{size:0,hover:{sizeOffset:6}},tooltip:{followCursor:!1}}}},{key:"brush",value:function(t){return p.extend(t,{chart:{toolbar:{autoSelected:"selection",show:!1},zoom:{enabled:!1}},dataLabels:{enabled:!1},stroke:{width:1},tooltip:{enabled:!1},xaxis:{tooltip:{enabled:!1}}})}},{key:"stacked100",value:function(t){t.dataLabels=t.dataLabels||{},t.dataLabels.formatter=t.dataLabels.formatter||void 0;var e=t.dataLabels.formatter;return t.yaxis.forEach((function(e,i){t.yaxis[i].min=0,t.yaxis[i].max=100})),"bar"===t.chart.type&&(t.dataLabels.formatter=e||function(t){return"number"==typeof t&&t?t.toFixed(0)+"%":t}),t}},{key:"convertCatToNumeric",value:function(t){return t.xaxis.convertedCatToNumeric=!0,t}},{key:"convertCatToNumericXaxis",value:function(t,e,i){t.xaxis.type="numeric",t.xaxis.labels=t.xaxis.labels||{},t.xaxis.labels.formatter=t.xaxis.labels.formatter||function(t){return p.isNumber(t)?Math.floor(t):t};var a=t.xaxis.labels.formatter,s=t.xaxis.categories&&t.xaxis.categories.length?t.xaxis.categories:t.labels;return i&&i.length&&(s=i.map((function(t){return Array.isArray(t)?t:String(t)}))),s&&s.length&&(t.xaxis.labels.formatter=function(t){return p.isNumber(t)?a(s[Math.floor(t)-1]):a(t)}),t.xaxis.categories=[],t.labels=[],t.xaxis.tickAmount=t.xaxis.tickAmount||"dataPoints",t}},{key:"bubble",value:function(){return{dataLabels:{style:{colors:["#fff"]}},tooltip:{shared:!1,intersect:!0},xaxis:{crosshairs:{width:0}},fill:{type:"solid",gradient:{shade:"light",inverse:!0,shadeIntensity:.55,opacityFrom:.4,opacityTo:.8}}}}},{key:"scatter",value:function(){return{dataLabels:{enabled:!1},tooltip:{shared:!1,intersect:!0},markers:{size:6,strokeWidth:1,hover:{sizeOffset:2}}}}},{key:"heatmap",value:function(){return{chart:{stacked:!1},fill:{opacity:1},dataLabels:{style:{colors:["#fff"]}},stroke:{colors:["#fff"]},tooltip:{followCursor:!0,marker:{show:!1},x:{show:!1}},legend:{position:"top",markers:{shape:"square",size:10,offsetY:2}},grid:{padding:{right:20}}}}},{key:"treemap",value:function(){return{chart:{zoom:{enabled:!1}},dataLabels:{style:{fontSize:14,fontWeight:600,colors:["#fff"]}},stroke:{show:!0,width:2,colors:["#fff"]},legend:{show:!1},fill:{gradient:{stops:[0,100]}},tooltip:{followCursor:!0,x:{show:!1}},grid:{padding:{left:0,right:0}},xaxis:{crosshairs:{show:!1},tooltip:{enabled:!1}}}}},{key:"pie",value:function(){return{chart:{toolbar:{show:!1}},plotOptions:{pie:{donut:{labels:{show:!1}}}},dataLabels:{formatter:function(t){return t.toFixed(1)+"%"},style:{colors:["#fff"]},background:{enabled:!1},dropShadow:{enabled:!0}},stroke:{colors:["#fff"]},fill:{opacity:1,gradient:{shade:"light",stops:[0,100]}},tooltip:{theme:"dark",fillSeriesColor:!0},legend:{position:"right"}}}},{key:"donut",value:function(){return{chart:{toolbar:{show:!1}},dataLabels:{formatter:function(t){return t.toFixed(1)+"%"},style:{colors:["#fff"]},background:{enabled:!1},dropShadow:{enabled:!0}},stroke:{colors:["#fff"]},fill:{opacity:1,gradient:{shade:"light",shadeIntensity:.35,stops:[80,100],opacityFrom:1,opacityTo:1}},tooltip:{theme:"dark",fillSeriesColor:!0},legend:{position:"right"}}}},{key:"polarArea",value:function(){return this.opts.yaxis[0].tickAmount=this.opts.yaxis[0].tickAmount?this.opts.yaxis[0].tickAmount:6,{chart:{toolbar:{show:!1}},dataLabels:{formatter:function(t){return t.toFixed(1)+"%"},enabled:!1},stroke:{show:!0,width:2},fill:{opacity:.7},tooltip:{theme:"dark",fillSeriesColor:!0},legend:{position:"right"}}}},{key:"radar",value:function(){return this.opts.yaxis[0].labels.offsetY=this.opts.yaxis[0].labels.offsetY?this.opts.yaxis[0].labels.offsetY:6,{dataLabels:{enabled:!1,style:{fontSize:"11px"}},stroke:{width:2},markers:{size:3,strokeWidth:1,strokeOpacity:1},fill:{opacity:.2},tooltip:{shared:!1,intersect:!0,followCursor:!0},grid:{show:!1},xaxis:{labels:{formatter:function(t){return t},style:{colors:["#a8a8a8"],fontSize:"11px"}},tooltip:{enabled:!1},crosshairs:{show:!1}}}}},{key:"radialBar",value:function(){return{chart:{animations:{dynamicAnimation:{enabled:!0,speed:800}},toolbar:{show:!1}},fill:{gradient:{shade:"dark",shadeIntensity:.4,inverseColors:!1,type:"diagonal2",opacityFrom:1,opacityTo:1,stops:[70,98,100]}},legend:{show:!1,position:"right"},tooltip:{enabled:!1,fillSeriesColor:!0}}}},{key:"_getBoxTooltip",value:function(t,e,i,a,s){var r=t.globals.seriesCandleO[e][i],o=t.globals.seriesCandleH[e][i],n=t.globals.seriesCandleM[e][i],l=t.globals.seriesCandleL[e][i],h=t.globals.seriesCandleC[e][i];return t.config.series[e].type&&t.config.series[e].type!==s?'
\n '.concat(t.config.series[e].name?t.config.series[e].name:"series-"+(e+1),": ").concat(t.globals.series[e][i],"\n
"):'
')+"
".concat(a[0],': ')+r+"
"+"
".concat(a[1],': ')+o+"
"+(n?"
".concat(a[2],': ')+n+"
":"")+"
".concat(a[3],': ')+l+"
"+"
".concat(a[4],': ')+h+"
"}}]),t}(),H=function(){function t(e){a(this,t),this.opts=e}return r(t,[{key:"init",value:function(t){var e=t.responsiveOverride,a=this.opts,s=new S,r=new R(a);this.chartType=a.chart.type,"histogram"===this.chartType&&(a.chart.type="bar",a=p.extend({plotOptions:{bar:{columnWidth:"99.99%"}}},a)),a=this.extendYAxis(a),a=this.extendAnnotations(a);var o=s.init(),n={};if(a&&"object"===i(a)){var l={};l=-1!==["line","area","bar","candlestick","boxPlot","rangeBar","histogram","bubble","scatter","heatmap","treemap","pie","polarArea","donut","radar","radialBar"].indexOf(a.chart.type)?r[a.chart.type]():r.line(),a.chart.brush&&a.chart.brush.enabled&&(l=r.brush(l)),a.chart.stacked&&"100%"===a.chart.stackType&&(a=r.stacked100(a)),this.checkForDarkTheme(window.Apex),this.checkForDarkTheme(a),a.xaxis=a.xaxis||window.Apex.xaxis||{},e||(a.xaxis.convertedCatToNumeric=!1),((a=this.checkForCatToNumericXAxis(this.chartType,l,a)).chart.sparkline&&a.chart.sparkline.enabled||window.Apex.chart&&window.Apex.chart.sparkline&&window.Apex.chart.sparkline.enabled)&&(l=r.sparkline(l)),n=p.extend(o,l)}var h=p.extend(n,window.Apex);return o=p.extend(h,a),o=this.handleUserInputErrors(o)}},{key:"checkForCatToNumericXAxis",value:function(t,e,i){var a=new R(i),s=("bar"===t||"boxPlot"===t)&&i.plotOptions&&i.plotOptions.bar&&i.plotOptions.bar.horizontal,r="pie"===t||"polarArea"===t||"donut"===t||"radar"===t||"radialBar"===t||"heatmap"===t,o="datetime"!==i.xaxis.type&&"numeric"!==i.xaxis.type,n=i.xaxis.tickPlacement?i.xaxis.tickPlacement:e.xaxis&&e.xaxis.tickPlacement;return s||r||!o||"between"===n||(i=a.convertCatToNumeric(i)),i}},{key:"extendYAxis",value:function(t,e){var i=new S;(void 0===t.yaxis||!t.yaxis||Array.isArray(t.yaxis)&&0===t.yaxis.length)&&(t.yaxis={}),t.yaxis.constructor!==Array&&window.Apex.yaxis&&window.Apex.yaxis.constructor!==Array&&(t.yaxis=p.extend(t.yaxis,window.Apex.yaxis)),t.yaxis.constructor!==Array?t.yaxis=[p.extend(i.yAxis,t.yaxis)]:t.yaxis=p.extendArray(t.yaxis,i.yAxis);var a=!1;t.yaxis.forEach((function(t){t.logarithmic&&(a=!0)}));var s=t.series;return e&&!s&&(s=e.config.series),a&&s.length!==t.yaxis.length&&s.length&&(t.yaxis=s.map((function(e,a){if(e.name||(s[a].name="series-".concat(a+1)),t.yaxis[a])return t.yaxis[a].seriesName=s[a].name,t.yaxis[a];var r=p.extend(i.yAxis,t.yaxis[0]);return r.show=!1,r}))),a&&s.length>1&&s.length!==t.yaxis.length&&console.warn("A multi-series logarithmic chart should have equal number of series and y-axes. Please make sure to equalize both."),t}},{key:"extendAnnotations",value:function(t){return void 0===t.annotations&&(t.annotations={},t.annotations.yaxis=[],t.annotations.xaxis=[],t.annotations.points=[]),t=this.extendYAxisAnnotations(t),t=this.extendXAxisAnnotations(t),t=this.extendPointAnnotations(t)}},{key:"extendYAxisAnnotations",value:function(t){var e=new S;return t.annotations.yaxis=p.extendArray(void 0!==t.annotations.yaxis?t.annotations.yaxis:[],e.yAxisAnnotation),t}},{key:"extendXAxisAnnotations",value:function(t){var e=new S;return t.annotations.xaxis=p.extendArray(void 0!==t.annotations.xaxis?t.annotations.xaxis:[],e.xAxisAnnotation),t}},{key:"extendPointAnnotations",value:function(t){var e=new S;return t.annotations.points=p.extendArray(void 0!==t.annotations.points?t.annotations.points:[],e.pointAnnotation),t}},{key:"checkForDarkTheme",value:function(t){t.theme&&"dark"===t.theme.mode&&(t.tooltip||(t.tooltip={}),"light"!==t.tooltip.theme&&(t.tooltip.theme="dark"),t.chart.foreColor||(t.chart.foreColor="#f6f7f8"),t.chart.background||(t.chart.background="#424242"),t.theme.palette||(t.theme.palette="palette4"))}},{key:"handleUserInputErrors",value:function(t){var e=t;if(e.tooltip.shared&&e.tooltip.intersect)throw new Error("tooltip.shared cannot be enabled when tooltip.intersect is true. Turn off any other option by setting it to false.");if("bar"===e.chart.type&&e.plotOptions.bar.horizontal){if(e.yaxis.length>1)throw new Error("Multiple Y Axis for bars are not supported. Switch to column chart by setting plotOptions.bar.horizontal=false");e.yaxis[0].reversed&&(e.yaxis[0].opposite=!0),e.xaxis.tooltip.enabled=!1,e.yaxis[0].tooltip.enabled=!1,e.chart.zoom.enabled=!1}return"bar"!==e.chart.type&&"rangeBar"!==e.chart.type||e.tooltip.shared&&"barWidth"===e.xaxis.crosshairs.width&&e.series.length>1&&(e.xaxis.crosshairs.width="tickWidth"),"candlestick"!==e.chart.type&&"boxPlot"!==e.chart.type||e.yaxis[0].reversed&&(console.warn("Reversed y-axis in ".concat(e.chart.type," chart is not supported.")),e.yaxis[0].reversed=!1),Array.isArray(e.stroke.width)&&"line"!==e.chart.type&&"area"!==e.chart.type&&(console.warn("stroke.width option accepts array only for line and area charts. Reverted back to Number"),e.stroke.width=e.stroke.width[0]),e}}]),t}(),D=function(){function t(){a(this,t)}return r(t,[{key:"initGlobalVars",value:function(t){t.series=[],t.seriesCandleO=[],t.seriesCandleH=[],t.seriesCandleM=[],t.seriesCandleL=[],t.seriesCandleC=[],t.seriesRangeStart=[],t.seriesRangeEnd=[],t.seriesRangeBar=[],t.seriesPercent=[],t.seriesGoals=[],t.seriesX=[],t.seriesZ=[],t.seriesNames=[],t.seriesTotals=[],t.seriesLog=[],t.seriesColors=[],t.stackedSeriesTotals=[],t.seriesXvalues=[],t.seriesYvalues=[],t.labels=[],t.categoryLabels=[],t.timescaleLabels=[],t.noLabelsProvided=!1,t.resizeTimer=null,t.selectionResizeTimer=null,t.delayedElements=[],t.pointsArray=[],t.dataLabelsRects=[],t.isXNumeric=!1,t.xaxisLabelsCount=0,t.skipLastTimelinelabel=!1,t.skipFirstTimelinelabel=!1,t.isDataXYZ=!1,t.isMultiLineX=!1,t.isMultipleYAxis=!1,t.maxY=-Number.MAX_VALUE,t.minY=Number.MIN_VALUE,t.minYArr=[],t.maxYArr=[],t.maxX=-Number.MAX_VALUE,t.minX=Number.MAX_VALUE,t.initialMaxX=-Number.MAX_VALUE,t.initialMinX=Number.MAX_VALUE,t.maxDate=0,t.minDate=Number.MAX_VALUE,t.minZ=Number.MAX_VALUE,t.maxZ=-Number.MAX_VALUE,t.minXDiff=Number.MAX_VALUE,t.yAxisScale=[],t.xAxisScale=null,t.xAxisTicksPositions=[],t.yLabelsCoords=[],t.yTitleCoords=[],t.barPadForNumericAxis=0,t.padHorizontal=0,t.xRange=0,t.yRange=[],t.zRange=0,t.dataPoints=0,t.xTickAmount=0}},{key:"globalVars",value:function(t){return{chartID:null,cuid:null,events:{beforeMount:[],mounted:[],updated:[],clicked:[],selection:[],dataPointSelection:[],zoomed:[],scrolled:[]},colors:[],clientX:null,clientY:null,fill:{colors:[]},stroke:{colors:[]},dataLabels:{style:{colors:[]}},radarPolygons:{fill:{colors:[]}},markers:{colors:[],size:t.markers.size,largestSize:0},animationEnded:!1,isTouchDevice:"ontouchstart"in window||navigator.msMaxTouchPoints,isDirty:!1,isExecCalled:!1,initialConfig:null,initialSeries:[],lastXAxis:[],lastYAxis:[],columnSeries:null,labels:[],timescaleLabels:[],noLabelsProvided:!1,allSeriesCollapsed:!1,collapsedSeries:[],collapsedSeriesIndices:[],ancillaryCollapsedSeries:[],ancillaryCollapsedSeriesIndices:[],risingSeries:[],dataFormatXNumeric:!1,capturedSeriesIndex:-1,capturedDataPointIndex:-1,selectedDataPoints:[],goldenPadding:35,invalidLogScale:!1,ignoreYAxisIndexes:[],yAxisSameScaleIndices:[],maxValsInArrayIndex:0,radialSize:0,selection:void 0,zoomEnabled:"zoom"===t.chart.toolbar.autoSelected&&t.chart.toolbar.tools.zoom&&t.chart.zoom.enabled,panEnabled:"pan"===t.chart.toolbar.autoSelected&&t.chart.toolbar.tools.pan,selectionEnabled:"selection"===t.chart.toolbar.autoSelected&&t.chart.toolbar.tools.selection,yaxis:null,mousedown:!1,lastClientPosition:{},visibleXRange:void 0,yValueDecimal:0,total:0,SVGNS:"http://www.w3.org/2000/svg",svgWidth:0,svgHeight:0,noData:!1,locale:{},dom:{},memory:{methodsToExec:[]},shouldAnimate:!0,skipLastTimelinelabel:!1,skipFirstTimelinelabel:!1,delayedElements:[],axisCharts:!0,isDataXYZ:!1,resized:!1,resizeTimer:null,comboCharts:!1,dataChanged:!1,previousPaths:[],allSeriesHasEqualX:!0,pointsArray:[],dataLabelsRects:[],lastDrawnDataLabelsIndexes:[],hasNullValues:!1,easing:null,zoomed:!1,gridWidth:0,gridHeight:0,rotateXLabels:!1,defaultLabels:!1,xLabelFormatter:void 0,yLabelFormatters:[],xaxisTooltipFormatter:void 0,ttKeyFormatter:void 0,ttVal:void 0,ttZFormatter:void 0,LINE_HEIGHT_RATIO:1.618,xAxisLabelsHeight:0,xAxisLabelsWidth:0,yAxisLabelsWidth:0,scaleX:1,scaleY:1,translateX:0,translateY:0,translateYAxisX:[],yAxisWidths:[],translateXAxisY:0,translateXAxisX:0,tooltip:null}}},{key:"init",value:function(t){var e=this.globalVars(t);return this.initGlobalVars(e),e.initialConfig=p.extend({},t),e.initialSeries=p.clone(t.series),e.lastXAxis=p.clone(e.initialConfig.xaxis),e.lastYAxis=p.clone(e.initialConfig.yaxis),e}}]),t}(),N=function(){function t(e){a(this,t),this.opts=e}return r(t,[{key:"init",value:function(){var t=new H(this.opts).init({responsiveOverride:!1});return{config:t,globals:(new D).init(t)}}}]),t}(),O=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.twoDSeries=[],this.threeDSeries=[],this.twoDSeriesX=[],this.seriesGoals=[],this.coreUtils=new y(this.ctx)}return r(t,[{key:"isMultiFormat",value:function(){return this.isFormatXY()||this.isFormat2DArray()}},{key:"isFormatXY",value:function(){var t=this.w.config.series.slice(),e=new z(this.ctx);if(this.activeSeriesIndex=e.getActiveConfigSeriesIndex(),void 0!==t[this.activeSeriesIndex].data&&t[this.activeSeriesIndex].data.length>0&&null!==t[this.activeSeriesIndex].data[0]&&void 0!==t[this.activeSeriesIndex].data[0].x&&null!==t[this.activeSeriesIndex].data[0])return!0}},{key:"isFormat2DArray",value:function(){var t=this.w.config.series.slice(),e=new z(this.ctx);if(this.activeSeriesIndex=e.getActiveConfigSeriesIndex(),void 0!==t[this.activeSeriesIndex].data&&t[this.activeSeriesIndex].data.length>0&&void 0!==t[this.activeSeriesIndex].data[0]&&null!==t[this.activeSeriesIndex].data[0]&&t[this.activeSeriesIndex].data[0].constructor===Array)return!0}},{key:"handleFormat2DArray",value:function(t,e){for(var i=this.w.config,a=this.w.globals,s="boxPlot"===i.chart.type||"boxPlot"===i.series[e].type,r=0;r=5?this.twoDSeries.push(p.parseNumber(t[e].data[r][4])):this.twoDSeries.push(p.parseNumber(t[e].data[r][1])),a.dataFormatXNumeric=!0),"datetime"===i.xaxis.type){var o=new Date(t[e].data[r][0]);o=new Date(o).getTime(),this.twoDSeriesX.push(o)}else this.twoDSeriesX.push(t[e].data[r][0]);for(var n=0;n-1&&(r=this.activeSeriesIndex);for(var o=0;o1&&void 0!==arguments[1]?arguments[1]:this.ctx,a=this.w.config,s=this.w.globals,r=new Y(i),o=a.labels.length>0?a.labels.slice():a.xaxis.categories.slice();s.isRangeBar="rangeBar"===a.chart.type&&s.isBarHorizontal;for(var n=function(){for(var t=0;t0&&(this.twoDSeriesX=o,s.seriesX.push(this.twoDSeriesX))),s.labels.push(this.twoDSeriesX);var h=t[l].data.map((function(t){return p.parseNumber(t)}));s.series.push(h)}s.seriesZ.push(this.threeDSeries),void 0!==t[l].name?s.seriesNames.push(t[l].name):s.seriesNames.push("series-"+parseInt(l+1,10)),void 0!==t[l].color?s.seriesColors.push(t[l].color):s.seriesColors.push(void 0)}return this.w}},{key:"parseDataNonAxisCharts",value:function(t){var e=this.w.globals,i=this.w.config;e.series=t.slice(),e.seriesNames=i.labels.slice();for(var a=0;a0)i.labels=e.xaxis.categories;else if(e.labels.length>0)i.labels=e.labels.slice();else if(this.fallbackToCategory){if(i.labels=i.labels[0],i.seriesRangeBar.length&&(i.seriesRangeBar.map((function(t){t.forEach((function(t){i.labels.indexOf(t.x)<0&&t.x&&i.labels.push(t.x)}))})),i.labels=i.labels.filter((function(t,e,i){return i.indexOf(t)===e}))),e.xaxis.convertedCatToNumeric)new R(e).convertCatToNumericXaxis(e,this.ctx,i.seriesX[0]),this._generateExternalLabels(t)}else this._generateExternalLabels(t)}},{key:"_generateExternalLabels",value:function(t){var e=this.w.globals,i=this.w.config,a=[];if(e.axisCharts){if(e.series.length>0)for(var s=0;s0&&i<100?t.toFixed(1):t.toFixed(0)}if(e.globals.isBarHorizontal)if(e.globals.maxY-e.globals.minYArr<4)return t.toFixed(1);return t.toFixed(0)}return t},"function"==typeof e.config.tooltip.x.formatter?e.globals.ttKeyFormatter=e.config.tooltip.x.formatter:e.globals.ttKeyFormatter=e.globals.xLabelFormatter,"function"==typeof e.config.xaxis.tooltip.formatter&&(e.globals.xaxisTooltipFormatter=e.config.xaxis.tooltip.formatter),(Array.isArray(e.config.tooltip.y)||void 0!==e.config.tooltip.y.formatter)&&(e.globals.ttVal=e.config.tooltip.y),void 0!==e.config.tooltip.z.formatter&&(e.globals.ttZFormatter=e.config.tooltip.z.formatter),void 0!==e.config.legend.formatter&&(e.globals.legendFormatter=e.config.legend.formatter),e.config.yaxis.forEach((function(i,a){void 0!==i.labels.formatter?e.globals.yLabelFormatters[a]=i.labels.formatter:e.globals.yLabelFormatters[a]=function(s){return e.globals.xyCharts?Array.isArray(s)?s.map((function(e){return t.defaultYFormatter(e,i,a)})):t.defaultYFormatter(s,i,a):s}})),e.globals}},{key:"heatmapLabelFormatters",value:function(){var t=this.w;if("heatmap"===t.config.chart.type){t.globals.yAxisScale[0].result=t.globals.seriesNames.slice();var e=t.globals.seriesNames.reduce((function(t,e){return t.length>e.length?t:e}),0);t.globals.yAxisScale[0].niceMax=e,t.globals.yAxisScale[0].niceMin=e}}}]),t}(),B=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"getLabel",value:function(t,e,i,a){var s=arguments.length>4&&void 0!==arguments[4]?arguments[4]:[],r=arguments.length>5&&void 0!==arguments[5]?arguments[5]:"12px",o=this.w,n=void 0===t[a]?"":t[a],l=n,h=o.globals.xLabelFormatter,c=o.config.xaxis.labels.formatter,d=!1,g=new W(this.ctx),u=n;l=g.xLabelFormat(h,n,u,{i:a,dateFormatter:new Y(this.ctx).formatDate,w:o}),void 0!==c&&(l=c(n,t[a],{i:a,dateFormatter:new Y(this.ctx).formatDate,w:o}));var p=function(t){var i=null;return e.forEach((function(t){"month"===t.unit?i="year":"day"===t.unit?i="month":"hour"===t.unit?i="day":"minute"===t.unit&&(i="hour")})),i===t};e.length>0?(d=p(e[a].unit),i=e[a].position,l=e[a].value):"datetime"===o.config.xaxis.type&&void 0===c&&(l=""),void 0===l&&(l=""),l=Array.isArray(l)?l:l.toString();var f=new b(this.ctx),x={};x=o.globals.rotateXLabels?f.getTextRects(l,parseInt(r,10),null,"rotate(".concat(o.config.xaxis.labels.rotate," 0 0)"),!1):f.getTextRects(l,parseInt(r,10));var v=!o.config.xaxis.labels.showDuplicates&&this.ctx.timeScale;return!Array.isArray(l)&&(0===l.indexOf("NaN")||0===l.toLowerCase().indexOf("invalid")||l.toLowerCase().indexOf("infinity")>=0||s.indexOf(l)>=0&&v)&&(l=""),{x:i,text:l,textRect:x,isBold:d}}},{key:"checkLabelBasedOnTickamount",value:function(t,e,i){var a=this.w,s=a.config.xaxis.tickAmount;return"dataPoints"===s&&(s=Math.round(a.globals.gridWidth/120)),s>i||t%Math.round(i/(s+1))==0||(e.text=""),e}},{key:"checkForOverflowingLabels",value:function(t,e,i,a,s){var r=this.w;if(0===t&&r.globals.skipFirstTimelinelabel&&(e.text=""),t===i-1&&r.globals.skipLastTimelinelabel&&(e.text=""),r.config.xaxis.labels.hideOverlappingLabels&&a.length>0){var o=s[s.length-1];e.x0){!0===n.config.yaxis[s].opposite&&(t+=a.width);for(var c=e;c>=0;c--){var d=h+e/10+n.config.yaxis[s].labels.offsetY-1;n.globals.isBarHorizontal&&(d=r*c),"heatmap"===n.config.chart.type&&(d+=r/2);var g=l.drawLine(t+i.offsetX-a.width+a.offsetX,d+a.offsetY,t+i.offsetX+a.offsetX,d+a.offsetY,a.color);o.add(g),h+=r}}}}]),t}(),V=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"scaleSvgNode",value:function(t,e){var i=parseFloat(t.getAttributeNS(null,"width")),a=parseFloat(t.getAttributeNS(null,"height"));t.setAttributeNS(null,"width",i*e),t.setAttributeNS(null,"height",a*e),t.setAttributeNS(null,"viewBox","0 0 "+i+" "+a)}},{key:"fixSvgStringForIe11",value:function(t){if(!p.isIE11())return t.replace(/ /g," ");var e=0,i=t.replace(/xmlns="http:\/\/www.w3.org\/2000\/svg"/g,(function(t){return 2===++e?'xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev"':t}));return i=(i=i.replace(/xmlns:NS\d+=""/g,"")).replace(/NS\d+:(\w+:\w+=")/g,"$1")}},{key:"getSvgString",value:function(t){var e=this.w.globals.dom.Paper.svg();if(1!==t){var i=this.w.globals.dom.Paper.node.cloneNode(!0);this.scaleSvgNode(i,t),e=(new XMLSerializer).serializeToString(i)}return this.fixSvgStringForIe11(e)}},{key:"cleanup",value:function(){var t=this.w,e=t.globals.dom.baseEl.getElementsByClassName("apexcharts-xcrosshairs"),i=t.globals.dom.baseEl.getElementsByClassName("apexcharts-ycrosshairs"),a=t.globals.dom.baseEl.querySelectorAll(".apexcharts-zoom-rect, .apexcharts-selection-rect");Array.prototype.forEach.call(a,(function(t){t.setAttribute("width",0)})),e&&e[0]&&(e[0].setAttribute("x",-500),e[0].setAttribute("x1",-500),e[0].setAttribute("x2",-500)),i&&i[0]&&(i[0].setAttribute("y",-100),i[0].setAttribute("y1",-100),i[0].setAttribute("y2",-100))}},{key:"svgUrl",value:function(){this.cleanup();var t=this.getSvgString(),e=new Blob([t],{type:"image/svg+xml;charset=utf-8"});return URL.createObjectURL(e)}},{key:"dataURI",value:function(t){var e=this;return new Promise((function(i){var a=e.w,s=t?t.scale||t.width/a.globals.svgWidth:1;e.cleanup();var r=document.createElement("canvas");r.width=a.globals.svgWidth*s,r.height=parseInt(a.globals.dom.elWrap.style.height,10)*s;var o="transparent"===a.config.chart.background?"#fff":a.config.chart.background,n=r.getContext("2d");n.fillStyle=o,n.fillRect(0,0,r.width*s,r.height*s);var l=e.getSvgString(s);if(window.canvg&&p.isIE11()){var h=window.canvg.Canvg.fromString(n,l,{ignoreClear:!0,ignoreDimensions:!0});h.start();var c=r.msToBlob();h.stop(),i({blob:c})}else{var d="data:image/svg+xml,"+encodeURIComponent(l),g=new Image;g.crossOrigin="anonymous",g.onload=function(){if(n.drawImage(g,0,0),r.msToBlob){var t=r.msToBlob();i({blob:t})}else{var e=r.toDataURL("image/png");i({imgURI:e})}},g.src=d}}))}},{key:"exportToSVG",value:function(){this.triggerDownload(this.svgUrl(),this.w.config.chart.toolbar.export.svg.filename,".svg")}},{key:"exportToPng",value:function(){var t=this;this.dataURI().then((function(e){var i=e.imgURI,a=e.blob;a?navigator.msSaveOrOpenBlob(a,t.w.globals.chartID+".png"):t.triggerDownload(i,t.w.config.chart.toolbar.export.png.filename,".png")}))}},{key:"exportToCSV",value:function(t){var e=this,i=t.series,a=t.columnDelimiter,s=t.lineDelimiter,r=void 0===s?"\n":s,o=this.w,n=[],l=[],h="",c=new O(this.ctx),d=new B(this.ctx),g=function(t){var i="";if(o.globals.axisCharts){if("category"===o.config.xaxis.type||o.config.xaxis.convertedCatToNumeric)if(o.globals.isBarHorizontal){var s=o.globals.yLabelFormatters[0],r=new z(e.ctx).getActiveConfigSeriesIndex();i=s(o.globals.labels[t],{seriesIndex:r,dataPointIndex:t,w:o})}else i=d.getLabel(o.globals.labels,o.globals.timescaleLabels,0,t).text;"datetime"===o.config.xaxis.type&&(o.config.xaxis.categories.length?i=o.config.xaxis.categories[t]:o.config.labels.length&&(i=o.config.labels[t]))}else i=o.config.labels[t];return Array.isArray(i)&&(i=i.join(" ")),p.isNumber(i)?i:i.split(a).join("")};n.push(o.config.chart.toolbar.export.csv.headerCategory),i.map((function(t,e){var i=t.name?t.name:"series-".concat(e);o.globals.axisCharts&&n.push(i.split(a).join("")?i.split(a).join(""):"series-".concat(e))})),o.globals.axisCharts||(n.push(o.config.chart.toolbar.export.csv.headerValue),l.push(n.join(a))),i.map((function(t,e){o.globals.axisCharts?function(t,e){if(n.length&&0===e&&l.push(n.join(a)),t.data&&t.data.length)for(var s=0;s=10?o.config.chart.toolbar.export.csv.dateFormatter(r):p.isNumber(r)?r:r.split(a).join("")));for(var h=0;h0&&!i.globals.isBarHorizontal&&(this.xaxisLabels=i.globals.timescaleLabels.slice()),i.config.xaxis.overwriteCategories&&(this.xaxisLabels=i.config.xaxis.overwriteCategories),this.drawnLabels=[],this.drawnLabelsRects=[],"top"===i.config.xaxis.position?this.offY=0:this.offY=i.globals.gridHeight+1,this.offY=this.offY+i.config.xaxis.axisBorder.offsetY,this.isCategoryBarHorizontal="bar"===i.config.chart.type&&i.config.plotOptions.bar.horizontal,this.xaxisFontSize=i.config.xaxis.labels.style.fontSize,this.xaxisFontFamily=i.config.xaxis.labels.style.fontFamily,this.xaxisForeColors=i.config.xaxis.labels.style.colors,this.xaxisBorderWidth=i.config.xaxis.axisBorder.width,this.isCategoryBarHorizontal&&(this.xaxisBorderWidth=i.config.yaxis[0].axisBorder.width.toString()),this.xaxisBorderWidth.indexOf("%")>-1?this.xaxisBorderWidth=i.globals.gridWidth*parseInt(this.xaxisBorderWidth,10)/100:this.xaxisBorderWidth=parseInt(this.xaxisBorderWidth,10),this.xaxisBorderHeight=i.config.xaxis.axisBorder.height,this.yaxis=i.config.yaxis[0]}return r(t,[{key:"drawXaxis",value:function(){var t,e=this,i=this.w,a=new b(this.ctx),s=a.group({class:"apexcharts-xaxis",transform:"translate(".concat(i.config.xaxis.offsetX,", ").concat(i.config.xaxis.offsetY,")")}),r=a.group({class:"apexcharts-xaxis-texts-g",transform:"translate(".concat(i.globals.translateXAxisX,", ").concat(i.globals.translateXAxisY,")")});s.add(r);for(var o=i.globals.padHorizontal,n=[],l=0;l1?h-1:h;t=i.globals.gridWidth/c,o=o+t/2+i.config.xaxis.labels.offsetX}else t=i.globals.gridWidth/n.length,o=o+t+i.config.xaxis.labels.offsetX;for(var d=function(s){var l=o-t/2+i.config.xaxis.labels.offsetX;0===s&&1===h&&t/2===o&&1===i.globals.dataPoints&&(l=i.globals.gridWidth/2);var c=e.axesUtils.getLabel(n,i.globals.timescaleLabels,l,s,e.drawnLabels,e.xaxisFontSize),d=28;i.globals.rotateXLabels&&(d=22);if((c=void 0!==i.config.xaxis.tickAmount&&"dataPoints"!==i.config.xaxis.tickAmount&&"datetime"!==i.config.xaxis.type?e.axesUtils.checkLabelBasedOnTickamount(s,c,h):e.axesUtils.checkForOverflowingLabels(s,c,h,e.drawnLabels,e.drawnLabelsRects)).text&&i.globals.xaxisLabelsCount++,i.config.xaxis.labels.show){var g=a.drawText({x:c.x,y:e.offY+i.config.xaxis.labels.offsetY+d-("top"===i.config.xaxis.position?i.globals.xAxisHeight+i.config.xaxis.axisTicks.height-2:0),text:c.text,textAnchor:"middle",fontWeight:c.isBold?600:i.config.xaxis.labels.style.fontWeight,fontSize:e.xaxisFontSize,fontFamily:e.xaxisFontFamily,foreColor:Array.isArray(e.xaxisForeColors)?i.config.xaxis.convertedCatToNumeric?e.xaxisForeColors[i.globals.minX+s-1]:e.xaxisForeColors[s]:e.xaxisForeColors,isPlainText:!1,cssClass:"apexcharts-xaxis-label "+i.config.xaxis.labels.style.cssClass});r.add(g);var u=document.createElementNS(i.globals.SVGNS,"title");u.textContent=Array.isArray(c.text)?c.text.join(" "):c.text,g.node.appendChild(u),""!==c.text&&(e.drawnLabels.push(c.text),e.drawnLabelsRects.push(c))}o+=t},g=0;g<=h-1;g++)d(g);if(void 0!==i.config.xaxis.title.text){var u=a.group({class:"apexcharts-xaxis-title"}),p=a.drawText({x:i.globals.gridWidth/2+i.config.xaxis.title.offsetX,y:this.offY+parseFloat(this.xaxisFontSize)+i.globals.xAxisLabelsHeight+i.config.xaxis.title.offsetY,text:i.config.xaxis.title.text,textAnchor:"middle",fontSize:i.config.xaxis.title.style.fontSize,fontFamily:i.config.xaxis.title.style.fontFamily,fontWeight:i.config.xaxis.title.style.fontWeight,foreColor:i.config.xaxis.title.style.color,cssClass:"apexcharts-xaxis-title-text "+i.config.xaxis.title.style.cssClass});u.add(p),s.add(u)}if(i.config.xaxis.axisBorder.show){var f=i.globals.barPadForNumericAxis,x=a.drawLine(i.globals.padHorizontal+i.config.xaxis.axisBorder.offsetX-f,this.offY,this.xaxisBorderWidth+f,this.offY,i.config.xaxis.axisBorder.color,0,this.xaxisBorderHeight);s.add(x)}return s}},{key:"drawXaxisInversed",value:function(t){var e,i,a=this,s=this.w,r=new b(this.ctx),o=s.config.yaxis[0].opposite?s.globals.translateYAxisX[t]:0,n=r.group({class:"apexcharts-yaxis apexcharts-xaxis-inversed",rel:t}),l=r.group({class:"apexcharts-yaxis-texts-g apexcharts-xaxis-inversed-texts-g",transform:"translate("+o+", 0)"});n.add(l);var h=[];if(s.config.yaxis[t].show)for(var c=0;ci.globals.gridWidth)){var s=this.offY+i.config.xaxis.axisTicks.offsetY,r=s+i.config.xaxis.axisTicks.height;if("top"===i.config.xaxis.position&&(r=s-i.config.xaxis.axisTicks.height),i.config.xaxis.axisTicks.show){var o=new b(this.ctx).drawLine(t+i.config.xaxis.axisTicks.offsetX,s+i.config.xaxis.offsetY,a+i.config.xaxis.axisTicks.offsetX,r+i.config.xaxis.offsetY,i.config.xaxis.axisTicks.color);e.add(o),o.node.classList.add("apexcharts-xaxis-tick")}}}},{key:"getXAxisTicksPositions",value:function(){var t=this.w,e=[],i=this.xaxisLabels.length,a=t.globals.padHorizontal;if(t.globals.timescaleLabels.length>0)for(var s=0;s0){var h=s[s.length-1].getBBox(),c=s[0].getBBox();h.x<-20&&s[s.length-1].parentNode.removeChild(s[s.length-1]),c.x+c.width>t.globals.gridWidth&&!t.globals.isBarHorizontal&&s[0].parentNode.removeChild(s[0]);for(var d=0;d0&&(this.xaxisLabels=i.globals.timescaleLabels.slice())}return r(t,[{key:"drawGridArea",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,e=this.w,i=new b(this.ctx);null===t&&(t=i.group({class:"apexcharts-grid"}));var a=i.drawLine(e.globals.padHorizontal,1,e.globals.padHorizontal,e.globals.gridHeight,"transparent"),s=i.drawLine(e.globals.padHorizontal,e.globals.gridHeight,e.globals.gridWidth,e.globals.gridHeight,"transparent");return t.add(s),t.add(a),t}},{key:"drawGrid",value:function(){var t=null;return this.w.globals.axisCharts&&(t=this.renderGrid(),this.drawGridArea(t.el)),t}},{key:"createGridMask",value:function(){var t=this.w,e=t.globals,i=new b(this.ctx),a=Array.isArray(t.config.stroke.width)?0:t.config.stroke.width;if(Array.isArray(t.config.stroke.width)){var s=0;t.config.stroke.width.forEach((function(t){s=Math.max(s,t)})),a=s}e.dom.elGridRectMask=document.createElementNS(e.SVGNS,"clipPath"),e.dom.elGridRectMask.setAttribute("id","gridRectMask".concat(e.cuid)),e.dom.elGridRectMarkerMask=document.createElementNS(e.SVGNS,"clipPath"),e.dom.elGridRectMarkerMask.setAttribute("id","gridRectMarkerMask".concat(e.cuid)),e.dom.elForecastMask=document.createElementNS(e.SVGNS,"clipPath"),e.dom.elForecastMask.setAttribute("id","forecastMask".concat(e.cuid)),e.dom.elNonForecastMask=document.createElementNS(e.SVGNS,"clipPath"),e.dom.elNonForecastMask.setAttribute("id","nonForecastMask".concat(e.cuid));var r=t.config.chart.type,o=0,n=0;("bar"===r||"rangeBar"===r||"candlestick"===r||"boxPlot"===r||t.globals.comboBarCount>0)&&t.globals.isXNumeric&&!t.globals.isBarHorizontal&&(o=t.config.grid.padding.left,n=t.config.grid.padding.right,e.barPadForNumericAxis>o&&(o=e.barPadForNumericAxis,n=e.barPadForNumericAxis)),e.dom.elGridRect=i.drawRect(-a/2-o-2,-a/2,e.gridWidth+a+n+o+4,e.gridHeight+a,0,"#fff"),new y(this).getLargestMarkerSize();var l=t.globals.markers.largestSize+1;e.dom.elGridRectMarker=i.drawRect(2*-l,2*-l,e.gridWidth+4*l,e.gridHeight+4*l,0,"#fff"),e.dom.elGridRectMask.appendChild(e.dom.elGridRect.node),e.dom.elGridRectMarkerMask.appendChild(e.dom.elGridRectMarker.node);var h=e.dom.baseEl.querySelector("defs");h.appendChild(e.dom.elGridRectMask),h.appendChild(e.dom.elForecastMask),h.appendChild(e.dom.elNonForecastMask),h.appendChild(e.dom.elGridRectMarkerMask)}},{key:"_drawGridLines",value:function(t){var e=t.i,i=t.x1,a=t.y1,s=t.x2,r=t.y2,o=t.xCount,n=t.parent,l=this.w;0===e&&l.globals.skipFirstTimelinelabel||e===o-1&&l.globals.skipLastTimelinelabel&&!l.config.xaxis.labels.formatter||"radar"===l.config.chart.type||(l.config.grid.xaxis.lines.show&&this._drawGridLine({x1:i,y1:a,x2:s,y2:r,parent:n}),new G(this.ctx).drawXaxisTicks(i,this.elg))}},{key:"_drawGridLine",value:function(t){var e=t.x1,i=t.y1,a=t.x2,s=t.y2,r=t.parent,o=this.w,n=r.node.classList.contains("apexcharts-gridlines-horizontal"),l=o.config.grid.strokeDashArray,h=o.globals.barPadForNumericAxis,c=new b(this).drawLine(e-(n?h:0),i,a+(n?h:0),s,o.config.grid.borderColor,l);c.node.classList.add("apexcharts-gridline"),r.add(c)}},{key:"_drawGridBandRect",value:function(t){var e=t.c,i=t.x1,a=t.y1,s=t.x2,r=t.y2,o=t.type,n=this.w,l=new b(this.ctx),h=n.globals.barPadForNumericAxis;if("column"!==o||"datetime"!==n.config.xaxis.type){var c=n.config.grid[o].colors[e],d=l.drawRect(i-("row"===o?h:0),a,s+("row"===o?2*h:0),r,0,c,n.config.grid[o].opacity);this.elg.add(d),d.attr("clip-path","url(#gridRectMask".concat(n.globals.cuid,")")),d.node.classList.add("apexcharts-grid-".concat(o))}}},{key:"_drawXYLines",value:function(t){var e=this,i=t.xCount,a=t.tickAmount,s=this.w;if(s.config.grid.xaxis.lines.show||s.config.xaxis.axisTicks.show){var r,o=s.globals.padHorizontal,n=s.globals.gridHeight;s.globals.timescaleLabels.length?function(t){for(var a=t.xC,s=t.x1,r=t.y1,o=t.x2,n=t.y2,l=0;l2));s++);return!t.globals.isBarHorizontal||this.isRangeBar?(i=this.xaxisLabels.length,this.isRangeBar&&(a=t.globals.labels.length,t.config.xaxis.tickAmount&&t.config.xaxis.labels.formatter&&(i=t.config.xaxis.tickAmount)),this._drawXYLines({xCount:i,tickAmount:a})):(i=a,a=t.globals.xTickAmount,this._drawInvertedXYLines({xCount:i,tickAmount:a})),this.drawGridBands(i,a),{el:this.elg,xAxisTickWidth:t.globals.gridWidth/i}}},{key:"drawGridBands",value:function(t,e){var i=this.w;if(void 0!==i.config.grid.row.colors&&i.config.grid.row.colors.length>0)for(var a=0,s=i.globals.gridHeight/e,r=i.globals.gridWidth,o=0,n=0;o=i.config.grid.row.colors.length&&(n=0),this._drawGridBandRect({c:n,x1:0,y1:a,x2:r,y2:s,type:"row"}),a+=i.globals.gridHeight/e;if(void 0!==i.config.grid.column.colors&&i.config.grid.column.colors.length>0)for(var l=i.globals.isBarHorizontal||"category"!==i.config.xaxis.type&&!i.config.xaxis.convertedCatToNumeric?t:t-1,h=i.globals.padHorizontal,c=i.globals.padHorizontal+i.globals.gridWidth/l,d=i.globals.gridHeight,g=0,u=0;g=i.config.grid.column.colors.length&&(u=0),this._drawGridBandRect({c:u,x1:h,y1:0,x2:c,y2:d,type:"column"}),h+=i.globals.gridWidth/l}}]),t}(),j=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"niceScale",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:10,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,s=arguments.length>4?arguments[4]:void 0,r=this.w,o=Math.abs(e-t);if("dataPoints"===(i=this._adjustTicksForSmallRange(i,a,o))&&(i=r.globals.dataPoints-1),t===Number.MIN_VALUE&&0===e||!p.isNumber(t)&&!p.isNumber(e)||t===Number.MIN_VALUE&&e===-Number.MAX_VALUE){t=0,e=i;var n=this.linearScale(t,e,i);return n}t>e?(console.warn("axis.min cannot be greater than axis.max"),e=t+.1):t===e&&(t=0===t?0:t-.5,e=0===e?2:e+.5);var l=[];o<1&&s&&("candlestick"===r.config.chart.type||"candlestick"===r.config.series[a].type||"boxPlot"===r.config.chart.type||"boxPlot"===r.config.series[a].type||r.globals.isRangeData)&&(e*=1.01);var h=i+1;h<2?h=2:h>2&&(h-=2);var c=o/h,d=Math.floor(p.log10(c)),g=Math.pow(10,d),u=Math.round(c/g);u<1&&(u=1);var f=u*g,x=f*Math.floor(t/f),b=f*Math.ceil(e/f),v=x;if(s&&o>2){for(;l.push(v),!((v+=f)>b););return{result:l,niceMin:l[0],niceMax:l[l.length-1]}}var m=t;(l=[]).push(m);for(var y=Math.abs(e-t)/i,w=0;w<=i;w++)m+=y,l.push(m);return l[l.length-2]>=e&&l.pop(),{result:l,niceMin:l[0],niceMax:l[l.length-1]}}},{key:"linearScale",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:10,a=arguments.length>3?arguments[3]:void 0,s=Math.abs(e-t);"dataPoints"===(i=this._adjustTicksForSmallRange(i,a,s))&&(i=this.w.globals.dataPoints-1);var r=s/i;i===Number.MAX_VALUE&&(i=10,r=1);for(var o=[],n=t;i>=0;)o.push(n),n+=r,i-=1;return{result:o,niceMin:o[0],niceMax:o[o.length-1]}}},{key:"logarithmicScale",value:function(t,e){for(var i=[],a=Math.ceil(Math.log(t)/Math.log(e))+1,s=0;s5)a.allSeriesCollapsed=!1,a.yAxisScale[t]=this.logarithmicScale(i,r.logBase);else if(i!==-Number.MAX_VALUE&&p.isNumber(i))if(a.allSeriesCollapsed=!1,void 0===r.min&&void 0===r.max||r.forceNiceScale){var n=void 0===s.yaxis[t].max&&void 0===s.yaxis[t].min||s.yaxis[t].forceNiceScale;a.yAxisScale[t]=this.niceScale(e,i,r.tickAmount?r.tickAmount:o<5&&o>1?o+1:5,t,n)}else a.yAxisScale[t]=this.linearScale(e,i,r.tickAmount,t);else a.yAxisScale[t]=this.linearScale(0,5,5)}},{key:"setXScale",value:function(t,e){var i=this.w,a=i.globals,s=i.config.xaxis,r=Math.abs(e-t);return e!==-Number.MAX_VALUE&&p.isNumber(e)?a.xAxisScale=this.linearScale(t,e,s.tickAmount?s.tickAmount:r<5&&r>1?r+1:5,0):a.xAxisScale=this.linearScale(0,5,5),a.xAxisScale}},{key:"setMultipleYScales",value:function(){var t=this,e=this.w.globals,i=this.w.config,a=e.minYArr.concat([]),s=e.maxYArr.concat([]),r=[];i.yaxis.forEach((function(e,o){var n=o;i.series.forEach((function(t,i){t.name===e.seriesName&&(n=i,o!==i?r.push({index:i,similarIndex:o,alreadyExists:!0}):r.push({index:i}))}));var l=a[n],h=s[n];t.setYScaleForIndex(o,l,h)})),this.sameScaleInMultipleAxes(a,s,r)}},{key:"sameScaleInMultipleAxes",value:function(t,e,i){var a=this,s=this.w.config,r=this.w.globals,o=[];i.forEach((function(t){t.alreadyExists&&(void 0===o[t.index]&&(o[t.index]=[]),o[t.index].push(t.index),o[t.index].push(t.similarIndex))})),r.yAxisSameScaleIndices=o,o.forEach((function(t,e){o.forEach((function(i,a){var s,r;e!==a&&(s=t,r=i,s.filter((function(t){return-1!==r.indexOf(t)}))).length>0&&(o[e]=o[e].concat(o[a]))}))}));var n=o.map((function(t){return t.filter((function(e,i){return t.indexOf(e)===i}))})).map((function(t){return t.sort()}));o=o.filter((function(t){return!!t}));var l=n.slice(),h=l.map((function(t){return JSON.stringify(t)}));l=l.filter((function(t,e){return h.indexOf(JSON.stringify(t))===e}));var c=[],d=[];t.forEach((function(t,i){l.forEach((function(a,s){a.indexOf(i)>-1&&(void 0===c[s]&&(c[s]=[],d[s]=[]),c[s].push({key:i,value:t}),d[s].push({key:i,value:e[i]}))}))}));var g=Array.apply(null,Array(l.length)).map(Number.prototype.valueOf,Number.MIN_VALUE),u=Array.apply(null,Array(l.length)).map(Number.prototype.valueOf,-Number.MAX_VALUE);c.forEach((function(t,e){t.forEach((function(t,i){g[e]=Math.min(t.value,g[e])}))})),d.forEach((function(t,e){t.forEach((function(t,i){u[e]=Math.max(t.value,u[e])}))})),t.forEach((function(t,e){d.forEach((function(t,i){var o=g[i],n=u[i];s.chart.stacked&&(n=0,t.forEach((function(t,e){t.value!==-Number.MAX_VALUE&&(n+=t.value),o!==Number.MIN_VALUE&&(o+=c[i][e].value)}))),t.forEach((function(i,l){t[l].key===e&&(void 0!==s.yaxis[e].min&&(o="function"==typeof s.yaxis[e].min?s.yaxis[e].min(r.minY):s.yaxis[e].min),void 0!==s.yaxis[e].max&&(n="function"==typeof s.yaxis[e].max?s.yaxis[e].max(r.maxY):s.yaxis[e].max),a.setYScaleForIndex(e,o,n))}))}))}))}},{key:"autoScaleY",value:function(t,e,i){t||(t=this);var a=t.w;if(a.globals.isMultipleYAxis||a.globals.collapsedSeries.length)return console.warn("autoScaleYaxis is not supported in a multi-yaxis chart."),e;var s=a.globals.seriesX[0],r=a.config.chart.stacked;return e.forEach((function(t,o){for(var n=0,l=0;l=i.xaxis.min){n=l;break}var h,c,d=a.globals.minYArr[o],g=a.globals.maxYArr[o],u=a.globals.stackedSeriesTotals;a.globals.series.forEach((function(o,l){var p=o[n];r?(p=u[n],h=c=p,u.forEach((function(t,e){s[e]<=i.xaxis.max&&s[e]>=i.xaxis.min&&(t>c&&null!==t&&(c=t),o[e]=i.xaxis.min){var r=t,o=t;a.globals.series.forEach((function(i,a){null!==t&&(r=Math.min(i[e],r),o=Math.max(i[e],o))})),o>c&&null!==o&&(c=o),rd&&(h=d),e.length>1?(e[l].min=void 0===t.min?h:t.min,e[l].max=void 0===t.max?c:t.max):(e[0].min=void 0===t.min?h:t.min,e[0].max=void 0===t.max?c:t.max)}))})),e}}]),t}(),U=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.scales=new j(e)}return r(t,[{key:"init",value:function(){this.setYRange(),this.setXRange(),this.setZRange()}},{key:"getMinYMaxY",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Number.MAX_VALUE,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:-Number.MAX_VALUE,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,s=this.w.config,r=this.w.globals,o=-Number.MAX_VALUE,n=Number.MIN_VALUE;null===a&&(a=t+1);var l=r.series,h=l,c=l;"candlestick"===s.chart.type?(h=r.seriesCandleL,c=r.seriesCandleH):"boxPlot"===s.chart.type?(h=r.seriesCandleO,c=r.seriesCandleC):r.isRangeData&&(h=r.seriesRangeStart,c=r.seriesRangeEnd);for(var d=t;dh[d][g]&&h[d][g]<0&&(n=h[d][g])):r.hasNullValues=!0}}return"rangeBar"===s.chart.type&&r.seriesRangeStart.length&&r.isBarHorizontal&&(n=e),"bar"===s.chart.type&&(n<0&&o<0&&(o=0),n===Number.MIN_VALUE&&(n=0)),{minY:n,maxY:o,lowestY:e,highestY:i}}},{key:"setYRange",value:function(){var t=this.w.globals,e=this.w.config;t.maxY=-Number.MAX_VALUE,t.minY=Number.MIN_VALUE;var i=Number.MAX_VALUE;if(t.isMultipleYAxis)for(var a=0;a=0&&i<=10||void 0!==e.yaxis[0].min||void 0!==e.yaxis[0].max)&&(o=0),t.minY=i-5*o/100,i>0&&t.minY<0&&(t.minY=0),t.maxY=t.maxY+5*o/100}if(e.yaxis.forEach((function(e,i){void 0!==e.max&&("number"==typeof e.max?t.maxYArr[i]=e.max:"function"==typeof e.max&&(t.maxYArr[i]=e.max(t.isMultipleYAxis?t.maxYArr[i]:t.maxY)),t.maxY=t.maxYArr[i]),void 0!==e.min&&("number"==typeof e.min?t.minYArr[i]=e.min:"function"==typeof e.min&&(t.minYArr[i]=e.min(t.isMultipleYAxis?t.minYArr[i]===Number.MIN_VALUE?0:t.minYArr[i]:t.minY)),t.minY=t.minYArr[i])})),t.isBarHorizontal){["min","max"].forEach((function(i){void 0!==e.xaxis[i]&&"number"==typeof e.xaxis[i]&&("min"===i?t.minY=e.xaxis[i]:t.maxY=e.xaxis[i])}))}return t.isMultipleYAxis?(this.scales.setMultipleYScales(),t.minY=i,t.yAxisScale.forEach((function(e,i){t.minYArr[i]=e.niceMin,t.maxYArr[i]=e.niceMax}))):(this.scales.setYScaleForIndex(0,t.minY,t.maxY),t.minY=t.yAxisScale[0].niceMin,t.maxY=t.yAxisScale[0].niceMax,t.minYArr[0]=t.yAxisScale[0].niceMin,t.maxYArr[0]=t.yAxisScale[0].niceMax),{minY:t.minY,maxY:t.maxY,minYArr:t.minYArr,maxYArr:t.maxYArr,yAxisScale:t.yAxisScale}}},{key:"setXRange",value:function(){var t=this.w.globals,e=this.w.config,i="numeric"===e.xaxis.type||"datetime"===e.xaxis.type||"category"===e.xaxis.type&&!t.noLabelsProvided||t.noLabelsProvided||t.isXNumeric;if(t.isXNumeric&&function(){for(var e=0;et.dataPoints&&0!==t.dataPoints&&(a=t.dataPoints-1)):"dataPoints"===e.xaxis.tickAmount?(t.series.length>1&&(a=t.series[t.maxValsInArrayIndex].length-1),t.isXNumeric&&(a=t.maxX-t.minX-1)):a=e.xaxis.tickAmount,t.xTickAmount=a,void 0!==e.xaxis.max&&"number"==typeof e.xaxis.max&&(t.maxX=e.xaxis.max),void 0!==e.xaxis.min&&"number"==typeof e.xaxis.min&&(t.minX=e.xaxis.min),void 0!==e.xaxis.range&&(t.minX=t.maxX-e.xaxis.range),t.minX!==Number.MAX_VALUE&&t.maxX!==-Number.MAX_VALUE)if(e.xaxis.convertedCatToNumeric&&!t.dataFormatXNumeric){for(var s=[],r=t.minX-1;r0&&(t.xAxisScale=this.scales.linearScale(1,t.labels.length,a-1),t.seriesX=t.labels.slice());i&&(t.labels=t.xAxisScale.result.slice())}return t.isBarHorizontal&&t.labels.length&&(t.xTickAmount=t.labels.length),this._handleSingleDataPoint(),this._getMinXDiff(),{minX:t.minX,maxX:t.maxX}}},{key:"setZRange",value:function(){var t=this.w.globals;if(t.isDataXYZ)for(var e=0;e0){var s=e-a[i-1];s>0&&(t.minXDiff=Math.min(s,t.minXDiff))}})),1===t.dataPoints&&t.minXDiff===Number.MAX_VALUE&&(t.minXDiff=.5)}))}},{key:"_setStackedMinMax",value:function(){var t=this.w.globals,e=[],i=[];if(t.series.length)for(var a=0;a0?s=s+parseFloat(t.series[o][a])+1e-4:r+=parseFloat(t.series[o][a])),o===t.series.length-1&&(e.push(s),i.push(r));for(var n=0;n=0;v--)x(v);if(void 0!==i.config.yaxis[t].title.text){var m=a.group({class:"apexcharts-yaxis-title"}),y=0;i.config.yaxis[t].opposite&&(y=i.globals.translateYAxisX[t]);var w=a.drawText({x:y,y:i.globals.gridHeight/2+i.globals.translateY+i.config.yaxis[t].title.offsetY,text:i.config.yaxis[t].title.text,textAnchor:"end",foreColor:i.config.yaxis[t].title.style.color,fontSize:i.config.yaxis[t].title.style.fontSize,fontWeight:i.config.yaxis[t].title.style.fontWeight,fontFamily:i.config.yaxis[t].title.style.fontFamily,cssClass:"apexcharts-yaxis-title-text "+i.config.yaxis[t].title.style.cssClass});m.add(w),l.add(m)}var k=i.config.yaxis[t].axisBorder,A=31+k.offsetX;if(i.config.yaxis[t].opposite&&(A=-31-k.offsetX),k.show){var S=a.drawLine(A,i.globals.translateY+k.offsetY-2,A,i.globals.gridHeight+i.globals.translateY+k.offsetY+2,k.color,0,k.width);l.add(S)}return i.config.yaxis[t].axisTicks.show&&this.axesUtils.drawYAxisTicks(A,c,k,i.config.yaxis[t].axisTicks,t,d,l),l}},{key:"drawYaxisInversed",value:function(t){var e=this.w,i=new b(this.ctx),a=i.group({class:"apexcharts-xaxis apexcharts-yaxis-inversed"}),s=i.group({class:"apexcharts-xaxis-texts-g",transform:"translate(".concat(e.globals.translateXAxisX,", ").concat(e.globals.translateXAxisY,")")});a.add(s);var r=e.globals.yAxisScale[t].result.length-1,o=e.globals.gridWidth/r+.1,n=o+e.config.xaxis.labels.offsetX,l=e.globals.xLabelFormatter,h=e.globals.yAxisScale[t].result.slice(),c=e.globals.timescaleLabels;c.length>0&&(this.xaxisLabels=c.slice(),r=(h=c.slice()).length),h=this.axesUtils.checkForReversedLabels(t,h);var d=c.length;if(e.config.xaxis.labels.show)for(var g=d?0:r;d?g=0;d?g++:g--){var u=h[g];u=l(u,g,e);var p=e.globals.gridWidth+e.globals.padHorizontal-(n-o+e.config.xaxis.labels.offsetX);if(c.length){var f=this.axesUtils.getLabel(h,c,p,g,this.drawnLabels,this.xaxisFontSize);p=f.x,u=f.text,this.drawnLabels.push(f.text),0===g&&e.globals.skipFirstTimelinelabel&&(u=""),g===h.length-1&&e.globals.skipLastTimelinelabel&&(u="")}var x=i.drawText({x:p,y:this.xAxisoffX+e.config.xaxis.labels.offsetY+30-("top"===e.config.xaxis.position?e.globals.xAxisHeight+e.config.xaxis.axisTicks.height-2:0),text:u,textAnchor:"middle",foreColor:Array.isArray(this.xaxisForeColors)?this.xaxisForeColors[t]:this.xaxisForeColors,fontSize:this.xaxisFontSize,fontFamily:this.xaxisFontFamily,fontWeight:e.config.xaxis.labels.style.fontWeight,isPlainText:!1,cssClass:"apexcharts-xaxis-label "+e.config.xaxis.labels.style.cssClass});s.add(x),x.tspan(u);var v=document.createElementNS(e.globals.SVGNS,"title");v.textContent=u,x.node.appendChild(v),n+=o}return this.inversedYAxisTitleText(a),this.inversedYAxisBorder(a),a}},{key:"inversedYAxisBorder",value:function(t){var e=this.w,i=new b(this.ctx),a=e.config.xaxis.axisBorder;if(a.show){var s=0;"bar"===e.config.chart.type&&e.globals.isXNumeric&&(s-=15);var r=i.drawLine(e.globals.padHorizontal+s+a.offsetX,this.xAxisoffX,e.globals.gridWidth,this.xAxisoffX,a.color,0,a.height);t.add(r)}}},{key:"inversedYAxisTitleText",value:function(t){var e=this.w,i=new b(this.ctx);if(void 0!==e.config.xaxis.title.text){var a=i.group({class:"apexcharts-xaxis-title apexcharts-yaxis-title-inversed"}),s=i.drawText({x:e.globals.gridWidth/2+e.config.xaxis.title.offsetX,y:this.xAxisoffX+parseFloat(this.xaxisFontSize)+parseFloat(e.config.xaxis.title.style.fontSize)+e.config.xaxis.title.offsetY+20,text:e.config.xaxis.title.text,textAnchor:"middle",fontSize:e.config.xaxis.title.style.fontSize,fontFamily:e.config.xaxis.title.style.fontFamily,fontWeight:e.config.xaxis.title.style.fontWeight,foreColor:e.config.xaxis.title.style.color,cssClass:"apexcharts-xaxis-title-text "+e.config.xaxis.title.style.cssClass});a.add(s),t.add(a)}}},{key:"yAxisTitleRotate",value:function(t,e){var i=this.w,a=new b(this.ctx),s={width:0,height:0},r={width:0,height:0},o=i.globals.dom.baseEl.querySelector(" .apexcharts-yaxis[rel='".concat(t,"'] .apexcharts-yaxis-texts-g"));null!==o&&(s=o.getBoundingClientRect());var n=i.globals.dom.baseEl.querySelector(".apexcharts-yaxis[rel='".concat(t,"'] .apexcharts-yaxis-title text"));if(null!==n&&(r=n.getBoundingClientRect()),null!==n){var l=this.xPaddingForYAxisTitle(t,s,r,e);n.setAttribute("x",l.xPos-(e?10:0))}if(null!==n){var h=a.rotateAroundCenter(n);n.setAttribute("transform","rotate(".concat(e?-1*i.config.yaxis[t].title.rotate:i.config.yaxis[t].title.rotate," ").concat(h.x," ").concat(h.y,")"))}}},{key:"xPaddingForYAxisTitle",value:function(t,e,i,a){var s=this.w,r=0,o=0,n=10;return void 0===s.config.yaxis[t].title.text||t<0?{xPos:o,padd:0}:(a?(o=e.width+s.config.yaxis[t].title.offsetX+i.width/2+n/2,0===(r+=1)&&(o-=n/2)):(o=-1*e.width+s.config.yaxis[t].title.offsetX+n/2+i.width/2,s.globals.isBarHorizontal&&(n=25,o=-1*e.width-s.config.yaxis[t].title.offsetX-n)),{xPos:o,padd:n})}},{key:"setYAxisXPosition",value:function(t,e){var i=this.w,a=0,s=0,r=18,o=1;i.config.yaxis.length>1&&(this.multipleYs=!0),i.config.yaxis.map((function(n,l){var h=i.globals.ignoreYAxisIndexes.indexOf(l)>-1||!n.show||n.floating||0===t[l].width,c=t[l].width+e[l].width;n.opposite?i.globals.isBarHorizontal?(s=i.globals.gridWidth+i.globals.translateX-1,i.globals.translateYAxisX[l]=s-n.labels.offsetX):(s=i.globals.gridWidth+i.globals.translateX+o,h||(o=o+c+20),i.globals.translateYAxisX[l]=s-n.labels.offsetX+20):(a=i.globals.translateX-r,h||(r=r+c+20),i.globals.translateYAxisX[l]=a+n.labels.offsetX)}))}},{key:"setYAxisTextAlignments",value:function(){var t=this.w,e=t.globals.dom.baseEl.getElementsByClassName("apexcharts-yaxis");(e=p.listToArray(e)).forEach((function(e,i){var a=t.config.yaxis[i];if(a&&void 0!==a.labels.align){var s=t.globals.dom.baseEl.querySelector(".apexcharts-yaxis[rel='".concat(i,"'] .apexcharts-yaxis-texts-g")),r=t.globals.dom.baseEl.querySelectorAll(".apexcharts-yaxis[rel='".concat(i,"'] .apexcharts-yaxis-label"));r=p.listToArray(r);var o=s.getBoundingClientRect();"left"===a.labels.align?(r.forEach((function(t,e){t.setAttribute("text-anchor","start")})),a.opposite||s.setAttribute("transform","translate(-".concat(o.width,", 0)"))):"center"===a.labels.align?(r.forEach((function(t,e){t.setAttribute("text-anchor","middle")})),s.setAttribute("transform","translate(".concat(o.width/2*(a.opposite?1:-1),", 0)"))):"right"===a.labels.align&&(r.forEach((function(t,e){t.setAttribute("text-anchor","end")})),a.opposite&&s.setAttribute("transform","translate(".concat(o.width,", 0)")))}}))}}]),t}(),Z=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.documentEvent=p.bind(this.documentEvent,this)}return r(t,[{key:"addEventListener",value:function(t,e){var i=this.w;i.globals.events.hasOwnProperty(t)?i.globals.events[t].push(e):i.globals.events[t]=[e]}},{key:"removeEventListener",value:function(t,e){var i=this.w;if(i.globals.events.hasOwnProperty(t)){var a=i.globals.events[t].indexOf(e);-1!==a&&i.globals.events[t].splice(a,1)}}},{key:"fireEvent",value:function(t,e){var i=this.w;if(i.globals.events.hasOwnProperty(t)){e&&e.length||(e=[]);for(var a=i.globals.events[t],s=a.length,r=0;r0&&(e=this.w.config.chart.locales.concat(window.Apex.chart.locales));var i=e.filter((function(e){return e.name===t}))[0];if(!i)throw new Error("Wrong locale name provided. Please make sure you set the correct locale name in options");var a=p.extend(A,i);this.w.globals.locale=a.options}}]),t}(),J=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"drawAxis",value:function(t,e){var i,a,s=this.w.globals,r=this.w.config,o=new G(this.ctx),n=new q(this.ctx);s.axisCharts&&"radar"!==t&&(s.isBarHorizontal?(a=n.drawYaxisInversed(0),i=o.drawXaxisInversed(0),s.dom.elGraphical.add(i),s.dom.elGraphical.add(a)):(i=o.drawXaxis(),s.dom.elGraphical.add(i),r.yaxis.map((function(t,e){-1===s.ignoreYAxisIndexes.indexOf(e)&&(a=n.drawYaxis(e),s.dom.Paper.add(a))}))))}}]),t}(),Q=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"drawXCrosshairs",value:function(){var t=this.w,e=new b(this.ctx),i=new x(this.ctx),a=t.config.xaxis.crosshairs.fill.gradient,s=t.config.xaxis.crosshairs.dropShadow,r=t.config.xaxis.crosshairs.fill.type,o=a.colorFrom,n=a.colorTo,l=a.opacityFrom,h=a.opacityTo,c=a.stops,d=s.enabled,g=s.left,u=s.top,f=s.blur,v=s.color,m=s.opacity,y=t.config.xaxis.crosshairs.fill.color;if(t.config.xaxis.crosshairs.show){"gradient"===r&&(y=e.drawGradient("vertical",o,n,l,h,null,c,null));var w=e.drawRect();1===t.config.xaxis.crosshairs.width&&(w=e.drawLine());var k=t.globals.gridHeight;(!p.isNumber(k)||k<0)&&(k=0);var A=t.config.xaxis.crosshairs.width;(!p.isNumber(A)||A<0)&&(A=0),w.attr({class:"apexcharts-xcrosshairs",x:0,y:0,y2:k,width:A,height:k,fill:y,filter:"none","fill-opacity":t.config.xaxis.crosshairs.opacity,stroke:t.config.xaxis.crosshairs.stroke.color,"stroke-width":t.config.xaxis.crosshairs.stroke.width,"stroke-dasharray":t.config.xaxis.crosshairs.stroke.dashArray}),d&&(w=i.dropShadow(w,{left:g,top:u,blur:f,color:v,opacity:m})),t.globals.dom.elGraphical.add(w)}}},{key:"drawYCrosshairs",value:function(){var t=this.w,e=new b(this.ctx),i=t.config.yaxis[0].crosshairs,a=t.globals.barPadForNumericAxis;if(t.config.yaxis[0].crosshairs.show){var s=e.drawLine(-a,0,t.globals.gridWidth+a,0,i.stroke.color,i.stroke.dashArray,i.stroke.width);s.attr({class:"apexcharts-ycrosshairs"}),t.globals.dom.elGraphical.add(s)}var r=e.drawLine(-a,0,t.globals.gridWidth+a,0,i.stroke.color,0,0);r.attr({class:"apexcharts-ycrosshairs-hidden"}),t.globals.dom.elGraphical.add(r)}}]),t}(),K=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"checkResponsiveConfig",value:function(t){var e=this,i=this.w,a=i.config;if(0!==a.responsive.length){var s=a.responsive.slice();s.sort((function(t,e){return t.breakpoint>e.breakpoint?1:e.breakpoint>t.breakpoint?-1:0})).reverse();var r=new H({}),o=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},a=s[0].breakpoint,o=window.innerWidth>0?window.innerWidth:screen.width;if(o>a){var n=y.extendArrayProps(r,i.globals.initialConfig,i);t=p.extend(n,t),t=p.extend(i.config,t),e.overrideResponsiveOptions(t)}else for(var l=0;l0&&"function"==typeof e.config.colors[0]&&(e.globals.colors=e.config.series.map((function(i,a){var s=e.config.colors[a];return s||(s=e.config.colors[0]),"function"==typeof s?(t.isColorFn=!0,s({value:e.globals.axisCharts?e.globals.series[a][0]?e.globals.series[a][0]:0:e.globals.series[a],seriesIndex:a,dataPointIndex:a,w:e})):s})))),e.globals.seriesColors.map((function(t,i){t&&(e.globals.colors[i]=t)})),e.config.theme.monochrome.enabled){var a=[],s=e.globals.series.length;(this.isBarDistributed||this.isHeatmapDistributed)&&(s=e.globals.series[0].length*e.globals.series.length);for(var r=e.config.theme.monochrome.color,o=1/(s/e.config.theme.monochrome.shadeIntensity),n=e.config.theme.monochrome.shadeTo,l=0,h=0;h2&&void 0!==arguments[2]?arguments[2]:null,a=this.w,s=e||a.globals.series.length;if(null===i&&(i=this.isBarDistributed||this.isHeatmapDistributed||"heatmap"===a.config.chart.type&&a.config.plotOptions.heatmap.colorScale.inverse),i&&a.globals.series.length&&(s=a.globals.series[a.globals.maxValsInArrayIndex].length*a.globals.series.length),t.lengtht.globals.svgWidth&&(this.dCtx.lgRect.width=t.globals.svgWidth/1.5),this.dCtx.lgRect}},{key:"getLargestStringFromMultiArr",value:function(t,e){var i=t;if(this.w.globals.isMultiLineX){var a=e.map((function(t,e){return Array.isArray(t)?t.length:1})),s=Math.max.apply(Math,g(a));i=e[a.indexOf(s)]}return i}}]),t}(),at=function(){function t(e){a(this,t),this.w=e.w,this.dCtx=e}return r(t,[{key:"getxAxisLabelsCoords",value:function(){var t,e=this.w,i=e.globals.labels.slice();if(e.config.xaxis.convertedCatToNumeric&&0===i.length&&(i=e.globals.categoryLabels),e.globals.timescaleLabels.length>0){var a=this.getxAxisTimeScaleLabelsCoords();t={width:a.width,height:a.height},e.globals.rotateXLabels=!1}else{this.dCtx.lgWidthForSideLegends="left"!==e.config.legend.position&&"right"!==e.config.legend.position||e.config.legend.floating?0:this.dCtx.lgRect.width;var s=e.globals.xLabelFormatter,r=p.getLargestStringFromArr(i),o=this.dCtx.dimHelpers.getLargestStringFromMultiArr(r,i);e.globals.isBarHorizontal&&(o=r=e.globals.yAxisScale[0].result.reduce((function(t,e){return t.length>e.length?t:e}),0));var n=new W(this.dCtx.ctx),l=r;r=n.xLabelFormat(s,r,l,{i:void 0,dateFormatter:new Y(this.dCtx.ctx).formatDate,w:e}),o=n.xLabelFormat(s,o,l,{i:void 0,dateFormatter:new Y(this.dCtx.ctx).formatDate,w:e}),(e.config.xaxis.convertedCatToNumeric&&void 0===r||""===String(r).trim())&&(o=r="1");var h=new b(this.dCtx.ctx),c=h.getTextRects(r,e.config.xaxis.labels.style.fontSize),d=c;if(r!==o&&(d=h.getTextRects(o,e.config.xaxis.labels.style.fontSize)),(t={width:c.width>=d.width?c.width:d.width,height:c.height>=d.height?c.height:d.height}).width*i.length>e.globals.svgWidth-this.dCtx.lgWidthForSideLegends-this.dCtx.yAxisWidth-this.dCtx.gridPad.left-this.dCtx.gridPad.right&&0!==e.config.xaxis.labels.rotate||e.config.xaxis.labels.rotateAlways){if(!e.globals.isBarHorizontal){e.globals.rotateXLabels=!0;var g=function(t){return h.getTextRects(t,e.config.xaxis.labels.style.fontSize,e.config.xaxis.labels.style.fontFamily,"rotate(".concat(e.config.xaxis.labels.rotate," 0 0)"),!1)};c=g(r),r!==o&&(d=g(o)),t.height=(c.height>d.height?c.height:d.height)/1.5,t.width=c.width>d.width?c.width:d.width}}else e.globals.rotateXLabels=!1}return e.config.xaxis.labels.show||(t={width:0,height:0}),{width:t.width,height:t.height}}},{key:"getxAxisTitleCoords",value:function(){var t=this.w,e=0,i=0;if(void 0!==t.config.xaxis.title.text){var a=new b(this.dCtx.ctx).getTextRects(t.config.xaxis.title.text,t.config.xaxis.title.style.fontSize);e=a.width,i=a.height}return{width:e,height:i}}},{key:"getxAxisTimeScaleLabelsCoords",value:function(){var t,e=this.w;this.dCtx.timescaleLabels=e.globals.timescaleLabels.slice();var i=this.dCtx.timescaleLabels.map((function(t){return t.value})),a=i.reduce((function(t,e){return void 0===t?(console.error("You have possibly supplied invalid Date format. Please supply a valid JavaScript Date"),0):t.length>e.length?t:e}),0);return 1.05*(t=new b(this.dCtx.ctx).getTextRects(a,e.config.xaxis.labels.style.fontSize)).width*i.length>e.globals.gridWidth&&0!==e.config.xaxis.labels.rotate&&(e.globals.overlappingXLabels=!0),t}},{key:"additionalPaddingXLabels",value:function(t){var e=this,i=this.w,a=i.globals,s=i.config,r=s.xaxis.type,o=t.width;a.skipLastTimelinelabel=!1,a.skipFirstTimelinelabel=!1;var n=i.config.yaxis[0].opposite&&i.globals.isBarHorizontal,l=function(t,n){(function(t){return-1!==a.collapsedSeriesIndices.indexOf(t)})(n)||function(t){if(e.dCtx.timescaleLabels&&e.dCtx.timescaleLabels.length){var n=e.dCtx.timescaleLabels[0],l=e.dCtx.timescaleLabels[e.dCtx.timescaleLabels.length-1].position+o/1.75-e.dCtx.yAxisWidthRight,h=n.position-o/1.75+e.dCtx.yAxisWidthLeft,c="right"===i.config.legend.position&&e.dCtx.lgRect.width>0?e.dCtx.lgRect.width:0;l>a.svgWidth-a.translateX-c&&(a.skipLastTimelinelabel=!0),h<-(t.show&&!t.floating||"bar"!==s.chart.type&&"candlestick"!==s.chart.type&&"rangeBar"!==s.chart.type&&"boxPlot"!==s.chart.type?10:o/1.75)&&(a.skipFirstTimelinelabel=!0)}else"datetime"===r?e.dCtx.gridPad.rightString(n.niceMax).length?c:n.niceMax,g=h(d,{seriesIndex:o,dataPointIndex:-1,w:e}),u=g;if(void 0!==g&&0!==g.length||(g=d),e.globals.isBarHorizontal){a=0;var f=e.globals.labels.slice();g=h(g=p.getLargestStringFromArr(f),{seriesIndex:o,dataPointIndex:-1,w:e}),u=t.dCtx.dimHelpers.getLargestStringFromMultiArr(g,f)}var x=new b(t.dCtx.ctx),v="rotate(".concat(r.labels.rotate," 0 0)"),m=x.getTextRects(g,r.labels.style.fontSize,r.labels.style.fontFamily,v,!1),y=m;g!==u&&(y=x.getTextRects(u,r.labels.style.fontSize,r.labels.style.fontFamily,v,!1)),i.push({width:(l>y.width||l>m.width?l:y.width>m.width?y.width:m.width)+a,height:y.height>m.height?y.height:m.height})}else i.push({width:0,height:0})})),i}},{key:"getyAxisTitleCoords",value:function(){var t=this,e=this.w,i=[];return e.config.yaxis.map((function(e,a){if(e.show&&void 0!==e.title.text){var s=new b(t.dCtx.ctx),r="rotate(".concat(e.title.rotate," 0 0)"),o=s.getTextRects(e.title.text,e.title.style.fontSize,e.title.style.fontFamily,r,!1);i.push({width:o.width,height:o.height})}else i.push({width:0,height:0})})),i}},{key:"getTotalYAxisWidth",value:function(){var t=this.w,e=0,i=0,a=0,s=t.globals.yAxisScale.length>1?10:0,r=new B(this.dCtx.ctx),o=function(o,n){var l=t.config.yaxis[n].floating,h=0;o.width>0&&!l?(h=o.width+s,function(e){return t.globals.ignoreYAxisIndexes.indexOf(e)>-1}(n)&&(h=h-o.width-s)):h=l||r.isYAxisHidden(n)?0:5,t.config.yaxis[n].opposite?a+=h:i+=h,e+=h};return t.globals.yLabelsCoords.map((function(t,e){o(t,e)})),t.globals.yTitleCoords.map((function(t,e){o(t,e)})),t.globals.isBarHorizontal&&!t.config.yaxis[0].floating&&(e=t.globals.yLabelsCoords[0].width+t.globals.yTitleCoords[0].width+15),this.dCtx.yAxisWidthLeft=i,this.dCtx.yAxisWidthRight=a,e}}]),t}(),rt=function(){function t(e){a(this,t),this.w=e.w,this.dCtx=e}return r(t,[{key:"gridPadForColumnsInNumericAxis",value:function(t){var e=this.w;if(e.globals.noData||e.globals.allSeriesCollapsed)return 0;var i=function(t){return"bar"===t||"rangeBar"===t||"candlestick"===t||"boxPlot"===t},a=e.config.chart.type,s=0,r=i(a)?e.config.series.length:1;if(e.globals.comboBarCount>0&&(r=e.globals.comboBarCount),e.globals.collapsedSeries.forEach((function(t){i(t.type)&&(r-=1)})),e.config.chart.stacked&&(r=1),(i(a)||e.globals.comboBarCount>0)&&e.globals.isXNumeric&&!e.globals.isBarHorizontal&&r>0){var o,n,l=Math.abs(e.globals.initialMaxX-e.globals.initialMinX);l<=3&&(l=e.globals.dataPoints),o=l/t,e.globals.minXDiff&&e.globals.minXDiff/o>0&&(n=e.globals.minXDiff/o),n>t/2&&(n/=2),(s=n/r*parseInt(e.config.plotOptions.bar.columnWidth,10)/100)<1&&(s=1),s=s/(r>1?1:1.5)+5,e.globals.barPadForNumericAxis=s}return s}},{key:"gridPadFortitleSubtitle",value:function(){var t=this,e=this.w,i=e.globals,a=this.dCtx.isSparkline||!e.globals.axisCharts?0:10;["title","subtitle"].forEach((function(i){void 0!==e.config[i].text?a+=e.config[i].margin:a+=t.dCtx.isSparkline||!e.globals.axisCharts?0:5})),!e.config.legend.show||"bottom"!==e.config.legend.position||e.config.legend.floating||e.globals.axisCharts||(a+=10);var s=this.dCtx.dimHelpers.getTitleSubtitleCoords("title"),r=this.dCtx.dimHelpers.getTitleSubtitleCoords("subtitle");i.gridHeight=i.gridHeight-s.height-r.height-a,i.translateY=i.translateY+s.height+r.height+a}},{key:"setGridXPosForDualYAxis",value:function(t,e){var i=this.w,a=new B(this.dCtx.ctx);i.config.yaxis.map((function(s,r){-1!==i.globals.ignoreYAxisIndexes.indexOf(r)||s.floating||a.isYAxisHidden(r)||(s.opposite&&(i.globals.translateX=i.globals.translateX-(e[r].width+t[r].width)-parseInt(i.config.yaxis[r].labels.style.fontSize,10)/1.2-12),i.globals.translateX<2&&(i.globals.translateX=2))}))}}]),t}(),ot=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.lgRect={},this.yAxisWidth=0,this.yAxisWidthLeft=0,this.yAxisWidthRight=0,this.xAxisHeight=0,this.isSparkline=this.w.config.chart.sparkline.enabled,this.dimHelpers=new it(this),this.dimYAxis=new st(this),this.dimXAxis=new at(this),this.dimGrid=new rt(this),this.lgWidthForSideLegends=0,this.gridPad=this.w.config.grid.padding,this.xPadRight=0,this.xPadLeft=0}return r(t,[{key:"plotCoords",value:function(){var t=this.w.globals;this.lgRect=this.dimHelpers.getLegendsRect(),t.axisCharts?this.setDimensionsForAxisCharts():this.setDimensionsForNonAxisCharts(),this.dimGrid.gridPadFortitleSubtitle(),t.gridHeight=t.gridHeight-this.gridPad.top-this.gridPad.bottom,t.gridWidth=t.gridWidth-this.gridPad.left-this.gridPad.right-this.xPadRight-this.xPadLeft;var e=this.dimGrid.gridPadForColumnsInNumericAxis(t.gridWidth);t.gridWidth=t.gridWidth-2*e,t.translateX=t.translateX+this.gridPad.left+this.xPadLeft+(e>0?e+4:0),t.translateY=t.translateY+this.gridPad.top}},{key:"setDimensionsForAxisCharts",value:function(){var t=this,e=this.w,i=e.globals,a=this.dimYAxis.getyAxisLabelsCoords(),s=this.dimYAxis.getyAxisTitleCoords();e.globals.yLabelsCoords=[],e.globals.yTitleCoords=[],e.config.yaxis.map((function(t,i){e.globals.yLabelsCoords.push({width:a[i].width,index:i}),e.globals.yTitleCoords.push({width:s[i].width,index:i})})),this.yAxisWidth=this.dimYAxis.getTotalYAxisWidth();var r=this.dimXAxis.getxAxisLabelsCoords(),o=this.dimXAxis.getxAxisTitleCoords();this.conditionalChecksForAxisCoords(r,o),i.translateXAxisY=e.globals.rotateXLabels?this.xAxisHeight/8:-4,i.translateXAxisX=e.globals.rotateXLabels&&e.globals.isXNumeric&&e.config.xaxis.labels.rotate<=-45?-this.xAxisWidth/4:0,e.globals.isBarHorizontal&&(i.rotateXLabels=!1,i.translateXAxisY=parseInt(e.config.xaxis.labels.style.fontSize,10)/1.5*-1),i.translateXAxisY=i.translateXAxisY+e.config.xaxis.labels.offsetY,i.translateXAxisX=i.translateXAxisX+e.config.xaxis.labels.offsetX;var n=this.yAxisWidth,l=this.xAxisHeight;i.xAxisLabelsHeight=this.xAxisHeight-o.height,i.xAxisLabelsWidth=this.xAxisWidth,i.xAxisHeight=this.xAxisHeight;var h=10;("radar"===e.config.chart.type||this.isSparkline)&&(n=0,l=i.goldenPadding),this.isSparkline&&(this.lgRect={height:0,width:0}),(this.isSparkline||"treemap"===e.config.chart.type)&&(n=0,l=0,h=0),this.isSparkline||this.dimXAxis.additionalPaddingXLabels(r);var c=function(){i.translateX=n,i.gridHeight=i.svgHeight-t.lgRect.height-l-(t.isSparkline||"treemap"===e.config.chart.type?0:e.globals.rotateXLabels?10:15),i.gridWidth=i.svgWidth-n};switch("top"===e.config.xaxis.position&&(h=i.xAxisHeight-e.config.xaxis.axisTicks.height-5),e.config.legend.position){case"bottom":i.translateY=h,c();break;case"top":i.translateY=this.lgRect.height+h,c();break;case"left":i.translateY=h,i.translateX=this.lgRect.width+n,i.gridHeight=i.svgHeight-l-12,i.gridWidth=i.svgWidth-this.lgRect.width-n;break;case"right":i.translateY=h,i.translateX=n,i.gridHeight=i.svgHeight-l-12,i.gridWidth=i.svgWidth-this.lgRect.width-n-5;break;default:throw new Error("Legend position not supported")}this.dimGrid.setGridXPosForDualYAxis(s,a),new q(this.ctx).setYAxisXPosition(a,s)}},{key:"setDimensionsForNonAxisCharts",value:function(){var t=this.w,e=t.globals,i=t.config,a=0;t.config.legend.show&&!t.config.legend.floating&&(a=20);var s="pie"===i.chart.type||"polarArea"===i.chart.type||"donut"===i.chart.type?"pie":"radialBar",r=i.plotOptions[s].offsetY,o=i.plotOptions[s].offsetX;if(!i.legend.show||i.legend.floating)return e.gridHeight=e.svgHeight-i.grid.padding.left+i.grid.padding.right,e.gridWidth=e.gridHeight,e.translateY=r,void(e.translateX=o+(e.svgWidth-e.gridWidth)/2);switch(i.legend.position){case"bottom":e.gridHeight=e.svgHeight-this.lgRect.height-e.goldenPadding,e.gridWidth=e.svgWidth,e.translateY=r-10,e.translateX=o+(e.svgWidth-e.gridWidth)/2;break;case"top":e.gridHeight=e.svgHeight-this.lgRect.height-e.goldenPadding,e.gridWidth=e.svgWidth,e.translateY=this.lgRect.height+r+10,e.translateX=o+(e.svgWidth-e.gridWidth)/2;break;case"left":e.gridWidth=e.svgWidth-this.lgRect.width-a,e.gridHeight="auto"!==i.chart.height?e.svgHeight:e.gridWidth,e.translateY=r,e.translateX=o+this.lgRect.width+a;break;case"right":e.gridWidth=e.svgWidth-this.lgRect.width-a-5,e.gridHeight="auto"!==i.chart.height?e.svgHeight:e.gridWidth,e.translateY=r,e.translateX=o+10;break;default:throw new Error("Legend position not supported")}}},{key:"conditionalChecksForAxisCoords",value:function(t,e){var i=this.w,a=t.height+e.height,s=i.globals.isMultiLineX?1.2:i.globals.LINE_HEIGHT_RATIO,r=i.globals.rotateXLabels?22:10,o=i.globals.rotateXLabels&&"bottom"===i.config.legend.position?10:0;this.xAxisHeight=a*s+r+o,this.xAxisWidth=t.width,this.xAxisHeight-e.height>i.config.xaxis.labels.maxHeight&&(this.xAxisHeight=i.config.xaxis.labels.maxHeight),i.config.xaxis.labels.minHeight&&this.xAxisHeightl&&(this.yAxisWidth=l)}}]),t}(),nt=function(){function t(e){a(this,t),this.w=e.w,this.lgCtx=e}return r(t,[{key:"getLegendStyles",value:function(){var t=document.createElement("style");t.setAttribute("type","text/css");var e=document.createTextNode("\t\n \t\n .apexcharts-legend {\t\n display: flex;\t\n overflow: auto;\t\n padding: 0 10px;\t\n }\t\n .apexcharts-legend.apx-legend-position-bottom, .apexcharts-legend.apx-legend-position-top {\t\n flex-wrap: wrap\t\n }\t\n .apexcharts-legend.apx-legend-position-right, .apexcharts-legend.apx-legend-position-left {\t\n flex-direction: column;\t\n bottom: 0;\t\n }\t\n .apexcharts-legend.apx-legend-position-bottom.apexcharts-align-left, .apexcharts-legend.apx-legend-position-top.apexcharts-align-left, .apexcharts-legend.apx-legend-position-right, .apexcharts-legend.apx-legend-position-left {\t\n justify-content: flex-start;\t\n }\t\n .apexcharts-legend.apx-legend-position-bottom.apexcharts-align-center, .apexcharts-legend.apx-legend-position-top.apexcharts-align-center {\t\n justify-content: center; \t\n }\t\n .apexcharts-legend.apx-legend-position-bottom.apexcharts-align-right, .apexcharts-legend.apx-legend-position-top.apexcharts-align-right {\t\n justify-content: flex-end;\t\n }\t\n .apexcharts-legend-series {\t\n cursor: pointer;\t\n line-height: normal;\t\n }\t\n .apexcharts-legend.apx-legend-position-bottom .apexcharts-legend-series, .apexcharts-legend.apx-legend-position-top .apexcharts-legend-series{\t\n display: flex;\t\n align-items: center;\t\n }\t\n .apexcharts-legend-text {\t\n position: relative;\t\n font-size: 14px;\t\n }\t\n .apexcharts-legend-text *, .apexcharts-legend-marker * {\t\n pointer-events: none;\t\n }\t\n .apexcharts-legend-marker {\t\n position: relative;\t\n display: inline-block;\t\n cursor: pointer;\t\n margin-right: 3px;\t\n border-style: solid;\n }\t\n \t\n .apexcharts-legend.apexcharts-align-right .apexcharts-legend-series, .apexcharts-legend.apexcharts-align-left .apexcharts-legend-series{\t\n display: inline-block;\t\n }\t\n .apexcharts-legend-series.apexcharts-no-click {\t\n cursor: auto;\t\n }\t\n .apexcharts-legend .apexcharts-hidden-zero-series, .apexcharts-legend .apexcharts-hidden-null-series {\t\n display: none !important;\t\n }\t\n .apexcharts-inactive-legend {\t\n opacity: 0.45;\t\n }");return t.appendChild(e),t}},{key:"getLegendBBox",value:function(){var t=this.w.globals.dom.baseEl.querySelector(".apexcharts-legend").getBoundingClientRect(),e=t.width;return{clwh:t.height,clww:e}}},{key:"appendToForeignObject",value:function(){var t=this.w.globals;t.dom.elLegendForeign=document.createElementNS(t.SVGNS,"foreignObject");var e=t.dom.elLegendForeign;e.setAttribute("x",0),e.setAttribute("y",0),e.setAttribute("width",t.svgWidth),e.setAttribute("height",t.svgHeight),t.dom.elLegendWrap.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),e.appendChild(t.dom.elLegendWrap),e.appendChild(this.getLegendStyles()),t.dom.Paper.node.insertBefore(e,t.dom.elGraphical.node)}},{key:"toggleDataSeries",value:function(t,e){var i=this,a=this.w;if(a.globals.axisCharts||"radialBar"===a.config.chart.type){a.globals.resized=!0;var s=null,r=null;if(a.globals.risingSeries=[],a.globals.axisCharts?(s=a.globals.dom.baseEl.querySelector(".apexcharts-series[data\\:realIndex='".concat(t,"']")),r=parseInt(s.getAttribute("data:realIndex"),10)):(s=a.globals.dom.baseEl.querySelector(".apexcharts-series[rel='".concat(t+1,"']")),r=parseInt(s.getAttribute("rel"),10)-1),e)[{cs:a.globals.collapsedSeries,csi:a.globals.collapsedSeriesIndices},{cs:a.globals.ancillaryCollapsedSeries,csi:a.globals.ancillaryCollapsedSeriesIndices}].forEach((function(t){i.riseCollapsedSeries(t.cs,t.csi,r)}));else this.hideSeries({seriesEl:s,realIndex:r})}else{var o=a.globals.dom.Paper.select(" .apexcharts-series[rel='".concat(t+1,"'] path")),n=a.config.chart.type;if("pie"===n||"polarArea"===n||"donut"===n){var l=a.config.plotOptions.pie.donut.labels;new b(this.lgCtx.ctx).pathMouseDown(o.members[0],null),this.lgCtx.ctx.pie.printDataLabelsInner(o.members[0].node,l)}o.fire("click")}}},{key:"hideSeries",value:function(t){var e=t.seriesEl,i=t.realIndex,a=this.w,s=p.clone(a.config.series);if(a.globals.axisCharts){var r=!1;if(a.config.yaxis[i]&&a.config.yaxis[i].show&&a.config.yaxis[i].showAlways&&(r=!0,a.globals.ancillaryCollapsedSeriesIndices.indexOf(i)<0&&(a.globals.ancillaryCollapsedSeries.push({index:i,data:s[i].data.slice(),type:e.parentNode.className.baseVal.split("-")[1]}),a.globals.ancillaryCollapsedSeriesIndices.push(i))),!r){a.globals.collapsedSeries.push({index:i,data:s[i].data.slice(),type:e.parentNode.className.baseVal.split("-")[1]}),a.globals.collapsedSeriesIndices.push(i);var o=a.globals.risingSeries.indexOf(i);a.globals.risingSeries.splice(o,1)}}else a.globals.collapsedSeries.push({index:i,data:s[i]}),a.globals.collapsedSeriesIndices.push(i);for(var n=e.childNodes,l=0;l0){for(var r=0;r-1&&(t[a].data=[])})):t.forEach((function(i,a){e.globals.collapsedSeriesIndices.indexOf(a)>-1&&(t[a]=0)})),t}}]),t}(),lt=function(){function t(e,i){a(this,t),this.ctx=e,this.w=e.w,this.onLegendClick=this.onLegendClick.bind(this),this.onLegendHovered=this.onLegendHovered.bind(this),this.isBarsDistributed="bar"===this.w.config.chart.type&&this.w.config.plotOptions.bar.distributed&&1===this.w.config.series.length,this.legendHelpers=new nt(this)}return r(t,[{key:"init",value:function(){var t=this.w,e=t.globals,i=t.config;if((i.legend.showForSingleSeries&&1===e.series.length||this.isBarsDistributed||e.series.length>1||!e.axisCharts)&&i.legend.show){for(;e.dom.elLegendWrap.firstChild;)e.dom.elLegendWrap.removeChild(e.dom.elLegendWrap.firstChild);this.drawLegends(),p.isIE11()?document.getElementsByTagName("head")[0].appendChild(this.legendHelpers.getLegendStyles()):this.legendHelpers.appendToForeignObject(),"bottom"===i.legend.position||"top"===i.legend.position?this.legendAlignHorizontal():"right"!==i.legend.position&&"left"!==i.legend.position||this.legendAlignVertical()}}},{key:"drawLegends",value:function(){var t=this,e=this.w,i=e.config.legend.fontFamily,a=e.globals.seriesNames,s=e.globals.colors.slice();if("heatmap"===e.config.chart.type){var r=e.config.plotOptions.heatmap.colorScale.ranges;a=r.map((function(t){return t.name?t.name:t.from+" - "+t.to})),s=r.map((function(t){return t.color}))}else this.isBarsDistributed&&(a=e.globals.labels.slice());e.config.legend.customLegendItems.length&&(a=e.config.legend.customLegendItems);for(var o=e.globals.legendFormatter,n=e.config.legend.inverseOrder,l=n?a.length-1:0;n?l>=0:l<=a.length-1;n?l--:l++){var h=o(a[l],{seriesIndex:l,w:e}),c=!1,d=!1;if(e.globals.collapsedSeries.length>0)for(var g=0;g0)for(var u=0;u0?l-10:0)+(h>0?h-10:0)}a.style.position="absolute",r=r+t+i.config.legend.offsetX,o=o+e+i.config.legend.offsetY,a.style.left=r+"px",a.style.top=o+"px","bottom"===i.config.legend.position?(a.style.top="auto",a.style.bottom=5-i.config.legend.offsetY+"px"):"right"===i.config.legend.position&&(a.style.left="auto",a.style.right=25+i.config.legend.offsetX+"px");["width","height"].forEach((function(t){a.style[t]&&(a.style[t]=parseInt(i.config.legend[t],10)+"px")}))}},{key:"legendAlignHorizontal",value:function(){var t=this.w;t.globals.dom.baseEl.querySelector(".apexcharts-legend").style.right=0;var e=this.legendHelpers.getLegendBBox(),i=new ot(this.ctx),a=i.dimHelpers.getTitleSubtitleCoords("title"),s=i.dimHelpers.getTitleSubtitleCoords("subtitle"),r=0;"bottom"===t.config.legend.position?r=-e.clwh/1.8:"top"===t.config.legend.position&&(r=a.height+s.height+t.config.title.margin+t.config.subtitle.margin-10),this.setLegendWrapXY(20,r)}},{key:"legendAlignVertical",value:function(){var t=this.w,e=this.legendHelpers.getLegendBBox(),i=0;"left"===t.config.legend.position&&(i=20),"right"===t.config.legend.position&&(i=t.globals.svgWidth-e.clww-10),this.setLegendWrapXY(i,20)}},{key:"onLegendHovered",value:function(t){var e=this.w,i=t.target.classList.contains("apexcharts-legend-text")||t.target.classList.contains("apexcharts-legend-marker");if("heatmap"===e.config.chart.type||this.isBarsDistributed){if(i){var a=parseInt(t.target.getAttribute("rel"),10)-1;this.ctx.events.fireEvent("legendHover",[this.ctx,a,this.w]),new z(this.ctx).highlightRangeInSeries(t,t.target)}}else!t.target.classList.contains("apexcharts-inactive-legend")&&i&&new z(this.ctx).toggleSeriesOnHover(t,t.target)}},{key:"onLegendClick",value:function(t){var e=this.w;if(!e.config.legend.customLegendItems.length&&(t.target.classList.contains("apexcharts-legend-text")||t.target.classList.contains("apexcharts-legend-marker"))){var i=parseInt(t.target.getAttribute("rel"),10)-1,a="true"===t.target.getAttribute("data:collapsed"),s=this.w.config.chart.events.legendClick;"function"==typeof s&&s(this.ctx,i,this.w),this.ctx.events.fireEvent("legendClick",[this.ctx,i,this.w]);var r=this.w.config.legend.markers.onClick;"function"==typeof r&&t.target.classList.contains("apexcharts-legend-marker")&&(r(this.ctx,i,this.w),this.ctx.events.fireEvent("legendMarkerClick",[this.ctx,i,this.w])),"treemap"!==e.config.chart.type&&"heatmap"!==e.config.chart.type&&!this.isBarsDistributed&&e.config.legend.onItemClick.toggleDataSeries&&this.legendHelpers.toggleDataSeries(i,a)}}}]),t}(),ht=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w;var i=this.w;this.ev=this.w.config.chart.events,this.selectedClass="apexcharts-selected",this.localeValues=this.w.globals.locale.toolbar,this.minX=i.globals.minX,this.maxX=i.globals.maxX}return r(t,[{key:"createToolbar",value:function(){var t=this,e=this.w,i=function(){return document.createElement("div")},a=i();if(a.setAttribute("class","apexcharts-toolbar"),a.style.top=e.config.chart.toolbar.offsetY+"px",a.style.right=3-e.config.chart.toolbar.offsetX+"px",e.globals.dom.elWrap.appendChild(a),this.elZoom=i(),this.elZoomIn=i(),this.elZoomOut=i(),this.elPan=i(),this.elSelection=i(),this.elZoomReset=i(),this.elMenuIcon=i(),this.elMenu=i(),this.elCustomIcons=[],this.t=e.config.chart.toolbar.tools,Array.isArray(this.t.customIcons))for(var s=0;s\n \n \n\n'),o("zoomOut",this.elZoomOut,'\n \n \n\n');var n=function(i){t.t[i]&&e.config.chart[i].enabled&&r.push({el:"zoom"===i?t.elZoom:t.elSelection,icon:"string"==typeof t.t[i]?t.t[i]:"zoom"===i?'\n \n \n \n':'\n \n \n',title:t.localeValues["zoom"===i?"selectionZoom":"selection"],class:e.globals.isTouchDevice?"apexcharts-element-hidden":"apexcharts-".concat(i,"-icon")})};n("zoom"),n("selection"),this.t.pan&&e.config.chart.zoom.enabled&&r.push({el:this.elPan,icon:"string"==typeof this.t.pan?this.t.pan:'\n \n \n \n \n \n \n \n',title:this.localeValues.pan,class:e.globals.isTouchDevice?"apexcharts-element-hidden":"apexcharts-pan-icon"}),o("reset",this.elZoomReset,'\n \n \n'),this.t.download&&r.push({el:this.elMenuIcon,icon:"string"==typeof this.t.download?this.t.download:'',title:this.localeValues.menu,class:"apexcharts-menu-icon"});for(var l=0;l0&&e.height>0&&this.slDraggableRect.selectize({points:"l, r",pointSize:8,pointType:"rect"}).resize({constraint:{minX:0,minY:0,maxX:t.globals.gridWidth,maxY:t.globals.gridHeight}}).on("resizing",this.selectionDragging.bind(this,"resizing"))}}},{key:"preselectedSelection",value:function(){var t=this.w,e=this.xyRatios;if(!t.globals.zoomEnabled)if(void 0!==t.globals.selection&&null!==t.globals.selection)this.drawSelectionRect(t.globals.selection);else if(void 0!==t.config.chart.selection.xaxis.min&&void 0!==t.config.chart.selection.xaxis.max){var i=(t.config.chart.selection.xaxis.min-t.globals.minX)/e.xRatio,a={x:i,y:0,width:t.globals.gridWidth-(t.globals.maxX-t.config.chart.selection.xaxis.max)/e.xRatio-i,height:t.globals.gridHeight,translateX:0,translateY:0,selectionEnabled:!0};this.drawSelectionRect(a),this.makeSelectionRectDraggable(),"function"==typeof t.config.chart.events.selection&&t.config.chart.events.selection(this.ctx,{xaxis:{min:t.config.chart.selection.xaxis.min,max:t.config.chart.selection.xaxis.max},yaxis:{}})}}},{key:"drawSelectionRect",value:function(t){var e=t.x,i=t.y,a=t.width,s=t.height,r=t.translateX,o=void 0===r?0:r,n=t.translateY,l=void 0===n?0:n,h=this.w,c=this.zoomRect,d=this.selectionRect;if(this.dragged||null!==h.globals.selection){var g={transform:"translate("+o+", "+l+")"};h.globals.zoomEnabled&&this.dragged&&(a<0&&(a=1),c.attr({x:e,y:i,width:a,height:s,fill:h.config.chart.zoom.zoomedArea.fill.color,"fill-opacity":h.config.chart.zoom.zoomedArea.fill.opacity,stroke:h.config.chart.zoom.zoomedArea.stroke.color,"stroke-width":h.config.chart.zoom.zoomedArea.stroke.width,"stroke-opacity":h.config.chart.zoom.zoomedArea.stroke.opacity}),b.setAttrs(c.node,g)),h.globals.selectionEnabled&&(d.attr({x:e,y:i,width:a>0?a:0,height:s>0?s:0,fill:h.config.chart.selection.fill.color,"fill-opacity":h.config.chart.selection.fill.opacity,stroke:h.config.chart.selection.stroke.color,"stroke-width":h.config.chart.selection.stroke.width,"stroke-dasharray":h.config.chart.selection.stroke.dashArray,"stroke-opacity":h.config.chart.selection.stroke.opacity}),b.setAttrs(d.node,g))}}},{key:"hideSelectionRect",value:function(t){t&&t.attr({x:0,y:0,width:0,height:0})}},{key:"selectionDrawing",value:function(t){var e=t.context,i=t.zoomtype,a=this.w,s=e,r=this.gridRect.getBoundingClientRect(),o=s.startX-1,n=s.startY,l=!1,h=!1,c=s.clientX-r.left-o,d=s.clientY-r.top-n,g={};return Math.abs(c+o)>a.globals.gridWidth?c=a.globals.gridWidth-o:s.clientX-r.left<0&&(c=o),o>s.clientX-r.left&&(l=!0,c=Math.abs(c)),n>s.clientY-r.top&&(h=!0,d=Math.abs(d)),g="x"===i?{x:l?o-c:o,y:0,width:c,height:a.globals.gridHeight}:"y"===i?{x:0,y:h?n-d:n,width:a.globals.gridWidth,height:d}:{x:l?o-c:o,y:h?n-d:n,width:c,height:d},s.drawSelectionRect(g),s.selectionDragging("resizing"),g}},{key:"selectionDragging",value:function(t,e){var i=this,a=this.w,s=this.xyRatios,r=this.selectionRect,o=0;"resizing"===t&&(o=30);var n=function(t){return parseFloat(r.node.getAttribute(t))},l={x:n("x"),y:n("y"),width:n("width"),height:n("height")};a.globals.selection=l,"function"==typeof a.config.chart.events.selection&&a.globals.selectionEnabled&&(clearTimeout(this.w.globals.selectionResizeTimer),this.w.globals.selectionResizeTimer=window.setTimeout((function(){var t=i.gridRect.getBoundingClientRect(),e=r.node.getBoundingClientRect(),o={xaxis:{min:a.globals.xAxisScale.niceMin+(e.left-t.left)*s.xRatio,max:a.globals.xAxisScale.niceMin+(e.right-t.left)*s.xRatio},yaxis:{min:a.globals.yAxisScale[0].niceMin+(t.bottom-e.bottom)*s.yRatio[0],max:a.globals.yAxisScale[0].niceMax-(e.top-t.top)*s.yRatio[0]}};a.config.chart.events.selection(i.ctx,o),a.config.chart.brush.enabled&&void 0!==a.config.chart.events.brushScrolled&&a.config.chart.events.brushScrolled(i.ctx,o)}),o))}},{key:"selectionDrawn",value:function(t){var e=t.context,i=t.zoomtype,a=this.w,s=e,r=this.xyRatios,o=this.ctx.toolbar;if(s.startX>s.endX){var n=s.startX;s.startX=s.endX,s.endX=n}if(s.startY>s.endY){var l=s.startY;s.startY=s.endY,s.endY=l}var h=void 0,c=void 0;a.globals.isRangeBar?(h=a.globals.yAxisScale[0].niceMin+s.startX*r.invertedYRatio,c=a.globals.yAxisScale[0].niceMin+s.endX*r.invertedYRatio):(h=a.globals.xAxisScale.niceMin+s.startX*r.xRatio,c=a.globals.xAxisScale.niceMin+s.endX*r.xRatio);var d=[],g=[];if(a.config.yaxis.forEach((function(t,e){d.push(a.globals.yAxisScale[e].niceMax-r.yRatio[e]*s.startY),g.push(a.globals.yAxisScale[e].niceMax-r.yRatio[e]*s.endY)})),s.dragged&&(s.dragX>10||s.dragY>10)&&h!==c)if(a.globals.zoomEnabled){var u=p.clone(a.globals.initialConfig.yaxis),f=p.clone(a.globals.initialConfig.xaxis);if(a.globals.zoomed=!0,a.config.xaxis.convertedCatToNumeric&&(h=Math.floor(h),c=Math.floor(c),h<1&&(h=1,c=a.globals.dataPoints),c-h<2&&(c=h+1)),"xy"!==i&&"x"!==i||(f={min:h,max:c}),"xy"!==i&&"y"!==i||u.forEach((function(t,e){u[e].min=g[e],u[e].max=d[e]})),a.config.chart.zoom.autoScaleYaxis){var x=new j(s.ctx);u=x.autoScaleY(s.ctx,u,{xaxis:f})}if(o){var b=o.getBeforeZoomRange(f,u);b&&(f=b.xaxis?b.xaxis:f,u=b.yaxis?b.yaxis:u)}var v={xaxis:f};a.config.chart.group||(v.yaxis=u),s.ctx.updateHelpers._updateOptions(v,!1,s.w.config.chart.animations.dynamicAnimation.enabled),"function"==typeof a.config.chart.events.zoomed&&o.zoomCallback(f,u)}else if(a.globals.selectionEnabled){var m,y=null;m={min:h,max:c},"xy"!==i&&"y"!==i||(y=p.clone(a.config.yaxis)).forEach((function(t,e){y[e].min=g[e],y[e].max=d[e]})),a.globals.selection=s.selection,"function"==typeof a.config.chart.events.selection&&a.config.chart.events.selection(s.ctx,{xaxis:m,yaxis:y})}}},{key:"panDragging",value:function(t){var e=t.context,i=this.w,a=e;if(void 0!==i.globals.lastClientPosition.x){var s=i.globals.lastClientPosition.x-a.clientX,r=i.globals.lastClientPosition.y-a.clientY;Math.abs(s)>Math.abs(r)&&s>0?this.moveDirection="left":Math.abs(s)>Math.abs(r)&&s<0?this.moveDirection="right":Math.abs(r)>Math.abs(s)&&r>0?this.moveDirection="up":Math.abs(r)>Math.abs(s)&&r<0&&(this.moveDirection="down")}i.globals.lastClientPosition={x:a.clientX,y:a.clientY};var o=i.globals.isRangeBar?i.globals.minY:i.globals.minX,n=i.globals.isRangeBar?i.globals.maxY:i.globals.maxX;i.config.xaxis.convertedCatToNumeric||a.panScrolled(o,n)}},{key:"delayedPanScrolled",value:function(){var t=this.w,e=t.globals.minX,i=t.globals.maxX,a=(t.globals.maxX-t.globals.minX)/2;"left"===this.moveDirection?(e=t.globals.minX+a,i=t.globals.maxX+a):"right"===this.moveDirection&&(e=t.globals.minX-a,i=t.globals.maxX-a),e=Math.floor(e),i=Math.floor(i),this.updateScrolledChart({xaxis:{min:e,max:i}},e,i)}},{key:"panScrolled",value:function(t,e){var i=this.w,a=this.xyRatios,s=p.clone(i.globals.initialConfig.yaxis),r=a.xRatio,o=i.globals.minX,n=i.globals.maxX;i.globals.isRangeBar&&(r=a.invertedYRatio,o=i.globals.minY,n=i.globals.maxY),"left"===this.moveDirection?(t=o+i.globals.gridWidth/15*r,e=n+i.globals.gridWidth/15*r):"right"===this.moveDirection&&(t=o-i.globals.gridWidth/15*r,e=n-i.globals.gridWidth/15*r),i.globals.isRangeBar||(ti.globals.initialMaxX)&&(t=o,e=n);var l={min:t,max:e};i.config.chart.zoom.autoScaleYaxis&&(s=new j(this.ctx).autoScaleY(this.ctx,s,{xaxis:l}));var h={xaxis:{min:t,max:e}};i.config.chart.group||(h.yaxis=s),this.updateScrolledChart(h,t,e)}},{key:"updateScrolledChart",value:function(t,e,i){var a=this.w;this.ctx.updateHelpers._updateOptions(t,!1,!1),"function"==typeof a.config.chart.events.scrolled&&a.config.chart.events.scrolled(this.ctx,{xaxis:{min:e,max:i}})}}]),i}(ht),dt=function(){function t(e){a(this,t),this.w=e.w,this.ttCtx=e,this.ctx=e.ctx}return r(t,[{key:"getNearestValues",value:function(t){var e=t.hoverArea,i=t.elGrid,a=t.clientX,s=t.clientY,r=this.w,o=i.getBoundingClientRect(),n=o.width,l=o.height,h=n/(r.globals.dataPoints-1),c=l/r.globals.dataPoints,d=this.hasBars();!r.globals.comboCharts&&!d||r.config.xaxis.convertedCatToNumeric||(h=n/r.globals.dataPoints);var g=a-o.left-r.globals.barPadForNumericAxis,u=s-o.top;g<0||u<0||g>n||u>l?(e.classList.remove("hovering-zoom"),e.classList.remove("hovering-pan")):r.globals.zoomEnabled?(e.classList.remove("hovering-pan"),e.classList.add("hovering-zoom")):r.globals.panEnabled&&(e.classList.remove("hovering-zoom"),e.classList.add("hovering-pan"));var f=Math.round(g/h),x=Math.floor(u/c);d&&!r.config.xaxis.convertedCatToNumeric&&(f=Math.ceil(g/h),f-=1);for(var b,v=null,m=null,y=[],w=0;w1?r=this.getFirstActiveXArray(i):o=0;var l=a[r][0],h=i[r][0],c=Math.abs(t-h),d=Math.abs(e-l),g=d+c;return a.map((function(s,r){s.map((function(s,l){var h=Math.abs(e-a[r][l]),u=Math.abs(t-i[r][l]),p=u+h;p0?e:-1})),a=0;a0)for(var a=0;a0}},{key:"getElBars",value:function(){return this.w.globals.dom.baseEl.querySelectorAll(".apexcharts-bar-series, .apexcharts-candlestick-series, .apexcharts-boxPlot-series, .apexcharts-rangebar-series")}},{key:"hasBars",value:function(){return this.getElBars().length>0}},{key:"getHoverMarkerSize",value:function(t){var e=this.w,i=e.config.markers.hover.size;return void 0===i&&(i=e.globals.markers.size[t]+e.config.markers.hover.sizeOffset),i}},{key:"toggleAllTooltipSeriesGroups",value:function(t){var e=this.w,i=this.ttCtx;0===i.allTooltipSeriesGroups.length&&(i.allTooltipSeriesGroups=e.globals.dom.baseEl.querySelectorAll(".apexcharts-tooltip-series-group"));for(var a=i.allTooltipSeriesGroups,s=0;s ').concat(i.attrs.name,""),e+="
".concat(i.val,"
")})),v.innerHTML=t+"",m.innerHTML=e+""};o?l.globals.seriesGoals[e][i]&&Array.isArray(l.globals.seriesGoals[e][i])?y():(v.innerHTML="",m.innerHTML=""):y()}else v.innerHTML="",m.innerHTML="";null!==p&&(a[e].querySelector(".apexcharts-tooltip-text-z-label").innerHTML=l.config.tooltip.z.title,a[e].querySelector(".apexcharts-tooltip-text-z-value").innerHTML=void 0!==p?p:"");o&&f[0]&&(null==c||l.globals.collapsedSeriesIndices.indexOf(e)>-1?f[0].parentNode.style.display="none":f[0].parentNode.style.display=l.config.tooltip.items.display)}},{key:"toggleActiveInactiveSeries",value:function(t){var e=this.w;if(t)this.tooltipUtil.toggleAllTooltipSeriesGroups("enable");else{this.tooltipUtil.toggleAllTooltipSeriesGroups("disable");var i=e.globals.dom.baseEl.querySelector(".apexcharts-tooltip-series-group");i&&(i.classList.add("apexcharts-active"),i.style.display=e.config.tooltip.items.display)}}},{key:"getValuesToPrint",value:function(t){var e=t.i,i=t.j,a=this.w,s=this.ctx.series.filteredSeriesX(),r="",o="",n=null,l=null,h={series:a.globals.series,seriesIndex:e,dataPointIndex:i,w:a},c=a.globals.ttZFormatter;null===i?l=a.globals.series[e]:a.globals.isXNumeric&&"treemap"!==a.config.chart.type?(r=s[e][i],0===s[e].length&&(r=s[this.tooltipUtil.getFirstActiveXArray(s)][i])):r=void 0!==a.globals.labels[i]?a.globals.labels[i]:"";var d=r;a.globals.isXNumeric&&"datetime"===a.config.xaxis.type?r=new W(this.ctx).xLabelFormat(a.globals.ttKeyFormatter,d,d,{i:void 0,dateFormatter:new Y(this.ctx).formatDate,w:this.w}):r=a.globals.isBarHorizontal?a.globals.yLabelFormatters[0](d,h):a.globals.xLabelFormatter(d,h);return void 0!==a.config.tooltip.x.formatter&&(r=a.globals.ttKeyFormatter(d,h)),a.globals.seriesZ.length>0&&a.globals.seriesZ[e].length>0&&(n=c(a.globals.seriesZ[e][i],a)),o="function"==typeof a.config.xaxis.tooltip.formatter?a.globals.xaxisTooltipFormatter(d,h):r,{val:Array.isArray(l)?l.join(" "):l,xVal:Array.isArray(r)?r.join(" "):r,xAxisTTVal:Array.isArray(o)?o.join(" "):o,zVal:n}}},{key:"handleCustomTooltip",value:function(t){var e=t.i,i=t.j,a=t.y1,s=t.y2,r=t.w,o=this.ttCtx.getElTooltip(),n=r.config.tooltip.custom;Array.isArray(n)&&n[e]&&(n=n[e]),o.innerHTML=n({ctx:this.ctx,series:r.globals.series,seriesIndex:e,dataPointIndex:i,y1:a,y2:s,w:r})}}]),t}(),ut=function(){function t(e){a(this,t),this.ttCtx=e,this.ctx=e.ctx,this.w=e.w}return r(t,[{key:"moveXCrosshairs",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,i=this.ttCtx,a=this.w,s=i.getElXCrosshairs(),r=t-i.xcrosshairsWidth/2,o=a.globals.labels.slice().length;if(null!==e&&(r=a.globals.gridWidth/o*e),null===s||a.globals.isBarHorizontal||(s.setAttribute("x",r),s.setAttribute("x1",r),s.setAttribute("x2",r),s.setAttribute("y2",a.globals.gridHeight),s.classList.add("apexcharts-active")),r<0&&(r=0),r>a.globals.gridWidth&&(r=a.globals.gridWidth),i.isXAxisTooltipEnabled){var n=r;"tickWidth"!==a.config.xaxis.crosshairs.width&&"barWidth"!==a.config.xaxis.crosshairs.width||(n=r+i.xcrosshairsWidth/2),this.moveXAxisTooltip(n)}}},{key:"moveYCrosshairs",value:function(t){var e=this.ttCtx;null!==e.ycrosshairs&&b.setAttrs(e.ycrosshairs,{y1:t,y2:t}),null!==e.ycrosshairsHidden&&b.setAttrs(e.ycrosshairsHidden,{y1:t,y2:t})}},{key:"moveXAxisTooltip",value:function(t){var e=this.w,i=this.ttCtx;if(null!==i.xaxisTooltip&&0!==i.xcrosshairsWidth){i.xaxisTooltip.classList.add("apexcharts-active");var a=i.xaxisOffY+e.config.xaxis.tooltip.offsetY+e.globals.translateY+1+e.config.xaxis.offsetY;if(t-=i.xaxisTooltip.getBoundingClientRect().width/2,!isNaN(t)){t+=e.globals.translateX;var s;s=new b(this.ctx).getTextRects(i.xaxisTooltipText.innerHTML),i.xaxisTooltipText.style.minWidth=s.width+"px",i.xaxisTooltip.style.left=t+"px",i.xaxisTooltip.style.top=a+"px"}}}},{key:"moveYAxisTooltip",value:function(t){var e=this.w,i=this.ttCtx;null===i.yaxisTTEls&&(i.yaxisTTEls=e.globals.dom.baseEl.querySelectorAll(".apexcharts-yaxistooltip"));var a=parseInt(i.ycrosshairsHidden.getAttribute("y1"),10),s=e.globals.translateY+a,r=i.yaxisTTEls[t].getBoundingClientRect().height,o=e.globals.translateYAxisX[t]-2;e.config.yaxis[t].opposite&&(o-=26),s-=r/2,-1===e.globals.ignoreYAxisIndexes.indexOf(t)?(i.yaxisTTEls[t].classList.add("apexcharts-active"),i.yaxisTTEls[t].style.top=s+"px",i.yaxisTTEls[t].style.left=o+e.config.yaxis[t].tooltip.offsetX+"px"):i.yaxisTTEls[t].classList.remove("apexcharts-active")}},{key:"moveTooltip",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,a=this.w,s=this.ttCtx,r=s.getElTooltip(),o=s.tooltipRect,n=null!==i?parseFloat(i):1,l=parseFloat(t)+n+5,h=parseFloat(e)+n/2;if(l>a.globals.gridWidth/2&&(l=l-o.ttWidth-n-15),l>a.globals.gridWidth-o.ttWidth-10&&(l=a.globals.gridWidth-o.ttWidth),l<-20&&(l=-20),a.config.tooltip.followCursor){var c=s.getElGrid(),d=c.getBoundingClientRect();h=s.e.clientY+a.globals.translateY-d.top-o.ttHeight/2}else a.globals.isBarHorizontal?h-=o.ttHeight:(o.ttHeight/2+h>a.globals.gridHeight&&(h=a.globals.gridHeight-o.ttHeight+a.globals.translateY),h<0&&(h=0));isNaN(l)||(l+=a.globals.translateX,r.style.left=l+"px",r.style.top=h+"px")}},{key:"moveMarkers",value:function(t,e){var i=this.w,a=this.ttCtx;if(i.globals.markers.size[t]>0)for(var s=i.globals.dom.baseEl.querySelectorAll(" .apexcharts-series[data\\:realIndex='".concat(t,"'] .apexcharts-marker")),r=0;r0&&(h.setAttribute("r",n),h.setAttribute("cx",i),h.setAttribute("cy",a)),this.moveXCrosshairs(i),r.fixedTooltip||this.moveTooltip(i,a,n)}}},{key:"moveDynamicPointsOnHover",value:function(t){var e,i=this.ttCtx,a=i.w,s=0,r=0,o=a.globals.pointsArray;e=new z(this.ctx).getActiveConfigSeriesIndex(!0);var n=i.tooltipUtil.getHoverMarkerSize(e);o[e]&&(s=o[e][t][0],r=o[e][t][1]);var l=i.tooltipUtil.getAllMarkers();if(null!==l)for(var h=0;h0?(l[h]&&l[h].setAttribute("r",n),l[h]&&l[h].setAttribute("cy",d)):l[h]&&l[h].setAttribute("r",0)}}if(this.moveXCrosshairs(s),!i.fixedTooltip){var g=r||a.globals.gridHeight;this.moveTooltip(s,g,n)}}},{key:"moveStickyTooltipOverBars",value:function(t){var e=this.w,i=this.ttCtx,a=e.globals.columnSeries?e.globals.columnSeries.length:e.globals.series.length,s=a>=2&&a%2==0?Math.floor(a/2):Math.floor(a/2)+1;e.globals.isBarHorizontal&&(s=new z(this.ctx).getActiveConfigSeriesIndex(!1,"desc")+1);var r=e.globals.dom.baseEl.querySelector(".apexcharts-bar-series .apexcharts-series[rel='".concat(s,"'] path[j='").concat(t,"'], .apexcharts-candlestick-series .apexcharts-series[rel='").concat(s,"'] path[j='").concat(t,"'], .apexcharts-boxPlot-series .apexcharts-series[rel='").concat(s,"'] path[j='").concat(t,"'], .apexcharts-rangebar-series .apexcharts-series[rel='").concat(s,"'] path[j='").concat(t,"']")),o=r?parseFloat(r.getAttribute("cx")):0,n=r?parseFloat(r.getAttribute("cy")):0,l=r?parseFloat(r.getAttribute("barWidth")):0,h=r?parseFloat(r.getAttribute("barHeight")):0,c=i.getElGrid().getBoundingClientRect(),d=r.classList.contains("apexcharts-candlestick-area")||r.classList.contains("apexcharts-boxPlot-area");if(e.globals.isXNumeric?(r&&!d&&(o-=a%2!=0?l/2:0),r&&d&&e.globals.comboCharts&&(o-=l/2)):e.globals.isBarHorizontal||(o=i.xAxisTicksPositions[t-1]+i.dataPointsDividedWidth/2,isNaN(o)&&(o=i.xAxisTicksPositions[t]-i.dataPointsDividedWidth/2)),e.globals.isBarHorizontal?n+=h/3:n=i.e.clientY-c.top-i.tooltipRect.ttHeight/2,e.globals.isBarHorizontal||this.moveXCrosshairs(o),!i.fixedTooltip){var g=n||e.globals.gridHeight;this.moveTooltip(o,g)}}}]),t}(),pt=function(){function t(e){a(this,t),this.w=e.w,this.ttCtx=e,this.ctx=e.ctx,this.tooltipPosition=new ut(e)}return r(t,[{key:"drawDynamicPoints",value:function(){var t=this.w,e=new b(this.ctx),i=new P(this.ctx),a=t.globals.dom.baseEl.querySelectorAll(".apexcharts-series");a=g(a),t.config.chart.stacked&&a.sort((function(t,e){return parseFloat(t.getAttribute("data:realIndex"))-parseFloat(e.getAttribute("data:realIndex"))}));for(var s=0;s2&&void 0!==arguments[2]?arguments[2]:null,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,s=this.w;"bubble"!==s.config.chart.type&&this.newPointSize(t,e);var r=e.getAttribute("cx"),o=e.getAttribute("cy");if(null!==i&&null!==a&&(r=i,o=a),this.tooltipPosition.moveXCrosshairs(r),!this.fixedTooltip){if("radar"===s.config.chart.type){var n=this.ttCtx.getElGrid(),l=n.getBoundingClientRect();r=this.ttCtx.e.clientX-l.left}this.tooltipPosition.moveTooltip(r,o,s.config.markers.hover.size)}}},{key:"enlargePoints",value:function(t){for(var e=this.w,i=this,a=this.ttCtx,s=t,r=e.globals.dom.baseEl.querySelectorAll(".apexcharts-series:not(.apexcharts-series-collapsed) .apexcharts-marker"),o=e.config.markers.hover.size,n=0;n=0?t[e].setAttribute("r",i):t[e].setAttribute("r",0)}}}]),t}(),ft=function(){function t(e){a(this,t),this.w=e.w,this.ttCtx=e}return r(t,[{key:"getAttr",value:function(t,e){return parseFloat(t.target.getAttribute(e))}},{key:"handleHeatTreeTooltip",value:function(t){var e=t.e,i=t.opt,a=t.x,s=t.y,r=t.type,o=this.ttCtx,n=this.w;if(e.target.classList.contains("apexcharts-".concat(r,"-rect"))){var l=this.getAttr(e,"i"),h=this.getAttr(e,"j"),c=this.getAttr(e,"cx"),d=this.getAttr(e,"cy"),g=this.getAttr(e,"width"),u=this.getAttr(e,"height");if(o.tooltipLabels.drawSeriesTexts({ttItems:i.ttItems,i:l,j:h,shared:!1,e:e}),n.globals.capturedSeriesIndex=l,n.globals.capturedDataPointIndex=h,a=c+o.tooltipRect.ttWidth/2+g,s=d+o.tooltipRect.ttHeight/2-u/2,o.tooltipPosition.moveXCrosshairs(c+g/2),a>n.globals.gridWidth/2&&(a=c-o.tooltipRect.ttWidth/2+g),o.w.config.tooltip.followCursor){var p=n.globals.dom.elWrap.getBoundingClientRect();a=n.globals.clientX-p.left-o.tooltipRect.ttWidth/2,s=n.globals.clientY-p.top-o.tooltipRect.ttHeight-5}}return{x:a,y:s}}},{key:"handleMarkerTooltip",value:function(t){var e,i,a=t.e,s=t.opt,r=t.x,o=t.y,n=this.w,l=this.ttCtx;if(a.target.classList.contains("apexcharts-marker")){var h=parseInt(s.paths.getAttribute("cx"),10),c=parseInt(s.paths.getAttribute("cy"),10),d=parseFloat(s.paths.getAttribute("val"));if(i=parseInt(s.paths.getAttribute("rel"),10),e=parseInt(s.paths.parentNode.parentNode.parentNode.getAttribute("rel"),10)-1,l.intersect){var g=p.findAncestor(s.paths,"apexcharts-series");g&&(e=parseInt(g.getAttribute("data:realIndex"),10))}if(l.tooltipLabels.drawSeriesTexts({ttItems:s.ttItems,i:e,j:i,shared:!l.showOnIntersect&&n.config.tooltip.shared,e:a}),"mouseup"===a.type&&l.markerClick(a,e,i),n.globals.capturedSeriesIndex=e,n.globals.capturedDataPointIndex=i,r=h,o=c+n.globals.translateY-1.4*l.tooltipRect.ttHeight,l.w.config.tooltip.followCursor){var u=l.getElGrid().getBoundingClientRect();o=l.e.clientY+n.globals.translateY-u.top}d<0&&(o=c),l.marker.enlargeCurrentPoint(i,s.paths,r,o)}return{x:r,y:o}}},{key:"handleBarTooltip",value:function(t){var e,i,a=t.e,s=t.opt,r=this.w,o=this.ttCtx,n=o.getElTooltip(),l=0,h=0,c=0,d=this.getBarTooltipXY({e:a,opt:s});e=d.i;var g=d.barHeight,u=d.j;r.globals.capturedSeriesIndex=e,r.globals.capturedDataPointIndex=u,r.globals.isBarHorizontal&&o.tooltipUtil.hasBars()||!r.config.tooltip.shared?(h=d.x,c=d.y,i=Array.isArray(r.config.stroke.width)?r.config.stroke.width[e]:r.config.stroke.width,l=h):r.globals.comboCharts||r.config.tooltip.shared||(l/=2),isNaN(c)?c=r.globals.svgHeight-o.tooltipRect.ttHeight:c<0&&(c=0);var p=parseInt(s.paths.parentNode.getAttribute("data:realIndex"),10),f=r.globals.isMultipleYAxis?r.config.yaxis[p]&&r.config.yaxis[p].reversed:r.config.yaxis[0].reversed;if(h+o.tooltipRect.ttWidth>r.globals.gridWidth&&!f?h-=o.tooltipRect.ttWidth:h<0&&(h=0),o.w.config.tooltip.followCursor){var x=o.getElGrid().getBoundingClientRect();c=o.e.clientY-x.top}null===o.tooltip&&(o.tooltip=r.globals.dom.baseEl.querySelector(".apexcharts-tooltip")),r.config.tooltip.shared||(r.globals.comboBarCount>0?o.tooltipPosition.moveXCrosshairs(l+i/2):o.tooltipPosition.moveXCrosshairs(l)),!o.fixedTooltip&&(!r.config.tooltip.shared||r.globals.isBarHorizontal&&o.tooltipUtil.hasBars())&&(f&&(h-=o.tooltipRect.ttWidth)<0&&(h=0),n.style.left=h+r.globals.translateX+"px",!f||r.globals.isBarHorizontal&&o.tooltipUtil.hasBars()||(c=c+g-2*(r.globals.series[e][u]<0?g:0)),o.tooltipRect.ttHeight+c>r.globals.gridHeight?(c=r.globals.gridHeight-o.tooltipRect.ttHeight+r.globals.translateY,n.style.top=c+"px"):n.style.top=c+r.globals.translateY-o.tooltipRect.ttHeight/2+"px")}},{key:"getBarTooltipXY",value:function(t){var e=t.e,i=t.opt,a=this.w,s=null,r=this.ttCtx,o=0,n=0,l=0,h=0,c=0,d=e.target.classList;if(d.contains("apexcharts-bar-area")||d.contains("apexcharts-candlestick-area")||d.contains("apexcharts-boxPlot-area")||d.contains("apexcharts-rangebar-area")){var g=e.target,u=g.getBoundingClientRect(),p=i.elGrid.getBoundingClientRect(),f=u.height;c=u.height;var x=u.width,b=parseInt(g.getAttribute("cx"),10),v=parseInt(g.getAttribute("cy"),10);h=parseFloat(g.getAttribute("barWidth"));var m="touchmove"===e.type?e.touches[0].clientX:e.clientX;s=parseInt(g.getAttribute("j"),10),o=parseInt(g.parentNode.getAttribute("rel"),10)-1;var y=g.getAttribute("data-range-y1"),w=g.getAttribute("data-range-y2");a.globals.comboCharts&&(o=parseInt(g.parentNode.getAttribute("data:realIndex"),10)),r.tooltipLabels.drawSeriesTexts({ttItems:i.ttItems,i:o,j:s,y1:y?parseInt(y,10):null,y2:w?parseInt(w,10):null,shared:!r.showOnIntersect&&a.config.tooltip.shared,e:e}),a.config.tooltip.followCursor?a.globals.isBarHorizontal?(n=m-p.left+15,l=v-r.dataPointsDividedHeight+f/2-r.tooltipRect.ttHeight/2):(n=a.globals.isXNumeric?b-x/2:b-r.dataPointsDividedWidth+x/2,l=e.clientY-p.top-r.tooltipRect.ttHeight/2-15):a.globals.isBarHorizontal?((n=b)0&&i.setAttribute("width",e.xcrosshairsWidth)}},{key:"handleYCrosshair",value:function(){var t=this.w,e=this.ttCtx;e.ycrosshairs=t.globals.dom.baseEl.querySelector(".apexcharts-ycrosshairs"),e.ycrosshairsHidden=t.globals.dom.baseEl.querySelector(".apexcharts-ycrosshairs-hidden")}},{key:"drawYaxisTooltipText",value:function(t,e,i){var a=this.ttCtx,s=this.w,r=s.globals.yLabelFormatters[t];if(a.yaxisTooltips[t]){var o=a.getElGrid().getBoundingClientRect(),n=(e-o.top)*i.yRatio[t],l=s.globals.maxYArr[t]-s.globals.minYArr[t],h=s.globals.minYArr[t]+(l-n);a.tooltipPosition.moveYCrosshairs(e-o.top),a.yaxisTooltipText[t].innerHTML=r(h),a.tooltipPosition.moveYAxisTooltip(t)}}}]),t}(),bt=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w;var i=this.w;this.tConfig=i.config.tooltip,this.tooltipUtil=new dt(this),this.tooltipLabels=new gt(this),this.tooltipPosition=new ut(this),this.marker=new pt(this),this.intersect=new ft(this),this.axesTooltip=new xt(this),this.showOnIntersect=this.tConfig.intersect,this.showTooltipTitle=this.tConfig.x.show,this.fixedTooltip=this.tConfig.fixed.enabled,this.xaxisTooltip=null,this.yaxisTTEls=null,this.isBarShared=!i.globals.isBarHorizontal&&this.tConfig.shared,this.lastHoverTime=Date.now()}return r(t,[{key:"getElTooltip",value:function(t){return t||(t=this),t.w.globals.dom.baseEl.querySelector(".apexcharts-tooltip")}},{key:"getElXCrosshairs",value:function(){return this.w.globals.dom.baseEl.querySelector(".apexcharts-xcrosshairs")}},{key:"getElGrid",value:function(){return this.w.globals.dom.baseEl.querySelector(".apexcharts-grid")}},{key:"drawTooltip",value:function(t){var e=this.w;this.xyRatios=t,this.isXAxisTooltipEnabled=e.config.xaxis.tooltip.enabled&&e.globals.axisCharts,this.yaxisTooltips=e.config.yaxis.map((function(t,i){return!!(t.show&&t.tooltip.enabled&&e.globals.axisCharts)})),this.allTooltipSeriesGroups=[],e.globals.axisCharts||(this.showTooltipTitle=!1);var i=document.createElement("div");if(i.classList.add("apexcharts-tooltip"),i.classList.add("apexcharts-theme-".concat(this.tConfig.theme)),e.globals.dom.elWrap.appendChild(i),e.globals.axisCharts){this.axesTooltip.drawXaxisTooltip(),this.axesTooltip.drawYaxisTooltip(),this.axesTooltip.setXCrosshairWidth(),this.axesTooltip.handleYCrosshair();var a=new G(this.ctx);this.xAxisTicksPositions=a.getXAxisTicksPositions()}if(!e.globals.comboCharts&&!this.tConfig.intersect&&"rangeBar"!==e.config.chart.type||this.tConfig.shared||(this.showOnIntersect=!0),0!==e.config.markers.size&&0!==e.globals.markers.largestSize||this.marker.drawDynamicPoints(this),e.globals.collapsedSeries.length!==e.globals.series.length){this.dataPointsDividedHeight=e.globals.gridHeight/e.globals.dataPoints,this.dataPointsDividedWidth=e.globals.gridWidth/e.globals.dataPoints,this.showTooltipTitle&&(this.tooltipTitle=document.createElement("div"),this.tooltipTitle.classList.add("apexcharts-tooltip-title"),this.tooltipTitle.style.fontFamily=this.tConfig.style.fontFamily||e.config.chart.fontFamily,this.tooltipTitle.style.fontSize=this.tConfig.style.fontSize,i.appendChild(this.tooltipTitle));var s=e.globals.series.length;(e.globals.xyCharts||e.globals.comboCharts)&&this.tConfig.shared&&(s=this.showOnIntersect?1:e.globals.series.length),this.legendLabels=e.globals.dom.baseEl.querySelectorAll(".apexcharts-legend-text"),this.ttItems=this.createTTElements(s),this.addSVGEvents()}}},{key:"createTTElements",value:function(t){for(var e=this,i=this.w,a=[],s=this.getElTooltip(),r=function(r){var o=document.createElement("div");o.classList.add("apexcharts-tooltip-series-group"),o.style.order=i.config.tooltip.inverseOrder?t-r:r+1,e.tConfig.shared&&e.tConfig.enabledOnSeries&&Array.isArray(e.tConfig.enabledOnSeries)&&e.tConfig.enabledOnSeries.indexOf(r)<0&&o.classList.add("apexcharts-tooltip-series-group-hidden");var n=document.createElement("span");n.classList.add("apexcharts-tooltip-marker"),n.style.backgroundColor=i.globals.colors[r],o.appendChild(n);var l=document.createElement("div");l.classList.add("apexcharts-tooltip-text"),l.style.fontFamily=e.tConfig.style.fontFamily||i.config.chart.fontFamily,l.style.fontSize=e.tConfig.style.fontSize,["y","goals","z"].forEach((function(t){var e=document.createElement("div");e.classList.add("apexcharts-tooltip-".concat(t,"-group"));var i=document.createElement("span");i.classList.add("apexcharts-tooltip-text-".concat(t,"-label")),e.appendChild(i);var a=document.createElement("span");a.classList.add("apexcharts-tooltip-text-".concat(t,"-value")),e.appendChild(a),l.appendChild(e)})),o.appendChild(l),s.appendChild(o),a.push(o)},o=0;o0&&this.addPathsEventListeners(u,c),this.tooltipUtil.hasBars()&&!this.tConfig.shared&&this.addDatapointEventsListeners(c)}}},{key:"drawFixedTooltipRect",value:function(){var t=this.w,e=this.getElTooltip(),i=e.getBoundingClientRect(),a=i.width+10,s=i.height+10,r=this.tConfig.fixed.offsetX,o=this.tConfig.fixed.offsetY,n=this.tConfig.fixed.position.toLowerCase();return n.indexOf("right")>-1&&(r=r+t.globals.svgWidth-a+10),n.indexOf("bottom")>-1&&(o=o+t.globals.svgHeight-s-10),e.style.left=r+"px",e.style.top=o+"px",{x:r,y:o,ttWidth:a,ttHeight:s}}},{key:"addDatapointEventsListeners",value:function(t){var e=this.w.globals.dom.baseEl.querySelectorAll(".apexcharts-series-markers .apexcharts-marker, .apexcharts-bar-area, .apexcharts-candlestick-area, .apexcharts-boxPlot-area, .apexcharts-rangebar-area");this.addPathsEventListeners(e,t)}},{key:"addPathsEventListeners",value:function(t,e){for(var i=this,a=function(a){var s={paths:t[a],tooltipEl:e.tooltipEl,tooltipY:e.tooltipY,tooltipX:e.tooltipX,elGrid:e.elGrid,hoverArea:e.hoverArea,ttItems:e.ttItems};["mousemove","mouseup","touchmove","mouseout","touchend"].map((function(e){return t[a].addEventListener(e,i.onSeriesHover.bind(i,s),{capture:!1,passive:!0})}))},s=0;s=100?this.seriesHover(t,e):(clearTimeout(this.seriesHoverTimeout),this.seriesHoverTimeout=setTimeout((function(){i.seriesHover(t,e)}),100-a))}},{key:"seriesHover",value:function(t,e){var i=this;this.lastHoverTime=Date.now();var a=[],s=this.w;s.config.chart.group&&(a=this.ctx.getGroupedCharts()),s.globals.axisCharts&&(s.globals.minX===-1/0&&s.globals.maxX===1/0||0===s.globals.dataPoints)||(a.length?a.forEach((function(a){var s=i.getElTooltip(a),r={paths:t.paths,tooltipEl:s,tooltipY:t.tooltipY,tooltipX:t.tooltipX,elGrid:t.elGrid,hoverArea:t.hoverArea,ttItems:a.w.globals.tooltip.ttItems};a.w.globals.minX===i.w.globals.minX&&a.w.globals.maxX===i.w.globals.maxX&&a.w.globals.tooltip.seriesHoverByContext({chartCtx:a,ttCtx:a.w.globals.tooltip,opt:r,e:e})})):this.seriesHoverByContext({chartCtx:this.ctx,ttCtx:this.w.globals.tooltip,opt:t,e:e}))}},{key:"seriesHoverByContext",value:function(t){var e=t.chartCtx,i=t.ttCtx,a=t.opt,s=t.e,r=e.w,o=this.getElTooltip();(i.tooltipRect={x:0,y:0,ttWidth:o.getBoundingClientRect().width,ttHeight:o.getBoundingClientRect().height},i.e=s,!i.tooltipUtil.hasBars()||r.globals.comboCharts||i.isBarShared)||this.tConfig.onDatasetHover.highlightDataSeries&&new z(e).toggleSeriesOnHover(s,s.target.parentNode);i.fixedTooltip&&i.drawFixedTooltipRect(),r.globals.axisCharts?i.axisChartsTooltips({e:s,opt:a,tooltipRect:i.tooltipRect}):i.nonAxisChartsTooltips({e:s,opt:a,tooltipRect:i.tooltipRect})}},{key:"axisChartsTooltips",value:function(t){var e,i,a=t.e,s=t.opt,r=this.w,o=s.elGrid.getBoundingClientRect(),n="touchmove"===a.type?a.touches[0].clientX:a.clientX,l="touchmove"===a.type?a.touches[0].clientY:a.clientY;if(this.clientY=l,this.clientX=n,r.globals.capturedSeriesIndex=-1,r.globals.capturedDataPointIndex=-1,lo.top+o.height)this.handleMouseOut(s);else{if(Array.isArray(this.tConfig.enabledOnSeries)&&!r.config.tooltip.shared){var h=parseInt(s.paths.getAttribute("index"),10);if(this.tConfig.enabledOnSeries.indexOf(h)<0)return void this.handleMouseOut(s)}var c=this.getElTooltip(),d=this.getElXCrosshairs(),g=r.globals.xyCharts||"bar"===r.config.chart.type&&!r.globals.isBarHorizontal&&this.tooltipUtil.hasBars()&&this.tConfig.shared||r.globals.comboCharts&&this.tooltipUtil.hasBars();if("mousemove"===a.type||"touchmove"===a.type||"mouseup"===a.type){null!==d&&d.classList.add("apexcharts-active");var u=this.yaxisTooltips.filter((function(t){return!0===t}));if(null!==this.ycrosshairs&&u.length&&this.ycrosshairs.classList.add("apexcharts-active"),g&&!this.showOnIntersect)this.handleStickyTooltip(a,n,l,s);else if("heatmap"===r.config.chart.type||"treemap"===r.config.chart.type){var p=this.intersect.handleHeatTreeTooltip({e:a,opt:s,x:e,y:i,type:r.config.chart.type});e=p.x,i=p.y,c.style.left=e+"px",c.style.top=i+"px"}else this.tooltipUtil.hasBars()&&this.intersect.handleBarTooltip({e:a,opt:s}),this.tooltipUtil.hasMarkers()&&this.intersect.handleMarkerTooltip({e:a,opt:s,x:e,y:i});if(this.yaxisTooltips.length)for(var f=0;fl.width?this.handleMouseOut(a):null!==n?this.handleStickyCapturedSeries(t,n,a,o):(this.tooltipUtil.isXoverlap(o)||s.globals.isBarHorizontal)&&this.create(t,this,0,o,a.ttItems)}},{key:"handleStickyCapturedSeries",value:function(t,e,i,a){var s=this.w;if(!this.tConfig.shared&&null===s.globals.series[e][a])return void this.handleMouseOut(i);void 0!==s.globals.series[e][a]?this.tConfig.shared&&this.tooltipUtil.isXoverlap(a)&&this.tooltipUtil.isInitialSeriesSameLen()?this.create(t,this,e,a,i.ttItems):this.create(t,this,e,a,i.ttItems,!1):this.tooltipUtil.isXoverlap(a)&&this.create(t,this,0,a,i.ttItems)}},{key:"deactivateHoverFilter",value:function(){for(var t=this.w,e=new b(this.ctx),i=t.globals.dom.Paper.select(".apexcharts-bar-area"),a=0;a5&&void 0!==arguments[5]?arguments[5]:null,o=this.w,n=e;"mouseup"===t.type&&this.markerClick(t,i,a),null===r&&(r=this.tConfig.shared);var l=this.tooltipUtil.hasMarkers(),h=this.tooltipUtil.getElBars();if(o.config.legend.tooltipHoverFormatter){var c=o.config.legend.tooltipHoverFormatter,d=Array.from(this.legendLabels);d.forEach((function(t){var e=t.getAttribute("data:default-text");t.innerHTML=decodeURIComponent(e)}));for(var g=0;g0?n.marker.enlargePoints(a):n.tooltipPosition.moveDynamicPointsOnHover(a)),this.tooltipUtil.hasBars()&&(this.barSeriesHeight=this.tooltipUtil.getBarsHeight(h),this.barSeriesHeight>0)){var v=new b(this.ctx),m=o.globals.dom.Paper.select(".apexcharts-bar-area[j='".concat(a,"']"));this.deactivateHoverFilter(),this.tooltipPosition.moveStickyTooltipOverBars(a);for(var y=0;y0&&(this.totalItems+=t[o].length);for(var n=this.graphics.group({class:"apexcharts-bar-series apexcharts-plot-series"}),l=0,h=0,c=function(r,o){var c=void 0,d=void 0,g=void 0,u=void 0,f=[],x=[],b=s.globals.comboCharts?i[r]:r;a.yRatio.length>1&&(a.yaxisIndex=b),a.isReversed=s.config.yaxis[a.yaxisIndex]&&s.config.yaxis[a.yaxisIndex].reversed;var v=a.graphics.group({class:"apexcharts-series",seriesName:p.escapeString(s.globals.seriesNames[b]),rel:r+1,"data:realIndex":b});a.ctx.series.addCollapsedClassToSeries(v,b);var m=a.graphics.group({class:"apexcharts-datalabels","data:realIndex":b}),y=0,w=0,k=a.initialPositions(l,h,c,d,g,u);h=k.y,y=k.barHeight,d=k.yDivision,u=k.zeroW,l=k.x,w=k.barWidth,c=k.xDivision,g=k.zeroH,a.yArrj=[],a.yArrjF=[],a.yArrjVal=[],a.xArrj=[],a.xArrjF=[],a.xArrjVal=[],1===a.prevY.length&&a.prevY[0].every((function(t){return isNaN(t)}))&&(a.prevY[0]=a.prevY[0].map((function(t){return g})),a.prevYF[0]=a.prevYF[0].map((function(t){return 0})));for(var A=0;A1?(i=l.globals.minXDiff/this.xRatio)*parseInt(this.barOptions.columnWidth,10)/100:n*parseInt(l.config.plotOptions.bar.columnWidth,10)/100,s=this.baseLineY[this.yaxisIndex]+(this.isReversed?l.globals.gridHeight:0)-(this.isReversed?2*this.baseLineY[this.yaxisIndex]:0),t=l.globals.padHorizontal+(i-n)/2),{x:t,y:e,yDivision:a,xDivision:i,barHeight:o,barWidth:n,zeroH:s,zeroW:r}}},{key:"drawStackedBarPaths",value:function(t){for(var e,i=t.indexes,a=t.barHeight,s=t.strokeWidth,r=t.zeroW,o=t.x,n=t.y,l=t.yDivision,h=t.elSeries,c=this.w,d=n,g=i.i,u=i.j,p=0,f=0;f0){var x=r;this.prevXVal[g-1][u]<0?x=this.series[g][u]>=0?this.prevX[g-1][u]+p-2*(this.isReversed?p:0):this.prevX[g-1][u]:this.prevXVal[g-1][u]>=0&&(x=this.series[g][u]>=0?this.prevX[g-1][u]:this.prevX[g-1][u]-p+2*(this.isReversed?p:0)),e=x}else e=r;o=null===this.series[g][u]?e:e+this.series[g][u]/this.invertedYRatio-2*(this.isReversed?this.series[g][u]/this.invertedYRatio:0);var b=this.barHelpers.getBarpaths({barYPosition:d,barHeight:a,x1:e,x2:o,strokeWidth:s,series:this.series,realIndex:i.realIndex,i:g,j:u,w:c});return this.barHelpers.barBackground({j:u,i:g,y1:d,y2:a,elSeries:h}),n+=l,{pathTo:b.pathTo,pathFrom:b.pathFrom,x:o,y:n}}},{key:"drawStackedColumnPaths",value:function(t){var e=t.indexes,i=t.x,a=t.y,s=t.xDivision,r=t.barWidth,o=t.zeroH;t.strokeWidth;var n=t.elSeries,l=this.w,h=e.i,c=e.j,d=e.bc;if(l.globals.isXNumeric){var g=l.globals.seriesX[h][c];g||(g=0),i=(g-l.globals.minX)/this.xRatio-r/2}for(var u,p=i,f=0,x=0;x0&&!l.globals.isXNumeric||h>0&&l.globals.isXNumeric&&l.globals.seriesX[h-1][c]===l.globals.seriesX[h][c]){var b,v,m=Math.min(this.yRatio.length+1,h+1);if(void 0!==this.prevY[h-1])for(var y=1;y=0?v-f+2*(this.isReversed?f:0):v;break}if(this.prevYVal[h-w][c]>=0){b=this.series[h][c]>=0?v:v+f-2*(this.isReversed?f:0);break}}void 0===b&&(b=l.globals.gridHeight),u=this.prevYF[0].every((function(t){return 0===t}))&&this.prevYF.slice(1,h).every((function(t){return t.every((function(t){return isNaN(t)}))}))?l.globals.gridHeight-o:b}else u=l.globals.gridHeight-o;a=u-this.series[h][c]/this.yRatio[this.yaxisIndex]+2*(this.isReversed?this.series[h][c]/this.yRatio[this.yaxisIndex]:0);var k=this.barHelpers.getColumnPaths({barXPosition:p,barWidth:r,y1:u,y2:a,yRatio:this.yRatio[this.yaxisIndex],strokeWidth:this.strokeWidth,series:this.series,realIndex:e.realIndex,i:h,j:c,w:l});return this.barHelpers.barBackground({bc:d,j:c,i:h,x1:p,x2:r,elSeries:n}),i+=s,{pathTo:k.pathTo,pathFrom:k.pathFrom,x:l.globals.isXNumeric?i-s:i,y:a}}}]),s}(E),mt=function(t){n(s,t);var i=d(s);function s(){return a(this,s),i.apply(this,arguments)}return r(s,[{key:"draw",value:function(t,i){var a=this,s=this.w,r=new b(this.ctx),o=new L(this.ctx);this.candlestickOptions=this.w.config.plotOptions.candlestick,this.boxOptions=this.w.config.plotOptions.boxPlot,this.isHorizontal=s.config.plotOptions.bar.horizontal;var n=new y(this.ctx,s);t=n.getLogSeries(t),this.series=t,this.yRatio=n.getLogYRatios(this.yRatio),this.barHelpers.initVariables(t);for(var l=r.group({class:"apexcharts-".concat(s.config.chart.type,"-series apexcharts-plot-series")}),h=function(n){a.isBoxPlot="boxPlot"===s.config.chart.type||"boxPlot"===s.config.series[n].type;var h,c,d,g,u=void 0,f=void 0,x=[],b=[],v=s.globals.comboCharts?i[n]:n,m=r.group({class:"apexcharts-series",seriesName:p.escapeString(s.globals.seriesNames[v]),rel:n+1,"data:realIndex":v});a.ctx.series.addCollapsedClassToSeries(m,v),t[n].length>0&&(a.visibleI=a.visibleI+1);var y,w;a.yRatio.length>1&&(a.yaxisIndex=v);var k=a.barHelpers.initialPositions();f=k.y,y=k.barHeight,c=k.yDivision,g=k.zeroW,u=k.x,w=k.barWidth,h=k.xDivision,d=k.zeroH,b.push(u+w/2);for(var A=r.group({class:"apexcharts-datalabels","data:realIndex":v}),S=function(i){var r=a.barHelpers.getStrokeWidth(n,i,v),l=null,p={indexes:{i:n,j:i,realIndex:v},x:u,y:f,strokeWidth:r,elSeries:m};l=a.isHorizontal?a.drawHorizontalBoxPaths(e(e({},p),{},{yDivision:c,barHeight:y,zeroW:g})):a.drawVerticalBoxPaths(e(e({},p),{},{xDivision:h,barWidth:w,zeroH:d})),f=l.y,u=l.x,i>0&&b.push(u+w/2),x.push(f),l.pathTo.forEach((function(e,h){var c=!a.isBoxPlot&&a.candlestickOptions.wick.useFillColor?l.color[h]:s.globals.stroke.colors[n],d=o.fillPath({seriesNumber:v,dataPointIndex:i,color:l.color[h],value:t[n][i]});a.renderSeries({realIndex:v,pathFill:d,lineFill:c,j:i,i:n,pathFrom:l.pathFrom,pathTo:e,strokeWidth:r,elSeries:m,x:u,y:f,series:t,barHeight:y,barWidth:w,elDataLabelsWrap:A,visibleSeries:a.visibleI,type:s.config.chart.type})}))},C=0;Cv.c&&(d=!1);var w=Math.min(v.o,v.c),k=Math.max(v.o,v.c),A=v.m;n.globals.isXNumeric&&(i=(n.globals.seriesX[x][c]-n.globals.minX)/this.xRatio-s/2);var S=i+s*this.visibleI;void 0===this.series[h][c]||null===this.series[h][c]?(w=r,k=r):(w=r-w/f,k=r-k/f,m=r-v.h/f,y=r-v.l/f,A=r-v.m/f);var C=l.move(S,r),L=l.move(S+s/2,w);return n.globals.previousPaths.length>0&&(L=this.getPreviousPath(x,c,!0)),C=this.isBoxPlot?[l.move(S,w)+l.line(S+s/2,w)+l.line(S+s/2,m)+l.line(S+s/4,m)+l.line(S+s-s/4,m)+l.line(S+s/2,m)+l.line(S+s/2,w)+l.line(S+s,w)+l.line(S+s,A)+l.line(S,A)+l.line(S,w+o/2),l.move(S,A)+l.line(S+s,A)+l.line(S+s,k)+l.line(S+s/2,k)+l.line(S+s/2,y)+l.line(S+s-s/4,y)+l.line(S+s/4,y)+l.line(S+s/2,y)+l.line(S+s/2,k)+l.line(S,k)+l.line(S,A)+"z"]:[l.move(S,k)+l.line(S+s/2,k)+l.line(S+s/2,m)+l.line(S+s/2,k)+l.line(S+s,k)+l.line(S+s,w)+l.line(S+s/2,w)+l.line(S+s/2,y)+l.line(S+s/2,w)+l.line(S,w)+l.line(S,k-o/2)],L+=l.move(S,w),n.globals.isXNumeric||(i+=a),{pathTo:C,pathFrom:L,x:i,y:k,barXPosition:S,color:this.isBoxPlot?p:d?[g]:[u]}}},{key:"drawHorizontalBoxPaths",value:function(t){var e=t.indexes;t.x;var i=t.y,a=t.yDivision,s=t.barHeight,r=t.zeroW,o=t.strokeWidth,n=this.w,l=new b(this.ctx),h=e.i,c=e.j,d=this.boxOptions.colors.lower;this.isBoxPlot&&(d=[this.boxOptions.colors.lower,this.boxOptions.colors.upper]);var g=this.invertedYRatio,u=e.realIndex,p=this.getOHLCValue(u,c),f=r,x=r,v=Math.min(p.o,p.c),m=Math.max(p.o,p.c),y=p.m;n.globals.isXNumeric&&(i=(n.globals.seriesX[u][c]-n.globals.minX)/this.invertedXRatio-s/2);var w=i+s*this.visibleI;void 0===this.series[h][c]||null===this.series[h][c]?(v=r,m=r):(v=r+v/g,m=r+m/g,f=r+p.h/g,x=r+p.l/g,y=r+p.m/g);var k=l.move(r,w),A=l.move(v,w+s/2);return n.globals.previousPaths.length>0&&(A=this.getPreviousPath(u,c,!0)),k=[l.move(v,w)+l.line(v,w+s/2)+l.line(f,w+s/2)+l.line(f,w+s/2-s/4)+l.line(f,w+s/2+s/4)+l.line(f,w+s/2)+l.line(v,w+s/2)+l.line(v,w+s)+l.line(y,w+s)+l.line(y,w)+l.line(v+o/2,w),l.move(y,w)+l.line(y,w+s)+l.line(m,w+s)+l.line(m,w+s/2)+l.line(x,w+s/2)+l.line(x,w+s-s/4)+l.line(x,w+s/4)+l.line(x,w+s/2)+l.line(m,w+s/2)+l.line(m,w)+l.line(y,w)+"z"],A+=l.move(v,w),n.globals.isXNumeric||(i+=a),{pathTo:k,pathFrom:A,x:m,y:i,barYPosition:w,color:d}}},{key:"getOHLCValue",value:function(t,e){var i=this.w;return{o:this.isBoxPlot?i.globals.seriesCandleH[t][e]:i.globals.seriesCandleO[t][e],h:this.isBoxPlot?i.globals.seriesCandleO[t][e]:i.globals.seriesCandleH[t][e],m:i.globals.seriesCandleM[t][e],l:this.isBoxPlot?i.globals.seriesCandleC[t][e]:i.globals.seriesCandleL[t][e],c:this.isBoxPlot?i.globals.seriesCandleL[t][e]:i.globals.seriesCandleC[t][e]}}}]),s}(E),yt=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"checkColorRange",value:function(){var t=this.w,e=!1,i=t.config.plotOptions[t.config.chart.type];return i.colorScale.ranges.length>0&&i.colorScale.ranges.map((function(t,i){t.from<=0&&(e=!0)})),e}},{key:"getShadeColor",value:function(t,e,i,a){var s=this.w,r=1,o=s.config.plotOptions[t].shadeIntensity,n=this.determineColor(t,e,i);s.globals.hasNegs||a?r=s.config.plotOptions[t].reverseNegativeShade?n.percent<0?n.percent/100*(1.25*o):(1-n.percent/100)*(1.25*o):n.percent<=0?1-(1+n.percent/100)*o:(1-n.percent/100)*o:(r=1-n.percent/100,"treemap"===t&&(r=(1-n.percent/100)*(1.25*o)));var l=n.color,h=new p;return s.config.plotOptions[t].enableShades&&(l="dark"===this.w.config.theme.mode?p.hexToRgba(h.shadeColor(-1*r,n.color),s.config.fill.opacity):p.hexToRgba(h.shadeColor(r,n.color),s.config.fill.opacity)),{color:l,colorProps:n}}},{key:"determineColor",value:function(t,e,i){var a=this.w,s=a.globals.series[e][i],r=a.config.plotOptions[t],o=r.colorScale.inverse?i:e;a.config.plotOptions[t].distributed&&(o=i);var n=a.globals.colors[o],l=null,h=Math.min.apply(Math,g(a.globals.series[e])),c=Math.max.apply(Math,g(a.globals.series[e]));r.distributed||"heatmap"!==t||(h=a.globals.minY,c=a.globals.maxY),void 0!==r.colorScale.min&&(h=r.colorScale.mina.globals.maxY?r.colorScale.max:a.globals.maxY);var d=Math.abs(c)+Math.abs(h),u=100*s/(0===d?d-1e-6:d);r.colorScale.ranges.length>0&&r.colorScale.ranges.map((function(t,e){if(s>=t.from&&s<=t.to){n=t.color,l=t.foreColor?t.foreColor:null,h=t.from,c=t.to;var i=Math.abs(c)+Math.abs(h);u=100*s/(0===i?i-1e-6:i)}}));return{color:n,foreColor:l,percent:u}}},{key:"calculateDataLabels",value:function(t){var e=t.text,i=t.x,a=t.y,s=t.i,r=t.j,o=t.colorProps,n=t.fontSize,l=this.w.config.dataLabels,h=new b(this.ctx),c=new M(this.ctx),d=null;if(l.enabled){d=h.group({class:"apexcharts-data-labels"});var g=l.offsetX,u=l.offsetY,p=i+g,f=a+parseFloat(l.style.fontSize)/3+u;c.plotDataLabelsText({x:p,y:f,text:e,i:s,j:r,color:o.foreColor,parent:d,fontSize:n,dataLabelsConfig:l})}return d}},{key:"addListeners",value:function(t){var e=new b(this.ctx);t.node.addEventListener("mouseenter",e.pathMouseEnter.bind(this,t)),t.node.addEventListener("mouseleave",e.pathMouseLeave.bind(this,t)),t.node.addEventListener("mousedown",e.pathMouseDown.bind(this,t))}}]),t}(),wt=function(){function t(e,i){a(this,t),this.ctx=e,this.w=e.w,this.xRatio=i.xRatio,this.yRatio=i.yRatio,this.dynamicAnim=this.w.config.chart.animations.dynamicAnimation,this.helpers=new yt(e),this.rectRadius=this.w.config.plotOptions.heatmap.radius,this.strokeWidth=this.w.config.stroke.show?this.w.config.stroke.width:0}return r(t,[{key:"draw",value:function(t){var e=this.w,i=new b(this.ctx),a=i.group({class:"apexcharts-heatmap"});a.attr("clip-path","url(#gridRectMask".concat(e.globals.cuid,")"));var s=e.globals.gridWidth/e.globals.dataPoints,r=e.globals.gridHeight/e.globals.series.length,o=0,n=!1;this.negRange=this.helpers.checkColorRange();var l=t.slice();e.config.yaxis[0].reversed&&(n=!0,l.reverse());for(var h=n?0:l.length-1;n?h=0;n?h++:h--){var c=i.group({class:"apexcharts-series apexcharts-heatmap-series",seriesName:p.escapeString(e.globals.seriesNames[h]),rel:h+1,"data:realIndex":h});if(this.ctx.series.addCollapsedClassToSeries(c,h),e.config.chart.dropShadow.enabled){var d=e.config.chart.dropShadow;new x(this.ctx).dropShadow(c,d,h)}for(var g=0,u=e.config.plotOptions.heatmap.shadeIntensity,f=0;f-1&&this.pieClicked(d),i.config.dataLabels.enabled){var A=w.x,S=w.y,C=100*u/this.fullAngle+"%";if(0!==u&&i.config.plotOptions.pie.dataLabels.minAngleToShowLabelthis.fullAngle?e.endAngle=e.endAngle-(a+o):a+o=this.fullAngle+this.w.config.plotOptions.pie.startAngle%this.fullAngle&&(n=this.fullAngle+this.w.config.plotOptions.pie.startAngle%this.fullAngle-.01),Math.ceil(n)>this.fullAngle&&(n-=this.fullAngle);var l=Math.PI*(n-90)/180,h=e.centerX+s*Math.cos(o),c=e.centerY+s*Math.sin(o),d=e.centerX+s*Math.cos(l),g=e.centerY+s*Math.sin(l),u=p.polarToCartesian(e.centerX,e.centerY,e.donutSize,n),f=p.polarToCartesian(e.centerX,e.centerY,e.donutSize,r),x=a>180?1:0,b=["M",h,c,"A",s,s,0,x,1,d,g];return"donut"===e.chartType?[].concat(b,["L",u.x,u.y,"A",e.donutSize,e.donutSize,0,x,0,f.x,f.y,"L",h,c,"z"]).join(" "):"pie"===e.chartType||"polarArea"===e.chartType?[].concat(b,["L",e.centerX,e.centerY,"L",h,c]).join(" "):[].concat(b).join(" ")}},{key:"drawPolarElements",value:function(t){var e=this.w,i=new j(this.ctx),a=new b(this.ctx),s=new kt(this.ctx),r=a.group(),o=a.group(),n=i.niceScale(0,Math.ceil(this.maxY),e.config.yaxis[0].tickAmount,0,!0),l=n.result.reverse(),h=n.result.length;this.maxY=n.niceMax;for(var c=e.globals.radialSize,d=c/(h-1),g=0;g1&&t.total.show&&(s=t.total.color);var o=r.globals.dom.baseEl.querySelector(".apexcharts-datalabel-label"),n=r.globals.dom.baseEl.querySelector(".apexcharts-datalabel-value");i=(0,t.value.formatter)(i,r),a||"function"!=typeof t.total.formatter||(i=t.total.formatter(r));var l=e===t.total.label;e=t.name.formatter(e,l,r),null!==o&&(o.textContent=e),null!==n&&(n.textContent=i),null!==o&&(o.style.fill=s)}},{key:"printDataLabelsInner",value:function(t,e){var i=this.w,a=t.getAttribute("data:value"),s=i.globals.seriesNames[parseInt(t.parentNode.getAttribute("rel"),10)-1];i.globals.series.length>1&&this.printInnerLabels(e,s,a,t);var r=i.globals.dom.baseEl.querySelector(".apexcharts-datalabels-group");null!==r&&(r.style.opacity=1)}},{key:"drawSpokes",value:function(t){var e=this,i=this.w,a=new b(this.ctx),s=i.config.plotOptions.polarArea.spokes;if(0!==s.strokeWidth){for(var r=[],o=360/i.globals.series.length,n=0;n1)o&&!e.total.showAlways?l({makeSliceOut:!1,printLabel:!0}):this.printInnerLabels(e,e.total.label,e.total.formatter(s));else if(l({makeSliceOut:!1,printLabel:!0}),!o)if(s.globals.selectedDataPoints.length&&s.globals.series.length>1)if(s.globals.selectedDataPoints[0].length>0){var h=s.globals.selectedDataPoints[0],c=s.globals.dom.baseEl.querySelector(".apexcharts-".concat(this.chartType.toLowerCase(),"-slice-").concat(h));this.printDataLabelsInner(c,e)}else r&&s.globals.selectedDataPoints.length&&0===s.globals.selectedDataPoints[0].length&&(r.style.opacity=0);else r&&s.globals.series.length>1&&(r.style.opacity=0)}}]),t}(),St=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.chartType=this.w.config.chart.type,this.initialAnim=this.w.config.chart.animations.enabled,this.dynamicAnim=this.initialAnim&&this.w.config.chart.animations.dynamicAnimation.enabled,this.animDur=0;var i=this.w;this.graphics=new b(this.ctx),this.lineColorArr=void 0!==i.globals.stroke.colors?i.globals.stroke.colors:i.globals.colors,this.defaultSize=i.globals.svgHeight0&&(b=i.getPreviousPath(n));for(var v=0;v=10?t.x>0?(i="start",a+=10):t.x<0&&(i="end",a-=10):i="middle",Math.abs(t.y)>=e-10&&(t.y<0?s-=10:t.y>0&&(s+=10)),{textAnchor:i,newX:a,newY:s}}},{key:"getPreviousPath",value:function(t){for(var e=this.w,i=null,a=0;a0&&parseInt(s.realIndex,10)===parseInt(t,10)&&void 0!==e.globals.previousPaths[a].paths[0]&&(i=e.globals.previousPaths[a].paths[0].d)}return i}},{key:"getDataPointsPos",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.dataPointsLen;t=t||[],e=e||[];for(var a=[],s=0;s=360&&(g=360-Math.abs(this.startAngle)-.1);var u=i.drawPath({d:"",stroke:c,strokeWidth:o*parseInt(h.strokeWidth,10)/100,fill:"none",strokeOpacity:h.opacity,classes:"apexcharts-radialbar-area"});if(h.dropShadow.enabled){var p=h.dropShadow;s.dropShadow(u,p)}l.add(u),u.attr("id","apexcharts-radialbarTrack-"+n),this.animatePaths(u,{centerX:t.centerX,centerY:t.centerY,endAngle:g,startAngle:d,size:t.size,i:n,totalItems:2,animBeginArr:0,dur:0,isTrack:!0,easing:e.globals.easing})}return a}},{key:"drawArcs",value:function(t){var e=this.w,i=new b(this.ctx),a=new L(this.ctx),s=new x(this.ctx),r=i.group(),o=this.getStrokeWidth(t);t.size=t.size-o/2;var n=e.config.plotOptions.radialBar.hollow.background,l=t.size-o*t.series.length-this.margin*t.series.length-o*parseInt(e.config.plotOptions.radialBar.track.strokeWidth,10)/100/2,h=l-e.config.plotOptions.radialBar.hollow.margin;void 0!==e.config.plotOptions.radialBar.hollow.image&&(n=this.drawHollowImage(t,r,l,n));var c=this.drawHollow({size:h,centerX:t.centerX,centerY:t.centerY,fill:n||"transparent"});if(e.config.plotOptions.radialBar.hollow.dropShadow.enabled){var d=e.config.plotOptions.radialBar.hollow.dropShadow;s.dropShadow(c,d)}var g=1;!this.radialDataLabels.total.show&&e.globals.series.length>1&&(g=0);var u=null;this.radialDataLabels.show&&(u=this.renderInnerDataLabels(this.radialDataLabels,{hollowSize:l,centerX:t.centerX,centerY:t.centerY,opacity:g})),"back"===e.config.plotOptions.radialBar.hollow.position&&(r.add(c),u&&r.add(u));var f=!1;e.config.plotOptions.radialBar.inverseOrder&&(f=!0);for(var v=f?t.series.length-1:0;f?v>=0:v100?100:t.series[v])/100,S=Math.round(this.totalAngle*A)+this.startAngle,C=void 0;e.globals.dataChanged&&(k=this.startAngle,C=Math.round(this.totalAngle*p.negToZero(e.globals.previousPaths[v])/100)+k),Math.abs(S)+Math.abs(w)>=360&&(S-=.01),Math.abs(C)+Math.abs(k)>=360&&(C-=.01);var P=S-w,T=Array.isArray(e.config.stroke.dashArray)?e.config.stroke.dashArray[v]:e.config.stroke.dashArray,M=i.drawPath({d:"",stroke:y,strokeWidth:o,fill:"none",fillOpacity:e.config.fill.opacity,classes:"apexcharts-radialbar-area apexcharts-radialbar-slice-"+v,strokeDashArray:T});if(b.setAttrs(M.node,{"data:angle":P,"data:value":t.series[v]}),e.config.chart.dropShadow.enabled){var I=e.config.chart.dropShadow;s.dropShadow(M,I,v)}s.setSelectionFilter(M,0,v),this.addListeners(M,this.radialDataLabels),m.add(M),M.attr({index:0,j:v});var z=0;!this.initialAnim||e.globals.resized||e.globals.dataChanged||(z=(S-w)/360*e.config.chart.animations.speed,this.animDur=z/(1.2*t.series.length)+this.animDur,this.animBeginArr.push(this.animDur)),e.globals.dataChanged&&(z=(S-w)/360*e.config.chart.animations.dynamicAnimation.speed,this.animDur=z/(1.2*t.series.length)+this.animDur,this.animBeginArr.push(this.animDur)),this.animatePaths(M,{centerX:t.centerX,centerY:t.centerY,endAngle:S,startAngle:w,prevEndAngle:C,prevStartAngle:k,size:t.size,i:v,totalItems:2,animBeginArr:this.animBeginArr,dur:z,shouldSetPrevPaths:!0,easing:e.globals.easing})}return{g:r,elHollow:c,dataLabels:u}}},{key:"drawHollow",value:function(t){var e=new b(this.ctx).drawCircle(2*t.size);return e.attr({class:"apexcharts-radialbar-hollow",cx:t.centerX,cy:t.centerY,r:t.size,fill:t.fill}),e}},{key:"drawHollowImage",value:function(t,e,i,a){var s=this.w,r=new L(this.ctx),o=p.randomId(),n=s.config.plotOptions.radialBar.hollow.image;if(s.config.plotOptions.radialBar.hollow.imageClipped)r.clippedImgArea({width:i,height:i,image:n,patternID:"pattern".concat(s.globals.cuid).concat(o)}),a="url(#pattern".concat(s.globals.cuid).concat(o,")");else{var l=s.config.plotOptions.radialBar.hollow.imageWidth,h=s.config.plotOptions.radialBar.hollow.imageHeight;if(void 0===l&&void 0===h){var c=s.globals.dom.Paper.image(n).loaded((function(e){this.move(t.centerX-e.width/2+s.config.plotOptions.radialBar.hollow.imageOffsetX,t.centerY-e.height/2+s.config.plotOptions.radialBar.hollow.imageOffsetY)}));e.add(c)}else{var d=s.globals.dom.Paper.image(n).loaded((function(e){this.move(t.centerX-l/2+s.config.plotOptions.radialBar.hollow.imageOffsetX,t.centerY-h/2+s.config.plotOptions.radialBar.hollow.imageOffsetY),this.size(l,h)}));e.add(d)}}return a}},{key:"getStrokeWidth",value:function(t){var e=this.w;return t.size*(100-parseInt(e.config.plotOptions.radialBar.hollow.size,10))/100/(t.series.length+1)-this.margin}}]),i}(At),Lt=function(){function t(e){a(this,t),this.w=e.w,this.lineCtx=e}return r(t,[{key:"sameValueSeriesFix",value:function(t,e){var i=this.w;if("line"===i.config.chart.type&&("gradient"===i.config.fill.type||"gradient"===i.config.fill.type[t])&&new y(this.lineCtx.ctx,i).seriesHaveSameValues(t)){var a=e[t].slice();a[a.length-1]=a[a.length-1]+1e-6,e[t]=a}return e}},{key:"calculatePoints",value:function(t){var e=t.series,i=t.realIndex,a=t.x,s=t.y,r=t.i,o=t.j,n=t.prevY,l=this.w,h=[],c=[];if(0===o){var d=this.lineCtx.categoryAxisCorrection+l.config.markers.offsetX;l.globals.isXNumeric&&(d=(l.globals.seriesX[i][0]-l.globals.minX)/this.lineCtx.xRatio+l.config.markers.offsetX),h.push(d),c.push(p.isNumber(e[r][0])?n+l.config.markers.offsetY:null),h.push(a+l.config.markers.offsetX),c.push(p.isNumber(e[r][o+1])?s+l.config.markers.offsetY:null)}else h.push(a+l.config.markers.offsetX),c.push(p.isNumber(e[r][o+1])?s+l.config.markers.offsetY:null);return{x:h,y:c}}},{key:"checkPreviousPaths",value:function(t){for(var e=t.pathFromLine,i=t.pathFromArea,a=t.realIndex,s=this.w,r=0;r0&&parseInt(o.realIndex,10)===parseInt(a,10)&&("line"===o.type?(this.lineCtx.appendPathFrom=!1,e=s.globals.previousPaths[r].paths[0].d):"area"===o.type&&(this.lineCtx.appendPathFrom=!1,i=s.globals.previousPaths[r].paths[0].d,s.config.stroke.show&&s.globals.previousPaths[r].paths[1]&&(e=s.globals.previousPaths[r].paths[1].d)))}return{pathFromLine:e,pathFromArea:i}}},{key:"determineFirstPrevY",value:function(t){var e=t.i,i=t.series,a=t.prevY,s=t.lineYPosition,r=this.w;if(void 0!==i[e][0])a=(s=r.config.chart.stacked&&e>0?this.lineCtx.prevSeriesY[e-1][0]:this.lineCtx.zeroY)-i[e][0]/this.lineCtx.yRatio[this.lineCtx.yaxisIndex]+2*(this.lineCtx.isReversed?i[e][0]/this.lineCtx.yRatio[this.lineCtx.yaxisIndex]:0);else if(r.config.chart.stacked&&e>0&&void 0===i[e][0])for(var o=e-1;o>=0;o--)if(null!==i[o][0]&&void 0!==i[o][0]){a=s=this.lineCtx.prevSeriesY[o][0];break}return{prevY:a,lineYPosition:s}}}]),t}(),Pt=function(){function t(e,i,s){a(this,t),this.ctx=e,this.w=e.w,this.xyRatios=i,this.pointsChart=!("bubble"!==this.w.config.chart.type&&"scatter"!==this.w.config.chart.type)||s,this.scatter=new T(this.ctx),this.noNegatives=this.w.globals.minX===Number.MAX_VALUE,this.lineHelpers=new Lt(this),this.markers=new P(this.ctx),this.prevSeriesY=[],this.categoryAxisCorrection=0,this.yaxisIndex=0}return r(t,[{key:"draw",value:function(t,e,i){var a=this.w,s=new b(this.ctx),r=a.globals.comboCharts?e:a.config.chart.type,o=s.group({class:"apexcharts-".concat(r,"-series apexcharts-plot-series")}),n=new y(this.ctx,a);this.yRatio=this.xyRatios.yRatio,this.zRatio=this.xyRatios.zRatio,this.xRatio=this.xyRatios.xRatio,this.baseLineY=this.xyRatios.baseLineY,t=n.getLogSeries(t),this.yRatio=n.getLogYRatios(this.yRatio);for(var l=[],h=0;h0&&(u=(a.globals.seriesX[c][0]-a.globals.minX)/this.xRatio),g.push(u);var p,f=u,x=f,v=this.zeroY;v=this.lineHelpers.determineFirstPrevY({i:h,series:t,prevY:v,lineYPosition:0}).prevY,d.push(v),p=v;var m=this._calculatePathsFrom({series:t,i:h,realIndex:c,prevX:x,prevY:v}),w=this._iterateOverDataPoints({series:t,realIndex:c,i:h,x:u,y:1,pX:f,pY:p,pathsFrom:m,linePaths:[],areaPaths:[],seriesIndex:i,lineYPosition:0,xArrj:g,yArrj:d});this._handlePaths({type:r,realIndex:c,i:h,paths:w}),this.elSeries.add(this.elPointsMain),this.elSeries.add(this.elDataLabelsWrap),l.push(this.elSeries)}if(a.config.chart.stacked)for(var k=l.length;k>0;k--)o.add(l[k-1]);else for(var A=0;A1&&(this.yaxisIndex=i),this.isReversed=a.config.yaxis[this.yaxisIndex]&&a.config.yaxis[this.yaxisIndex].reversed,this.zeroY=a.globals.gridHeight-this.baseLineY[this.yaxisIndex]-(this.isReversed?a.globals.gridHeight:0)+(this.isReversed?2*this.baseLineY[this.yaxisIndex]:0),this.areaBottomY=this.zeroY,(this.zeroY>a.globals.gridHeight||"end"===a.config.plotOptions.area.fillTo)&&(this.areaBottomY=a.globals.gridHeight),this.categoryAxisCorrection=this.xDivision/2,this.elSeries=s.group({class:"apexcharts-series",seriesName:p.escapeString(a.globals.seriesNames[i])}),this.elPointsMain=s.group({class:"apexcharts-series-markers-wrap","data:realIndex":i}),this.elDataLabelsWrap=s.group({class:"apexcharts-datalabels","data:realIndex":i});var r=t[e].length===a.globals.dataPoints;this.elSeries.attr({"data:longestSeries":r,rel:e+1,"data:realIndex":i}),this.appendPathFrom=!0}},{key:"_calculatePathsFrom",value:function(t){var e,i,a,s,r=t.series,o=t.i,n=t.realIndex,l=t.prevX,h=t.prevY,c=this.w,d=new b(this.ctx);if(null===r[o][0]){for(var g=0;g0){var u=this.lineHelpers.checkPreviousPaths({pathFromLine:a,pathFromArea:s,realIndex:n});a=u.pathFromLine,s=u.pathFromArea}return{prevX:l,prevY:h,linePath:e,areaPath:i,pathFromLine:a,pathFromArea:s}}},{key:"_handlePaths",value:function(t){var i=t.type,a=t.realIndex,s=t.i,r=t.paths,o=this.w,n=new b(this.ctx),l=new L(this.ctx);this.prevSeriesY.push(r.yArrj),o.globals.seriesXvalues[a]=r.xArrj,o.globals.seriesYvalues[a]=r.yArrj;var h=o.config.forecastDataPoints;if(h.count>0){var c=o.globals.seriesXvalues[a][o.globals.seriesXvalues[a].length-h.count-1],d=n.drawRect(c,0,o.globals.gridWidth,o.globals.gridHeight,0);o.globals.dom.elForecastMask.appendChild(d.node);var g=n.drawRect(0,0,c,o.globals.gridHeight,0);o.globals.dom.elNonForecastMask.appendChild(g.node)}this.pointsChart||o.globals.delayedElements.push({el:this.elPointsMain.node,index:a});var u={i:s,realIndex:a,animationDelay:s,initialSpeed:o.config.chart.animations.speed,dataChangeSpeed:o.config.chart.animations.dynamicAnimation.speed,className:"apexcharts-".concat(i)};if("area"===i)for(var p=l.fillPath({seriesNumber:a}),f=0;f0){var k=n.renderPaths(y);k.node.setAttribute("stroke-dasharray",h.dashArray),h.strokeWidth&&k.node.setAttribute("stroke-width",h.strokeWidth),this.elSeries.add(k),k.attr("clip-path","url(#forecastMask".concat(o.globals.cuid,")")),w.attr("clip-path","url(#nonForecastMask".concat(o.globals.cuid,")"))}}}}},{key:"_iterateOverDataPoints",value:function(t){for(var e=t.series,i=t.realIndex,a=t.i,s=t.x,r=t.y,o=t.pX,n=t.pY,l=t.pathsFrom,h=t.linePaths,c=t.areaPaths,d=t.seriesIndex,g=t.lineYPosition,u=t.xArrj,f=t.yArrj,x=this.w,v=new b(this.ctx),m=this.yRatio,y=l.prevY,w=l.linePath,k=l.areaPath,A=l.pathFromLine,S=l.pathFromArea,C=p.isNumber(x.globals.minYArr[i])?x.globals.minYArr[i]:x.globals.minY,L=x.globals.dataPoints>1?x.globals.dataPoints-1:x.globals.dataPoints,P=0;P0&&x.globals.collapsedSeries.length-1){e--;break}return e>=0?e:0}(a-1)][P+1]}else g=this.zeroY;else g=this.zeroY;r=T?g-C/m[this.yaxisIndex]+2*(this.isReversed?C/m[this.yaxisIndex]:0):g-e[a][P+1]/m[this.yaxisIndex]+2*(this.isReversed?e[a][P+1]/m[this.yaxisIndex]:0),u.push(s),f.push(r);var I=this.lineHelpers.calculatePoints({series:e,x:s,y:r,realIndex:i,i:a,j:P,prevY:y}),z=this._createPaths({series:e,i:a,realIndex:i,j:P,x:s,y:r,pX:o,pY:n,linePath:w,areaPath:k,linePaths:h,areaPaths:c,seriesIndex:d});c=z.areaPaths,h=z.linePaths,o=z.pX,n=z.pY,k=z.areaPath,w=z.linePath,this.appendPathFrom&&(A+=v.line(s,this.zeroY),S+=v.line(s,this.zeroY)),this.handleNullDataPoints(e,I,a,P,i),this._handleMarkersAndLabels({pointsPos:I,series:e,x:s,y:r,prevY:y,i:a,j:P,realIndex:i})}return{yArrj:f,xArrj:u,pathFromArea:S,areaPaths:c,pathFromLine:A,linePaths:h}}},{key:"_handleMarkersAndLabels",value:function(t){var e=t.pointsPos;t.series,t.x,t.y,t.prevY;var i=t.i,a=t.j,s=t.realIndex,r=this.w,o=new M(this.ctx);if(this.pointsChart)this.scatter.draw(this.elSeries,a,{realIndex:s,pointsPos:e,zRatio:this.zRatio,elParent:this.elPointsMain});else{r.globals.series[i].length>1&&this.elPointsMain.node.classList.add("apexcharts-element-hidden");var n=this.markers.plotChartMarkers(e,s,a+1);null!==n&&this.elPointsMain.add(n)}var l=o.drawDataLabel(e,s,a+1,null);null!==l&&this.elDataLabelsWrap.add(l)}},{key:"_createPaths",value:function(t){var e=t.series,i=t.i,a=t.realIndex,s=t.j,r=t.x,o=t.y,n=t.pX,l=t.pY,h=t.linePath,c=t.areaPath,d=t.linePaths,g=t.areaPaths,u=t.seriesIndex,p=this.w,f=new b(this.ctx),x=p.config.stroke.curve,v=this.areaBottomY;if(Array.isArray(p.config.stroke.curve)&&(x=Array.isArray(u)?p.config.stroke.curve[u[i]]:p.config.stroke.curve[i]),"smooth"===x){var m=.35*(r-n);p.globals.hasNullValues?(null!==e[i][s]&&(null!==e[i][s+1]?(h=f.move(n,l)+f.curve(n+m,l,r-m,o,r+1,o),c=f.move(n+1,l)+f.curve(n+m,l,r-m,o,r+1,o)+f.line(r,v)+f.line(n,v)+"z"):(h=f.move(n,l),c=f.move(n,l)+"z")),d.push(h),g.push(c)):(h+=f.curve(n+m,l,r-m,o,r,o),c+=f.curve(n+m,l,r-m,o,r,o)),n=r,l=o,s===e[i].length-2&&(c=c+f.curve(n,l,r,o,r,v)+f.move(r,o)+"z",p.globals.hasNullValues||(d.push(h),g.push(c)))}else{if(null===e[i][s+1]){h+=f.move(r,o);var y=p.globals.isXNumeric?(p.globals.seriesX[a][s]-p.globals.minX)/this.xRatio:r-this.xDivision;c=c+f.line(y,v)+f.move(r,o)+"z"}null===e[i][s]&&(h+=f.move(r,o),c+=f.move(r,v)),"stepline"===x?(h=h+f.line(r,null,"H")+f.line(null,o,"V"),c=c+f.line(r,null,"H")+f.line(null,o,"V")):"straight"===x&&(h+=f.line(r,o),c+=f.line(r,o)),s===e[i].length-2&&(c=c+f.line(r,v)+f.move(r,o)+"z",d.push(h),g.push(c))}return{linePaths:d,areaPaths:g,pX:n,pY:l,linePath:h,areaPath:c}}},{key:"handleNullDataPoints",value:function(t,e,i,a,s){var r=this.w;if(null===t[i][a]&&r.config.markers.showNullDataPoints||1===t[i].length){var o=this.markers.plotChartMarkers(e,s,a+1,this.strokeWidth-r.config.markers.strokeWidth/2,!0);null!==o&&this.elPointsMain.add(o)}}}]),t}();window.TreemapSquared={},window.TreemapSquared.generate=function(){function t(e,i,a,s){this.xoffset=e,this.yoffset=i,this.height=s,this.width=a,this.shortestEdge=function(){return Math.min(this.height,this.width)},this.getCoordinates=function(t){var e,i=[],a=this.xoffset,s=this.yoffset,o=r(t)/this.height,n=r(t)/this.width;if(this.width>=this.height)for(e=0;e=this.height){var a=e/this.height,s=this.width-a;i=new t(this.xoffset+a,this.yoffset,s,this.height)}else{var r=e/this.width,o=this.height-r;i=new t(this.xoffset,this.yoffset+r,this.width,o)}return i}}function e(e,a,s,o,n){return o=void 0===o?0:o,n=void 0===n?0:n,function(t){var e,i,a=[];for(e=0;e=o}(e,l=t[0],n)?(e.push(l),i(t.slice(1),e,s,o)):(h=s.cutArea(r(e),o),o.push(s.getCoordinates(e)),i(t,[],h,o)),o;o.push(s.getCoordinates(e))}function a(t,e){var i=Math.min.apply(Math,t),a=Math.max.apply(Math,t),s=r(t);return Math.max(Math.pow(e,2)*a/Math.pow(s,2),Math.pow(s,2)/(Math.pow(e,2)*i))}function s(t){return t&&t.constructor===Array}function r(t){var e,i=0;for(e=0;es-i&&n.width<=r-a){var l=o.rotateAroundCenter(t.node);t.node.setAttribute("transform","rotate(-90 ".concat(l.x," ").concat(l.y,")"))}}},{key:"animateTreemap",value:function(t,e,i,a){var s=new f(this.ctx);s.animateRect(t,{x:e.x,y:e.y,width:e.width,height:e.height},{x:i.x,y:i.y,width:i.width,height:i.height},a,(function(){s.animationCompleted(t)}))}}]),t}(),zt=86400,Xt=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w,this.timeScaleArray=[],this.utc=this.w.config.xaxis.labels.datetimeUTC}return r(t,[{key:"calculateTimeScaleTicks",value:function(t,i){var a=this,s=this.w;if(s.globals.allSeriesCollapsed)return s.globals.labels=[],s.globals.timescaleLabels=[],[];var r=new Y(this.ctx),o=(i-t)/864e5;this.determineInterval(o),s.globals.disableZoomIn=!1,s.globals.disableZoomOut=!1,o<.00011574074074074075?s.globals.disableZoomIn=!0:o>5e4&&(s.globals.disableZoomOut=!0);var n=r.getTimeUnitsfromTimestamp(t,i,this.utc),l=s.globals.gridWidth/o,h=l/24,c=h/60,d=c/60,g=Math.floor(24*o),u=Math.floor(1440*o),p=Math.floor(o*zt),f=Math.floor(o),x=Math.floor(o/30),b=Math.floor(o/365),v={minMillisecond:n.minMillisecond,minSecond:n.minSecond,minMinute:n.minMinute,minHour:n.minHour,minDate:n.minDate,minMonth:n.minMonth,minYear:n.minYear},m={firstVal:v,currentMillisecond:v.minMillisecond,currentSecond:v.minSecond,currentMinute:v.minMinute,currentHour:v.minHour,currentMonthDate:v.minDate,currentDate:v.minDate,currentMonth:v.minMonth,currentYear:v.minYear,daysWidthOnXAxis:l,hoursWidthOnXAxis:h,minutesWidthOnXAxis:c,secondsWidthOnXAxis:d,numberOfSeconds:p,numberOfMinutes:u,numberOfHours:g,numberOfDays:f,numberOfMonths:x,numberOfYears:b};switch(this.tickInterval){case"years":this.generateYearScale(m);break;case"months":case"half_year":this.generateMonthScale(m);break;case"months_days":case"months_fortnight":case"days":case"week_days":this.generateDayScale(m);break;case"hours":this.generateHourScale(m);break;case"minutes_fives":case"minutes":this.generateMinuteScale(m);break;case"seconds_tens":case"seconds_fives":case"seconds":this.generateSecondScale(m)}var y=this.timeScaleArray.map((function(t){var i={position:t.position,unit:t.unit,year:t.year,day:t.day?t.day:1,hour:t.hour?t.hour:0,month:t.month+1};return"month"===t.unit?e(e({},i),{},{day:1,value:t.value+1}):"day"===t.unit||"hour"===t.unit?e(e({},i),{},{value:t.value}):"minute"===t.unit?e(e({},i),{},{value:t.value,minute:t.value}):"second"===t.unit?e(e({},i),{},{value:t.value,minute:t.minute,second:t.second}):t}));return y.filter((function(t){var e=1,i=Math.ceil(s.globals.gridWidth/120),r=t.value;void 0!==s.config.xaxis.tickAmount&&(i=s.config.xaxis.tickAmount),y.length>i&&(e=Math.floor(y.length/i));var o=!1,n=!1;switch(a.tickInterval){case"years":"year"===t.unit&&(o=!0);break;case"half_year":e=7,"year"===t.unit&&(o=!0);break;case"months":e=1,"year"===t.unit&&(o=!0);break;case"months_fortnight":e=15,"year"!==t.unit&&"month"!==t.unit||(o=!0),30===r&&(n=!0);break;case"months_days":e=10,"month"===t.unit&&(o=!0),30===r&&(n=!0);break;case"week_days":e=8,"month"===t.unit&&(o=!0);break;case"days":e=1,"month"===t.unit&&(o=!0);break;case"hours":"day"===t.unit&&(o=!0);break;case"minutes_fives":r%5!=0&&(n=!0);break;case"seconds_tens":r%10!=0&&(n=!0);break;case"seconds_fives":r%5!=0&&(n=!0)}if("hours"===a.tickInterval||"minutes_fives"===a.tickInterval||"seconds_tens"===a.tickInterval||"seconds_fives"===a.tickInterval){if(!n)return!0}else if((r%e==0||o)&&!n)return!0}))}},{key:"recalcDimensionsBasedOnFormat",value:function(t,e){var i=this.w,a=this.formatDates(t),s=this.removeOverlappingTS(a);i.globals.timescaleLabels=s.slice(),new ot(this.ctx).plotCoords()}},{key:"determineInterval",value:function(t){var e=24*t,i=60*e;switch(!0){case t/365>5:this.tickInterval="years";break;case t>800:this.tickInterval="half_year";break;case t>180:this.tickInterval="months";break;case t>90:this.tickInterval="months_fortnight";break;case t>60:this.tickInterval="months_days";break;case t>30:this.tickInterval="week_days";break;case t>2:this.tickInterval="days";break;case e>2.4:this.tickInterval="hours";break;case i>15:this.tickInterval="minutes_fives";break;case i>5:this.tickInterval="minutes";break;case i>1:this.tickInterval="seconds_tens";break;case 60*i>20:this.tickInterval="seconds_fives";break;default:this.tickInterval="seconds"}}},{key:"generateYearScale",value:function(t){var e=t.firstVal,i=t.currentMonth,a=t.currentYear,s=t.daysWidthOnXAxis,r=t.numberOfYears,o=e.minYear,n=0,l=new Y(this.ctx),h="year";if(e.minDate>1||e.minMonth>0){var c=l.determineRemainingDaysOfYear(e.minYear,e.minMonth,e.minDate);n=(l.determineDaysOfYear(e.minYear)-c+1)*s,o=e.minYear+1,this.timeScaleArray.push({position:n,value:o,unit:h,year:o,month:p.monthMod(i+1)})}else 1===e.minDate&&0===e.minMonth&&this.timeScaleArray.push({position:n,value:o,unit:h,year:a,month:p.monthMod(i+1)});for(var d=o,g=n,u=0;u1){l=(h.determineDaysOfMonths(a+1,e.minYear)-i+1)*r,n=p.monthMod(a+1);var g=s+d,u=p.monthMod(n),f=n;0===n&&(c="year",f=g,u=1,g+=d+=1),this.timeScaleArray.push({position:l,value:f,unit:c,year:g,month:u})}else this.timeScaleArray.push({position:l,value:n,unit:c,year:s,month:p.monthMod(a)});for(var x=n+1,b=l,v=0,m=1;vo.determineDaysOfMonths(e+1,i)?(h=1,n="month",g=e+=1,e):e},d=(24-e.minHour)*s,g=l,u=c(h,i,a);0===e.minHour&&1===e.minDate?(d=0,g=p.monthMod(e.minMonth),n="month",h=e.minDate,r++):1!==e.minDate&&0===e.minHour&&0===e.minMinute&&(d=0,l=e.minDate,g=l,u=c(h=l,i,a)),this.timeScaleArray.push({position:d,value:g,unit:n,year:this._getYear(a,u,0),month:p.monthMod(u),day:h});for(var f=d,x=0;xn.determineDaysOfMonths(e+1,s)&&(x=1,e+=1),{month:e,date:x}},c=function(t,e){return t>n.determineDaysOfMonths(e+1,s)?e+=1:e},d=60-(e.minMinute+e.minSecond/60),g=d*r,u=e.minHour+1,f=u+1;60===d&&(g=0,f=(u=e.minHour)+1);var x=i,b=c(x,a);this.timeScaleArray.push({position:g,value:u,unit:l,day:x,hour:f,year:s,month:p.monthMod(b)});for(var v=g,m=0;m=24)f=0,l="day",b=h(x+=1,b).month,b=c(x,b);var y=this._getYear(s,b,0);v=0===f&&0===m?d*r:60*r+v;var w=0===f?x:f;this.timeScaleArray.push({position:v,value:w,unit:l,hour:f,day:x,year:y,month:p.monthMod(b)}),f++}}},{key:"generateMinuteScale",value:function(t){for(var e=t.currentMillisecond,i=t.currentSecond,a=t.currentMinute,s=t.currentHour,r=t.currentDate,o=t.currentMonth,n=t.currentYear,l=t.minutesWidthOnXAxis,h=t.secondsWidthOnXAxis,c=t.numberOfMinutes,d=a+1,g=r,u=o,f=n,x=s,b=(60-i-e/1e3)*h,v=0;v=60&&(d=0,24===(x+=1)&&(x=0)),this.timeScaleArray.push({position:b,value:d,unit:"minute",hour:x,minute:d,day:g,year:this._getYear(f,u,0),month:p.monthMod(u)}),b+=l,d++}},{key:"generateSecondScale",value:function(t){for(var e=t.currentMillisecond,i=t.currentSecond,a=t.currentMinute,s=t.currentHour,r=t.currentDate,o=t.currentMonth,n=t.currentYear,l=t.secondsWidthOnXAxis,h=t.numberOfSeconds,c=i+1,d=a,g=r,u=o,f=n,x=s,b=(1e3-e)/1e3*l,v=0;v=60&&(c=0,++d>=60&&(d=0,24===++x&&(x=0))),this.timeScaleArray.push({position:b,value:c,unit:"second",hour:x,minute:d,second:c,day:g,year:this._getYear(f,u,0),month:p.monthMod(u)}),b+=l,c++}},{key:"createRawDateString",value:function(t,e){var i=t.year;return 0===t.month&&(t.month=1),i+="-"+("0"+t.month.toString()).slice(-2),"day"===t.unit?i+="day"===t.unit?"-"+("0"+e).slice(-2):"-01":i+="-"+("0"+(t.day?t.day:"1")).slice(-2),"hour"===t.unit?i+="hour"===t.unit?"T"+("0"+e).slice(-2):"T00":i+="T"+("0"+(t.hour?t.hour:"0")).slice(-2),"minute"===t.unit?i+=":"+("0"+e).slice(-2):i+=":"+(t.minute?("0"+t.minute).slice(-2):"00"),"second"===t.unit?i+=":"+("0"+e).slice(-2):i+=":00",this.utc&&(i+=".000Z"),i}},{key:"formatDates",value:function(t){var e=this,i=this.w;return t.map((function(t){var a=t.value.toString(),s=new Y(e.ctx),r=e.createRawDateString(t,a),o=s.getDate(s.parseDate(r));if(e.utc||(o=s.getDate(s.parseDateWithTimezone(r))),void 0===i.config.xaxis.labels.format){var n="dd MMM",l=i.config.xaxis.labels.datetimeFormatter;"year"===t.unit&&(n=l.year),"month"===t.unit&&(n=l.month),"day"===t.unit&&(n=l.day),"hour"===t.unit&&(n=l.hour),"minute"===t.unit&&(n=l.minute),"second"===t.unit&&(n=l.second),a=s.formatDate(o,n)}else a=s.formatDate(o,i.config.xaxis.labels.format);return{dateString:r,position:t.position,value:a,unit:t.unit,year:t.year,month:t.month}}))}},{key:"removeOverlappingTS",value:function(t){var e,i=this,a=new b(this.ctx),s=!1;t.length>0&&t[0].value&&t.every((function(e){return e.value.length===t[0].value.length}))&&(s=!0,e=a.getTextRects(t[0].value).width);var r=0,o=t.map((function(o,n){if(n>0&&i.w.config.xaxis.labels.hideOverlappingLabels){var l=s?e:a.getTextRects(t[r].value).width,h=t[r].position;return o.position>h+l+10?(r=n,o):null}return o}));return o=o.filter((function(t){return null!==t}))}},{key:"_getYear",value:function(t,e,i){return t+Math.floor(e/12)+i}}]),t}(),Et=function(){function t(e,i){a(this,t),this.ctx=i,this.w=i.w,this.el=e}return r(t,[{key:"setupElements",value:function(){var t=this.w.globals,e=this.w.config,i=e.chart.type;t.axisCharts=["line","area","bar","rangeBar","candlestick","boxPlot","scatter","bubble","radar","heatmap","treemap"].indexOf(i)>-1,t.xyCharts=["line","area","bar","rangeBar","candlestick","boxPlot","scatter","bubble"].indexOf(i)>-1,t.isBarHorizontal=("bar"===e.chart.type||"rangeBar"===e.chart.type||"boxPlot"===e.chart.type)&&e.plotOptions.bar.horizontal,t.chartClass=".apexcharts"+t.chartID,t.dom.baseEl=this.el,t.dom.elWrap=document.createElement("div"),b.setAttrs(t.dom.elWrap,{id:t.chartClass.substring(1),class:"apexcharts-canvas "+t.chartClass.substring(1)}),this.el.appendChild(t.dom.elWrap),t.dom.Paper=new window.SVG.Doc(t.dom.elWrap),t.dom.Paper.attr({class:"apexcharts-svg","xmlns:data":"ApexChartsNS",transform:"translate(".concat(e.chart.offsetX,", ").concat(e.chart.offsetY,")")}),t.dom.Paper.node.style.background=e.chart.background,this.setSVGDimensions(),t.dom.elGraphical=t.dom.Paper.group().attr({class:"apexcharts-inner apexcharts-graphical"}),t.dom.elAnnotations=t.dom.Paper.group().attr({class:"apexcharts-annotations"}),t.dom.elDefs=t.dom.Paper.defs(),t.dom.elLegendWrap=document.createElement("div"),t.dom.elLegendWrap.classList.add("apexcharts-legend"),t.dom.elWrap.appendChild(t.dom.elLegendWrap),t.dom.Paper.add(t.dom.elGraphical),t.dom.elGraphical.add(t.dom.elDefs)}},{key:"plotChartType",value:function(t,e){var i=this.w,a=i.config,s=i.globals,r={series:[],i:[]},o={series:[],i:[]},n={series:[],i:[]},l={series:[],i:[]},h={series:[],i:[]},c={series:[],i:[]},d={series:[],i:[]};s.series.map((function(e,g){var u=0;void 0!==t[g].type?("column"===t[g].type||"bar"===t[g].type?(s.series.length>1&&a.plotOptions.bar.horizontal&&console.warn("Horizontal bars are not supported in a mixed/combo chart. Please turn off `plotOptions.bar.horizontal`"),h.series.push(e),h.i.push(g),u++,i.globals.columnSeries=h.series):"area"===t[g].type?(o.series.push(e),o.i.push(g),u++):"line"===t[g].type?(r.series.push(e),r.i.push(g),u++):"scatter"===t[g].type?(n.series.push(e),n.i.push(g)):"bubble"===t[g].type?(l.series.push(e),l.i.push(g),u++):"candlestick"===t[g].type?(c.series.push(e),c.i.push(g),u++):"boxPlot"===t[g].type?(d.series.push(e),d.i.push(g),u++):console.warn("You have specified an unrecognized chart type. Available types for this property are line/area/column/bar/scatter/bubble"),u>1&&(s.comboCharts=!0)):(r.series.push(e),r.i.push(g))}));var g=new Pt(this.ctx,e),u=new mt(this.ctx,e);this.ctx.pie=new At(this.ctx);var p=new Ct(this.ctx);this.ctx.rangeBar=new F(this.ctx,e);var f=new St(this.ctx),x=[];if(s.comboCharts){if(o.series.length>0&&x.push(g.draw(o.series,"area",o.i)),h.series.length>0)if(i.config.chart.stacked){var b=new vt(this.ctx,e);x.push(b.draw(h.series,h.i))}else this.ctx.bar=new E(this.ctx,e),x.push(this.ctx.bar.draw(h.series,h.i));if(r.series.length>0&&x.push(g.draw(r.series,"line",r.i)),c.series.length>0&&x.push(u.draw(c.series,c.i)),d.series.length>0&&x.push(u.draw(d.series,d.i)),n.series.length>0){var v=new Pt(this.ctx,e,!0);x.push(v.draw(n.series,"scatter",n.i))}if(l.series.length>0){var m=new Pt(this.ctx,e,!0);x.push(m.draw(l.series,"bubble",l.i))}}else switch(a.chart.type){case"line":x=g.draw(s.series,"line");break;case"area":x=g.draw(s.series,"area");break;case"bar":if(a.chart.stacked)x=new vt(this.ctx,e).draw(s.series);else this.ctx.bar=new E(this.ctx,e),x=this.ctx.bar.draw(s.series);break;case"candlestick":x=new mt(this.ctx,e).draw(s.series);break;case"boxPlot":x=new mt(this.ctx,e).draw(s.series);break;case"rangeBar":x=this.ctx.rangeBar.draw(s.series);break;case"heatmap":x=new wt(this.ctx,e).draw(s.series);break;case"treemap":x=new It(this.ctx,e).draw(s.series);break;case"pie":case"donut":case"polarArea":x=this.ctx.pie.draw(s.series);break;case"radialBar":x=p.draw(s.series);break;case"radar":x=f.draw(s.series);break;default:x=g.draw(s.series)}return x}},{key:"setSVGDimensions",value:function(){var t=this.w.globals,e=this.w.config;t.svgWidth=e.chart.width,t.svgHeight=e.chart.height;var i=p.getDimensions(this.el),a=e.chart.width.toString().split(/[0-9]+/g).pop();"%"===a?p.isNumber(i[0])&&(0===i[0].width&&(i=p.getDimensions(this.el.parentNode)),t.svgWidth=i[0]*parseInt(e.chart.width,10)/100):"px"!==a&&""!==a||(t.svgWidth=parseInt(e.chart.width,10));var s=e.chart.height.toString().split(/[0-9]+/g).pop();if("auto"!==t.svgHeight&&""!==t.svgHeight)if("%"===s){var r=p.getDimensions(this.el.parentNode);t.svgHeight=r[1]*parseInt(e.chart.height,10)/100}else t.svgHeight=parseInt(e.chart.height,10);else t.axisCharts?t.svgHeight=t.svgWidth/1.61:t.svgHeight=t.svgWidth/1.2;if(t.svgWidth<0&&(t.svgWidth=0),t.svgHeight<0&&(t.svgHeight=0),b.setAttrs(t.dom.Paper.node,{width:t.svgWidth,height:t.svgHeight}),"%"!==s){var o=e.chart.sparkline.enabled?0:t.axisCharts?e.chart.parentHeightOffset:0;t.dom.Paper.node.parentNode.parentNode.style.minHeight=t.svgHeight+o+"px"}t.dom.elWrap.style.width=t.svgWidth+"px",t.dom.elWrap.style.height=t.svgHeight+"px"}},{key:"shiftGraphPosition",value:function(){var t=this.w.globals,e=t.translateY,i={transform:"translate("+t.translateX+", "+e+")"};b.setAttrs(t.dom.elGraphical.node,i)}},{key:"resizeNonAxisCharts",value:function(){var t=this.w,e=t.globals,i=0,a=t.config.chart.sparkline.enabled?1:15;a+=t.config.grid.padding.bottom,"top"!==t.config.legend.position&&"bottom"!==t.config.legend.position||!t.config.legend.show||t.config.legend.floating||(i=new lt(this.ctx).legendHelpers.getLegendBBox().clwh+10);var s=t.globals.dom.baseEl.querySelector(".apexcharts-radialbar, .apexcharts-pie"),r=2.05*t.globals.radialSize;if(s&&!t.config.chart.sparkline.enabled&&0!==t.config.plotOptions.radialBar.startAngle){var o=p.getBoundingClientRect(s);r=o.bottom;var n=o.bottom-o.top;r=Math.max(2.05*t.globals.radialSize,n)}var l=r+e.translateY+i+a;e.dom.elLegendForeign&&e.dom.elLegendForeign.setAttribute("height",l),e.dom.elWrap.style.height=l+"px",b.setAttrs(e.dom.Paper.node,{height:l}),e.dom.Paper.node.parentNode.parentNode.style.minHeight=l+"px"}},{key:"coreCalculations",value:function(){new U(this.ctx).init()}},{key:"resetGlobals",value:function(){var t=this,e=function(){return t.w.config.series.map((function(t){return[]}))},i=new D,a=this.w.globals;i.initGlobalVars(a),a.seriesXvalues=e(),a.seriesYvalues=e()}},{key:"isMultipleY",value:function(){if(this.w.config.yaxis.constructor===Array&&this.w.config.yaxis.length>1)return this.w.globals.isMultipleYAxis=!0,!0}},{key:"xySettings",value:function(){var t=null,e=this.w;if(e.globals.axisCharts){if("back"===e.config.xaxis.crosshairs.position)new Q(this.ctx).drawXCrosshairs();if("back"===e.config.yaxis[0].crosshairs.position)new Q(this.ctx).drawYCrosshairs();if("datetime"===e.config.xaxis.type&&void 0===e.config.xaxis.labels.formatter){this.ctx.timeScale=new Xt(this.ctx);var i=[];isFinite(e.globals.minX)&&isFinite(e.globals.maxX)&&!e.globals.isBarHorizontal?i=this.ctx.timeScale.calculateTimeScaleTicks(e.globals.minX,e.globals.maxX):e.globals.isBarHorizontal&&(i=this.ctx.timeScale.calculateTimeScaleTicks(e.globals.minY,e.globals.maxY)),this.ctx.timeScale.recalcDimensionsBasedOnFormat(i)}t=new y(this.ctx).getCalculatedRatios()}return t}},{key:"updateSourceChart",value:function(t){this.ctx.w.globals.selection=void 0,this.ctx.updateHelpers._updateOptions({chart:{selection:{xaxis:{min:t.w.globals.minX,max:t.w.globals.maxX}}}},!1,!1)}},{key:"setupBrushHandler",value:function(){var t=this,i=this.w;if(i.config.chart.brush.enabled&&"function"!=typeof i.config.chart.events.selection){var a=i.config.chart.brush.targets||[i.config.chart.brush.target];a.forEach((function(e){var i=ApexCharts.getChartByID(e);i.w.globals.brushSource=t.ctx,"function"!=typeof i.w.config.chart.events.zoomed&&(i.w.config.chart.events.zoomed=function(){t.updateSourceChart(i)}),"function"!=typeof i.w.config.chart.events.scrolled&&(i.w.config.chart.events.scrolled=function(){t.updateSourceChart(i)})})),i.config.chart.events.selection=function(t,s){a.forEach((function(t){var a=ApexCharts.getChartByID(t),r=p.clone(i.config.yaxis);if(i.config.chart.brush.autoScaleYaxis&&1===a.w.globals.series.length){var o=new j(a);r=o.autoScaleY(a,r,s)}var n=a.w.config.yaxis.reduce((function(t,i,s){return[].concat(g(t),[e(e({},a.w.config.yaxis[s]),{},{min:r[0].min,max:r[0].max})])}),[]);a.ctx.updateHelpers._updateOptions({xaxis:{min:s.xaxis.min,max:s.xaxis.max},yaxis:n},!1,!1,!1,!1)}))}}}}]),t}(),Yt=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"_updateOptions",value:function(t){var e=this,a=arguments.length>1&&void 0!==arguments[1]&&arguments[1],s=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],r=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],o=arguments.length>4&&void 0!==arguments[4]&&arguments[4];return new Promise((function(n){var l=[e.ctx];r&&(l=e.ctx.getSyncedCharts()),e.ctx.w.globals.isExecCalled&&(l=[e.ctx],e.ctx.w.globals.isExecCalled=!1),l.forEach((function(r,h){var c=r.w;return c.globals.shouldAnimate=s,a||(c.globals.resized=!0,c.globals.dataChanged=!0,s&&r.series.getPreviousPaths()),t&&"object"===i(t)&&(r.config=new H(t),t=y.extendArrayProps(r.config,t,c),r.w.globals.chartID!==e.ctx.w.globals.chartID&&delete t.series,c.config=p.extend(c.config,t),o&&(c.globals.lastXAxis=t.xaxis?p.clone(t.xaxis):[],c.globals.lastYAxis=t.yaxis?p.clone(t.yaxis):[],c.globals.initialConfig=p.extend({},c.config),c.globals.initialSeries=p.clone(c.config.series))),r.update(t).then((function(){h===l.length-1&&n(r)}))}))}))}},{key:"_updateSeries",value:function(t,e){var i=this,a=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return new Promise((function(s){var r,o=i.w;return o.globals.shouldAnimate=e,o.globals.dataChanged=!0,e&&i.ctx.series.getPreviousPaths(),o.globals.axisCharts?(0===(r=t.map((function(t,e){return i._extendSeries(t,e)}))).length&&(r=[{data:[]}]),o.config.series=r):o.config.series=t.slice(),a&&(o.globals.initialSeries=p.clone(o.config.series)),i.ctx.update().then((function(){s(i.ctx)}))}))}},{key:"_extendSeries",value:function(t,i){var a=this.w,s=a.config.series[i];return e(e({},a.config.series[i]),{},{name:t.name?t.name:s&&s.name,color:t.color?t.color:s&&s.color,type:t.type?t.type:s&&s.type,data:t.data?t.data:s&&s.data})}},{key:"toggleDataPointSelection",value:function(t,e){var i=this.w,a=null,s=".apexcharts-series[data\\:realIndex='".concat(t,"']");return i.globals.axisCharts?a=i.globals.dom.Paper.select("".concat(s," path[j='").concat(e,"'], ").concat(s," circle[j='").concat(e,"'], ").concat(s," rect[j='").concat(e,"']")).members[0]:void 0===e&&(a=i.globals.dom.Paper.select("".concat(s," path[j='").concat(t,"']")).members[0],"pie"!==i.config.chart.type&&"polarArea"!==i.config.chart.type&&"donut"!==i.config.chart.type||this.ctx.pie.pieClicked(t)),a?(new b(this.ctx).pathMouseDown(a,null),a.node?a.node:null):(console.warn("toggleDataPointSelection: Element not found"),null)}},{key:"forceXAxisUpdate",value:function(t){var e=this.w;if(["min","max"].forEach((function(i){void 0!==t.xaxis[i]&&(e.config.xaxis[i]=t.xaxis[i],e.globals.lastXAxis[i]=t.xaxis[i])})),t.xaxis.categories&&t.xaxis.categories.length&&(e.config.xaxis.categories=t.xaxis.categories),e.config.xaxis.convertedCatToNumeric){var i=new R(t);t=i.convertCatToNumericXaxis(t,this.ctx)}return t}},{key:"forceYAxisUpdate",value:function(t){var e=this.w;return e.config.chart.stacked&&"100%"===e.config.chart.stackType&&(Array.isArray(t.yaxis)?t.yaxis.forEach((function(e,i){t.yaxis[i].min=0,t.yaxis[i].max=100})):(t.yaxis.min=0,t.yaxis.max=100)),t}},{key:"revertDefaultAxisMinMax",value:function(t){var e=this,i=this.w,a=i.globals.lastXAxis,s=i.globals.lastYAxis;t&&t.xaxis&&(a=t.xaxis),t&&t.yaxis&&(s=t.yaxis),i.config.xaxis.min=a.min,i.config.xaxis.max=a.max;var r=function(t){void 0!==s[t]&&(i.config.yaxis[t].min=s[t].min,i.config.yaxis[t].max=s[t].max)};i.config.yaxis.map((function(t,a){i.globals.zoomed||void 0!==s[a]?r(a):void 0!==e.ctx.opts.yaxis[a]&&(t.min=e.ctx.opts.yaxis[a].min,t.max=e.ctx.opts.yaxis[a].max)}))}}]),t}();Tt="undefined"!=typeof window?window:void 0,Mt=function(t,e){var a=(void 0!==this?this:t).SVG=function(t){if(a.supported)return t=new a.Doc(t),a.parser.draw||a.prepare(),t};if(a.ns="http://www.w3.org/2000/svg",a.xmlns="http://www.w3.org/2000/xmlns/",a.xlink="http://www.w3.org/1999/xlink",a.svgjs="http://svgjs.dev",a.supported=!0,!a.supported)return!1;a.did=1e3,a.eid=function(t){return"Svgjs"+d(t)+a.did++},a.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},a.extend=function(){var t,e;e=(t=[].slice.call(arguments)).pop();for(var i=t.length-1;i>=0;i--)if(t[i])for(var s in e)t[i].prototype[s]=e[s];a.Set&&a.Set.inherit&&a.Set.inherit()},a.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,a.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&a.extend(e,t.extend),t.construct&&a.extend(t.parent||a.Container,t.construct),e},a.adopt=function(e){return e?e.instance?e.instance:((i="svg"==e.nodeName?e.parentNode instanceof t.SVGElement?new a.Nested:new a.Doc:"linearGradient"==e.nodeName?new a.Gradient("linear"):"radialGradient"==e.nodeName?new a.Gradient("radial"):a[d(e.nodeName)]?new(a[d(e.nodeName)]):new a.Element(e)).type=e.nodeName,i.node=e,e.instance=i,i instanceof a.Doc&&i.namespace().defs(),i.setData(JSON.parse(e.getAttribute("svgjs:data"))||{}),i):null;var i},a.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new a.Doc(t):a.adopt(e.documentElement).nested()).size(2,0);a.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden").node,poly:i.polyline().node,path:i.path().node,native:a.create("svg")}},a.parser={native:a.create("svg")},e.addEventListener("DOMContentLoaded",(function(){a.parser.draw||a.prepare()}),!1),a.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,transforms:/\)\s*,?\s*/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,delimiter:/[\s,]+/,hyphen:/([^e])\-/gi,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,numbersWithDots:/((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,dots:/\./g},a.utils={map:function(t,e){for(var i=t.length,a=[],s=0;s1?1:t,new a.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),a.Color.test=function(t){return t+="",a.regex.isHex.test(t)||a.regex.isRgb.test(t)},a.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},a.Color.isColor=function(t){return a.Color.isRgb(t)||a.Color.test(t)},a.Array=function(t,e){0==(t=(t||[]).valueOf()).length&&e&&(t=e.valueOf()),this.value=this.parse(t)},a.extend(a.Array,{toString:function(){return this.value.join(" ")},valueOf:function(){return this.value},parse:function(t){return t=t.valueOf(),Array.isArray(t)?t:this.split(t)}}),a.PointArray=function(t,e){a.Array.call(this,t,e||[[0,0]])},a.PointArray.prototype=new a.Array,a.PointArray.prototype.constructor=a.PointArray;for(var s={M:function(t,e,i){return e.x=i.x=t[0],e.y=i.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},Z:function(t,e,i){return e.x=i.x,e.y=i.y,["Z"]}},r="mlhvqtcsaz".split(""),o=0,n=r.length;ol);return r},bbox:function(){return a.parser.draw||a.prepare(),a.parser.path.setAttribute("d",this.toString()),a.parser.path.getBBox()}}),a.Number=a.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-34e37:34e37:"string"==typeof t?(e=t.match(a.regex.numberAndUnit))&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof a.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return t=new a.Number(t),new a.Number(this+t,this.unit||t.unit)},minus:function(t){return t=new a.Number(t),new a.Number(this-t,this.unit||t.unit)},times:function(t){return t=new a.Number(t),new a.Number(this*t,this.unit||t.unit)},divide:function(t){return t=new a.Number(t),new a.Number(this/t,this.unit||t.unit)},to:function(t){var e=new a.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new a.Number(t),t.relative&&(this.destination.value+=this.value),this},at:function(t){return this.destination?new a.Number(this.destination).minus(this).times(t).plus(this):this}}}),a.Element=a.invent({create:function(t){this._stroke=a.defaults.attrs.stroke,this._event=null,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=u(this,t,e);return this.width(new a.Number(i.width)).height(new a.Number(i.height))},clone:function(t){this.writeDataToDom();var e=x(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},show:function(){return this.style("display","")},hide:function(){return this.style("display","none")},visible:function(){return"none"!=this.style("display")},toString:function(){return this.attr("id")},classes:function(){var t=this.attr("class");return null==t?[]:t.trim().split(a.regex.delimiter)},hasClass:function(t){return-1!=this.classes().indexOf(t)},addClass:function(t){if(!this.hasClass(t)){var e=this.classes();e.push(t),this.attr("class",e.join(" "))}return this},removeClass:function(t){return this.hasClass(t)&&this.attr("class",this.classes().filter((function(e){return e!=t})).join(" ")),this},toggleClass:function(t){return this.hasClass(t)?this.removeClass(t):this.addClass(t)},reference:function(t){return a.get(this.attr(t))},parent:function(e){var i=this;if(!i.node.parentNode)return null;if(i=a.adopt(i.node.parentNode),!e)return i;for(;i&&i.node instanceof t.SVGElement;){if("string"==typeof e?i.matches(e):i instanceof e)return i;if(!i.node.parentNode||"#document"==i.node.parentNode.nodeName)return null;i=a.adopt(i.node.parentNode)}},doc:function(){return this instanceof a.Doc?this:this.parent(a.Doc)},parents:function(t){var e=[],i=this;do{if(!(i=i.parent(t))||!i.node)break;e.push(i)}while(i.parent);return e},matches:function(t){return function(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}(this.node,t)},native:function(){return this.node},svg:function(t){var i=e.createElement("svg");if(!(t&&this instanceof a.Parent))return i.appendChild(t=e.createElement("svg")),this.writeDataToDom(),t.appendChild(this.node.cloneNode(!0)),i.innerHTML.replace(/^/,"").replace(/<\/svg>$/,"");i.innerHTML=""+t.replace(/\n/,"").replace(/<([\w:-]+)([^<]+?)\/>/g,"<$1$2>")+"";for(var s=0,r=i.firstChild.childNodes.length;s":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)}},a.morph=function(t){return function(e,i){return new a.MorphObj(e,i).at(t)}},a.Situation=a.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new a.Number(t.duration).valueOf(),this.delay=new a.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),a.FX=a.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,s){"object"===i(t)&&(e=t.ease,s=t.delay,t=t.duration);var r=new a.Situation({duration:t||1e3,delay:s||0,ease:a.easing[e||"-"]||e});return this.queue(r),this},target:function(t){return t&&t instanceof a.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=t.requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){t.cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.active=!0,this.startCurrent()),this},startCurrent:function(){return this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations().step()},queue:function(t){return("function"==typeof t||t instanceof a.Situation)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){return this.stop(),this.situation=this.situations.shift(),this.situation&&(this.situation instanceof a.Situation?this.start():this.situation.call(this)),this},initAnimations:function(){var t,e=this.situation;if(e.init)return this;for(var i in e.animations){t=this.target()[i](),Array.isArray(t)||(t=[t]),Array.isArray(e.animations[i])||(e.animations[i]=[e.animations[i]]);for(var s=t.length;s--;)e.animations[i][s]instanceof a.Number&&(t[s]=new a.Number(t[s])),e.animations[i][s]=t[s].morph(e.animations[i][s])}for(var i in e.attrs)e.attrs[i]=new a.MorphObj(this.target().attr(i),e.attrs[i]);for(var i in e.styles)e.styles[i]=new a.MorphObj(this.target().style(i),e.styles[i]);return e.initialTransformation=this.target().matrixify(),e.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){var i=this.active;return this.active=!1,e&&this.clearQueue(),t&&this.situation&&(!i&&this.startCurrent(),this.atEnd()),this.stopAnimFrame(),this.clearCurrent()},after:function(t){var e=this.last();return this.target().on("finished.fx",(function i(a){a.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))})),this._callStart()},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,a.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after((function(){this.off("during.fx",i)})),this._callStart()},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this._callStart()},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,this._callStart()},step:function(t){var e,i,a;t||(this.absPos=this.timeToAbsPos(+new Date)),!1!==this.situation.loops?(e=Math.max(this.absPos,0),i=Math.floor(e),!0===this.situation.loops||ithis.lastPos&&r<=s&&(this.situation.once[r].call(this.target(),this.pos,s),delete this.situation.once[r]);return this.active&&this.target().fire("during",{pos:this.pos,eased:s,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.situations.length||(this.target().off(".fx"),this.active=!1)),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=s,this):this},eachAt:function(){var t,e=this,i=this.target(),s=this.situation;for(var r in s.animations)t=[].concat(s.animations[r]).map((function(t){return"string"!=typeof t&&t.at?t.at(s.ease(e.pos),e.pos):t})),i[r].apply(i,t);for(var r in s.attrs)t=[r].concat(s.attrs[r]).map((function(t){return"string"!=typeof t&&t.at?t.at(s.ease(e.pos),e.pos):t})),i.attr.apply(i,t);for(var r in s.styles)t=[r].concat(s.styles[r]).map((function(t){return"string"!=typeof t&&t.at?t.at(s.ease(e.pos),e.pos):t})),i.style.apply(i,t);if(s.transforms.length){t=s.initialTransformation,r=0;for(var o=s.transforms.length;r=0;--s)this[m[s]]=null!=t[m[s]]?t[m[s]]:e[m[s]]},extend:{extract:function(){var t=p(this,0,1);p(this,1,0);var e=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(e*Math.PI/180)+this.f*Math.sin(e*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(e*Math.PI/180)+this.e*Math.sin(-e*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),rotation:e,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new a.Matrix(this)}},clone:function(){return new a.Matrix(this)},morph:function(t){return this.destination=new a.Matrix(t),this},multiply:function(t){return new a.Matrix(this.native().multiply(function(t){return t instanceof a.Matrix||(t=new a.Matrix(t)),t}(t).native()))},inverse:function(){return new a.Matrix(this.native().inverse())},translate:function(t,e){return new a.Matrix(this.native().translate(t||0,e||0))},native:function(){for(var t=a.parser.native.createSVGMatrix(),e=m.length-1;e>=0;e--)t[m[e]]=this[m[e]];return t},toString:function(){return"matrix("+v(this.a)+","+v(this.b)+","+v(this.c)+","+v(this.d)+","+v(this.e)+","+v(this.f)+")"}},parent:a.Element,construct:{ctm:function(){return new a.Matrix(this.node.getCTM())},screenCTM:function(){if(this instanceof a.Nested){var t=this.rect(1,1),e=t.node.getScreenCTM();return t.remove(),new a.Matrix(e)}return new a.Matrix(this.node.getScreenCTM())}}}),a.Point=a.invent({create:function(t,e){var a;a=Array.isArray(t)?{x:t[0],y:t[1]}:"object"===i(t)?{x:t.x,y:t.y}:null!=t?{x:t,y:null!=e?e:t}:{x:0,y:0},this.x=a.x,this.y=a.y},extend:{clone:function(){return new a.Point(this)},morph:function(t,e){return this.destination=new a.Point(t,e),this}}}),a.extend(a.Element,{point:function(t,e){return new a.Point(t,e).transform(this.screenCTM().inverse())}}),a.extend(a.Element,{attr:function(t,e,s){if(null==t){for(t={},s=(e=this.node.attributes).length-1;s>=0;s--)t[e[s].nodeName]=a.regex.isNumber.test(e[s].nodeValue)?parseFloat(e[s].nodeValue):e[s].nodeValue;return t}if("object"===i(t))for(var r in t)this.attr(r,t[r]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return null==(e=this.node.getAttribute(t))?a.defaults.attrs[t]:a.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(a.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof a.Image&&(e=this.doc().defs().pattern(0,0,(function(){this.add(e)})))),"number"==typeof e?e=new a.Number(e):a.Color.isColor(e)?e=new a.Color(e):Array.isArray(e)&&(e=new a.Array(e)),"leading"==t?this.leading&&this.leading(e):"string"==typeof s?this.node.setAttributeNS(s,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),a.extend(a.Element,{transform:function(t,e){var s;return"object"!==i(t)?(s=new a.Matrix(this).extract(),"string"==typeof t?s[t]:s):(s=new a.Matrix(this),e=!!e||!!t.relative,null!=t.a&&(s=e?s.multiply(new a.Matrix(t)):new a.Matrix(t)),this.attr("transform",s))}}),a.extend(a.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){return(this.attr("transform")||"").split(a.regex.transforms).slice(0,-1).map((function(t){var e=t.trim().split("(");return[e[0],e[1].split(a.regex.delimiter).map((function(t){return parseFloat(t)}))]})).reduce((function(t,e){return"matrix"==e[0]?t.multiply(f(e[1])):t[e[0]].apply(t,e[1])}),new a.Matrix)},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),a.Transformation=a.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.constructor.call(this,[].slice.call(arguments));if(Array.isArray(t))for(var a=0,s=this.arguments.length;a=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return a.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){for(var i=this.children(),s=0,r=i.length;s=0;i--)e.childNodes[i]instanceof t.SVGElement&&x(e.childNodes[i]);return a.adopt(e).id(a.eid(e.nodeName))}function b(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){return Math.abs(t)>1e-37?t:0}["fill","stroke"].forEach((function(t){var e={};e[t]=function(e){if(void 0===e)return this;if("string"==typeof e||a.Color.isRgb(e)||e&&"function"==typeof e.fill)this.attr(t,e);else for(var i=l[t].length-1;i>=0;i--)null!=e[l[t][i]]&&this.attr(l.prefix(t,l[t][i]),e[l[t][i]]);return this},a.extend(a.Element,a.FX,e)})),a.extend(a.Element,a.FX,{translate:function(t,e){return this.transform({x:t,y:e})},matrix:function(t){return this.attr("transform",new a.Matrix(6==arguments.length?[].slice.call(arguments):t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x(new a.Number(t).plus(this instanceof a.FX?0:this.x()),!0)},dy:function(t){return this.y(new a.Number(t).plus(this instanceof a.FX?0:this.y()),!0)}}),a.extend(a.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),a.Set=a.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){for(var t=[].slice.call(arguments),e=0,i=t.length;e-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members}},construct:{set:function(t){return new a.Set(t)}}}),a.FX.Set=a.invent({create:function(t){this.set=t}}),a.Set.inherit=function(){var t=[];for(var e in a.Shape.prototype)"function"==typeof a.Shape.prototype[e]&&"function"!=typeof a.Set.prototype[e]&&t.push(e);for(var e in t.forEach((function(t){a.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),a.get=function(t){var i=e.getElementById(function(t){var e=(t||"").toString().match(a.regex.reference);if(e)return e[1]}(t)||t);return a.adopt(i)},a.select=function(t,i){return new a.Set(a.utils.map((i||e).querySelectorAll(t),(function(t){return a.adopt(t)})))},a.extend(a.Parent,{select:function(t){return a.select(t,this.node)}});var m="abcdef".split("");if("function"!=typeof t.CustomEvent){var y=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var a=e.createEvent("CustomEvent");return a.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),a};y.prototype=t.Event.prototype,a.CustomEvent=y}else a.CustomEvent=t.CustomEvent;return a},"function"==typeof define&&define.amd?define((function(){return Mt(Tt,Tt.document)})):"object"===("undefined"==typeof exports?"undefined":i(exports))&&"undefined"!=typeof module?module.exports=Tt.document?Mt(Tt,Tt.document):function(t){return Mt(t,t.document)}:Tt.SVG=Mt(Tt,Tt.document), +/*! svg.filter.js - v2.0.2 - 2016-02-24 + * https://github.com/wout/svg.filter.js + * Copyright (c) 2016 Wout Fierens; Licensed MIT */ +function(){SVG.Filter=SVG.invent({create:"filter",inherit:SVG.Parent,extend:{source:"SourceGraphic",sourceAlpha:"SourceAlpha",background:"BackgroundImage",backgroundAlpha:"BackgroundAlpha",fill:"FillPaint",stroke:"StrokePaint",autoSetIn:!0,put:function(t,e){return this.add(t,e),!t.attr("in")&&this.autoSetIn&&t.attr("in",this.source),t.attr("result")||t.attr("result",t),t},blend:function(t,e,i){return this.put(new SVG.BlendEffect(t,e,i))},colorMatrix:function(t,e){return this.put(new SVG.ColorMatrixEffect(t,e))},convolveMatrix:function(t){return this.put(new SVG.ConvolveMatrixEffect(t))},componentTransfer:function(t){return this.put(new SVG.ComponentTransferEffect(t))},composite:function(t,e,i){return this.put(new SVG.CompositeEffect(t,e,i))},flood:function(t,e){return this.put(new SVG.FloodEffect(t,e))},offset:function(t,e){return this.put(new SVG.OffsetEffect(t,e))},image:function(t){return this.put(new SVG.ImageEffect(t))},merge:function(){var t=[void 0];for(var e in arguments)t.push(arguments[e]);return this.put(new(SVG.MergeEffect.bind.apply(SVG.MergeEffect,t)))},gaussianBlur:function(t,e){return this.put(new SVG.GaussianBlurEffect(t,e))},morphology:function(t,e){return this.put(new SVG.MorphologyEffect(t,e))},diffuseLighting:function(t,e,i){return this.put(new SVG.DiffuseLightingEffect(t,e,i))},displacementMap:function(t,e,i,a,s){return this.put(new SVG.DisplacementMapEffect(t,e,i,a,s))},specularLighting:function(t,e,i,a){return this.put(new SVG.SpecularLightingEffect(t,e,i,a))},tile:function(){return this.put(new SVG.TileEffect)},turbulence:function(t,e,i,a,s){return this.put(new SVG.TurbulenceEffect(t,e,i,a,s))},toString:function(){return"url(#"+this.attr("id")+")"}}}),SVG.extend(SVG.Defs,{filter:function(t){var e=this.put(new SVG.Filter);return"function"==typeof t&&t.call(e,e),e}}),SVG.extend(SVG.Container,{filter:function(t){return this.defs().filter(t)}}),SVG.extend(SVG.Element,SVG.G,SVG.Nested,{filter:function(t){return this.filterer=t instanceof SVG.Element?t:this.doc().filter(t),this.doc()&&this.filterer.doc()!==this.doc()&&this.doc().defs().add(this.filterer),this.attr("filter",this.filterer),this.filterer},unfilter:function(t){return this.filterer&&!0===t&&this.filterer.remove(),delete this.filterer,this.attr("filter",null)}}),SVG.Effect=SVG.invent({create:function(){this.constructor.call(this)},inherit:SVG.Element,extend:{in:function(t){return null==t?this.parent()&&this.parent().select('[result="'+this.attr("in")+'"]').get(0)||this.attr("in"):this.attr("in",t)},result:function(t){return null==t?this.attr("result"):this.attr("result",t)},toString:function(){return this.result()}}}),SVG.ParentEffect=SVG.invent({create:function(){this.constructor.call(this)},inherit:SVG.Parent,extend:{in:function(t){return null==t?this.parent()&&this.parent().select('[result="'+this.attr("in")+'"]').get(0)||this.attr("in"):this.attr("in",t)},result:function(t){return null==t?this.attr("result"):this.attr("result",t)},toString:function(){return this.result()}}});var t={blend:function(t,e){return this.parent()&&this.parent().blend(this,t,e)},colorMatrix:function(t,e){return this.parent()&&this.parent().colorMatrix(t,e).in(this)},convolveMatrix:function(t){return this.parent()&&this.parent().convolveMatrix(t).in(this)},componentTransfer:function(t){return this.parent()&&this.parent().componentTransfer(t).in(this)},composite:function(t,e){return this.parent()&&this.parent().composite(this,t,e)},flood:function(t,e){return this.parent()&&this.parent().flood(t,e)},offset:function(t,e){return this.parent()&&this.parent().offset(t,e).in(this)},image:function(t){return this.parent()&&this.parent().image(t)},merge:function(){return this.parent()&&this.parent().merge.apply(this.parent(),[this].concat(arguments))},gaussianBlur:function(t,e){return this.parent()&&this.parent().gaussianBlur(t,e).in(this)},morphology:function(t,e){return this.parent()&&this.parent().morphology(t,e).in(this)},diffuseLighting:function(t,e,i){return this.parent()&&this.parent().diffuseLighting(t,e,i).in(this)},displacementMap:function(t,e,i,a){return this.parent()&&this.parent().displacementMap(this,t,e,i,a)},specularLighting:function(t,e,i,a){return this.parent()&&this.parent().specularLighting(t,e,i,a).in(this)},tile:function(){return this.parent()&&this.parent().tile().in(this)},turbulence:function(t,e,i,a,s){return this.parent()&&this.parent().turbulence(t,e,i,a,s).in(this)}};SVG.extend(SVG.Effect,t),SVG.extend(SVG.ParentEffect,t),SVG.ChildEffect=SVG.invent({create:function(){this.constructor.call(this)},inherit:SVG.Element,extend:{in:function(t){this.attr("in",t)}}});var e={blend:function(t,e,i){this.attr({in:t,in2:e,mode:i||"normal"})},colorMatrix:function(t,e){"matrix"==t&&(e=s(e)),this.attr({type:t,values:void 0===e?null:e})},convolveMatrix:function(t){t=s(t),this.attr({order:Math.sqrt(t.split(" ").length),kernelMatrix:t})},composite:function(t,e,i){this.attr({in:t,in2:e,operator:i})},flood:function(t,e){this.attr("flood-color",t),null!=e&&this.attr("flood-opacity",e)},offset:function(t,e){this.attr({dx:t,dy:e})},image:function(t){this.attr("href",t,SVG.xlink)},displacementMap:function(t,e,i,a,s){this.attr({in:t,in2:e,scale:i,xChannelSelector:a,yChannelSelector:s})},gaussianBlur:function(t,e){null!=t||null!=e?this.attr("stdDeviation",r(Array.prototype.slice.call(arguments))):this.attr("stdDeviation","0 0")},morphology:function(t,e){this.attr({operator:t,radius:e})},tile:function(){},turbulence:function(t,e,i,a,s){this.attr({numOctaves:e,seed:i,stitchTiles:a,baseFrequency:t,type:s})}},i={merge:function(){var t;if(arguments[0]instanceof SVG.Set){var e=this;arguments[0].each((function(t){this instanceof SVG.MergeNode?e.put(this):(this instanceof SVG.Effect||this instanceof SVG.ParentEffect)&&e.put(new SVG.MergeNode(this))}))}else{t=Array.isArray(arguments[0])?arguments[0]:arguments;for(var i=0;i1&&(T*=a=Math.sqrt(a),M*=a);s=(new SVG.Matrix).rotate(I).scale(1/T,1/M).rotate(-I),F=F.transform(s),R=R.transform(s),r=[R.x-F.x,R.y-F.y],n=r[0]*r[0]+r[1]*r[1],o=Math.sqrt(n),r[0]/=o,r[1]/=o,l=n<4?Math.sqrt(1-n/4):0,z===X&&(l*=-1);h=new SVG.Point((R.x+F.x)/2+l*-r[1],(R.y+F.y)/2+l*r[0]),c=new SVG.Point(F.x-h.x,F.y-h.y),d=new SVG.Point(R.x-h.x,R.y-h.y),g=Math.acos(c.x/Math.sqrt(c.x*c.x+c.y*c.y)),c.y<0&&(g*=-1);u=Math.acos(d.x/Math.sqrt(d.x*d.x+d.y*d.y)),d.y<0&&(u*=-1);X&&g>u&&(u+=2*Math.PI);!X&&gr.maxX-e.width&&(o=(a=r.maxX-e.width)-this.startPoints.box.x),null!=r.minY&&sr.maxY-e.height&&(n=(s=r.maxY-e.height)-this.startPoints.box.y),null!=r.snapToGrid&&(a-=a%r.snapToGrid,s-=s%r.snapToGrid,o-=o%r.snapToGrid,n-=n%r.snapToGrid),this.el instanceof SVG.G?this.el.matrix(this.startPoints.transform).transform({x:o,y:n},!0):this.el.move(a,s));return i},t.prototype.end=function(t){var e=this.drag(t);this.el.fire("dragend",{event:t,p:e,m:this.m,handler:this}),SVG.off(window,"mousemove.drag"),SVG.off(window,"touchmove.drag"),SVG.off(window,"mouseup.drag"),SVG.off(window,"touchend.drag")},SVG.extend(SVG.Element,{draggable:function(e,i){"function"!=typeof e&&"object"!=typeof e||(i=e,e=!0);var a=this.remember("_draggable")||new t(this);return(e=void 0===e||e)?a.init(i||{},e):(this.off("mousedown.drag"),this.off("touchstart.drag")),this}})}.call(void 0),function(){function t(t){this.el=t,t.remember("_selectHandler",this),this.pointSelection={isSelected:!1},this.rectSelection={isSelected:!1},this.pointsList={lt:[0,0],rt:["width",0],rb:["width","height"],lb:[0,"height"],t:["width",0],r:["width","height"],b:["width","height"],l:[0,"height"]},this.pointCoord=function(t,e,i){var a="string"!=typeof t?t:e[t];return i?a/2:a},this.pointCoords=function(t,e){var i=this.pointsList[t];return{x:this.pointCoord(i[0],e,"t"===t||"b"===t),y:this.pointCoord(i[1],e,"r"===t||"l"===t)}}}t.prototype.init=function(t,e){var i=this.el.bbox();this.options={};var a=this.el.selectize.defaults.points;for(var s in this.el.selectize.defaults)this.options[s]=this.el.selectize.defaults[s],void 0!==e[s]&&(this.options[s]=e[s]);var r=["points","pointsExclude"];for(var s in r){var o=this.options[r[s]];"string"==typeof o?o=o.length>0?o.split(/\s*,\s*/i):[]:"boolean"==typeof o&&"points"===r[s]&&(o=o?a:[]),this.options[r[s]]=o}this.options.points=[a,this.options.points].reduce((function(t,e){return t.filter((function(t){return e.indexOf(t)>-1}))})),this.options.points=[this.options.points,this.options.pointsExclude].reduce((function(t,e){return t.filter((function(t){return e.indexOf(t)<0}))})),this.parent=this.el.parent(),this.nested=this.nested||this.parent.group(),this.nested.matrix(new SVG.Matrix(this.el).translate(i.x,i.y)),this.options.deepSelect&&-1!==["line","polyline","polygon"].indexOf(this.el.type)?this.selectPoints(t):this.selectRect(t),this.observe(),this.cleanup()},t.prototype.selectPoints=function(t){return this.pointSelection.isSelected=t,this.pointSelection.set||(this.pointSelection.set=this.parent.set(),this.drawPoints()),this},t.prototype.getPointArray=function(){var t=this.el.bbox();return this.el.array().valueOf().map((function(e){return[e[0]-t.x,e[1]-t.y]}))},t.prototype.drawPoints=function(){for(var t=this,e=this.getPointArray(),i=0,a=e.length;i0&&this.parameters.box.height-i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x+i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize-i[0]);i=this.checkAspectRatio(i),this.el.move(this.parameters.box.x+i[0],this.parameters.box.y+i[1]).size(this.parameters.box.width-i[0],this.parameters.box.height-i[1])}};break;case"rt":this.calc=function(t,e){var i=this.snapToGrid(t,e,2);if(this.parameters.box.width+i[0]>0&&this.parameters.box.height-i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x-i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize+i[0]);i=this.checkAspectRatio(i,!0),this.el.move(this.parameters.box.x,this.parameters.box.y+i[1]).size(this.parameters.box.width+i[0],this.parameters.box.height-i[1])}};break;case"rb":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);if(this.parameters.box.width+i[0]>0&&this.parameters.box.height+i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x-i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize+i[0]);i=this.checkAspectRatio(i),this.el.move(this.parameters.box.x,this.parameters.box.y).size(this.parameters.box.width+i[0],this.parameters.box.height+i[1])}};break;case"lb":this.calc=function(t,e){var i=this.snapToGrid(t,e,1);if(this.parameters.box.width-i[0]>0&&this.parameters.box.height+i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x+i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize-i[0]);i=this.checkAspectRatio(i,!0),this.el.move(this.parameters.box.x+i[0],this.parameters.box.y).size(this.parameters.box.width-i[0],this.parameters.box.height+i[1])}};break;case"t":this.calc=function(t,e){var i=this.snapToGrid(t,e,2);if(this.parameters.box.height-i[1]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x,this.parameters.box.y+i[1]).height(this.parameters.box.height-i[1])}};break;case"r":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);if(this.parameters.box.width+i[0]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x,this.parameters.box.y).width(this.parameters.box.width+i[0])}};break;case"b":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);if(this.parameters.box.height+i[1]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x,this.parameters.box.y).height(this.parameters.box.height+i[1])}};break;case"l":this.calc=function(t,e){var i=this.snapToGrid(t,e,1);if(this.parameters.box.width-i[0]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x+i[0],this.parameters.box.y).width(this.parameters.box.width-i[0])}};break;case"rot":this.calc=function(t,e){var i=t+this.parameters.p.x,a=e+this.parameters.p.y,s=Math.atan2(this.parameters.p.y-this.parameters.box.y-this.parameters.box.height/2,this.parameters.p.x-this.parameters.box.x-this.parameters.box.width/2),r=Math.atan2(a-this.parameters.box.y-this.parameters.box.height/2,i-this.parameters.box.x-this.parameters.box.width/2),o=this.parameters.rotation+180*(r-s)/Math.PI+this.options.snapToAngle/2;this.el.center(this.parameters.box.cx,this.parameters.box.cy).rotate(o-o%this.options.snapToAngle,this.parameters.box.cx,this.parameters.box.cy)};break;case"point":this.calc=function(t,e){var i=this.snapToGrid(t,e,this.parameters.pointCoords[0],this.parameters.pointCoords[1]),a=this.el.array().valueOf();a[this.parameters.i][0]=this.parameters.pointCoords[0]+i[0],a[this.parameters.i][1]=this.parameters.pointCoords[1]+i[1],this.el.plot(a)}}this.el.fire("resizestart",{dx:this.parameters.x,dy:this.parameters.y,event:t}),SVG.on(window,"touchmove.resize",(function(t){e.update(t||window.event)})),SVG.on(window,"touchend.resize",(function(){e.done()})),SVG.on(window,"mousemove.resize",(function(t){e.update(t||window.event)})),SVG.on(window,"mouseup.resize",(function(){e.done()}))},t.prototype.update=function(t){if(t){var e=this._extractPosition(t),i=this.transformPoint(e.x,e.y),a=i.x-this.parameters.p.x,s=i.y-this.parameters.p.y;this.lastUpdateCall=[a,s],this.calc(a,s),this.el.fire("resizing",{dx:a,dy:s,event:t})}else this.lastUpdateCall&&this.calc(this.lastUpdateCall[0],this.lastUpdateCall[1])},t.prototype.done=function(){this.lastUpdateCall=null,SVG.off(window,"mousemove.resize"),SVG.off(window,"mouseup.resize"),SVG.off(window,"touchmove.resize"),SVG.off(window,"touchend.resize"),this.el.fire("resizedone")},t.prototype.snapToGrid=function(t,e,i,a){var s;return void 0!==a?s=[(i+t)%this.options.snapToGrid,(a+e)%this.options.snapToGrid]:(i=null==i?3:i,s=[(this.parameters.box.x+t+(1&i?0:this.parameters.box.width))%this.options.snapToGrid,(this.parameters.box.y+e+(2&i?0:this.parameters.box.height))%this.options.snapToGrid]),t<0&&(s[0]-=this.options.snapToGrid),e<0&&(s[1]-=this.options.snapToGrid),t-=Math.abs(s[0])o.maxX&&(t=o.maxX-s),void 0!==o.minY&&r+eo.maxY&&(e=o.maxY-r),[t,e]},t.prototype.checkAspectRatio=function(t,e){if(!this.options.saveAspectRatio)return t;var i=t.slice(),a=this.parameters.box.width/this.parameters.box.height,s=this.parameters.box.width+t[0],r=this.parameters.box.height-t[1],o=s/r;return oa&&(i[0]=this.parameters.box.width-r*a,e&&(i[0]=-i[0])),i},SVG.extend(SVG.Element,{resize:function(e){return(this.remember("_resizeHandler")||new t(this)).init(e||{}),this}}),SVG.Element.prototype.resize.defaults={snapToAngle:.1,snapToGrid:1,constraint:{},saveAspectRatio:!1}}).call(this)}(),void 0===window.Apex&&(window.Apex={});var Ft=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"initModules",value:function(){this.ctx.publicMethods=["updateOptions","updateSeries","appendData","appendSeries","toggleSeries","showSeries","hideSeries","setLocale","resetSeries","zoomX","toggleDataPointSelection","dataURI","addXaxisAnnotation","addYaxisAnnotation","addPointAnnotation","clearAnnotations","removeAnnotation","paper","destroy"],this.ctx.eventList=["click","mousedown","mousemove","mouseleave","touchstart","touchmove","touchleave","mouseup","touchend"],this.ctx.animations=new f(this.ctx),this.ctx.axes=new J(this.ctx),this.ctx.core=new Et(this.ctx.el,this.ctx),this.ctx.config=new H({}),this.ctx.data=new O(this.ctx),this.ctx.grid=new _(this.ctx),this.ctx.graphics=new b(this.ctx),this.ctx.coreUtils=new y(this.ctx),this.ctx.crosshairs=new Q(this.ctx),this.ctx.events=new Z(this.ctx),this.ctx.exports=new V(this.ctx),this.ctx.localization=new $(this.ctx),this.ctx.options=new S,this.ctx.responsive=new K(this.ctx),this.ctx.series=new z(this.ctx),this.ctx.theme=new tt(this.ctx),this.ctx.formatters=new W(this.ctx),this.ctx.titleSubtitle=new et(this.ctx),this.ctx.legend=new lt(this.ctx),this.ctx.toolbar=new ht(this.ctx),this.ctx.dimensions=new ot(this.ctx),this.ctx.updateHelpers=new Yt(this.ctx),this.ctx.zoomPanSelection=new ct(this.ctx),this.ctx.w.globals.tooltip=new bt(this.ctx)}}]),t}(),Rt=function(){function t(e){a(this,t),this.ctx=e,this.w=e.w}return r(t,[{key:"clear",value:function(t){var e=t.isUpdating;this.ctx.zoomPanSelection&&this.ctx.zoomPanSelection.destroy(),this.ctx.toolbar&&this.ctx.toolbar.destroy(),this.ctx.animations=null,this.ctx.axes=null,this.ctx.annotations=null,this.ctx.core=null,this.ctx.data=null,this.ctx.grid=null,this.ctx.series=null,this.ctx.responsive=null,this.ctx.theme=null,this.ctx.formatters=null,this.ctx.titleSubtitle=null,this.ctx.legend=null,this.ctx.dimensions=null,this.ctx.options=null,this.ctx.crosshairs=null,this.ctx.zoomPanSelection=null,this.ctx.updateHelpers=null,this.ctx.toolbar=null,this.ctx.localization=null,this.ctx.w.globals.tooltip=null,this.clearDomElements({isUpdating:e})}},{key:"killSVG",value:function(t){t.each((function(t,e){this.removeClass("*"),this.off(),this.stop()}),!0),t.ungroup(),t.clear()}},{key:"clearDomElements",value:function(t){var e=this,i=t.isUpdating,a=this.w.globals.dom.Paper.node;a.parentNode&&a.parentNode.parentNode&&!i&&(a.parentNode.parentNode.style.minHeight="unset");var s=this.w.globals.dom.baseEl;s&&this.ctx.eventList.forEach((function(t){s.removeEventListener(t,e.ctx.events.documentEvent)}));var r=this.w.globals.dom;if(null!==this.ctx.el)for(;this.ctx.el.firstChild;)this.ctx.el.removeChild(this.ctx.el.firstChild);this.killSVG(r.Paper),r.Paper.remove(),r.elWrap=null,r.elGraphical=null,r.elAnnotations=null,r.elLegendWrap=null,r.baseEl=null,r.elGridRect=null,r.elGridRectMask=null,r.elGridRectMarkerMask=null,r.elForecastMask=null,r.elNonForecastMask=null,r.elDefs=null}}]),t}(),Ht=new WeakMap;return function(){function t(e,i){a(this,t),this.opts=i,this.ctx=this,this.w=new N(i).init(),this.el=e,this.w.globals.cuid=p.randomId(),this.w.globals.chartID=this.w.config.chart.id?p.escapeString(this.w.config.chart.id):this.w.globals.cuid,new Ft(this).initModules(),this.create=p.bind(this.create,this),this.windowResizeHandler=this._windowResizeHandler.bind(this),this.parentResizeHandler=this._parentResizeCallback.bind(this)}return r(t,[{key:"render",value:function(){var t=this;return new Promise((function(e,i){if(null!==t.el){void 0===Apex._chartInstances&&(Apex._chartInstances=[]),t.w.config.chart.id&&Apex._chartInstances.push({id:t.w.globals.chartID,group:t.w.config.chart.group,chart:t}),t.setLocale(t.w.config.chart.defaultLocale);var a=t.w.config.chart.events.beforeMount;if("function"==typeof a&&a(t,t.w),t.events.fireEvent("beforeMount",[t,t.w]),window.addEventListener("resize",t.windowResizeHandler),h=t.el.parentNode,c=t.parentResizeHandler,d=!1,g=new ResizeObserver((function(t){d&&c.call(h,t),d=!0})),h.nodeType===Node.DOCUMENT_FRAGMENT_NODE?Array.from(h.children).forEach((function(t){return g.observe(t)})):g.observe(h),Ht.set(c,g),!t.css){var s=t.el.getRootNode&&t.el.getRootNode(),r=p.is("ShadowRoot",s),o=t.el.ownerDocument,n=o.getElementById("apexcharts-css");!r&&n||(t.css=document.createElement("style"),t.css.id="apexcharts-css",t.css.textContent='.apexcharts-canvas {\n position: relative;\n user-select: none;\n /* cannot give overflow: hidden as it will crop tooltips which overflow outside chart area */\n}\n\n\n/* scrollbar is not visible by default for legend, hence forcing the visibility */\n.apexcharts-canvas ::-webkit-scrollbar {\n -webkit-appearance: none;\n width: 6px;\n}\n\n.apexcharts-canvas ::-webkit-scrollbar-thumb {\n border-radius: 4px;\n background-color: rgba(0, 0, 0, .5);\n box-shadow: 0 0 1px rgba(255, 255, 255, .5);\n -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, .5);\n}\n\n\n.apexcharts-inner {\n position: relative;\n}\n\n.apexcharts-text tspan {\n font-family: inherit;\n}\n\n.legend-mouseover-inactive {\n transition: 0.15s ease all;\n opacity: 0.20;\n}\n\n.apexcharts-series-collapsed {\n opacity: 0;\n}\n\n.apexcharts-tooltip {\n border-radius: 5px;\n box-shadow: 2px 2px 6px -4px #999;\n cursor: default;\n font-size: 14px;\n left: 62px;\n opacity: 0;\n pointer-events: none;\n position: absolute;\n top: 20px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n white-space: nowrap;\n z-index: 12;\n transition: 0.15s ease all;\n}\n\n.apexcharts-tooltip.apexcharts-active {\n opacity: 1;\n transition: 0.15s ease all;\n}\n\n.apexcharts-tooltip.apexcharts-theme-light {\n border: 1px solid #e3e3e3;\n background: rgba(255, 255, 255, 0.96);\n}\n\n.apexcharts-tooltip.apexcharts-theme-dark {\n color: #fff;\n background: rgba(30, 30, 30, 0.8);\n}\n\n.apexcharts-tooltip * {\n font-family: inherit;\n}\n\n\n.apexcharts-tooltip-title {\n padding: 6px;\n font-size: 15px;\n margin-bottom: 4px;\n}\n\n.apexcharts-tooltip.apexcharts-theme-light .apexcharts-tooltip-title {\n background: #ECEFF1;\n border-bottom: 1px solid #ddd;\n}\n\n.apexcharts-tooltip.apexcharts-theme-dark .apexcharts-tooltip-title {\n background: rgba(0, 0, 0, 0.7);\n border-bottom: 1px solid #333;\n}\n\n.apexcharts-tooltip-text-y-value,\n.apexcharts-tooltip-text-goals-value,\n.apexcharts-tooltip-text-z-value {\n display: inline-block;\n font-weight: 600;\n margin-left: 5px;\n}\n\n.apexcharts-tooltip-text-y-label:empty,\n.apexcharts-tooltip-text-y-value:empty,\n.apexcharts-tooltip-text-goals-label:empty,\n.apexcharts-tooltip-text-goals-value:empty,\n.apexcharts-tooltip-text-z-value:empty {\n display: none;\n}\n\n.apexcharts-tooltip-text-y-value,\n.apexcharts-tooltip-text-goals-value,\n.apexcharts-tooltip-text-z-value {\n font-weight: 600;\n}\n\n.apexcharts-tooltip-text-goals-label, \n.apexcharts-tooltip-text-goals-value {\n padding: 6px 0 5px;\n}\n\n.apexcharts-tooltip-goals-group, \n.apexcharts-tooltip-text-goals-label, \n.apexcharts-tooltip-text-goals-value {\n display: flex;\n}\n.apexcharts-tooltip-text-goals-label:not(:empty),\n.apexcharts-tooltip-text-goals-value:not(:empty) {\n margin-top: -6px;\n}\n\n.apexcharts-tooltip-marker {\n width: 12px;\n height: 12px;\n position: relative;\n top: 0px;\n margin-right: 10px;\n border-radius: 50%;\n}\n\n.apexcharts-tooltip-series-group {\n padding: 0 10px;\n display: none;\n text-align: left;\n justify-content: left;\n align-items: center;\n}\n\n.apexcharts-tooltip-series-group.apexcharts-active .apexcharts-tooltip-marker {\n opacity: 1;\n}\n\n.apexcharts-tooltip-series-group.apexcharts-active,\n.apexcharts-tooltip-series-group:last-child {\n padding-bottom: 4px;\n}\n\n.apexcharts-tooltip-series-group-hidden {\n opacity: 0;\n height: 0;\n line-height: 0;\n padding: 0 !important;\n}\n\n.apexcharts-tooltip-y-group {\n padding: 6px 0 5px;\n}\n\n.apexcharts-tooltip-box, .apexcharts-custom-tooltip {\n padding: 4px 8px;\n}\n\n.apexcharts-tooltip-boxPlot {\n display: flex;\n flex-direction: column-reverse;\n}\n\n.apexcharts-tooltip-box>div {\n margin: 4px 0;\n}\n\n.apexcharts-tooltip-box span.value {\n font-weight: bold;\n}\n\n.apexcharts-tooltip-rangebar {\n padding: 5px 8px;\n}\n\n.apexcharts-tooltip-rangebar .category {\n font-weight: 600;\n color: #777;\n}\n\n.apexcharts-tooltip-rangebar .series-name {\n font-weight: bold;\n display: block;\n margin-bottom: 5px;\n}\n\n.apexcharts-xaxistooltip {\n opacity: 0;\n padding: 9px 10px;\n pointer-events: none;\n color: #373d3f;\n font-size: 13px;\n text-align: center;\n border-radius: 2px;\n position: absolute;\n z-index: 10;\n background: #ECEFF1;\n border: 1px solid #90A4AE;\n transition: 0.15s ease all;\n}\n\n.apexcharts-xaxistooltip.apexcharts-theme-dark {\n background: rgba(0, 0, 0, 0.7);\n border: 1px solid rgba(0, 0, 0, 0.5);\n color: #fff;\n}\n\n.apexcharts-xaxistooltip:after,\n.apexcharts-xaxistooltip:before {\n left: 50%;\n border: solid transparent;\n content: " ";\n height: 0;\n width: 0;\n position: absolute;\n pointer-events: none;\n}\n\n.apexcharts-xaxistooltip:after {\n border-color: rgba(236, 239, 241, 0);\n border-width: 6px;\n margin-left: -6px;\n}\n\n.apexcharts-xaxistooltip:before {\n border-color: rgba(144, 164, 174, 0);\n border-width: 7px;\n margin-left: -7px;\n}\n\n.apexcharts-xaxistooltip-bottom:after,\n.apexcharts-xaxistooltip-bottom:before {\n bottom: 100%;\n}\n\n.apexcharts-xaxistooltip-top:after,\n.apexcharts-xaxistooltip-top:before {\n top: 100%;\n}\n\n.apexcharts-xaxistooltip-bottom:after {\n border-bottom-color: #ECEFF1;\n}\n\n.apexcharts-xaxistooltip-bottom:before {\n border-bottom-color: #90A4AE;\n}\n\n.apexcharts-xaxistooltip-bottom.apexcharts-theme-dark:after {\n border-bottom-color: rgba(0, 0, 0, 0.5);\n}\n\n.apexcharts-xaxistooltip-bottom.apexcharts-theme-dark:before {\n border-bottom-color: rgba(0, 0, 0, 0.5);\n}\n\n.apexcharts-xaxistooltip-top:after {\n border-top-color: #ECEFF1\n}\n\n.apexcharts-xaxistooltip-top:before {\n border-top-color: #90A4AE;\n}\n\n.apexcharts-xaxistooltip-top.apexcharts-theme-dark:after {\n border-top-color: rgba(0, 0, 0, 0.5);\n}\n\n.apexcharts-xaxistooltip-top.apexcharts-theme-dark:before {\n border-top-color: rgba(0, 0, 0, 0.5);\n}\n\n.apexcharts-xaxistooltip.apexcharts-active {\n opacity: 1;\n transition: 0.15s ease all;\n}\n\n.apexcharts-yaxistooltip {\n opacity: 0;\n padding: 4px 10px;\n pointer-events: none;\n color: #373d3f;\n font-size: 13px;\n text-align: center;\n border-radius: 2px;\n position: absolute;\n z-index: 10;\n background: #ECEFF1;\n border: 1px solid #90A4AE;\n}\n\n.apexcharts-yaxistooltip.apexcharts-theme-dark {\n background: rgba(0, 0, 0, 0.7);\n border: 1px solid rgba(0, 0, 0, 0.5);\n color: #fff;\n}\n\n.apexcharts-yaxistooltip:after,\n.apexcharts-yaxistooltip:before {\n top: 50%;\n border: solid transparent;\n content: " ";\n height: 0;\n width: 0;\n position: absolute;\n pointer-events: none;\n}\n\n.apexcharts-yaxistooltip:after {\n border-color: rgba(236, 239, 241, 0);\n border-width: 6px;\n margin-top: -6px;\n}\n\n.apexcharts-yaxistooltip:before {\n border-color: rgba(144, 164, 174, 0);\n border-width: 7px;\n margin-top: -7px;\n}\n\n.apexcharts-yaxistooltip-left:after,\n.apexcharts-yaxistooltip-left:before {\n left: 100%;\n}\n\n.apexcharts-yaxistooltip-right:after,\n.apexcharts-yaxistooltip-right:before {\n right: 100%;\n}\n\n.apexcharts-yaxistooltip-left:after {\n border-left-color: #ECEFF1;\n}\n\n.apexcharts-yaxistooltip-left:before {\n border-left-color: #90A4AE;\n}\n\n.apexcharts-yaxistooltip-left.apexcharts-theme-dark:after {\n border-left-color: rgba(0, 0, 0, 0.5);\n}\n\n.apexcharts-yaxistooltip-left.apexcharts-theme-dark:before {\n border-left-color: rgba(0, 0, 0, 0.5);\n}\n\n.apexcharts-yaxistooltip-right:after {\n border-right-color: #ECEFF1;\n}\n\n.apexcharts-yaxistooltip-right:before {\n border-right-color: #90A4AE;\n}\n\n.apexcharts-yaxistooltip-right.apexcharts-theme-dark:after {\n border-right-color: rgba(0, 0, 0, 0.5);\n}\n\n.apexcharts-yaxistooltip-right.apexcharts-theme-dark:before {\n border-right-color: rgba(0, 0, 0, 0.5);\n}\n\n.apexcharts-yaxistooltip.apexcharts-active {\n opacity: 1;\n}\n\n.apexcharts-yaxistooltip-hidden {\n display: none;\n}\n\n.apexcharts-xcrosshairs,\n.apexcharts-ycrosshairs {\n pointer-events: none;\n opacity: 0;\n transition: 0.15s ease all;\n}\n\n.apexcharts-xcrosshairs.apexcharts-active,\n.apexcharts-ycrosshairs.apexcharts-active {\n opacity: 1;\n transition: 0.15s ease all;\n}\n\n.apexcharts-ycrosshairs-hidden {\n opacity: 0;\n}\n\n.apexcharts-selection-rect {\n cursor: move;\n}\n\n.svg_select_boundingRect, .svg_select_points_rot {\n pointer-events: none;\n opacity: 0;\n visibility: hidden;\n}\n.apexcharts-selection-rect + g .svg_select_boundingRect,\n.apexcharts-selection-rect + g .svg_select_points_rot {\n opacity: 0;\n visibility: hidden;\n}\n\n.apexcharts-selection-rect + g .svg_select_points_l,\n.apexcharts-selection-rect + g .svg_select_points_r {\n cursor: ew-resize;\n opacity: 1;\n visibility: visible;\n}\n\n.svg_select_points {\n fill: #efefef;\n stroke: #333;\n rx: 2;\n}\n\n.apexcharts-svg.apexcharts-zoomable.hovering-zoom {\n cursor: crosshair\n}\n\n.apexcharts-svg.apexcharts-zoomable.hovering-pan {\n cursor: move\n}\n\n.apexcharts-zoom-icon,\n.apexcharts-zoomin-icon,\n.apexcharts-zoomout-icon,\n.apexcharts-reset-icon,\n.apexcharts-pan-icon,\n.apexcharts-selection-icon,\n.apexcharts-menu-icon,\n.apexcharts-toolbar-custom-icon {\n cursor: pointer;\n width: 20px;\n height: 20px;\n line-height: 24px;\n color: #6E8192;\n text-align: center;\n}\n\n.apexcharts-zoom-icon svg,\n.apexcharts-zoomin-icon svg,\n.apexcharts-zoomout-icon svg,\n.apexcharts-reset-icon svg,\n.apexcharts-menu-icon svg {\n fill: #6E8192;\n}\n\n.apexcharts-selection-icon svg {\n fill: #444;\n transform: scale(0.76)\n}\n\n.apexcharts-theme-dark .apexcharts-zoom-icon svg,\n.apexcharts-theme-dark .apexcharts-zoomin-icon svg,\n.apexcharts-theme-dark .apexcharts-zoomout-icon svg,\n.apexcharts-theme-dark .apexcharts-reset-icon svg,\n.apexcharts-theme-dark .apexcharts-pan-icon svg,\n.apexcharts-theme-dark .apexcharts-selection-icon svg,\n.apexcharts-theme-dark .apexcharts-menu-icon svg,\n.apexcharts-theme-dark .apexcharts-toolbar-custom-icon svg {\n fill: #f3f4f5;\n}\n\n.apexcharts-canvas .apexcharts-zoom-icon.apexcharts-selected svg,\n.apexcharts-canvas .apexcharts-selection-icon.apexcharts-selected svg,\n.apexcharts-canvas .apexcharts-reset-zoom-icon.apexcharts-selected svg {\n fill: #008FFB;\n}\n\n.apexcharts-theme-light .apexcharts-selection-icon:not(.apexcharts-selected):hover svg,\n.apexcharts-theme-light .apexcharts-zoom-icon:not(.apexcharts-selected):hover svg,\n.apexcharts-theme-light .apexcharts-zoomin-icon:hover svg,\n.apexcharts-theme-light .apexcharts-zoomout-icon:hover svg,\n.apexcharts-theme-light .apexcharts-reset-icon:hover svg,\n.apexcharts-theme-light .apexcharts-menu-icon:hover svg {\n fill: #333;\n}\n\n.apexcharts-selection-icon,\n.apexcharts-menu-icon {\n position: relative;\n}\n\n.apexcharts-reset-icon {\n margin-left: 5px;\n}\n\n.apexcharts-zoom-icon,\n.apexcharts-reset-icon,\n.apexcharts-menu-icon {\n transform: scale(0.85);\n}\n\n.apexcharts-zoomin-icon,\n.apexcharts-zoomout-icon {\n transform: scale(0.7)\n}\n\n.apexcharts-zoomout-icon {\n margin-right: 3px;\n}\n\n.apexcharts-pan-icon {\n transform: scale(0.62);\n position: relative;\n left: 1px;\n top: 0px;\n}\n\n.apexcharts-pan-icon svg {\n fill: #fff;\n stroke: #6E8192;\n stroke-width: 2;\n}\n\n.apexcharts-pan-icon.apexcharts-selected svg {\n stroke: #008FFB;\n}\n\n.apexcharts-pan-icon:not(.apexcharts-selected):hover svg {\n stroke: #333;\n}\n\n.apexcharts-toolbar {\n position: absolute;\n z-index: 11;\n max-width: 176px;\n text-align: right;\n border-radius: 3px;\n padding: 0px 6px 2px 6px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.apexcharts-menu {\n background: #fff;\n position: absolute;\n top: 100%;\n border: 1px solid #ddd;\n border-radius: 3px;\n padding: 3px;\n right: 10px;\n opacity: 0;\n min-width: 110px;\n transition: 0.15s ease all;\n pointer-events: none;\n}\n\n.apexcharts-menu.apexcharts-menu-open {\n opacity: 1;\n pointer-events: all;\n transition: 0.15s ease all;\n}\n\n.apexcharts-menu-item {\n padding: 6px 7px;\n font-size: 12px;\n cursor: pointer;\n}\n\n.apexcharts-theme-light .apexcharts-menu-item:hover {\n background: #eee;\n}\n\n.apexcharts-theme-dark .apexcharts-menu {\n background: rgba(0, 0, 0, 0.7);\n color: #fff;\n}\n\n@media screen and (min-width: 768px) {\n .apexcharts-canvas:hover .apexcharts-toolbar {\n opacity: 1;\n }\n}\n\n.apexcharts-datalabel.apexcharts-element-hidden {\n opacity: 0;\n}\n\n.apexcharts-pie-label,\n.apexcharts-datalabels,\n.apexcharts-datalabel,\n.apexcharts-datalabel-label,\n.apexcharts-datalabel-value {\n cursor: default;\n pointer-events: none;\n}\n\n.apexcharts-pie-label-delay {\n opacity: 0;\n animation-name: opaque;\n animation-duration: 0.3s;\n animation-fill-mode: forwards;\n animation-timing-function: ease;\n}\n\n.apexcharts-canvas .apexcharts-element-hidden {\n opacity: 0;\n}\n\n.apexcharts-hide .apexcharts-series-points {\n opacity: 0;\n}\n\n.apexcharts-gridline,\n.apexcharts-annotation-rect,\n.apexcharts-tooltip .apexcharts-marker,\n.apexcharts-area-series .apexcharts-area,\n.apexcharts-line,\n.apexcharts-zoom-rect,\n.apexcharts-toolbar svg,\n.apexcharts-area-series .apexcharts-series-markers .apexcharts-marker.no-pointer-events,\n.apexcharts-line-series .apexcharts-series-markers .apexcharts-marker.no-pointer-events,\n.apexcharts-radar-series path,\n.apexcharts-radar-series polygon {\n pointer-events: none;\n}\n\n\n/* markers */\n\n.apexcharts-marker {\n transition: 0.15s ease all;\n}\n\n@keyframes opaque {\n 0% {\n opacity: 0;\n }\n 100% {\n opacity: 1;\n }\n}\n\n\n/* Resize generated styles */\n\n@keyframes resizeanim {\n from {\n opacity: 0;\n }\n to {\n opacity: 0;\n }\n}\n\n.resize-triggers {\n animation: 1ms resizeanim;\n visibility: hidden;\n opacity: 0;\n}\n\n.resize-triggers,\n.resize-triggers>div,\n.contract-trigger:before {\n content: " ";\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n.resize-triggers>div {\n background: #eee;\n overflow: auto;\n}\n\n.contract-trigger:before {\n width: 200%;\n height: 200%;\n}',r?s.prepend(t.css):o.head.appendChild(t.css))}var l=t.create(t.w.config.series,{});if(!l)return e(t);t.mount(l).then((function(){"function"==typeof t.w.config.chart.events.mounted&&t.w.config.chart.events.mounted(t,t.w),t.events.fireEvent("mounted",[t,t.w]),e(l)})).catch((function(t){i(t)}))}else i(new Error("Element not found"));var h,c,d,g}))}},{key:"create",value:function(t,e){var i=this.w;new Ft(this).initModules();var a=this.w.globals;(a.noData=!1,a.animationEnded=!1,this.responsive.checkResponsiveConfig(e),i.config.xaxis.convertedCatToNumeric)&&new R(i.config).convertCatToNumericXaxis(i.config,this.ctx);if(null===this.el)return a.animationEnded=!0,null;if(this.core.setupElements(),"treemap"===i.config.chart.type&&(i.config.grid.show=!1,i.config.yaxis[0].show=!1),0===a.svgWidth)return a.animationEnded=!0,null;var s=y.checkComboSeries(t);a.comboCharts=s.comboCharts,a.comboBarCount=s.comboBarCount;var r=t.every((function(t){return t.data&&0===t.data.length}));(0===t.length||r)&&this.series.handleNoData(),this.events.setupEventHandlers(),this.data.parseData(t),this.theme.init(),new P(this).setGlobalMarkerSize(),this.formatters.setLabelFormatters(),this.titleSubtitle.draw(),a.noData&&a.collapsedSeries.length!==a.series.length&&!i.config.legend.showForSingleSeries||this.legend.init(),this.series.hasAllSeriesEqualX(),a.axisCharts&&(this.core.coreCalculations(),"category"!==i.config.xaxis.type&&this.formatters.setLabelFormatters(),this.ctx.toolbar.minX=i.globals.minX,this.ctx.toolbar.maxX=i.globals.maxX),this.formatters.heatmapLabelFormatters(),this.dimensions.plotCoords();var o=this.core.xySettings();this.grid.createGridMask();var n=this.core.plotChartType(t,o),l=new M(this);l.bringForward(),i.config.dataLabels.background.enabled&&l.dataLabelsBackground(),this.core.shiftGraphPosition();var h={plot:{left:i.globals.translateX,top:i.globals.translateY,width:i.globals.gridWidth,height:i.globals.gridHeight}};return{elGraph:n,xyRatios:o,elInner:i.globals.dom.elGraphical,dimensions:h}}},{key:"mount",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,i=this,a=i.w;return new Promise((function(s,r){if(null===i.el)return r(new Error("Not enough data to display or target element not found"));(null===e||a.globals.allSeriesCollapsed)&&i.series.handleNoData(),"treemap"!==a.config.chart.type&&i.axes.drawAxis(a.config.chart.type,e.xyRatios),i.grid=new _(i);var o=i.grid.drawGrid();i.annotations=new C(i),i.annotations.drawImageAnnos(),i.annotations.drawTextAnnos(),"back"===a.config.grid.position&&o&&a.globals.dom.elGraphical.add(o.el);var n=new G(t.ctx),l=new q(t.ctx);if(null!==o&&(n.xAxisLabelCorrections(o.xAxisTickWidth),l.setYAxisTextAlignments(),a.config.yaxis.map((function(t,e){-1===a.globals.ignoreYAxisIndexes.indexOf(e)&&l.yAxisTitleRotate(e,t.opposite)}))),"back"===a.config.annotations.position&&(a.globals.dom.Paper.add(a.globals.dom.elAnnotations),i.annotations.drawAxesAnnotations()),Array.isArray(e.elGraph))for(var h=0;h0&&a.globals.memory.methodsToExec.forEach((function(t){t.method(t.params,!1,t.context)})),a.globals.axisCharts||a.globals.noData||i.core.resizeNonAxisCharts(),s(i)}))}},{key:"destroy",value:function(){var t,e;window.removeEventListener("resize",this.windowResizeHandler),this.el.parentNode,t=this.parentResizeHandler,(e=Ht.get(t))&&(e.disconnect(),Ht.delete(t));var i=this.w.config.chart.id;i&&Apex._chartInstances.forEach((function(t,e){t.id===p.escapeString(i)&&Apex._chartInstances.splice(e,1)})),new Rt(this.ctx).clear({isUpdating:!1})}},{key:"updateOptions",value:function(t){var e=this,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1],a=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],s=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],r=!(arguments.length>4&&void 0!==arguments[4])||arguments[4],o=this.w;return o.globals.selection=void 0,t.series&&(this.series.resetSeries(!1,!0,!1),t.series.length&&t.series[0].data&&(t.series=t.series.map((function(t,i){return e.updateHelpers._extendSeries(t,i)}))),this.updateHelpers.revertDefaultAxisMinMax()),t.xaxis&&(t=this.updateHelpers.forceXAxisUpdate(t)),t.yaxis&&(t=this.updateHelpers.forceYAxisUpdate(t)),o.globals.collapsedSeriesIndices.length>0&&this.series.clearPreviousPaths(),t.theme&&(t=this.theme.updateThemeOptions(t)),this.updateHelpers._updateOptions(t,i,a,s,r)}},{key:"updateSeries",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];return this.series.resetSeries(!1),this.updateHelpers.revertDefaultAxisMinMax(),this.updateHelpers._updateSeries(t,e,i)}},{key:"appendSeries",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],a=this.w.config.series.slice();return a.push(t),this.series.resetSeries(!1),this.updateHelpers.revertDefaultAxisMinMax(),this.updateHelpers._updateSeries(a,e,i)}},{key:"appendData",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=this;i.w.globals.dataChanged=!0,i.series.getPreviousPaths();for(var a=i.w.config.series.slice(),s=0;s0&&void 0!==arguments[0])||arguments[0],e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];this.series.resetSeries(t,e)}},{key:"addEventListener",value:function(t,e){this.events.addEventListener(t,e)}},{key:"removeEventListener",value:function(t,e){this.events.removeEventListener(t,e)}},{key:"addXaxisAnnotation",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,a=this;i&&(a=i),a.annotations.addXaxisAnnotationExternal(t,e,a)}},{key:"addYaxisAnnotation",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,a=this;i&&(a=i),a.annotations.addYaxisAnnotationExternal(t,e,a)}},{key:"addPointAnnotation",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,a=this;i&&(a=i),a.annotations.addPointAnnotationExternal(t,e,a)}},{key:"clearAnnotations",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0,e=this;t&&(e=t),e.annotations.clearAnnotations(e)}},{key:"removeAnnotation",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0,i=this;e&&(i=e),i.annotations.removeAnnotation(i,t)}},{key:"getChartArea",value:function(){return this.w.globals.dom.baseEl.querySelector(".apexcharts-inner")}},{key:"getSeriesTotalXRange",value:function(t,e){return this.coreUtils.getSeriesTotalsXRange(t,e)}},{key:"getHighestValueInSeries",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=new U(this.ctx);return e.getMinYMaxY(t).highestY}},{key:"getLowestValueInSeries",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=new U(this.ctx);return e.getMinYMaxY(t).lowestY}},{key:"getSeriesTotal",value:function(){return this.w.globals.seriesTotals}},{key:"toggleDataPointSelection",value:function(t,e){return this.updateHelpers.toggleDataPointSelection(t,e)}},{key:"zoomX",value:function(t,e){this.ctx.toolbar.zoomUpdateOptions(t,e)}},{key:"setLocale",value:function(t){this.localization.setCurrentLocaleValues(t)}},{key:"dataURI",value:function(t){return new V(this.ctx).dataURI(t)}},{key:"paper",value:function(){return this.w.globals.dom.Paper}},{key:"_parentResizeCallback",value:function(){this.w.globals.animationEnded&&this.w.config.chart.redrawOnParentResize&&this._windowResize()}},{key:"_windowResize",value:function(){var t=this;clearTimeout(this.w.globals.resizeTimer),this.w.globals.resizeTimer=window.setTimeout((function(){t.w.globals.resized=!0,t.w.globals.dataChanged=!1,t.ctx.update()}),150)}},{key:"_windowResizeHandler",value:function(){var t=this.w.config.chart.redrawOnWindowResize;"function"==typeof t&&(t=t()),t&&this._windowResize()}}],[{key:"getChartByID",value:function(t){var e=p.escapeString(t),i=Apex._chartInstances.filter((function(t){return t.id===e}))[0];return i&&i.chart}},{key:"initOnLoad",value:function(){for(var e=document.querySelectorAll("[data-apexcharts]"),i=0;i2?s-2:0),o=2;o + * @copyright 2011 Colin Mollenhour + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + * @package Credis_Client + */ + +if( ! defined('CRLF')) define('CRLF', sprintf('%s%s', chr(13), chr(10))); + +/** + * Credis-specific errors, wraps native Redis errors + */ +class CredisException extends Exception +{ + + const CODE_TIMED_OUT = 1; + const CODE_DISCONNECTED = 2; + + public function __construct($message, $code = 0, $exception = NULL) + { + if ($exception && get_class($exception) == 'RedisException' && strpos($message,'read error on connection') === 0) { + $code = CredisException::CODE_DISCONNECTED; + } + parent::__construct($message, $code, $exception); + } + +} + +/** + * Credis_Client, a lightweight Redis PHP standalone client and phpredis wrapper + * + * Server/Connection: + * @method Credis_Client pipeline() + * @method Credis_Client multi() + * @method Credis_Client watch(string ...$keys) + * @method Credis_Client unwatch() + * @method array exec() + * @method string|Credis_Client flushAll() + * @method string|Credis_Client flushDb() + * @method array|Credis_Client info(string $section = null) + * @method bool|array|Credis_Client config(string $setGet, string $key, string $value = null) + * @method array|Credis_Client role() + * @method array|Credis_Client time() + * @method int|Credis_Client dbsize() + * + * Keys: + * @method int|Credis_Client del(string|array $key) + * @method int|Credis_Client exists(string $key) + * @method int|Credis_Client expire(string $key, int $seconds) + * @method int|Credis_Client expireAt(string $key, int $timestamp) + * @method array|Credis_Client keys(string $key) + * @method int|Credis_Client persist(string $key) + * @method bool|Credis_Client rename(string $key, string $newKey) + * @method bool|Credis_Client renameNx(string $key, string $newKey) + * @method array|Credis_Client sort(string $key, string $arg1, string $valueN = null) + * @method int|Credis_Client ttl(string $key) + * @method string|Credis_Client type(string $key) + * + * Scalars: + * @method int|Credis_Client append(string $key, string $value) + * @method int|Credis_Client decr(string $key) + * @method int|Credis_Client decrBy(string $key, int $decrement) + * @method false|string|Credis_Client get(string $key) + * @method int|Credis_Client getBit(string $key, int $offset) + * @method string|Credis_Client getRange(string $key, int $start, int $end) + * @method string|Credis_Client getSet(string $key, string $value) + * @method int|Credis_Client incr(string $key) + * @method int|Credis_Client incrBy(string $key, int $decrement) + * @method false|array|Credis_Client mGet(array $keys) + * @method bool|Credis_Client mSet(array $keysValues) + * @method int|Credis_Client mSetNx(array $keysValues) + * @method bool|Credis_Client set(string $key, string $value, int | array $options = null) + * @method int|Credis_Client setBit(string $key, int $offset, int $value) + * @method bool|Credis_Client setEx(string $key, int $seconds, string $value) + * @method int|Credis_Client setNx(string $key, string $value) + * @method int |Credis_Client setRange(string $key, int $offset, int $value) + * @method int|Credis_Client strLen(string $key) + * + * Sets: + * @method int|Credis_Client sAdd(string $key, mixed $value, string $valueN = null) + * @method int|Credis_Client sRem(string $key, mixed $value, string $valueN = null) + * @method array|Credis_Client sMembers(string $key) + * @method array|Credis_Client sUnion(mixed $keyOrArray, string $valueN = null) + * @method array|Credis_Client sInter(mixed $keyOrArray, string $valueN = null) + * @method array |Credis_Client sDiff(mixed $keyOrArray, string $valueN = null) + * @method string|Credis_Client sPop(string $key) + * @method int|Credis_Client sCard(string $key) + * @method int|Credis_Client sIsMember(string $key, string $member) + * @method int|Credis_Client sMove(string $source, string $dest, string $member) + * @method string|array|Credis_Client sRandMember(string $key, int $count = null) + * @method int|Credis_Client sUnionStore(string $dest, string $key1, string $key2 = null) + * @method int|Credis_Client sInterStore(string $dest, string $key1, string $key2 = null) + * @method int|Credis_Client sDiffStore(string $dest, string $key1, string $key2 = null) + * + * Hashes: + * @method bool|int|Credis_Client hSet(string $key, string $field, string $value) + * @method bool|Credis_Client hSetNx(string $key, string $field, string $value) + * @method bool|string|Credis_Client hGet(string $key, string $field) + * @method bool|int|Credis_Client hLen(string $key) + * @method bool|Credis_Client hDel(string $key, string $field) + * @method array|Credis_Client hKeys(string $key, string $field) + * @method array|Credis_Client hVals(string $key) + * @method array|Credis_Client hGetAll(string $key) + * @method bool|Credis_Client hExists(string $key, string $field) + * @method int|Credis_Client hIncrBy(string $key, string $field, int $value) + * @method float|Credis_Client hIncrByFloat(string $key, string $member, float $value) + * @method bool|Credis_Client hMSet(string $key, array $keysValues) + * @method array|Credis_Client hMGet(string $key, array $fields) + * + * Lists: + * @method array|null|Credis_Client blPop(string $keyN, int $timeout) + * @method array|null|Credis_Client brPop(string $keyN, int $timeout) + * @method array|null |Credis_Client brPoplPush(string $source, string $destination, int $timeout) + * @method string|null|Credis_Client lIndex(string $key, int $index) + * @method int|Credis_Client lInsert(string $key, string $beforeAfter, string $pivot, string $value) + * @method int|Credis_Client lLen(string $key) + * @method string|null|Credis_Client lPop(string $key) + * @method int|Credis_Client lPush(string $key, mixed $value, mixed $valueN = null) + * @method int|Credis_Client lPushX(string $key, mixed $value) + * @method array|Credis_Client lRange(string $key, int $start, int $stop) + * @method int|Credis_Client lRem(string $key, int $count, mixed $value) + * @method bool|Credis_Client lSet(string $key, int $index, mixed $value) + * @method bool|Credis_Client lTrim(string $key, int $start, int $stop) + * @method string|null|Credis_Client rPop(string $key) + * @method string|null|Credis_Client rPoplPush(string $source, string $destination) + * @method int|Credis_Client rPush(string $key, mixed $value, mixed $valueN = null) + * @method int |Credis_Client rPushX(string $key, mixed $value) + * + * Sorted Sets: + * @method int|Credis_Client zAdd(string $key, double $score, string $value) + * @method int|Credis_Client zCard(string $key) + * @method int|Credis_Client zSize(string $key) + * @method int|Credis_Client zCount(string $key, mixed $start, mixed $stop) + * @method int|Credis_Client zIncrBy(string $key, double $value, string $member) + * @method array|Credis_Client zRangeByScore(string $key, mixed $start, mixed $stop, array $args = null) + * @method array|Credis_Client zRevRangeByScore(string $key, mixed $start, mixed $stop, array $args = null) + * @method int|Credis_Client zRemRangeByScore(string $key, mixed $start, mixed $stop) + * @method array|Credis_Client zRange(string $key, mixed $start, mixed $stop, array $args = null) + * @method array|Credis_Client zRevRange(string $key, mixed $start, mixed $stop, array $args = null) + * @method int|Credis_Client zRank(string $key, string $member) + * @method int|Credis_Client zRevRank(string $key, string $member) + * @method int|Credis_Client zRem(string $key, string $member) + * @method int|Credis_Client zDelete(string $key, string $member) + * TODO + * + * Pub/Sub + * @method int |Credis_Client publish(string $channel, string $message) + * @method int|array|Credis_Client pubsub(string $subCommand, $arg = null) + * + * Scripting: + * @method string|int|Credis_Client script(string $command, string $arg1 = null) + * @method string|int|array|bool|Credis_Client eval(string $script, array $keys = null, array $args = null) + * @method string|int|array|bool|Credis_Client evalSha(string $script, array $keys = null, array $args = null) + */ +class Credis_Client { + + const VERSION = '1.11.4'; + + const TYPE_STRING = 'string'; + const TYPE_LIST = 'list'; + const TYPE_SET = 'set'; + const TYPE_ZSET = 'zset'; + const TYPE_HASH = 'hash'; + const TYPE_NONE = 'none'; + + const FREAD_BLOCK_SIZE = 8192; + + /** + * Socket connection to the Redis server or Redis library instance + * @var resource|Redis + */ + protected $redis; + protected $redisMulti; + + /** + * Host of the Redis server + * @var string + */ + protected $host; + + /** + * Scheme of the Redis server (tcp, tls, tlsv1.2, unix) + * @var string|null + */ + protected $scheme; + + /** + * SSL Meta information + * @var string + */ + protected $sslMeta; + + /** + * Port on which the Redis server is running + * @var int|null + */ + protected $port; + + /** + * Timeout for connecting to Redis server + * @var float|null + */ + protected $timeout; + + /** + * Timeout for reading response from Redis server + * @var float|null + */ + protected $readTimeout; + + /** + * Unique identifier for persistent connections + * @var string + */ + protected $persistent; + + /** + * @var bool + */ + protected $closeOnDestruct = TRUE; + + /** + * @var bool + */ + protected $connected = FALSE; + + /** + * @var bool + */ + protected $standalone; + + /** + * @var int + */ + protected $maxConnectRetries = 0; + + /** + * @var int + */ + protected $connectFailures = 0; + + /** + * @var bool + */ + protected $usePipeline = FALSE; + + /** + * @var array + */ + protected $commandNames; + + /** + * @var string + */ + protected $commands; + + /** + * @var bool + */ + protected $isMulti = FALSE; + + /** + * @var bool + */ + protected $isWatching = FALSE; + + /** + * @var string|null + */ + protected $authUsername; + + /** + * @var string|null + */ + protected $authPassword; + + /** + * @var int + */ + protected $selectedDb = 0; + + /** + * Aliases for backwards compatibility with phpredis + * @var array + */ + protected $wrapperMethods = array('delete' => 'del', 'getkeys' => 'keys', 'sremove' => 'srem'); + + /** + * @var array|callable|null + */ + protected $renamedCommands; + + /** + * @var int + */ + protected $requests = 0; + + /** + * @var bool + */ + protected $subscribed = false; + + /** @var bool */ + protected $oldPhpRedis = false; + + /** @var array */ + protected $tlsOptions = []; + + + /** + * @var bool + */ + protected $isTls = false; + + /** + * Gets Useful Meta debug information about the SSL + * + * @return string + */ + public function getSslMeta() + { + return $this->sslMeta; + } + + /** + * Creates a Redisent connection to the Redis server on host {@link $host} and port {@link $port}. + * $host may also be a path to a unix socket or a string in the form of tcp://[hostname]:[port] or unix://[path] + * + * @param string $host The hostname of the Redis server + * @param int|null $port The port number of the Redis server + * @param float|null $timeout Timeout period in seconds + * @param string $persistent Flag to establish persistent connection + * @param int $db The selected database of the Redis server + * @param string|null $password The authentication password of the Redis server + * @param string|null $username The authentication username of the Redis server + * @param array|null $tlsOptions The TLS/SSL context options. See https://www.php.net/manual/en/context.ssl.php for details + */ + public function __construct($host = '127.0.0.1', $port = 6379, $timeout = null, $persistent = '', $db = 0, $password = null, $username = null, array $tlsOptions = null) + { + $this->host = (string) $host; + if ($port !== null) { + $this->port = (int) $port; + } + $this->scheme = null; + $this->timeout = $timeout; + $this->persistent = (string) $persistent; + $this->standalone = ! extension_loaded('redis'); + $this->authPassword = $password; + $this->authUsername = $username; + $this->selectedDb = (int)$db; + $this->convertHost(); + if ($tlsOptions) { + $this->setTlsOptions($tlsOptions); + } + // PHP Redis extension support TLS/ACL AUTH since 5.3.0 + $this->oldPhpRedis = (bool)version_compare(phpversion('redis'),'5.3.0','<'); + if (( + $this->isTls + || $this->authUsername !== null + ) + && !$this->standalone && $this->oldPhpRedis){ + $this->standalone = true; + } + } + + public function __destruct() + { + if ($this->closeOnDestruct) { + $this->close(); + } + } + + /** + * @return bool + */ + public function isSubscribed() + { + return $this->subscribed; + } + + /** + * Return the host of the Redis instance + * @return string + */ + public function getHost() + { + return $this->host; + } + /** + * Return the port of the Redis instance + * @return int|null + */ + public function getPort() + { + return $this->port; + } + + /** + * @return bool + */ + public function isTls() + { + return $this->isTls; + } + + /** + * Return the selected database + * @return int + */ + public function getSelectedDb() + { + return $this->selectedDb; + } + /** + * @return string + */ + public function getPersistence() + { + return $this->persistent; + } + /** + * @throws CredisException + * @return Credis_Client + */ + public function forceStandalone() + { + if ($this->standalone) { + return $this; + } + if($this->connected) { + throw new CredisException('Cannot force Credis_Client to use standalone PHP driver after a connection has already been established.'); + } + $this->standalone = TRUE; + return $this; + } + + /** + * @param int $retries + * @return Credis_Client + */ + public function setMaxConnectRetries($retries) + { + $this->maxConnectRetries = $retries; + return $this; + } + + /** + * @param bool $flag + * @return Credis_Client + */ + public function setCloseOnDestruct($flag) + { + $this->closeOnDestruct = $flag; + return $this; + } + + public function setTlsOptions(array $tlsOptions) + { + if($this->connected) { + throw new CredisException('Cannot change TLS options after a connection has already been established.'); + } + $this->tlsOptions = $tlsOptions; + } + + protected function convertHost() + { + if (preg_match('#^(tcp|tls|ssl|tlsv\d(?:\.\d)?|unix)://(.+)$#', $this->host, $matches)) { + $this->isTls = strpos($matches[1], 'tls') === 0 || strpos($matches[1], 'ssl') === 0; + if($this->isTls || $matches[1] === 'tcp') { + $this->scheme = $matches[1]; + if ( ! preg_match('#^([^:]+)(:([0-9]+))?(/(.+))?$#', $matches[2], $matches)) { + throw new CredisException('Invalid host format; expected '.$this->scheme.'://host[:port][/persistence_identifier]'); + } + $this->host = $matches[1]; + $this->port = (int) (isset($matches[3]) ? $matches[3] : $this->port); + $this->persistent = isset($matches[5]) ? $matches[5] : $this->persistent; + } else { + $this->host = $matches[2]; + $this->port = NULL; + $this->scheme = 'unix'; + if (substr($this->host,0,1) != '/') { + throw new CredisException('Invalid unix socket format; expected unix:///path/to/redis.sock'); + } + } + } + if ($this->port !== NULL && substr($this->host,0,1) == '/') { + $this->port = NULL; + $this->scheme = 'unix'; + } + if (!$this->scheme) { + $this->scheme = 'tcp'; + } + } + + /** + * @throws CredisException + * @return Credis_Client + */ + public function connect() + { + if ($this->connected) { + return $this; + } + $this->close(true); + $tlsOptions = $this->isTls ? $this->tlsOptions : []; + if ($this->standalone) { + $flags = STREAM_CLIENT_CONNECT; + $remote_socket = $this->port === NULL + ? $this->scheme.'://'.$this->host + : $this->scheme.'://'.$this->host.':'.$this->port; + if ($this->persistent && $this->port !== NULL) { + // Persistent connections to UNIX sockets are not supported + $remote_socket .= '/'.$this->persistent; + $flags = $flags | STREAM_CLIENT_PERSISTENT; + } + if ($this->isTls) { + $tlsOptions = array_merge($tlsOptions, [ + 'capture_peer_cert' => true, + 'capture_peer_cert_chain' => true, + 'capture_session_meta' => true, + ]); + } + + // passing $context as null errors before php 8.0 + $context = stream_context_create(['ssl' => $tlsOptions]); + + $result = $this->redis = @stream_socket_client($remote_socket, $errno, $errstr, $this->timeout !== null ? $this->timeout : 2.5, $flags, $context); + + if ($result && $this->isTls) { + $this->sslMeta = stream_context_get_options($context); + } + } + else { + if ( ! $this->redis) { + $this->redis = new Redis; + } + $socketTimeout = $this->timeout ?: 0.0; + try + { + if ($this->oldPhpRedis) + { + $result = $this->persistent + ? $this->redis->pconnect($this->host, (int)$this->port, $socketTimeout, $this->persistent) + : $this->redis->connect($this->host, (int)$this->port, $socketTimeout); + } + else + { + // 7th argument is non-documented TLS options. But it only exists on the newer versions of phpredis + if ($tlsOptions) { + $context = ['stream' => $tlsOptions]; + } else { + $context = []; + } + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $result = $this->persistent + ? $this->redis->pconnect($this->scheme.'://'.$this->host, (int)$this->port, $socketTimeout, $this->persistent, 0, 0.0, $context) + : $this->redis->connect($this->scheme.'://'.$this->host, (int)$this->port, $socketTimeout, null, 0, 0.0, $context); + } + } + catch(Exception $e) + { + // Some applications will capture the php error that phpredis can sometimes generate and throw it as an Exception + $result = false; + $errno = 1; + $errstr = $e->getMessage(); + } + } + + // Use recursion for connection retries + if ( ! $result) { + $this->connectFailures++; + if ($this->connectFailures <= $this->maxConnectRetries) { + return $this->connect(); + } + $failures = $this->connectFailures; + $this->connectFailures = 0; + throw new CredisException(sprintf("Connection to Redis%s %s://%s failed after %s failures.%s", + $this->standalone ? ' standalone' : '', + $this->scheme, + $this->host.($this->port ? ':'.$this->port : ''), + $failures, + (isset($errno) && isset($errstr) ? "Last Error : ({$errno}) {$errstr}" : "") + )); + } + + $this->connectFailures = 0; + $this->connected = TRUE; + + // Set read timeout + if ($this->readTimeout) { + $this->setReadTimeout($this->readTimeout); + } + if($this->authPassword) { + $this->auth($this->authPassword, $this->authUsername); + } + if($this->selectedDb !== 0) { + $this->select($this->selectedDb); + } + return $this; + } + /** + * @return bool + */ + public function isConnected() + { + return $this->connected; + } + /** + * Set the read timeout for the connection. Use 0 to disable timeouts entirely (or use a very long timeout + * if not supported). + * + * @param float $timeout 0 (or -1) for no timeout, otherwise number of seconds + * @throws CredisException + * @return Credis_Client + */ + public function setReadTimeout($timeout) + { + if ($timeout < -1) { + throw new CredisException('Timeout values less than -1 are not accepted.'); + } + $this->readTimeout = $timeout; + if ($this->isConnected()) { + if ($this->standalone) { + $timeout = $timeout <= 0 ? 315360000 : $timeout; // Ten-year timeout + stream_set_blocking($this->redis, TRUE); + stream_set_timeout($this->redis, (int) floor($timeout), ($timeout - floor($timeout)) * 1000000); + } else if (defined('Redis::OPT_READ_TIMEOUT')) { + // supported in phpredis 2.2.3 + // a timeout value of -1 means reads will not timeout + $timeout = $timeout == 0 ? -1 : $timeout; + $this->redis->setOption(Redis::OPT_READ_TIMEOUT, $timeout); + } + } + return $this; + } + + /** + * @return bool + */ + public function close($force = FALSE) + { + $result = TRUE; + if ($this->redis && ($force || $this->connected && ! $this->persistent)) { + try { + if (is_callable(array($this->redis, 'close'))) { + $this->redis->close(); + } else { + @fclose($this->redis); + $this->redis = null; + } + } catch (Exception $e) { + ; // Ignore exceptions on close + } + $this->connected = $this->usePipeline = $this->isMulti = $this->isWatching = FALSE; + } + return $result; + } + + /** + * Enabled command renaming and provide mapping method. Supported methods are: + * + * 1. renameCommand('foo') // Salted md5 hash for all commands -> md5('foo'.$command) + * 2. renameCommand(function($command){ return 'my'.$command; }); // Callable + * 3. renameCommand('get', 'foo') // Single command -> alias + * 4. renameCommand(['get' => 'foo', 'set' => 'bar']) // Full map of [command -> alias] + * + * @param string|callable|array $command + * @param string|null $alias + * @return $this + */ + public function renameCommand($command, $alias = NULL) + { + if ( ! $this->standalone) { + $this->forceStandalone(); + } + if ($alias === NULL) { + $this->renamedCommands = $command; + } else { + if ( ! $this->renamedCommands) { + $this->renamedCommands = array(); + } + $this->renamedCommands[$command] = $alias; + } + return $this; + } + + /** + * @param $command + * @return string + */ + public function getRenamedCommand($command) + { + static $map; + + // Command renaming not enabled + if ($this->renamedCommands === NULL) { + return $command; + } + + // Initialize command map + if ($map === NULL) { + if (is_array($this->renamedCommands)) { + $map = $this->renamedCommands; + } else { + $map = array(); + } + } + + // Generate and return cached result + if ( ! isset($map[$command])) { + // String means all commands are hashed with salted md5 + if (is_string($this->renamedCommands)) { + $map[$command] = md5($this->renamedCommands.$command); + } + // Would already be set in $map if it was intended to be renamed + else if (is_array($this->renamedCommands)) { + return $command; + } + // User-supplied function + else if (is_callable($this->renamedCommands)) { + $map[$command] = call_user_func($this->renamedCommands, $command); + } + } + return $map[$command]; + } + + /** + * @param string $password + * @param string|null $username + * @return bool + */ + public function auth($password, $username = null) + { + if ($username !== null) { + $response = $this->__call('auth', array($username, $password)); + $this->authUsername= $username; + } else { + $response = $this->__call('auth', array($password)); + } + $this->authPassword = $password; + return $response; + } + + /** + * @param int $index + * @return bool + */ + public function select($index) + { + $response = $this->__call('select', array($index)); + $this->selectedDb = (int) $index; + return $response; + } + + /** + * @param string|array $pattern + * @return array + */ + public function pUnsubscribe() + { + list($command, $channel, $subscribedChannels) = $this->__call('punsubscribe', func_get_args()); + $this->subscribed = $subscribedChannels > 0; + return array($command, $channel, $subscribedChannels); + } + + /** + * @param int $Iterator + * @param string $pattern + * @param int $count + * @return bool|array + */ + public function scan(&$Iterator, $pattern = null, $count = null) + { + return $this->__call('scan', array(&$Iterator, $pattern, $count)); + } + + /** + * @param int $Iterator + * @param string $field + * @param string $pattern + * @param int $count + * @return bool|array + */ + public function hscan(&$Iterator, $field, $pattern = null, $count = null) + { + return $this->__call('hscan', array($field, &$Iterator, $pattern, $count)); + } + + /** + * @param int $Iterator + * @param string $field + * @param string $pattern + * @param int $Iterator + * @return bool|array + */ + public function sscan(&$Iterator, $field, $pattern = null, $count = null) + { + return $this->__call('sscan', array($field, &$Iterator, $pattern, $count)); + } + + /** + * @param int $Iterator + * @param string $field + * @param string $pattern + * @param int $Iterator + * @return bool|array + */ + public function zscan(&$Iterator, $field, $pattern = null, $count = null) + { + return $this->__call('zscan', array($field, &$Iterator, $pattern, $count)); + } + + /** + * @param string|array $patterns + * @param $callback + * @return $this|array|bool|Credis_Client|mixed|null|string + * @throws CredisException + */ + public function pSubscribe($patterns, $callback) + { + if ( ! $this->standalone) { + return $this->__call('pSubscribe', array((array)$patterns, $callback)); + } + + // Standalone mode: use infinite loop to subscribe until timeout + $patternCount = is_array($patterns) ? count($patterns) : 1; + while ($patternCount--) { + if (isset($status)) { + list($command, $pattern, $status) = $this->read_reply(); + } else { + list($command, $pattern, $status) = $this->__call('psubscribe', array($patterns)); + } + $this->subscribed = $status > 0; + if ( ! $status) { + throw new CredisException('Invalid pSubscribe response.'); + } + } + while ($this->subscribed) { + list($type, $pattern, $channel, $message) = $this->read_reply(); + if ($type != 'pmessage') { + throw new CredisException('Received non-pmessage reply.'); + } + $callback($this, $pattern, $channel, $message); + } + return null; + } + + /** + * @param string|array $pattern + * @return array + */ + public function unsubscribe() + { + list($command, $channel, $subscribedChannels) = $this->__call('unsubscribe', func_get_args()); + $this->subscribed = $subscribedChannels > 0; + return array($command, $channel, $subscribedChannels); + } + + /** + * @param string|array $channels + * @param $callback + * @throws CredisException + * @return $this|array|bool|Credis_Client|mixed|null|string + */ + public function subscribe($channels, $callback) + { + if ( ! $this->standalone) { + return $this->__call('subscribe', array((array)$channels, $callback)); + } + + // Standalone mode: use infinite loop to subscribe until timeout + $channelCount = is_array($channels) ? count($channels) : 1; + while ($channelCount--) { + if (isset($status)) { + list($command, $channel, $status) = $this->read_reply(); + } else { + list($command, $channel, $status) = $this->__call('subscribe', array($channels)); + } + $this->subscribed = $status > 0; + if ( ! $status) { + throw new CredisException('Invalid subscribe response.'); + } + } + while ($this->subscribed) { + list($type, $channel, $message) = $this->read_reply(); + if ($type != 'message') { + throw new CredisException('Received non-message reply.'); + } + $callback($this, $channel, $message); + } + return null; + } + + /** + * @param string|null $name + * @return string|Credis_Client + */ + public function ping($name = null) + { + return $this->__call('ping', $name ? array($name) : array()); + } + + /** + * @param string $command + * @param array $args + * + * @return array|Credis_Client + */ + public function rawCommand($command, array $args) + { + if($this->standalone) + { + return $this->__call($command, $args); + } + else + { + \array_unshift($args, $command); + return $this->__call('rawCommand', $args); + } + } + + public function __call($name, $args) + { + // Lazy connection + $this->connect(); + + $name = strtolower($name); + + // Send request via native PHP + if($this->standalone) + { + $trackedArgs = array(); + switch ($name) { + case 'eval': + case 'evalsha': + $script = array_shift($args); + $keys = (array) array_shift($args); + $eArgs = (array) array_shift($args); + $args = array($script, count($keys), $keys, $eArgs); + break; + case 'zinterstore': + case 'zunionstore': + $dest = array_shift($args); + $keys = (array) array_shift($args); + $weights = array_shift($args); + $aggregate = array_shift($args); + $args = array($dest, count($keys), $keys); + if ($weights) { + $args[] = (array) $weights; + } + if ($aggregate) { + $args[] = $aggregate; + } + break; + case 'set': + // The php redis module has different behaviour with ttl + // https://github.com/phpredis/phpredis#set + if (count($args) === 3 && is_int($args[2])) { + $args = array($args[0], $args[1], array('EX', $args[2])); + } elseif (count($args) === 3 && is_array($args[2])) { + $tmp_args = $args; + $args = array($tmp_args[0], $tmp_args[1]); + foreach ($tmp_args[2] as $k=>$v) { + if (is_string($k)) { + $args[] = array($k,$v); + } elseif (is_int($k)) { + $args[] = $v; + } + } + unset($tmp_args); + } + break; + case 'scan': + $trackedArgs = array(&$args[0]); + if (empty($trackedArgs[0])) + { + $trackedArgs[0] = 0; + } + $eArgs = array($trackedArgs[0]); + if (!empty($args[1])) + { + $eArgs[] = 'MATCH'; + $eArgs[] = $args[1]; + } + if (!empty($args[2])) + { + $eArgs[] = 'COUNT'; + $eArgs[] = $args[2]; + } + $args = $eArgs; + break; + case 'sscan': + case 'zscan': + case 'hscan': + $trackedArgs = array(&$args[1]); + if (empty($trackedArgs[0])) + { + $trackedArgs[0] = 0; + } + $eArgs = array($args[0],$trackedArgs[0]); + if (!empty($args[2])) + { + $eArgs[] = 'MATCH'; + $eArgs[] = $args[2]; + } + if (!empty($args[3])) + { + $eArgs[] = 'COUNT'; + $eArgs[] = $args[3]; + } + $args = $eArgs; + break; + case 'zrangebyscore': + case 'zrevrangebyscore': + case 'zrange': + case 'zrevrange': + if (isset($args[3]) && is_array($args[3])) { + // map options + $cArgs = array(); + if (!empty($args[3]['withscores'])) { + $cArgs[] = 'withscores'; + } + if (($name == 'zrangebyscore' || $name == 'zrevrangebyscore') && array_key_exists('limit', $args[3])) { + $cArgs[] = array('limit' => $args[3]['limit']); + } + $args[3] = $cArgs; + $trackedArgs = $cArgs; + } + break; + case 'mget': + if (isset($args[0]) && is_array($args[0])) + { + $args = array_values($args[0]); + } + break; + case 'hmset': + if (isset($args[1]) && is_array($args[1])) + { + $cArgs = array(); + foreach($args[1] as $id => $value) + { + $cArgs[] = $id; + $cArgs[] = $value; + } + $args[1] = $cArgs; + } + break; + case 'zsize': + $name = 'zcard'; + break; + case 'zdelete': + $name = 'zrem'; + break; + case 'hmget': + // hmget needs to track the keys for rehydrating the results + if (isset($args[1])) + { + $trackedArgs = $args[1]; + } + break; + } + // Flatten arguments + $args = self::_flattenArguments($args); + + // In pipeline mode + if($this->usePipeline) + { + if($name === 'pipeline') { + throw new CredisException('A pipeline is already in use and only one pipeline is supported.'); + } + else if($name === 'exec') { + if($this->isMulti) { + $this->commandNames[] = array($name, $trackedArgs); + $this->commands .= self::_prepare_command(array($this->getRenamedCommand($name))); + } + + // Write request + if($this->commands) { + $this->write_command($this->commands); + } + $this->commands = NULL; + + // Read response + $queuedResponses = array(); + $response = array(); + foreach($this->commandNames as $command) { + list($name, $arguments) = $command; + $result = $this->read_reply($name, true); + if ($result !== null) + { + $result = $this->decode_reply($name, $result, $arguments); + } + else + { + $queuedResponses[] = $command; + } + $response[] = $result; + } + + if($this->isMulti) { + $response = array_pop($response); + foreach($queuedResponses as $key => $command) + { + list($name, $arguments) = $command; + $response[$key] = $this->decode_reply($name, $response[$key], $arguments); + } + } + + $this->commandNames = NULL; + $this->usePipeline = $this->isMulti = FALSE; + return $response; + } + else if ($name === 'discard') + { + $this->commands = NULL; + $this->commandNames = NULL; + $this->usePipeline = $this->isMulti = FALSE; + } + else { + if($name === 'multi') { + $this->isMulti = TRUE; + } + array_unshift($args, $this->getRenamedCommand($name)); + $this->commandNames[] = array($name, $trackedArgs); + $this->commands .= self::_prepare_command($args); + return $this; + } + } + + // Start pipeline mode + if($name === 'pipeline') + { + $this->usePipeline = TRUE; + $this->commandNames = array(); + $this->commands = ''; + return $this; + } + + // If unwatching, allow reconnect with no error thrown + if($name === 'unwatch') { + $this->isWatching = FALSE; + } + + // Non-pipeline mode + array_unshift($args, $this->getRenamedCommand($name)); + $command = self::_prepare_command($args); + $this->write_command($command); + $response = $this->read_reply($name); + $response = $this->decode_reply($name, $response, $trackedArgs); + + // Watch mode disables reconnect so error is thrown + if($name == 'watch') { + $this->isWatching = TRUE; + } + // Transaction mode + else if($this->isMulti && ($name == 'exec' || $name == 'discard')) { + $this->isMulti = FALSE; + } + // Started transaction + else if($this->isMulti || $name == 'multi') { + $this->isMulti = TRUE; + $response = $this; + } + } + + // Send request via phpredis client + else + { + // Tweak arguments + switch($name) { + case 'get': // optimize common cases + case 'set': + case 'hget': + case 'hset': + case 'setex': + case 'mset': + case 'msetnx': + case 'hmset': + case 'hmget': + case 'del': + case 'zrangebyscore': + case 'zrevrangebyscore': + break; + case 'zrange': + case 'zrevrange': + if (isset($args[3]) && is_array($args[3])) + { + $cArgs = $args[3]; + $args[3] = !empty($cArgs['withscores']); + } + $args = self::_flattenArguments($args); + break; + case 'zinterstore': + case 'zunionstore': + $cArgs = array(); + $cArgs[] = array_shift($args); // destination + $cArgs[] = array_shift($args); // keys + if(isset($args[0]) and isset($args[0]['weights'])) { + $cArgs[] = (array) $args[0]['weights']; + } else { + $cArgs[] = null; + } + if(isset($args[0]) and isset($args[0]['aggregate'])) { + $cArgs[] = strtoupper($args[0]['aggregate']); + } + $args = $cArgs; + break; + case 'mget': + if(isset($args[0]) && ! is_array($args[0])) { + $args = array($args); + } + break; + case 'lrem': + $args = array($args[0], $args[2], $args[1]); + break; + case 'eval': + case 'evalsha': + if (isset($args[1]) && is_array($args[1])) { + $cKeys = $args[1]; + } elseif (isset($args[1]) && is_string($args[1])) { + $cKeys = array($args[1]); + } else { + $cKeys = array(); + } + if (isset($args[2]) && is_array($args[2])) { + $cArgs = $args[2]; + } elseif (isset($args[2]) && is_string($args[2])) { + $cArgs = array($args[2]); + } else { + $cArgs = array(); + } + $args = array($args[0], array_merge($cKeys, $cArgs), count($cKeys)); + break; + case 'subscribe': + case 'psubscribe': + break; + case 'scan': + case 'sscan': + case 'hscan': + case 'zscan': + // allow phpredis to see the caller's reference + //$param_ref =& $args[0]; + break; + case 'auth': + // For phpredis pre-v5.3, the type signature is string, not array|string + $args = $this->oldPhpRedis ? $args : array($args); + break; + default: + // Flatten arguments + $args = self::_flattenArguments($args); + } + + try { + // Proxy pipeline mode to the phpredis library + if($name == 'pipeline' || $name == 'multi') { + if($this->isMulti) { + return $this; + } else { + $this->isMulti = TRUE; + $this->redisMulti = call_user_func_array(array($this->redis, $name), $args); + return $this; + } + } + else if($name == 'exec' || $name == 'discard') { + $this->isMulti = FALSE; + $response = $this->redisMulti->$name(); + $this->redisMulti = NULL; + #echo "> $name : ".substr(print_r($response, TRUE),0,100)."\n"; + return $response; + } + + // Use aliases to be compatible with phpredis wrapper + if(isset($this->wrapperMethods[$name])) { + $name = $this->wrapperMethods[$name]; + } + + // Multi and pipeline return self for chaining + if($this->isMulti) { + call_user_func_array(array($this->redisMulti, $name), $args); + return $this; + } + + + // Send request, retry one time when using persistent connections on the first request only + $this->requests++; + try { + $response = call_user_func_array(array($this->redis, $name), $args); + } catch (RedisException $e) { + if ($this->persistent && $this->requests == 1 && $e->getMessage() == 'read error on connection') { + $this->close(true); + $this->connect(); + $response = call_user_func_array(array($this->redis, $name), $args); + } else { + throw $e; + } + } + } + // Wrap exceptions + catch(RedisException $e) { + $code = 0; + if ( ! ($result = $this->redis->IsConnected())) { + $this->close(true); + $code = CredisException::CODE_DISCONNECTED; + } + throw new CredisException($e->getMessage(), $code, $e); + } + + #echo "> $name : ".substr(print_r($response, TRUE),0,100)."\n"; + + // change return values where it is too difficult to minim in standalone mode + switch($name) + { + case 'type': + $typeMap = array( + self::TYPE_NONE, + self::TYPE_STRING, + self::TYPE_SET, + self::TYPE_LIST, + self::TYPE_ZSET, + self::TYPE_HASH, + ); + $response = $typeMap[$response]; + break; + + // Handle scripting errors + case 'eval': + case 'evalsha': + case 'script': + $error = $this->redis->getLastError(); + $this->redis->clearLastError(); + if ($error && substr($error,0,8) == 'NOSCRIPT') { + $response = NULL; + } else if ($error) { + throw new CredisException($error); + } + break; + case 'exists': + // smooth over phpredis-v4 vs earlier difference to match documented credis return results + $response = (int) $response; + break; + case 'ping': + if ($response) { + if ($response === true) { + $response = isset($args[0]) ? $args[0] : "PONG"; + } else if ($response[0] === '+') { + $response = substr($response, 1); + } + } + break; + case 'auth': + if (is_bool($response) && $response === true){ + $this->redis->clearLastError(); + } + default: + $error = $this->redis->getLastError(); + $this->redis->clearLastError(); + if ($error) { + throw new CredisException(rtrim($error)); + } + break; + } + } + + return $response; + } + + protected function write_command($command) + { + // Reconnect on lost connection (Redis server "timeout" exceeded since last command) + if(feof($this->redis)) { + // If a watch or transaction was in progress and connection was lost, throw error rather than reconnect + // since transaction/watch state will be lost. + if(($this->isMulti && ! $this->usePipeline) || $this->isWatching) { + $this->close(true); + throw new CredisException('Lost connection to Redis server during watch or transaction.'); + } + $this->close(true); + $this->connect(); + if($this->authPassword) { + $this->auth($this->authPassword); + } + if($this->selectedDb != 0) { + $this->select($this->selectedDb); + } + } + + $commandLen = strlen($command); + $lastFailed = FALSE; + for ($written = 0; $written < $commandLen; $written += $fwrite) { + $fwrite = fwrite($this->redis, substr($command, $written)); + if ($fwrite === FALSE || ($fwrite == 0 && $lastFailed)) { + $this->close(true); + throw new CredisException('Failed to write entire command to stream'); + } + $lastFailed = $fwrite == 0; + } + } + + protected function read_reply($name = '', $returnQueued = false) + { + $reply = fgets($this->redis); + if($reply === FALSE) { + $info = stream_get_meta_data($this->redis); + $this->close(true); + if ($info['timed_out']) { + throw new CredisException('Read operation timed out.', CredisException::CODE_TIMED_OUT); + } else { + throw new CredisException('Lost connection to Redis server.', CredisException::CODE_DISCONNECTED); + } + } + $reply = rtrim($reply, CRLF); + #echo "> $name: $reply\n"; + $replyType = substr($reply, 0, 1); + switch ($replyType) { + /* Error reply */ + case '-': + if($this->isMulti || $this->usePipeline) { + $response = FALSE; + } else if ($name == 'evalsha' && substr($reply,0,9) == '-NOSCRIPT') { + $response = NULL; + } else { + throw new CredisException(substr($reply,0,4) == '-ERR' ? 'ERR '.substr($reply, 5) : substr($reply,1)); + } + break; + /* Inline reply */ + case '+': + $response = substr($reply, 1); + if($response == 'OK') { + return TRUE; + } + if($response == 'QUEUED') { + return $returnQueued ? null : true; + } + break; + /* Bulk reply */ + case '$': + if ($reply == '$-1') return FALSE; + $size = (int) substr($reply, 1); + $response = stream_get_contents($this->redis, $size + 2); + if( ! $response) { + $this->close(true); + throw new CredisException('Error reading reply.'); + } + $response = substr($response, 0, $size); + break; + /* Multi-bulk reply */ + case '*': + $count = substr($reply, 1); + if ($count == '-1') return FALSE; + + $response = array(); + for ($i = 0; $i < $count; $i++) { + $response[] = $this->read_reply(); + } + break; + /* Integer reply */ + case ':': + $response = intval(substr($reply, 1)); + break; + default: + throw new CredisException('Invalid response: '.print_r($reply, TRUE)); + break; + } + + return $response; + } + + protected function decode_reply($name, $response, array &$arguments = array() ) + { + // Smooth over differences between phpredis and standalone response + switch ($name) + { + case '': // Minor optimization for multi-bulk replies + break; + case 'config': + case 'hgetall': + $keys = $values = array(); + while ($response) + { + $keys[] = array_shift($response); + $values[] = array_shift($response); + } + $response = count($keys) ? array_combine($keys, $values) : array(); + break; + case 'info': + $lines = explode(CRLF, trim($response, CRLF)); + $response = array(); + foreach ($lines as $line) + { + if (!$line || substr($line, 0, 1) == '#') + { + continue; + } + list($key, $value) = explode(':', $line, 2); + $response[$key] = $value; + } + break; + case 'ttl': + if ($response === -1) + { + $response = false; + } + break; + case 'hmget': + if (count($arguments) != count($response)) + { + throw new CredisException( + 'hmget arguments and response do not match: ' . print_r($arguments, true) . ' ' . print_r( + $response, true + ) + ); + } + // rehydrate results into key => value form + $response = array_combine($arguments, $response); + break; + + case 'scan': + case 'sscan': + $arguments[0] = intval(array_shift($response)); + $response = empty($response[0]) ? array() : $response[0]; + break; + case 'hscan': + case 'zscan': + $arguments[0] = intval(array_shift($response)); + $response = empty($response[0]) ? array() : $response[0]; + if (!empty($response) && is_array($response)) + { + $count = count($response); + $out = array(); + for ($i = 0; $i < $count; $i += 2) + { + $out[$response[$i]] = $response[$i + 1]; + } + $response = $out; + } + break; + case 'zrangebyscore': + case 'zrevrangebyscore': + case 'zrange': + case 'zrevrange': + if (in_array('withscores', $arguments, true)) + { + // Map array of values into key=>score list like phpRedis does + $item = null; + $out = array(); + foreach ($response as $value) + { + if ($item == null) + { + $item = $value; + } + else + { + // 2nd value is the score + $out[$item] = (float)$value; + $item = null; + } + } + $response = $out; + } + break; + } + + return $response; + } + + /** + * Build the Redis unified protocol command + * + * @param array $args + * @return string + */ + private static function _prepare_command($args) + { + return sprintf('*%d%s%s%s', count($args), CRLF, implode(CRLF, array_map([static::class, '_map'], $args)), CRLF); + } + + private static function _map($arg) + { + return sprintf('$%d%s%s', strlen($arg), CRLF, $arg); + } + + /** + * Flatten arguments + * + * If an argument is an array, the key is inserted as argument followed by the array values + * array('zrangebyscore', '-inf', 123, array('limit' => array('0', '1'))) + * becomes + * array('zrangebyscore', '-inf', 123, 'limit', '0', '1') + * + * @param array $in + * @return array + */ + private static function _flattenArguments(array $arguments, &$out = array()) + { + foreach ($arguments as $key => $arg) { + if (!is_int($key)) { + $out[] = $key; + } + + if (is_array($arg)) { + self::_flattenArguments($arg, $out); + } else { + $out[] = $arg; + } + } + + return $out; + } +} diff --git a/redis-cache/dependencies/colinmollenhour/credis/Cluster.php b/redis-cache/dependencies/colinmollenhour/credis/Cluster.php new file mode 100644 index 0000000..eda43b8 --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/Cluster.php @@ -0,0 +1,343 @@ + + * @copyright 2009 Justin Poliey + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + * @package Credis + */ + +/** + * A generalized Credis_Client interface for a cluster of Redis servers + * + * @deprecated + */ +class Credis_Cluster +{ + /** + * Collection of Credis_Client objects attached to Redis servers + * @var Credis_Client[] + */ + protected $clients; + /** + * If a server is set as master, all write commands go to that one + * @var Credis_Client + */ + protected $masterClient; + /** + * Aliases of Credis_Client objects attached to Redis servers, used to route commands to specific servers + * @see Credis_Cluster::to + * @var array + */ + protected $aliases; + + /** + * Hash ring of Redis server nodes + * @var array + */ + protected $ring; + + /** + * Individual nodes of pointers to Redis servers on the hash ring + * @var array + */ + protected $nodes; + + /** + * The commands that are not subject to hashing + * @var array + * @access protected + */ + protected $dont_hash; + + /** + * Currently working cluster-wide database number. + * @var int + */ + protected $selectedDb = 0; + + /** + * Creates an interface to a cluster of Redis servers + * Each server should be in the format: + * array( + * 'host' => hostname, + * 'port' => port, + * 'db' => db, + * 'password' => password, + * 'timeout' => timeout, + * 'alias' => alias, + * 'persistent' => persistence_identifier, + * 'master' => master + * 'write_only'=> true/false + * ) + * + * @param array $servers The Redis servers in the cluster. + * @param int $replicas + * @param bool $standAlone + * @throws CredisException + */ + public function __construct($servers, $replicas = 128, $standAlone = false) + { + $this->clients = array(); + $this->masterClient = null; + $this->aliases = array(); + $this->ring = array(); + $this->replicas = (int)$replicas; + $client = null; + foreach ($servers as $server) + { + if(is_array($server)){ + $client = new Credis_Client( + $server['host'], + $server['port'], + isset($server['timeout']) ? $server['timeout'] : 2.5, + isset($server['persistent']) ? $server['persistent'] : '', + isset($server['db']) ? $server['db'] : 0, + isset($server['password']) ? $server['password'] : null + ); + if (isset($server['alias'])) { + $this->aliases[$server['alias']] = $client; + } + if(isset($server['master']) && $server['master'] === true){ + $this->masterClient = $client; + if(isset($server['write_only']) && $server['write_only'] === true){ + continue; + } + } + } elseif($server instanceof Credis_Client){ + $client = $server; + } else { + throw new CredisException('Server should either be an array or an instance of Credis_Client'); + } + if($standAlone) { + $client->forceStandalone(); + } + $this->clients[] = $client; + for ($replica = 0; $replica <= $this->replicas; $replica++) { + $md5num = hexdec(substr(md5($client->getHost().':'.$client->getPort().'-'.$replica),0,7)); + $this->ring[$md5num] = count($this->clients)-1; + } + } + ksort($this->ring, SORT_NUMERIC); + $this->nodes = array_keys($this->ring); + $this->dont_hash = array_flip(array( + 'RANDOMKEY', 'DBSIZE', 'PIPELINE', 'EXEC', + 'SELECT', 'MOVE', 'FLUSHDB', 'FLUSHALL', + 'SAVE', 'BGSAVE', 'LASTSAVE', 'SHUTDOWN', + 'INFO', 'MONITOR', 'SLAVEOF' + )); + if($this->masterClient !== null && count($this->clients()) == 0){ + $this->clients[] = $this->masterClient; + for ($replica = 0; $replica <= $this->replicas; $replica++) { + $md5num = hexdec(substr(md5($this->masterClient->getHost().':'.$this->masterClient->getHost().'-'.$replica),0,7)); + $this->ring[$md5num] = count($this->clients)-1; + } + $this->nodes = array_keys($this->ring); + } + } + + /** + * @param Credis_Client $masterClient + * @param bool $writeOnly + * @return Credis_Cluster + */ + public function setMasterClient(Credis_Client $masterClient, $writeOnly=false) + { + if(!$masterClient instanceof Credis_Client){ + throw new CredisException('Master client should be an instance of Credis_Client'); + } + $this->masterClient = $masterClient; + if (!isset($this->aliases['master'])) { + $this->aliases['master'] = $masterClient; + } + if(!$writeOnly){ + $this->clients[] = $this->masterClient; + for ($replica = 0; $replica <= $this->replicas; $replica++) { + $md5num = hexdec(substr(md5($this->masterClient->getHost().':'.$this->masterClient->getHost().'-'.$replica),0,7)); + $this->ring[$md5num] = count($this->clients)-1; + } + $this->nodes = array_keys($this->ring); + } + return $this; + } + /** + * Get a client by index or alias. + * + * @param string|int $alias + * @throws CredisException + * @return Credis_Client + */ + public function client($alias) + { + if (is_int($alias) && isset($this->clients[$alias])) { + return $this->clients[$alias]; + } + else if (isset($this->aliases[$alias])) { + return $this->aliases[$alias]; + } + throw new CredisException("Client $alias does not exist."); + } + + /** + * Get an array of all clients + * + * @return array|Credis_Client[] + */ + public function clients() + { + return $this->clients; + } + + /** + * Execute a command on all clients + * + * @return array + */ + public function all() + { + $args = func_get_args(); + $name = array_shift($args); + $results = array(); + foreach($this->clients as $client) { + $results[] = call_user_func_array([$client, $name], $args); + } + return $results; + } + + /** + * Get the client that the key would hash to. + * + * @param string $key + * @return \Credis_Client + */ + public function byHash($key) + { + return $this->clients[$this->hash($key)]; + } + + /** + * @param int $index + * @return void + */ + public function select($index) + { + $this->selectedDb = (int) $index; + } + + /** + * Execute a Redis command on the cluster with automatic consistent hashing and read/write splitting + * + * @param string $name + * @param array $args + * @return mixed + */ + public function __call($name, $args) + { + if($this->masterClient !== null && !$this->isReadOnlyCommand($name)){ + $client = $this->masterClient; + }elseif (count($this->clients()) == 1 || isset($this->dont_hash[strtoupper($name)]) || !isset($args[0])) { + $client = $this->clients[0]; + } + else { + $hashKey = $args[0]; + if (is_array($hashKey)) { + $hashKey = join('|', $hashKey); + } + $client = $this->byHash($hashKey); + } + // Ensure that current client is working on the same database as expected. + if ($client->getSelectedDb() != $this->selectedDb) { + $client->select($this->selectedDb); + } + return call_user_func_array([$client, $name], $args); + } + + /** + * Get client index for a key by searching ring with binary search + * + * @param string $key The key to hash + * @return int The index of the client object associated with the hash of the key + */ + public function hash($key) + { + $needle = hexdec(substr(md5($key),0,7)); + $server = $min = 0; + $max = count($this->nodes) - 1; + while ($max >= $min) { + $position = (int) (($min + $max) / 2); + $server = $this->nodes[$position]; + if ($needle < $server) { + $max = $position - 1; + } + else if ($needle > $server) { + $min = $position + 1; + } + else { + break; + } + } + return $this->ring[$server]; + } + + public function isReadOnlyCommand($command) + { + static $readOnlyCommands = array( + 'DBSIZE' => true, + 'INFO' => true, + 'MONITOR' => true, + 'EXISTS' => true, + 'TYPE' => true, + 'KEYS' => true, + 'SCAN' => true, + 'RANDOMKEY' => true, + 'TTL' => true, + 'GET' => true, + 'MGET' => true, + 'SUBSTR' => true, + 'STRLEN' => true, + 'GETRANGE' => true, + 'GETBIT' => true, + 'LLEN' => true, + 'LRANGE' => true, + 'LINDEX' => true, + 'SCARD' => true, + 'SISMEMBER' => true, + 'SINTER' => true, + 'SUNION' => true, + 'SDIFF' => true, + 'SMEMBERS' => true, + 'SSCAN' => true, + 'SRANDMEMBER' => true, + 'ZRANGE' => true, + 'ZREVRANGE' => true, + 'ZRANGEBYSCORE' => true, + 'ZREVRANGEBYSCORE' => true, + 'ZCARD' => true, + 'ZSCORE' => true, + 'ZCOUNT' => true, + 'ZRANK' => true, + 'ZREVRANK' => true, + 'ZSCAN' => true, + 'HGET' => true, + 'HMGET' => true, + 'HEXISTS' => true, + 'HLEN' => true, + 'HKEYS' => true, + 'HVALS' => true, + 'HGETALL' => true, + 'HSCAN' => true, + 'PING' => true, + 'AUTH' => true, + 'SELECT' => true, + 'ECHO' => true, + 'QUIT' => true, + 'OBJECT' => true, + 'BITCOUNT' => true, + 'TIME' => true, + 'SORT' => true, + ); + return array_key_exists(strtoupper($command), $readOnlyCommands); + } +} + diff --git a/redis-cache/dependencies/colinmollenhour/credis/LICENSE b/redis-cache/dependencies/colinmollenhour/credis/LICENSE new file mode 100644 index 0000000..c0a2f41 --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2009 Justin Poliey +Copyright (c) 2011 Colin Mollenhour + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/redis-cache/dependencies/colinmollenhour/credis/Module.php b/redis-cache/dependencies/colinmollenhour/credis/Module.php new file mode 100644 index 0000000..70b9ba9 --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/Module.php @@ -0,0 +1,68 @@ + + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + * @package Credis_Module + */ +class Credis_Module +{ + const MODULE_COUNTING_BLOOM_FILTER = 'CBF'; + + /** @var Credis_Client */ + protected $client; + + /** @var string */ + protected $moduleName; + + /** + * @param Credis_Client $client + * @param string $module + */ + public function __construct(Credis_Client $client, $module = null) + { + $client->forceStandalone(); // Redis Modules command not currently supported by phpredis + $this->client = $client; + + if (isset($module)) { + $this->setModule($module); + } + } + + /** + * Clean up client on destruct + */ + public function __destruct() + { + $this->client->close(); + } + + /** + * @param $moduleName + * @return $this + */ + public function setModule($moduleName) + { + $this->moduleName = (string) $moduleName; + + return $this; + } + + /** + * @param string $name + * @param string $args + * @return mixed + */ + public function __call($name, $args) + { + if ($this->moduleName === null) { + throw new \LogicException('Module must be set.'); + } + + return call_user_func(array($this->client, sprintf('%s.%s', $this->moduleName, $name)), $args); + } +} diff --git a/redis-cache/dependencies/colinmollenhour/credis/README.markdown b/redis-cache/dependencies/colinmollenhour/credis/README.markdown new file mode 100644 index 0000000..0a9f09a --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/README.markdown @@ -0,0 +1,214 @@ +![Build Status](https://github.com/colinmollenhour/credis/actions/workflows/ci.yml/badge.svg) + +# Credis + +Credis is a lightweight interface to the [Redis](http://redis.io/) key-value store which wraps the [phpredis](https://github.com/nicolasff/phpredis) +library when available for better performance. This project was forked from one of the many redisent forks. + +## Getting Started + +Credis_Client uses methods named the same as Redis commands, and translates return values to the appropriate +PHP equivalents. + +```php +require 'Credis/Client.php'; +$redis = new Credis_Client('localhost'); +$redis->set('awesome', 'absolutely'); +echo sprintf('Is Credis awesome? %s.\n', $redis->get('awesome')); + +// When arrays are given as arguments they are flattened automatically +$redis->rpush('particles', array('proton','electron','neutron')); +$particles = $redis->lrange('particles', 0, -1); +``` +Redis error responses will be wrapped in a CredisException class and thrown. + +Credis_Client also supports transparent command renaming. Write code using the original command names and the +client will send the aliased commands to the server transparently. Specify the renamed commands using a prefix +for md5, a callable function, individual aliases, or an array map of aliases. See "Redis Security":http://redis.io/topics/security for more info. + +## Supported connection string formats + +```php +$redis = new Credis_Client(/* connection string */); +``` + +### Unix socket connection string + +`unix:///path/to/redis.sock` + +### TCP connection string + +`tcp://host[:port][/persistence_identifier]` + +### TLS connection string + +`tls://host[:port][/persistence_identifier]` + +or + +`tlsv1.2://host[:port][/persistence_identifier]` + +Before php 7.2, `tls://` only supports TLSv1.0, either `ssl://` or `tlsv1.2` can be used to force TLSv1.2 support. + +Recent versions of redis do not support the protocols/cyphers that older versions of php default to, which may result in cryptic connection failures. + +#### Enable transport level security (TLS) + +Use TLS connection string `tls://127.0.0.1:6379` instead of TCP connection `tcp://127.0.0.1:6379` string in order to enable transport level security. + +```php +require 'Credis/Client.php'; +$redis = new Credis_Client('tls://127.0.0.1:6379'); +$redis->set('awesome', 'absolutely'); +echo sprintf('Is Credis awesome? %s.\n', $redis->get('awesome')); + +// When arrays are given as arguments they are flattened automatically +$redis->rpush('particles', array('proton','electron','neutron')); +$particles = $redis->lrange('particles', 0, -1); +``` + +## Clustering your servers + +Credis also includes a way for developers to fully utilize the scalability of Redis with multiple servers and [consistent hashing](http://en.wikipedia.org/wiki/Consistent_hashing). +Using the [Credis_Cluster](Cluster.php) class, you can use Credis the same way, except that keys will be hashed across multiple servers. +Here is how to set up a cluster: + +### Basic clustering example +```php + '127.0.0.1', 'port' => 6379, 'alias'=>'alpha'), + array('host' => '127.0.0.1', 'port' => 6380, 'alias'=>'beta') +)); +$cluster->set('key','value'); +echo "Alpha: ".$cluster->client('alpha')->get('key').PHP_EOL; +echo "Beta: ".$cluster->client('beta')->get('key').PHP_EOL; +``` + +### Explicit definition of replicas + +The consistent hashing strategy stores keys on a so called "ring". The position of each key is relative to the position of its target node. The target node that has the closest position will be the selected node for that specific key. + +To avoid an uneven distribution of keys (especially on small clusters), it is common to duplicate target nodes. Based on the number of replicas, each target node will exist *n times* on the "ring". + +The following example explicitly sets the number of replicas to 5. Both Redis instances will have 5 copies. The default value is 128. + +```php + '127.0.0.1', 'port' => 6379, 'alias'=>'alpha'), + array('host' => '127.0.0.1', 'port' => 6380, 'alias'=>'beta') + ), 5 +); +$cluster->set('key','value'); +echo "Alpha: ".$cluster->client('alpha')->get('key').PHP_EOL; +echo "Beta: ".$cluster->client('beta')->get('key').PHP_EOL; +``` + +## Master/slave replication + +The [Credis_Cluster](Cluster.php) class can also be used for [master/slave replication](http://redis.io/topics/replication). +Credis_Cluster will automatically perform *read/write splitting* and send the write requests exclusively to the master server. +Read requests will be handled by all servers unless you set the *write_only* flag to true in the connection string of the master server. + +### Redis server settings for master/slave replication + +Setting up master/slave replication is simple and only requires adding the following line to the config of the slave server: + +``` +slaveof 127.0.0.1 6379 +``` + +### Basic master/slave example +```php + '127.0.0.1', 'port' => 6379, 'alias'=>'master', 'master'=>true), + array('host' => '127.0.0.1', 'port' => 6380, 'alias'=>'slave') +)); +$cluster->set('key','value'); +echo $cluster->get('key').PHP_EOL; +echo $cluster->client('slave')->get('key').PHP_EOL; + +$cluster->client('master')->set('key2','value'); +echo $cluster->client('slave')->get('key2').PHP_EOL; +``` + +### No read on master + +The following example illustrates how to disable reading on the master server. This will cause the master server only to be used for writing. +This should only happen when you have enough write calls to create a certain load on the master server. Otherwise this is an inefficient usage of server resources. + +```php + '127.0.0.1', 'port' => 6379, 'alias'=>'master', 'master'=>true, 'write_only'=>true), + array('host' => '127.0.0.1', 'port' => 6380, 'alias'=>'slave') +)); +$cluster->set('key','value'); +echo $cluster->get('key').PHP_EOL; +``` +## Automatic failover with Sentinel + +[Redis Sentinel](http://redis.io/topics/sentinel) is a system that can monitor Redis instances. You register master servers and Sentinel automatically detects its slaves. + +When a master server dies, Sentinel will make sure one of the slaves is promoted to be the new master. This autofailover mechanism will also demote failed masters to avoid data inconsistency. + +The [Credis_Sentinel](Sentinel.php) class interacts with the *Redis Sentinel* instance(s) and acts as a proxy. Sentinel will automatically create [Credis_Cluster](Cluster.php) objects and will set the master and slaves accordingly. + +Sentinel uses the same protocol as Redis. In the example below we register the Sentinel server running on port *26379* and assign it to the [Credis_Sentinel](Sentinel.php) object. +We then ask Sentinel the hostname and port for the master server known as *mymaster*. By calling the *getCluster* method we immediately get a [Credis_Cluster](Cluster.php) object that allows us to perform basic Redis calls. + +```php +getMasterAddressByName('mymaster'); +$cluster = $sentinel->getCluster('mymaster'); + +echo 'Writing to master: '.$masterAddress[0].' on port '.$masterAddress[1].PHP_EOL; +$cluster->set('key','value'); +echo $cluster->get('key').PHP_EOL; +``` +### Additional parameters + +Because [Credis_Sentinel](Sentinel.php) will create [Credis_Cluster](Cluster.php) objects using the *"getCluster"* or *"createCluster"* methods, additional parameters can be passed. + +First of all there's the *"write_only"* flag. You can also define the selected database and the number of replicas. And finally there's a *"selectRandomSlave"* option. + +The *"selectRandomSlave"* flag is used in setups for masters that have multiple slaves. The Credis_Sentinel will either select one random slave to be used when creating the Credis_Cluster object or to pass them all and use the built-in hashing. + +The example below shows how to use these 3 options. It selects database 2, sets the number of replicas to 10, it doesn't select a random slave and doesn't allow reading on the master server. + +```php +getCluster('mymaster',2,10,false,true); +$cluster->set('key','value'); +echo $cluster->get('key').PHP_EOL; +``` + +## About + +© 2011 [Colin Mollenhour](http://colin.mollenhour.com) +© 2009 [Justin Poliey](http://justinpoliey.com) diff --git a/redis-cache/dependencies/colinmollenhour/credis/Sentinel.php b/redis-cache/dependencies/colinmollenhour/credis/Sentinel.php new file mode 100644 index 0000000..b5f4116 --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/Sentinel.php @@ -0,0 +1,424 @@ + + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + * @package Credis_Sentinel + */ +class Credis_Sentinel +{ + /** + * Contains a client that connects to a Sentinel node. + * Sentinel uses the same protocol as Redis which makes using Credis_Client convenient. + * @var Credis_Client + */ + protected $_client; + + /** + * Contains an active instance of Credis_Cluster per master pool + * @var array + */ + protected $_cluster = array(); + + /** + * Contains an active instance of Credis_Client representing a master + * @var array + */ + protected $_master = array(); + + /** + * Contains an array Credis_Client objects representing all slaves per master pool + * @var array + */ + protected $_slaves = array(); + + /** + * Use the phpredis extension or the standalone implementation + * @var bool + * @deprecated + */ + protected $_standAlone = false; + + /** + * Store the AUTH password used by Credis_Client instances + * @var string + */ + protected $_password = ''; + /** + * Store the AUTH username used by Credis_Client instances (Redis v6+) + * @var string + */ + protected $_username = ''; + /** + * @var null|float + */ + protected $_timeout; + /** + * @var string + */ + protected $_persistent; + /** + * @var int + */ + protected $_db; + /** + * @var string|null + */ + protected $_replicaCmd = null; + /** + * @var string|null + */ + protected $_redisVersion = null; + + /** + * Connect with a Sentinel node. Sentinel will do the master and slave discovery + * + * @param Credis_Client $client + * @param string $password (deprecated - use setClientPassword) + * @throws CredisException + */ + public function __construct(Credis_Client $client, $password = NULL, $username = NULL) + { + $client->forceStandalone(); // SENTINEL command not currently supported by phpredis + $this->_client = $client; + $this->_password = $password; + $this->_username = $username; + $this->_timeout = NULL; + $this->_persistent = ''; + $this->_db = 0; + } + + /** + * Clean up client on destruct + */ + public function __destruct() + { + $this->_client->close(); + } + + /** + * @param float $timeout + * @return $this + */ + public function setClientTimeout($timeout) + { + $this->_timeout = $timeout; + return $this; + } + + /** + * @param string $persistent + * @return $this + */ + public function setClientPersistent($persistent) + { + $this->_persistent = $persistent; + return $this; + } + + /** + * @param int $db + * @return $this + */ + public function setClientDatabase($db) + { + $this->_db = $db; + return $this; + } + + /** + * @param null|string $password + * @return $this + */ + public function setClientPassword($password) + { + $this->_password = $password; + return $this; + } + + /** + * @param null|string $username + * @return $this + */ + public function setClientUsername($username) + { + $this->_username = $username; + return $this; + } + + /** + * @param null|string $replicaCmd + * @return $this + */ + public function setReplicaCommand($replicaCmd) + { + $this->_replicaCmd = $replicaCmd; + return $this; + } + + public function detectRedisVersion() + { + if ($this->_redisVersion !== null && $this->_replicaCmd !== null) { + return; + } + $serverInfo = $this->info('server'); + $this->_redisVersion = $serverInfo['redis_version']; + // Redis v7+ renames the replica command to 'replicas' instead of 'slaves' + $this->_replicaCmd = version_compare($this->_redisVersion, '7.0.0', '>=') ? 'replicas' : 'slaves'; + } + + /** + * @return Credis_Sentinel + * @deprecated + */ + public function forceStandalone() + { + $this->_standAlone = true; + return $this; + } + + /** + * Discover the master node automatically and return an instance of Credis_Client that connects to the master + * + * @param string $name + * @return Credis_Client + * @throws CredisException + */ + public function createMasterClient($name) + { + $master = $this->getMasterAddressByName($name); + if(!isset($master[0]) || !isset($master[1])){ + throw new CredisException('Master not found'); + } + return new Credis_Client($master[0], $master[1], $this->_timeout, $this->_persistent, $this->_db, $this->_password, $this->_username); + } + + /** + * If a Credis_Client object exists for a master, return it. Otherwise create one and return it + * @param string $name + * @return Credis_Client + */ + public function getMasterClient($name) + { + if(!isset($this->_master[$name])){ + $this->_master[$name] = $this->createMasterClient($name); + } + return $this->_master[$name]; + } + + /** + * Discover the slave nodes automatically and return an array of Credis_Client objects + * + * @param string $name + * @return Credis_Client[] + * @throws CredisException + */ + public function createSlaveClients($name) + { + $slaves = $this->slaves($name); + $workingSlaves = array(); + foreach($slaves as $slave) { + if(!isset($slave[9])){ + throw new CredisException('Can\' retrieve slave status'); + } + if(!strstr($slave[9],'s_down') && !strstr($slave[9],'disconnected')) { + $workingSlaves[] = new Credis_Client($slave[3], $slave[5], $this->_timeout, $this->_persistent, $this->_db, $this->_password, $this->_username); + } + } + return $workingSlaves; + } + + /** + * If an array of Credis_Client objects exist for a set of slaves, return them. Otherwise create and return them + * @param string $name + * @return Credis_Client[] + */ + public function getSlaveClients($name) + { + if(!isset($this->_slaves[$name])){ + $this->_slaves[$name] = $this->createSlaveClients($name); + } + return $this->_slaves[$name]; + } + + /** + * Returns a Redis cluster object containing a random slave and the master + * When $selectRandomSlave is true, only one random slave is passed. + * When $selectRandomSlave is false, all clients are passed and hashing is applied in Credis_Cluster + * When $writeOnly is false, the master server will also be used for read commands. + * When $masterOnly is true, only the master server will also be used for both read and write commands. $writeOnly will be ignored and forced to set to false. + * @param string $name + * @param int $db + * @param int $replicas + * @param bool $selectRandomSlave + * @param bool $writeOnly + * @param bool $masterOnly + * @return Credis_Cluster + * @throws CredisException + * @deprecated + */ + public function createCluster($name, $db=0, $replicas=128, $selectRandomSlave=true, $writeOnly=false, $masterOnly=false) + { + $clients = array(); + $workingClients = array(); + $master = $this->master($name); + if(strstr($master[9],'s_down') || strstr($master[9],'disconnected')) { + throw new CredisException('The master is down'); + } + if (!$masterOnly) { + $slaves = $this->slaves($name); + foreach($slaves as $slave){ + if(!strstr($slave[9],'s_down') && !strstr($slave[9],'disconnected')) { + $workingClients[] = array('host'=>$slave[3],'port'=>$slave[5],'master'=>false,'db'=>$db,'password'=>$this->_password); + } + } + if(count($workingClients)>0){ + if($selectRandomSlave){ + if(!$writeOnly){ + $workingClients[] = array('host'=>$master[3],'port'=>$master[5],'master'=>false,'db'=>$db,'password'=>$this->_password); + } + $clients[] = $workingClients[rand(0,count($workingClients)-1)]; + } else { + $clients = $workingClients; + } + } + } else { + $writeOnly = false; + } + $clients[] = array('host'=>$master[3],'port'=>$master[5], 'db'=>$db ,'master'=>true,'write_only'=>$writeOnly,'password'=>$this->_password); + return new Credis_Cluster($clients,$replicas,$this->_standAlone); + } + + /** + * If a Credis_Cluster object exists, return it. Otherwise create one and return it. + * @param string $name + * @param int $db + * @param int $replicas + * @param bool $selectRandomSlave + * @param bool $writeOnly + * @param bool $masterOnly + * @return Credis_Cluster + * @throws CredisException + * @deprecated + */ + public function getCluster($name, $db=0, $replicas=128, $selectRandomSlave=true, $writeOnly=false, $masterOnly=false) + { + if(!isset($this->_cluster[$name])){ + $this->_cluster[$name] = $this->createCluster($name, $db, $replicas, $selectRandomSlave, $writeOnly, $masterOnly); + } + return $this->_cluster[$name]; + } + + /** + * Catch-all method + * @param string $name + * @param array $args + * @return mixed + */ + public function __call($name, $args) + { + array_unshift($args,$name); + return call_user_func(array($this->_client,'sentinel'),$args); + } + + /** + * get information block for the sentinel instance + * + * @param string|NUll $section + * + * @return array + */ + public function info($section = null) + { + if ($section) + { + return $this->_client->info($section); + } + return $this->_client->info(); + } + + /** + * Return information about all registered master servers + * @return mixed + */ + public function masters() + { + return $this->_client->sentinel('masters'); + } + + /** + * Return all information for slaves that are associated with a single master + * @param string $name + * @return mixed + */ + public function slaves($name) + { + if ($this->_replicaCmd === null) { + $this->detectRedisVersion(); + } + return $this->_client->sentinel($this->_replicaCmd,$name); + } + + /** + * Get the information for a specific master + * @param string $name + * @return mixed + */ + public function master($name) + { + return $this->_client->sentinel('master',$name); + } + + /** + * Get the hostname and port for a specific master + * @param string $name + * @return mixed + */ + public function getMasterAddressByName($name) + { + return $this->_client->sentinel('get-master-addr-by-name',$name); + } + + /** + * Check if the Sentinel is still responding + * @return string|Credis_Client + */ + public function ping() + { + return $this->_client->ping(); + } + + /** + * Perform an auto-failover which will re-elect another master and make the current master a slave + * @param string $name + * @return mixed + */ + public function failover($name) + { + return $this->_client->sentinel('failover',$name); + } + + /** + * @return string + */ + public function getHost() + { + return $this->_client->getHost(); + } + + /** + * @return int + */ + public function getPort() + { + return $this->_client->getPort(); + } +} diff --git a/redis-cache/dependencies/colinmollenhour/credis/composer.json b/redis-cache/dependencies/colinmollenhour/credis/composer.json new file mode 100644 index 0000000..2b781e6 --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/composer.json @@ -0,0 +1,27 @@ +{ + "name": "colinmollenhour/credis", + "type": "library", + "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.", + "homepage": "https://github.com/colinmollenhour/credis", + "license": "MIT", + "authors": [ + { + "name": "Colin Mollenhour", + "email": "colin@mollenhour.com" + } + ], + "require": { + "php": ">=5.6.0" + }, + "suggest": { + "ext-redis": "Improved performance for communicating with redis" + }, + "autoload": { + "classmap": [ + "Client.php", + "Cluster.php", + "Sentinel.php", + "Module.php" + ] + } +} diff --git a/redis-cache/dependencies/colinmollenhour/credis/phpunit_local.sh b/redis-cache/dependencies/colinmollenhour/credis/phpunit_local.sh new file mode 100755 index 0000000..218d1c7 --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/phpunit_local.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# This script runs unit tests locally in environment similar to Travis-CI +# It runs tests in different PHP versions with suitable PHPUnite version. +# +# You can see results of unit tests execution in console. +# Also all execution logs are saved to files phpunit_.log +# +# Prerequisites for running unit tests on local machine: +# - docker +# - docker-compose +# +# You can find definition of all test environments in folder testenv/ +# This folder is not automatically synced with .travis.yml +# If you add new PHP version to .travis.yml then you'll need to adjust files in testenv/ + +cd testenv + +# build containers and run tests +docker-compose build && docker-compose up + +# save logs to log file +docker-compose logs --no-color --timestamps | sort >"../phpunit_$(date '+%Y%m%d-%H%M%S').log" + +# remove containers +docker-compose rm -f diff --git a/redis-cache/dependencies/colinmollenhour/credis/testenv/docker-compose.yml b/redis-cache/dependencies/colinmollenhour/credis/testenv/docker-compose.yml new file mode 100644 index 0000000..c527d8f --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/testenv/docker-compose.yml @@ -0,0 +1,32 @@ +version: '2' +services: + + php-56: + build: env/php-5.6/ + volumes: + - ../:/src/ + + php-70: + build: env/php-7.0/ + volumes: + - ../:/src/ + + php-71: + build: env/php-7.1/ + volumes: + - ../:/src/ + + php-72: + build: env/php-7.2/ + volumes: + - ../:/src/ + + php-73: + build: env/php-7.3/ + volumes: + - ../:/src/ + + php-74: + build: env/php-7.4/ + volumes: + - ../:/src/ diff --git a/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-5.6/Dockerfile b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-5.6/Dockerfile new file mode 100644 index 0000000..c6378a4 --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-5.6/Dockerfile @@ -0,0 +1,24 @@ +FROM php:5.6 +ENV phpunit_verison 5.7 +ENV redis_version 4.0.11 + +RUN apt-get update && \ + apt-get install -y wget + +RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ + chmod +x phpunit-${phpunit_verison}.phar && \ + mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit + +# install php extension +RUN yes '' | pecl install -f redis-4.3.0 && \ + docker-php-ext-enable redis + +# install redis server +RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ + tar -xzf redis-${redis_version}.tar.gz && \ + make -s -C redis-${redis_version} -j + +CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ + cp -rp /src /app && \ + cd /app && \ + phpunit diff --git a/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.0/Dockerfile b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.0/Dockerfile new file mode 100644 index 0000000..82dfef9 --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.0/Dockerfile @@ -0,0 +1,25 @@ +FROM php:7.0 +ENV phpunit_verison 6.5 +ENV redis_version 6.0.8 + +RUN apt-get update && \ + apt-get install -y wget libssl-dev + +RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ + chmod +x phpunit-${phpunit_verison}.phar && \ + mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit + +# install php extension +RUN yes '' | pecl install -f redis && \ + docker-php-ext-enable redis + +# install redis server +RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ + tar -xzf redis-${redis_version}.tar.gz && \ + export BUILD_TLS=yes && \ + make -s -C redis-${redis_version} -j + +CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ + cp -rp /src /app && \ + cd /app && \ + phpunit diff --git a/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.1/Dockerfile b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.1/Dockerfile new file mode 100644 index 0000000..e75a4c0 --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.1/Dockerfile @@ -0,0 +1,25 @@ +FROM php:7.1 +ENV phpunit_verison 7.5 +ENV redis_version 6.0.8 + +RUN apt-get update && \ + apt-get install -y wget libssl-dev + +RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ + chmod +x phpunit-${phpunit_verison}.phar && \ + mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit + +# install php extension +RUN yes '' | pecl install -f redis && \ + docker-php-ext-enable redis + +# install redis server +RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ + tar -xzf redis-${redis_version}.tar.gz && \ + export BUILD_TLS=yes && \ + make -s -C redis-${redis_version} -j + +CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ + cp -rp /src /app && \ + cd /app && \ + phpunit diff --git a/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.2/Dockerfile b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.2/Dockerfile new file mode 100644 index 0000000..0e34aab --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.2/Dockerfile @@ -0,0 +1,25 @@ +FROM php:7.2 +ENV phpunit_verison 7.5 +ENV redis_version 6.0.8 + +RUN apt-get update && \ + apt-get install -y wget libssl-dev + +RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ + chmod +x phpunit-${phpunit_verison}.phar && \ + mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit + +# install php extension +RUN yes '' | pecl install -f redis && \ + docker-php-ext-enable redis + +# install redis server +RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ + tar -xzf redis-${redis_version}.tar.gz && \ + export BUILD_TLS=yes && \ + make -s -C redis-${redis_version} -j + +CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ + cp -rp /src /app && \ + cd /app && \ + phpunit diff --git a/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.3/Dockerfile b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.3/Dockerfile new file mode 100644 index 0000000..279ffa9 --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.3/Dockerfile @@ -0,0 +1,25 @@ +FROM php:7.3 +ENV phpunit_verison 7.5 +ENV redis_version 6.0.8 + +RUN apt-get update && \ + apt-get install -y wget libssl-dev + +RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ + chmod +x phpunit-${phpunit_verison}.phar && \ + mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit + +# install php extension +RUN yes '' | pecl install -f redis && \ + docker-php-ext-enable redis + +# install redis server +RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ + tar -xzf redis-${redis_version}.tar.gz && \ + export BUILD_TLS=yes && \ + make -s -C redis-${redis_version} -j + +CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ + cp -rp /src /app && \ + cd /app && \ + phpunit diff --git a/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.4/Dockerfile b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.4/Dockerfile new file mode 100644 index 0000000..5de537e --- /dev/null +++ b/redis-cache/dependencies/colinmollenhour/credis/testenv/env/php-7.4/Dockerfile @@ -0,0 +1,25 @@ +FROM php:7.4 +ENV phpunit_verison 7.5 +ENV redis_version 6.0.8 + +RUN apt-get update && \ + apt-get install -y wget libssl-dev + +RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ + chmod +x phpunit-${phpunit_verison}.phar && \ + mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit + +# install php extension +RUN yes '' | pecl install -f redis && \ + docker-php-ext-enable redis + +# install redis server +RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ + tar -xzf redis-${redis_version}.tar.gz && \ + export BUILD_TLS=yes && \ + make -s -C redis-${redis_version} -j + +CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ + cp -rp /src /app && \ + cd /app && \ + phpunit diff --git a/redis-cache/dependencies/predis/predis/LICENSE b/redis-cache/dependencies/predis/predis/LICENSE new file mode 100644 index 0000000..9a8cd86 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2009-2020 Daniele Alessandri (original work) +Copyright (c) 2021-2023 Till Krüss (modified work) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/redis-cache/dependencies/predis/predis/README.md b/redis-cache/dependencies/predis/predis/README.md new file mode 100644 index 0000000..913af58 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/README.md @@ -0,0 +1,473 @@ +# Predis # + +[![Software license][ico-license]](LICENSE) +[![Latest stable][ico-version-stable]][link-releases] +[![Latest development][ico-version-dev]][link-releases] +[![Monthly installs][ico-downloads-monthly]][link-downloads] +[![Build status][ico-build]][link-actions] +[![Coverage Status][ico-coverage]][link-coverage] + +A flexible and feature-complete [Redis](http://redis.io) client for PHP 7.2 and newer. + +More details about this project can be found on the [frequently asked questions](FAQ.md). + + +## Main features ## + +- Support for Redis from __3.0__ to __7.0__. +- Support for clustering using client-side sharding and pluggable keyspace distributors. +- Support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0). +- Support for master-slave replication setups and [redis-sentinel](http://redis.io/topics/sentinel). +- Transparent key prefixing of keys using a customizable prefix strategy. +- Command pipelining on both single nodes and clusters (client-side sharding only). +- Abstraction for Redis transactions (Redis >= 2.0) and CAS operations (Redis >= 2.2). +- Abstraction for Lua scripting (Redis >= 2.6) and automatic switching between `EVALSHA` or `EVAL`. +- Abstraction for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8) based on PHP iterators. +- Connections are established lazily by the client upon the first command and can be persisted. +- Connections can be established via TCP/IP (also TLS/SSL-encrypted) or UNIX domain sockets. +- Support for custom connection classes for providing different network or protocol backends. +- Flexible system for defining custom commands and override the default ones. + + +## How to _install_ and use Predis ## + +This library can be found on [Packagist](http://packagist.org/packages/predis/predis) for an easier +management of projects dependencies using [Composer](http://packagist.org/about-composer). +Compressed archives of each release are [available on GitHub](https://github.com/predis/predis/releases). + +```shell +composer require predis/predis +``` + + +### Loading the library ### + +Predis relies on the autoloading features of PHP to load its files when needed and complies with the +[PSR-4 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md). +Autoloading is handled automatically when dependencies are managed through Composer, but it is also +possible to leverage its own autoloader in projects or scripts lacking any autoload facility: + +```php +// Prepend a base path if Predis is not available in your "include_path". +require 'Predis/Autoloader.php'; + +Predis\Autoloader::register(); +``` + + +### Connecting to Redis ### + +When creating a client instance without passing any connection parameter, Predis assumes `127.0.0.1` +and `6379` as default host and port. The default timeout for the `connect()` operation is 5 seconds: + +```php +$client = new Predis\Client(); +$client->set('foo', 'bar'); +$value = $client->get('foo'); +``` + +Connection parameters can be supplied either in the form of URI strings or named arrays. The latter +is the preferred way to supply parameters, but URI strings can be useful when parameters are read +from non-structured or partially-structured sources: + +```php +// Parameters passed using a named array: +$client = new Predis\Client([ + 'scheme' => 'tcp', + 'host' => '10.0.0.1', + 'port' => 6379, +]); + +// Same set of parameters, passed using an URI string: +$client = new Predis\Client('tcp://10.0.0.1:6379'); +``` + +Password protected servers can be accessed by adding `password` to the parameters set. When ACLs are +enabled on Redis >= 6.0, both `username` and `password` are required for user authentication. + +It is also possible to connect to local instances of Redis using UNIX domain sockets, in this case +the parameters must use the `unix` scheme and specify a path for the socket file: + +```php +$client = new Predis\Client(['scheme' => 'unix', 'path' => '/path/to/redis.sock']); +$client = new Predis\Client('unix:/path/to/redis.sock'); +``` + +The client can leverage TLS/SSL encryption to connect to secured remote Redis instances without the +need to configure an SSL proxy like stunnel. This can be useful when connecting to nodes running on +various cloud hosting providers. Encryption can be enabled with using the `tls` scheme and an array +of suitable [options](http://php.net/manual/context.ssl.php) passed via the `ssl` parameter: + +```php +// Named array of connection parameters: +$client = new Predis\Client([ + 'scheme' => 'tls', + 'ssl' => ['cafile' => 'private.pem', 'verify_peer' => true], +]); + +// Same set of parameters, but using an URI string: +$client = new Predis\Client('tls://127.0.0.1?ssl[cafile]=private.pem&ssl[verify_peer]=1'); +``` + +The connection schemes [`redis`](http://www.iana.org/assignments/uri-schemes/prov/redis) (alias of +`tcp`) and [`rediss`](http://www.iana.org/assignments/uri-schemes/prov/rediss) (alias of `tls`) are +also supported, with the difference that URI strings containing these schemes are parsed following +the rules described on their respective IANA provisional registration documents. + +The actual list of supported connection parameters can vary depending on each connection backend so +it is recommended to refer to their specific documentation or implementation for details. + +Predis can aggregate multiple connections when providing an array of connection parameters and the +appropriate option to instruct the client about how to aggregate them (clustering, replication or a +custom aggregation logic). Named arrays and URI strings can be mixed when providing configurations +for each node: + +```php +$client = new Predis\Client([ + 'tcp://10.0.0.1?alias=first-node', ['host' => '10.0.0.2', 'alias' => 'second-node'], +], [ + 'cluster' => 'predis', +]); +``` + +See the [aggregate connections](#aggregate-connections) section of this document for more details. + +Connections to Redis are lazy meaning that the client connects to a server only if and when needed. +While it is recommended to let the client do its own stuff under the hood, there may be times when +it is still desired to have control of when the connection is opened or closed: this can easily be +achieved by invoking `$client->connect()` and `$client->disconnect()`. Please note that the effect +of these methods on aggregate connections may differ depending on each specific implementation. + + +### Client configuration ### + +Many aspects and behaviors of the client can be configured by passing specific client options to the +second argument of `Predis\Client::__construct()`: + +```php +$client = new Predis\Client($parameters, ['prefix' => 'sample:']); +``` + +Options are managed using a mini DI-alike container and their values can be lazily initialized only +when needed. The client options supported by default in Predis are: + + - `prefix`: prefix string applied to every key found in commands. + - `exceptions`: whether the client should throw or return responses upon Redis errors. + - `connections`: list of connection backends or a connection factory instance. + - `cluster`: specifies a cluster backend (`predis`, `redis` or callable). + - `replication`: specifies a replication backend (`predis`, `sentinel` or callable). + - `aggregate`: configures the client with a custom aggregate connection (callable). + - `parameters`: list of default connection parameters for aggregate connections. + - `commands`: specifies a command factory instance to use through the library. + +Users can also provide custom options with values or callable objects (for lazy initialization) that +are stored in the options container for later use through the library. + + +### Aggregate connections ### + +Aggregate connections are the foundation upon which Predis implements clustering and replication and +they are used to group multiple connections to single Redis nodes and hide the specific logic needed +to handle them properly depending on the context. Aggregate connections usually require an array of +connection parameters along with the appropriate client option when creating a new client instance. + +#### Cluster #### + +Predis can be configured to work in clustering mode with a traditional client-side sharding approach +to create a cluster of independent nodes and distribute the keyspace among them. This approach needs +some sort of external health monitoring of nodes and requires the keyspace to be rebalanced manually +when nodes are added or removed: + +```php +$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3']; +$options = ['cluster' => 'predis']; + +$client = new Predis\Client($parameters); +``` + +Along with Redis 3.0, a new supervised and coordinated type of clustering was introduced in the form +of [redis-cluster](http://redis.io/topics/cluster-tutorial). This kind of approach uses a different +algorithm to distribute the keyspaces, with Redis nodes coordinating themselves by communicating via +a gossip protocol to handle health status, rebalancing, nodes discovery and request redirection. In +order to connect to a cluster managed by redis-cluster, the client requires a list of its nodes (not +necessarily complete since it will automatically discover new nodes if necessary) and the `cluster` +client options set to `redis`: + +```php +$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3']; +$options = ['cluster' => 'redis']; + +$client = new Predis\Client($parameters, $options); +``` + +#### Replication #### + +The client can be configured to operate in a single master / multiple slaves setup to provide better +service availability. When using replication, Predis recognizes read-only commands and sends them to +a random slave in order to provide some sort of load-balancing and switches to the master as soon as +it detects a command that performs any kind of operation that would end up modifying the keyspace or +the value of a key. Instead of raising a connection error when a slave fails, the client attempts to +fall back to a different slave among the ones provided in the configuration. + +The basic configuration needed to use the client in replication mode requires one Redis server to be +identified as the master (this can be done via connection parameters by setting the `role` parameter +to `master`) and one or more slaves (in this case setting `role` to `slave` for slaves is optional): + +```php +$parameters = ['tcp://10.0.0.1?role=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3']; +$options = ['replication' => 'predis']; + +$client = new Predis\Client($parameters, $options); +``` + +The above configuration has a static list of servers and relies entirely on the client's logic, but +it is possible to rely on [`redis-sentinel`](http://redis.io/topics/sentinel) for a more robust HA +environment with sentinel servers acting as a source of authority for clients for service discovery. +The minimum configuration required by the client to work with redis-sentinel is a list of connection +parameters pointing to a bunch of sentinel instances, the `replication` option set to `sentinel` and +the `service` option set to the name of the service: + +```php +$sentinels = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3']; +$options = ['replication' => 'sentinel', 'service' => 'mymaster']; + +$client = new Predis\Client($sentinels, $options); +``` + +If the master and slave nodes are configured to require an authentication from clients, a password +must be provided via the global `parameters` client option. This option can also be used to specify +a different database index. The client options array would then look like this: + +```php +$options = [ + 'replication' => 'sentinel', + 'service' => 'mymaster', + 'parameters' => [ + 'password' => $secretpassword, + 'database' => 10, + ], +]; +``` + +While Predis is able to distinguish commands performing write and read-only operations, `EVAL` and +`EVALSHA` represent a corner case in which the client switches to the master node because it cannot +tell when a Lua script is safe to be executed on slaves. While this is indeed the default behavior, +when certain Lua scripts do not perform write operations it is possible to provide an hint to tell +the client to stick with slaves for their execution: + +```php +$parameters = ['tcp://10.0.0.1?role=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3']; +$options = ['replication' => function () { + // Set scripts that won't trigger a switch from a slave to the master node. + $strategy = new Predis\Replication\ReplicationStrategy(); + $strategy->setScriptReadOnly($LUA_SCRIPT); + + return new Predis\Connection\Replication\MasterSlaveReplication($strategy); +}]; + +$client = new Predis\Client($parameters, $options); +$client->eval($LUA_SCRIPT, 0); // Sticks to slave using `eval`... +$client->evalsha(sha1($LUA_SCRIPT), 0); // ... and `evalsha`, too. +``` + +The [`examples`](examples/) directory contains a few scripts that demonstrate how the client can be +configured and used to leverage replication in both basic and complex scenarios. + + +### Command pipelines ### + +Pipelining can help with performances when many commands need to be sent to a server by reducing the +latency introduced by network round-trip timings. Pipelining also works with aggregate connections. +The client can execute the pipeline inside a callable block or return a pipeline instance with the +ability to chain commands thanks to its fluent interface: + +```php +// Executes a pipeline inside the given callable block: +$responses = $client->pipeline(function ($pipe) { + for ($i = 0; $i < 1000; $i++) { + $pipe->set("key:$i", str_pad($i, 4, '0', 0)); + $pipe->get("key:$i"); + } +}); + +// Returns a pipeline that can be chained thanks to its fluent interface: +$responses = $client->pipeline()->set('foo', 'bar')->get('foo')->execute(); +``` + + +### Transactions ### + +The client provides an abstraction for Redis transactions based on `MULTI` and `EXEC` with a similar +interface to command pipelines: + +```php +// Executes a transaction inside the given callable block: +$responses = $client->transaction(function ($tx) { + $tx->set('foo', 'bar'); + $tx->get('foo'); +}); + +// Returns a transaction that can be chained thanks to its fluent interface: +$responses = $client->transaction()->set('foo', 'bar')->get('foo')->execute(); +``` + +This abstraction can perform check-and-set operations thanks to `WATCH` and `UNWATCH` and provides +automatic retries of transactions aborted by Redis when `WATCH`ed keys are touched. For an example +of a transaction using CAS you can see [the following example](examples/transaction_using_cas.php). + + +### Adding new commands ### + +While we try to update Predis to stay up to date with all the commands available in Redis, you might +prefer to stick with an old version of the library or provide a different way to filter arguments or +parse responses for specific commands. To achieve that, Predis provides the ability to implement new +command classes to define or override commands in the default command factory used by the client: + +```php +// Define a new command by extending Predis\Command\Command: +class BrandNewRedisCommand extends Predis\Command\Command +{ + public function getId() + { + return 'NEWCMD'; + } +} + +// Inject your command in the current command factory: +$client = new Predis\Client($parameters, [ + 'commands' => [ + 'newcmd' => 'BrandNewRedisCommand', + ], +]); + +$response = $client->newcmd(); +``` + +There is also a method to send raw commands without filtering their arguments or parsing responses. +Users must provide the list of arguments for the command as an array, following the signatures as +defined by the [Redis documentation for commands](http://redis.io/commands): + +```php +$response = $client->executeRaw(['SET', 'foo', 'bar']); +``` + + +### Script commands ### + +While it is possible to leverage [Lua scripting](http://redis.io/commands/eval) on Redis 2.6+ using +directly [`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha), +Predis offers script commands as an higher level abstraction built upon them to make things simple. +Script commands can be registered in the command factory used by the client and are accessible as if +they were plain Redis commands, but they define Lua scripts that get transmitted to the server for +remote execution. Internally they use [`EVALSHA`](http://redis.io/commands/evalsha) by default and +identify a script by its SHA1 hash to save bandwidth, but [`EVAL`](http://redis.io/commands/eval) +is used as a fall back when needed: + +```php +// Define a new script command by extending Predis\Command\ScriptCommand: +class ListPushRandomValue extends Predis\Command\ScriptCommand +{ + public function getKeysCount() + { + return 1; + } + + public function getScript() + { + return << [ + 'lpushrand' => 'ListPushRandomValue', + ], +]); + +$response = $client->lpushrand('random_values', $seed = mt_rand()); +``` + + +### Customizable connection backends ### + +Developers can create their own connection classes to support whole new network backends, extend +existing classes or provide completely different implementations. Connection classes must implement +`Predis\Connection\NodeConnectionInterface` or extend `Predis\Connection\AbstractConnection`: + +```php +class MyConnectionClass implements Predis\Connection\NodeConnectionInterface +{ + // Implementation goes here... +} + +// Use MyConnectionClass to handle connections for the `tcp` scheme: +$client = new Predis\Client('tcp://127.0.0.1', [ + 'connections' => ['tcp' => 'MyConnectionClass'], +]); +``` + +For a more in-depth insight on how to create new connection backends you can refer to the actual +implementation of the standard connection classes available in the `Predis\Connection` namespace. + + +## Development ## + + +### Reporting bugs and contributing code ### + +Contributions to Predis are highly appreciated either in the form of pull requests for new features, +bug fixes, or just bug reports. We only ask you to adhere to issue and pull request templates. + + +### Test suite ### + +__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running +in production environments or containing data you are interested in! + +Predis has a comprehensive test suite covering every aspect of the library and that can optionally +perform integration tests against a running instance of Redis (required >= 2.4.0 in order to verify +the correct behavior of the implementation of each command. Integration tests for unsupported Redis +commands are automatically skipped. If you do not have Redis up and running, integration tests can +be disabled. See [the tests README](tests/README.md) for more details about testing this library. + +Predis uses GitHub Actions for continuous integration and the history for past and current builds can be +found [on its actions page](https://github.com/predis/predis/actions). + + +## Other ## + + +### Project related links ### + +- [Source code](https://github.com/predis/predis) +- [Wiki](https://github.com/predis/predis/wiki) +- [Issue tracker](https://github.com/predis/predis/issues) + + +### Author ### + +- [Till Krüss](https://till.im) ([Twitter](http://twitter.com/tillkruss)) +- [Daniele Alessandri](mailto:suppakilla@gmail.com) ([twitter](http://twitter.com/JoL1hAHN)) + + +### License ### + +The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)). + +[ico-license]: https://img.shields.io/github/license/predis/predis.svg?style=flat-square +[ico-version-stable]: https://img.shields.io/github/v/tag/predis/predis?label=stable&style=flat-square +[ico-version-dev]: https://img.shields.io/github/v/tag/predis/predis?include_prereleases&label=pre-release&style=flat-square +[ico-downloads-monthly]: https://img.shields.io/packagist/dm/predis/predis.svg?style=flat-square +[ico-build]: https://img.shields.io/github/actions/workflow/status/predis/predis/tests.yml?branch=main&style=flat-square +[ico-coverage]: https://img.shields.io/coverallsCoverage/github/predis/predis?style=flat-square + +[link-releases]: https://github.com/predis/predis/releases +[link-actions]: https://github.com/predis/predis/actions +[link-downloads]: https://packagist.org/packages/predis/predis/stats +[link-coverage]: https://coveralls.io/github/predis/predis diff --git a/redis-cache/dependencies/predis/predis/autoload.php b/redis-cache/dependencies/predis/predis/autoload.php new file mode 100644 index 0000000..5d96d68 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/autoload.php @@ -0,0 +1,12 @@ +options = $options; + } + + public static function fromCommandLine() + { + $parameters = array( + 'c:' => 'class:', + 'r::' => 'realm::', + 'o::' => 'output::', + 'x::' => 'overwrite::' + ); + + $getops = getopt(implode(array_keys($parameters)), $parameters); + + $options = array( + 'overwrite' => false, + 'tests' => __DIR__.'/../tests/Predis', + ); + + foreach ($getops as $option => $value) { + switch ($option) { + case 'c': + case 'class': + $options['class'] = $value; + break; + + case 'r': + case 'realm': + $options['realm'] = $value; + break; + + case 'o': + case 'output': + $options['output'] = $value; + break; + + case 'x': + case 'overwrite': + $options['overwrite'] = true; + break; + } + } + + if (!isset($options['class'])) { + throw new RuntimeException("Missing 'class' option."); + } + + if (!isset($options['realm'])) { + throw new RuntimeException("Missing 'realm' option."); + } + + $options['fqn'] = "Predis\\Command\\Redis\\{$options['class']}"; + $options['path'] = "Command/Redis/{$options['class']}.php"; + + $source = __DIR__.'/../src/'.$options['path']; + if (!file_exists($source)) { + throw new RuntimeException("Cannot find class file for {$options['fqn']} in $source."); + } + + if (!isset($options['output'])) { + $options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', '_Test.php', $options['path'])); + } + + return new self($options); + } + + protected function getTestRealm() + { + if (empty($this->options['realm'])) { + throw new RuntimeException('Invalid value for realm has been specified (empty).'); + } + + return $this->options['realm']; + } + + public function generate() + { + $reflection = new ReflectionClass($class = $this->options['fqn']); + + if (!$reflection->isInstantiable()) { + throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed."); + } + + if (!$reflection->implementsInterface('Predis\Command\CommandInterface')) { + throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface."); + } + + /* + * @var CommandInterface + */ + $instance = $reflection->newInstance(); + + $buffer = $this->getTestCaseBuffer($instance); + + return $buffer; + } + + public function save() + { + $options = $this->options; + if (file_exists($options['output']) && !$options['overwrite']) { + throw new RuntimeException("File {$options['output']} already exist. Specify the --overwrite option to overwrite the existing file."); + } + file_put_contents($options['output'], $this->generate()); + } + + protected function getTestCaseBuffer(CommandInterface $instance) + { + $id = $instance->getId(); + $fqn = get_class($instance); + $fqnParts = explode('\\', $fqn); + $class = array_pop($fqnParts) . "Test"; + $realm = $this->getTestRealm(); + + $buffer =<<markTestIncomplete('This test has not been implemented yet.'); + + \$arguments = array(/* add arguments */); + \$expected = array(/* add arguments */); + + \$command = \$this->getCommand(); + \$command->setArguments(\$arguments); + + \$this->assertSame(\$expected, \$command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + \$this->markTestIncomplete('This test has not been implemented yet.'); + + \$raw = null; + \$expected = null; + + \$command = \$this->getCommand(); + + \$this->assertSame(\$expected, \$command->parseResponse(\$raw)); + } + +PHP; + + if ($instance instanceof PrefixableCommandInterface) { + $buffer .=<<markTestIncomplete('This test has not been implemented yet.'); + + \$arguments = array(/* add arguments */); + \$expected = array(/* add arguments */); + + \$command = \$this->getCommandWithArgumentsArray(\$arguments); + \$command->prefixKeys('prefix:'); + + \$this->assertSame(\$expected, \$command->getArguments()); + } + + /** + * @group disconnected + */ + public function testPrefixKeysIgnoredOnEmptyArguments(): void + { + \$command = \$this->getCommand(); + \$command->prefixKeys('prefix:'); + + \$this->assertSame(array(), \$command->getArguments()); + } + +PHP; + } + + return "$buffer}\n"; + } +} + +// ------------------------------------------------------------------------- // + +require __DIR__.'/../autoload.php'; + +$generator = CommandTestCaseGenerator::fromCommandLine(); +$generator->save(); diff --git a/redis-cache/dependencies/predis/predis/composer.json b/redis-cache/dependencies/predis/predis/composer.json new file mode 100644 index 0000000..7968991 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/composer.json @@ -0,0 +1,48 @@ +{ + "name": "predis/predis", + "type": "library", + "description": "A flexible and feature-complete Redis client for PHP.", + "keywords": ["nosql", "redis", "predis"], + "homepage": "http://github.com/predis/predis", + "license": "MIT", + "support": { + "issues": "https://github.com/predis/predis/issues" + }, + "authors": [ + { + "name": "Till Krüss", + "homepage": "https://till.im", + "role": "Maintainer" + } + ], + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/tillkruss" + } + ], + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^8.0 || ~9.4.4" + }, + "scripts": { + "phpstan": "phpstan analyse", + "style": "php-cs-fixer fix --diff --dry-run", + "style:fix": "php-cs-fixer fix" + }, + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "config": { + "sort-packages": true, + "preferred-install": "dist" + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/redis-cache/dependencies/predis/predis/src/Autoloader.php b/redis-cache/dependencies/predis/predis/src/Autoloader.php new file mode 100644 index 0000000..054f7bb --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Autoloader.php @@ -0,0 +1,64 @@ + + * @author Daniele Alessandri + * @codeCoverageIgnore + */ +class Autoloader +{ + private $directory; + private $prefix; + private $prefixLength; + + /** + * @param string $baseDirectory Base directory where the source files are located. + */ + public function __construct($baseDirectory = __DIR__) + { + $this->directory = $baseDirectory; + $this->prefix = __NAMESPACE__ . '\\'; + $this->prefixLength = strlen($this->prefix); + } + + /** + * Registers the autoloader class with the PHP SPL autoloader. + * + * @param bool $prepend Prepend the autoloader on the stack instead of appending it. + */ + public static function register($prepend = false) + { + spl_autoload_register([new self(), 'autoload'], true, $prepend); + } + + /** + * Loads a class from a file using its fully qualified name. + * + * @param string $className Fully qualified name of a class. + */ + public function autoload($className) + { + if (0 === strpos($className, $this->prefix)) { + $parts = explode('\\', substr($className, $this->prefixLength)); + $filepath = $this->directory . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $parts) . '.php'; + + if (is_file($filepath)) { + require $filepath; + } + } + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Client.php b/redis-cache/dependencies/predis/predis/src/Client.php new file mode 100644 index 0000000..91c126f --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Client.php @@ -0,0 +1,566 @@ + + */ +class Client implements ClientInterface, IteratorAggregate +{ + public const VERSION = '2.1.2'; + + /** @var OptionsInterface */ + private $options; + + /** @var ConnectionInterface */ + private $connection; + + /** @var Command\FactoryInterface */ + private $commands; + + /** + * @param mixed $parameters Connection parameters for one or more servers. + * @param mixed $options Options to configure some behaviours of the client. + */ + public function __construct($parameters = null, $options = null) + { + $this->options = static::createOptions($options ?? new Options()); + $this->connection = static::createConnection($this->options, $parameters ?? new Parameters()); + $this->commands = $this->options->commands; + } + + /** + * Creates a new set of client options for the client. + * + * @param array|OptionsInterface $options Set of client options + * + * @return OptionsInterface + * @throws InvalidArgumentException + */ + protected static function createOptions($options) + { + if (is_array($options)) { + return new Options($options); + } elseif ($options instanceof OptionsInterface) { + return $options; + } else { + throw new InvalidArgumentException('Invalid type for client options'); + } + } + + /** + * Creates single or aggregate connections from supplied arguments. + * + * This method accepts the following types to create a connection instance: + * + * - Array (dictionary: single connection, indexed: aggregate connections) + * - String (URI for a single connection) + * - Callable (connection initializer callback) + * - Instance of Predis\Connection\ParametersInterface (used as-is) + * - Instance of Predis\Connection\ConnectionInterface (returned as-is) + * + * When a callable is passed, it receives the original set of client options + * and must return an instance of Predis\Connection\ConnectionInterface. + * + * Connections are created using the connection factory (in case of single + * connections) or a specialized aggregate connection initializer (in case + * of cluster and replication) retrieved from the supplied client options. + * + * @param OptionsInterface $options Client options container + * @param mixed $parameters Connection parameters + * + * @return ConnectionInterface + * @throws InvalidArgumentException + */ + protected static function createConnection(OptionsInterface $options, $parameters) + { + if ($parameters instanceof ConnectionInterface) { + return $parameters; + } + + if ($parameters instanceof ParametersInterface || is_string($parameters)) { + return $options->connections->create($parameters); + } + + if (is_array($parameters)) { + if (!isset($parameters[0])) { + return $options->connections->create($parameters); + } elseif ($options->defined('cluster') && $initializer = $options->cluster) { + return $initializer($parameters, true); + } elseif ($options->defined('replication') && $initializer = $options->replication) { + return $initializer($parameters, true); + } elseif ($options->defined('aggregate') && $initializer = $options->aggregate) { + return $initializer($parameters, false); + } else { + throw new InvalidArgumentException( + 'Array of connection parameters requires `cluster`, `replication` or `aggregate` client option' + ); + } + } + + if (is_callable($parameters)) { + $connection = call_user_func($parameters, $options); + + if (!$connection instanceof ConnectionInterface) { + throw new InvalidArgumentException('Callable parameters must return a valid connection'); + } + + return $connection; + } + + throw new InvalidArgumentException('Invalid type for connection parameters'); + } + + /** + * {@inheritdoc} + */ + public function getCommandFactory() + { + return $this->commands; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->options; + } + + /** + * Creates a new client using a specific underlying connection. + * + * This method allows to create a new client instance by picking a specific + * connection out of an aggregate one, with the same options of the original + * client instance. + * + * The specified selector defines which logic to use to look for a suitable + * connection by the specified value. Supported selectors are: + * + * - `id` + * - `key` + * - `slot` + * - `command` + * - `alias` + * - `role` + * + * Internally the client relies on duck-typing and follows this convention: + * + * $selector string => getConnectionBy$selector($value) method + * + * This means that support for specific selectors may vary depending on the + * actual logic implemented by connection classes and there is no interface + * binding a connection class to implement any of these. + * + * @param string $selector Type of selector. + * @param mixed $value Value to be used by the selector. + * + * @return ClientInterface + */ + public function getClientBy($selector, $value) + { + $selector = strtolower($selector); + + if (!in_array($selector, ['id', 'key', 'slot', 'role', 'alias', 'command'])) { + throw new InvalidArgumentException("Invalid selector type: `$selector`"); + } + + if (!method_exists($this->connection, $method = "getConnectionBy$selector")) { + $class = get_class($this->connection); + throw new InvalidArgumentException("Selecting connection by $selector is not supported by $class"); + } + + if (!$connection = $this->connection->$method($value)) { + throw new InvalidArgumentException("Cannot find a connection by $selector matching `$value`"); + } + + return new static($connection, $this->getOptions()); + } + + /** + * Opens the underlying connection and connects to the server. + */ + public function connect() + { + $this->connection->connect(); + } + + /** + * Closes the underlying connection and disconnects from the server. + */ + public function disconnect() + { + $this->connection->disconnect(); + } + + /** + * Closes the underlying connection and disconnects from the server. + * + * This is the same as `Client::disconnect()` as it does not actually send + * the `QUIT` command to Redis, but simply closes the connection. + */ + public function quit() + { + $this->disconnect(); + } + + /** + * Returns the current state of the underlying connection. + * + * @return bool + */ + public function isConnected() + { + return $this->connection->isConnected(); + } + + /** + * {@inheritdoc} + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Executes a command without filtering its arguments, parsing the response, + * applying any prefix to keys or throwing exceptions on Redis errors even + * regardless of client options. + * + * It is possible to identify Redis error responses from normal responses + * using the second optional argument which is populated by reference. + * + * @param array $arguments Command arguments as defined by the command signature. + * @param bool $error Set to TRUE when Redis returned an error response. + * + * @return mixed + */ + public function executeRaw(array $arguments, &$error = null) + { + $error = false; + $commandID = array_shift($arguments); + + $response = $this->connection->executeCommand( + new RawCommand($commandID, $arguments) + ); + + if ($response instanceof ResponseInterface) { + if ($response instanceof ErrorResponseInterface) { + $error = true; + } + + return (string) $response; + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function __call($commandID, $arguments) + { + return $this->executeCommand( + $this->createCommand($commandID, $arguments) + ); + } + + /** + * {@inheritdoc} + */ + public function createCommand($commandID, $arguments = []) + { + return $this->commands->create($commandID, $arguments); + } + + /** + * @param $name + * @return ContainerInterface + */ + public function __get($name) + { + return ContainerFactory::create($this, $name); + } + + /** + * @param $name + * @param $value + * @return mixed + */ + public function __set($name, $value) + { + throw new RuntimeException('Not allowed'); + } + + /** + * @param $name + * @return mixed + */ + public function __isset($name) + { + throw new RuntimeException('Not allowed'); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $response = $this->connection->executeCommand($command); + + if ($response instanceof ResponseInterface) { + if ($response instanceof ErrorResponseInterface) { + $response = $this->onErrorResponse($command, $response); + } + + return $response; + } + + return $command->parseResponse($response); + } + + /** + * Handles -ERR responses returned by Redis. + * + * @param CommandInterface $command Redis command that generated the error. + * @param ErrorResponseInterface $response Instance of the error response. + * + * @return mixed + * @throws ServerException + */ + protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response) + { + if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') { + $response = $this->executeCommand($command->getEvalCommand()); + + if (!$response instanceof ResponseInterface) { + $response = $command->parseResponse($response); + } + + return $response; + } + + if ($this->options->exceptions) { + throw new ServerException($response->getMessage()); + } + + return $response; + } + + /** + * Executes the specified initializer method on `$this` by adjusting the + * actual invocation depending on the arity (0, 1 or 2 arguments). This is + * simply an utility method to create Redis contexts instances since they + * follow a common initialization path. + * + * @param string $initializer Method name. + * @param array $argv Arguments for the method. + * + * @return mixed + */ + private function sharedContextFactory($initializer, $argv = null) + { + switch (count($argv)) { + case 0: + return $this->$initializer(); + + case 1: + return is_array($argv[0]) + ? $this->$initializer($argv[0]) + : $this->$initializer(null, $argv[0]); + + case 2: + [$arg0, $arg1] = $argv; + + return $this->$initializer($arg0, $arg1); + + default: + return $this->$initializer($this, $argv); + } + } + + /** + * Creates a new pipeline context and returns it, or returns the results of + * a pipeline executed inside the optionally provided callable object. + * + * @param mixed ...$arguments Array of options, a callable for execution, or both. + * + * @return Pipeline|array + */ + public function pipeline(...$arguments) + { + return $this->sharedContextFactory('createPipeline', func_get_args()); + } + + /** + * Actual pipeline context initializer method. + * + * @param array $options Options for the context. + * @param mixed $callable Optional callable used to execute the context. + * + * @return Pipeline|array + */ + protected function createPipeline(array $options = null, $callable = null) + { + if (isset($options['atomic']) && $options['atomic']) { + $class = 'Predis\Pipeline\Atomic'; + } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) { + $class = 'Predis\Pipeline\FireAndForget'; + } else { + $class = 'Predis\Pipeline\Pipeline'; + } + + /* + * @var ClientContextInterface + */ + $pipeline = new $class($this); + + if (isset($callable)) { + return $pipeline->execute($callable); + } + + return $pipeline; + } + + /** + * Creates a new transaction context and returns it, or returns the results + * of a transaction executed inside the optionally provided callable object. + * + * @param mixed ...$arguments Array of options, a callable for execution, or both. + * + * @return MultiExecTransaction|array + */ + public function transaction(...$arguments) + { + return $this->sharedContextFactory('createTransaction', func_get_args()); + } + + /** + * Actual transaction context initializer method. + * + * @param array $options Options for the context. + * @param mixed $callable Optional callable used to execute the context. + * + * @return MultiExecTransaction|array + */ + protected function createTransaction(array $options = null, $callable = null) + { + $transaction = new MultiExecTransaction($this, $options); + + if (isset($callable)) { + return $transaction->execute($callable); + } + + return $transaction; + } + + /** + * Creates a new publish/subscribe context and returns it, or starts its loop + * inside the optionally provided callable object. + * + * @param mixed ...$arguments Array of options, a callable for execution, or both. + * + * @return PubSubConsumer|null + */ + public function pubSubLoop(...$arguments) + { + return $this->sharedContextFactory('createPubSub', func_get_args()); + } + + /** + * Actual publish/subscribe context initializer method. + * + * @param array $options Options for the context. + * @param mixed $callable Optional callable used to execute the context. + * + * @return PubSubConsumer|null + */ + protected function createPubSub(array $options = null, $callable = null) + { + $pubsub = new PubSubConsumer($this, $options); + + if (!isset($callable)) { + return $pubsub; + } + + foreach ($pubsub as $message) { + if (call_user_func($callable, $pubsub, $message) === false) { + $pubsub->stop(); + } + } + + return null; + } + + /** + * Creates a new monitor consumer and returns it. + * + * @return MonitorConsumer + */ + public function monitor() + { + return new MonitorConsumer($this); + } + + /** + * @return Traversable + */ + #[ReturnTypeWillChange] + public function getIterator() + { + $clients = []; + $connection = $this->getConnection(); + + if (!$connection instanceof Traversable) { + return new ArrayIterator([ + (string) $connection => new static($connection, $this->getOptions()), + ]); + } + + foreach ($connection as $node) { + $clients[(string) $node] = new static($node, $this->getOptions()); + } + + return new ArrayIterator($clients); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/ClientContextInterface.php b/redis-cache/dependencies/predis/predis/src/ClientContextInterface.php new file mode 100644 index 0000000..c241e83 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/ClientContextInterface.php @@ -0,0 +1,238 @@ +commands = $this->getDefaultCommands(); + } + + /** + * Returns the default map of supported commands with their handlers. + * + * @return array + */ + protected function getDefaultCommands() + { + $getKeyFromFirstArgument = [$this, 'getKeyFromFirstArgument']; + $getKeyFromAllArguments = [$this, 'getKeyFromAllArguments']; + + return [ + /* commands operating on the key space */ + 'EXISTS' => $getKeyFromAllArguments, + 'DEL' => $getKeyFromAllArguments, + 'TYPE' => $getKeyFromFirstArgument, + 'EXPIRE' => $getKeyFromFirstArgument, + 'EXPIREAT' => $getKeyFromFirstArgument, + 'PERSIST' => $getKeyFromFirstArgument, + 'PEXPIRE' => $getKeyFromFirstArgument, + 'PEXPIREAT' => $getKeyFromFirstArgument, + 'TTL' => $getKeyFromFirstArgument, + 'PTTL' => $getKeyFromFirstArgument, + 'SORT' => [$this, 'getKeyFromSortCommand'], + 'DUMP' => $getKeyFromFirstArgument, + 'RESTORE' => $getKeyFromFirstArgument, + + /* commands operating on string values */ + 'APPEND' => $getKeyFromFirstArgument, + 'DECR' => $getKeyFromFirstArgument, + 'DECRBY' => $getKeyFromFirstArgument, + 'GET' => $getKeyFromFirstArgument, + 'GETBIT' => $getKeyFromFirstArgument, + 'MGET' => $getKeyFromAllArguments, + 'SET' => $getKeyFromFirstArgument, + 'GETRANGE' => $getKeyFromFirstArgument, + 'GETSET' => $getKeyFromFirstArgument, + 'INCR' => $getKeyFromFirstArgument, + 'INCRBY' => $getKeyFromFirstArgument, + 'INCRBYFLOAT' => $getKeyFromFirstArgument, + 'SETBIT' => $getKeyFromFirstArgument, + 'SETEX' => $getKeyFromFirstArgument, + 'MSET' => [$this, 'getKeyFromInterleavedArguments'], + 'MSETNX' => [$this, 'getKeyFromInterleavedArguments'], + 'SETNX' => $getKeyFromFirstArgument, + 'SETRANGE' => $getKeyFromFirstArgument, + 'STRLEN' => $getKeyFromFirstArgument, + 'SUBSTR' => $getKeyFromFirstArgument, + 'BITOP' => [$this, 'getKeyFromBitOp'], + 'BITCOUNT' => $getKeyFromFirstArgument, + 'BITFIELD' => $getKeyFromFirstArgument, + + /* commands operating on lists */ + 'LINSERT' => $getKeyFromFirstArgument, + 'LINDEX' => $getKeyFromFirstArgument, + 'LLEN' => $getKeyFromFirstArgument, + 'LPOP' => $getKeyFromFirstArgument, + 'RPOP' => $getKeyFromFirstArgument, + 'RPOPLPUSH' => $getKeyFromAllArguments, + 'BLPOP' => [$this, 'getKeyFromBlockingListCommands'], + 'BRPOP' => [$this, 'getKeyFromBlockingListCommands'], + 'BRPOPLPUSH' => [$this, 'getKeyFromBlockingListCommands'], + 'LPUSH' => $getKeyFromFirstArgument, + 'LPUSHX' => $getKeyFromFirstArgument, + 'RPUSH' => $getKeyFromFirstArgument, + 'RPUSHX' => $getKeyFromFirstArgument, + 'LRANGE' => $getKeyFromFirstArgument, + 'LREM' => $getKeyFromFirstArgument, + 'LSET' => $getKeyFromFirstArgument, + 'LTRIM' => $getKeyFromFirstArgument, + + /* commands operating on sets */ + 'SADD' => $getKeyFromFirstArgument, + 'SCARD' => $getKeyFromFirstArgument, + 'SDIFF' => $getKeyFromAllArguments, + 'SDIFFSTORE' => $getKeyFromAllArguments, + 'SINTER' => $getKeyFromAllArguments, + 'SINTERSTORE' => $getKeyFromAllArguments, + 'SUNION' => $getKeyFromAllArguments, + 'SUNIONSTORE' => $getKeyFromAllArguments, + 'SISMEMBER' => $getKeyFromFirstArgument, + 'SMEMBERS' => $getKeyFromFirstArgument, + 'SSCAN' => $getKeyFromFirstArgument, + 'SPOP' => $getKeyFromFirstArgument, + 'SRANDMEMBER' => $getKeyFromFirstArgument, + 'SREM' => $getKeyFromFirstArgument, + + /* commands operating on sorted sets */ + 'ZADD' => $getKeyFromFirstArgument, + 'ZCARD' => $getKeyFromFirstArgument, + 'ZCOUNT' => $getKeyFromFirstArgument, + 'ZINCRBY' => $getKeyFromFirstArgument, + 'ZINTERSTORE' => [$this, 'getKeyFromZsetAggregationCommands'], + 'ZRANGE' => $getKeyFromFirstArgument, + 'ZRANGEBYSCORE' => $getKeyFromFirstArgument, + 'ZRANK' => $getKeyFromFirstArgument, + 'ZREM' => $getKeyFromFirstArgument, + 'ZREMRANGEBYRANK' => $getKeyFromFirstArgument, + 'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument, + 'ZREVRANGE' => $getKeyFromFirstArgument, + 'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument, + 'ZREVRANK' => $getKeyFromFirstArgument, + 'ZSCORE' => $getKeyFromFirstArgument, + 'ZUNIONSTORE' => [$this, 'getKeyFromZsetAggregationCommands'], + 'ZSCAN' => $getKeyFromFirstArgument, + 'ZLEXCOUNT' => $getKeyFromFirstArgument, + 'ZRANGEBYLEX' => $getKeyFromFirstArgument, + 'ZREMRANGEBYLEX' => $getKeyFromFirstArgument, + 'ZREVRANGEBYLEX' => $getKeyFromFirstArgument, + + /* commands operating on hashes */ + 'HDEL' => $getKeyFromFirstArgument, + 'HEXISTS' => $getKeyFromFirstArgument, + 'HGET' => $getKeyFromFirstArgument, + 'HGETALL' => $getKeyFromFirstArgument, + 'HMGET' => $getKeyFromFirstArgument, + 'HMSET' => $getKeyFromFirstArgument, + 'HINCRBY' => $getKeyFromFirstArgument, + 'HINCRBYFLOAT' => $getKeyFromFirstArgument, + 'HKEYS' => $getKeyFromFirstArgument, + 'HLEN' => $getKeyFromFirstArgument, + 'HSET' => $getKeyFromFirstArgument, + 'HSETNX' => $getKeyFromFirstArgument, + 'HVALS' => $getKeyFromFirstArgument, + 'HSCAN' => $getKeyFromFirstArgument, + 'HSTRLEN' => $getKeyFromFirstArgument, + + /* commands operating on HyperLogLog */ + 'PFADD' => $getKeyFromFirstArgument, + 'PFCOUNT' => $getKeyFromAllArguments, + 'PFMERGE' => $getKeyFromAllArguments, + + /* scripting */ + 'EVAL' => [$this, 'getKeyFromScriptingCommands'], + 'EVALSHA' => [$this, 'getKeyFromScriptingCommands'], + + /* commands performing geospatial operations */ + 'GEOADD' => $getKeyFromFirstArgument, + 'GEOHASH' => $getKeyFromFirstArgument, + 'GEOPOS' => $getKeyFromFirstArgument, + 'GEODIST' => $getKeyFromFirstArgument, + 'GEORADIUS' => [$this, 'getKeyFromGeoradiusCommands'], + 'GEORADIUSBYMEMBER' => [$this, 'getKeyFromGeoradiusCommands'], + ]; + } + + /** + * Returns the list of IDs for the supported commands. + * + * @return array + */ + public function getSupportedCommands() + { + return array_keys($this->commands); + } + + /** + * Sets an handler for the specified command ID. + * + * The signature of the callback must have a single parameter of type + * Predis\Command\CommandInterface. + * + * When the callback argument is omitted or NULL, the previously associated + * handler for the specified command ID is removed. + * + * @param string $commandID Command ID. + * @param mixed $callback A valid callable object, or NULL to unset the handler. + * + * @throws InvalidArgumentException + */ + public function setCommandHandler($commandID, $callback = null) + { + $commandID = strtoupper($commandID); + + if (!isset($callback)) { + unset($this->commands[$commandID]); + + return; + } + + if (!is_callable($callback)) { + throw new InvalidArgumentException( + 'The argument must be a callable object or NULL.' + ); + } + + $this->commands[$commandID] = $callback; + } + + /** + * Extracts the key from the first argument of a command instance. + * + * @param CommandInterface $command Command instance. + * + * @return string + */ + protected function getKeyFromFirstArgument(CommandInterface $command) + { + return $command->getArgument(0); + } + + /** + * Extracts the key from a command with multiple keys only when all keys in + * the arguments array produce the same hash. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromAllArguments(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if (!$this->checkSameSlotForKeys($arguments)) { + return null; + } + + return $arguments[0]; + } + + /** + * Extracts the key from a command with multiple keys only when all keys in + * the arguments array produce the same hash. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromInterleavedArguments(CommandInterface $command) + { + $arguments = $command->getArguments(); + $keys = []; + + for ($i = 0; $i < count($arguments); $i += 2) { + $keys[] = $arguments[$i]; + } + + if (!$this->checkSameSlotForKeys($keys)) { + return null; + } + + return $arguments[0]; + } + + /** + * Extracts the key from SORT command. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromSortCommand(CommandInterface $command) + { + $arguments = $command->getArguments(); + $firstKey = $arguments[0]; + + if (1 === $argc = count($arguments)) { + return $firstKey; + } + + $keys = [$firstKey]; + + for ($i = 1; $i < $argc; ++$i) { + if (strtoupper($arguments[$i]) === 'STORE') { + $keys[] = $arguments[++$i]; + } + } + + if (!$this->checkSameSlotForKeys($keys)) { + return null; + } + + return $firstKey; + } + + /** + * Extracts the key from BLPOP and BRPOP commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromBlockingListCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if (!$this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) { + return null; + } + + return $arguments[0]; + } + + /** + * Extracts the key from BITOP command. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromBitOp(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if (!$this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) { + return null; + } + + return $arguments[1]; + } + + /** + * Extracts the key from GEORADIUS and GEORADIUSBYMEMBER commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromGeoradiusCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + $argc = count($arguments); + $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; + + if ($argc > $startIndex) { + $keys = [$arguments[0]]; + + for ($i = $startIndex; $i < $argc; ++$i) { + $argument = strtoupper($arguments[$i]); + if ($argument === 'STORE' || $argument === 'STOREDIST') { + $keys[] = $arguments[++$i]; + } + } + + if (!$this->checkSameSlotForKeys($keys)) { + return null; + } + } + + return $arguments[0]; + } + + /** + * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromZsetAggregationCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + $keys = array_merge([$arguments[0]], array_slice($arguments, 2, $arguments[1])); + + if (!$this->checkSameSlotForKeys($keys)) { + return null; + } + + return $arguments[0]; + } + + /** + * Extracts the key from EVAL and EVALSHA commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromScriptingCommands(CommandInterface $command) + { + $keys = $command instanceof ScriptCommand + ? $command->getKeys() + : array_slice($args = $command->getArguments(), 2, $args[1]); + + if (!$keys || !$this->checkSameSlotForKeys($keys)) { + return null; + } + + return $keys[0]; + } + + /** + * {@inheritdoc} + */ + public function getSlot(CommandInterface $command) + { + $slot = $command->getSlot(); + + if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) { + $key = call_user_func($this->commands[$cmdID], $command); + + if (isset($key)) { + $slot = $this->getSlotByKey($key); + $command->setSlot($slot); + } + } + + return $slot; + } + + /** + * Checks if the specified array of keys will generate the same hash. + * + * @param array $keys Array of keys. + * + * @return bool + */ + protected function checkSameSlotForKeys(array $keys) + { + if (!$count = count($keys)) { + return false; + } + + $currentSlot = $this->getSlotByKey($keys[0]); + + for ($i = 1; $i < $count; ++$i) { + $nextSlot = $this->getSlotByKey($keys[$i]); + + if ($currentSlot !== $nextSlot) { + return false; + } + + $currentSlot = $nextSlot; + } + + return true; + } + + /** + * Returns only the hashable part of a key (delimited by "{...}"), or the + * whole key if a key tag is not found in the string. + * + * @param string $key A key. + * + * @return string + */ + protected function extractKeyTag($key) + { + if (false !== $start = strpos($key, '{')) { + if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) { + $key = substr($key, $start, $end - $start); + } + } + + return $key; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Cluster/Distributor/DistributorInterface.php b/redis-cache/dependencies/predis/predis/src/Cluster/Distributor/DistributorInterface.php new file mode 100644 index 0000000..593d9bb --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Cluster/Distributor/DistributorInterface.php @@ -0,0 +1,81 @@ + + */ +class HashRing implements DistributorInterface, HashGeneratorInterface +{ + public const DEFAULT_REPLICAS = 128; + public const DEFAULT_WEIGHT = 100; + + private $ring; + private $ringKeys; + private $ringKeysCount; + private $replicas; + private $nodeHashCallback; + private $nodes = []; + + /** + * @param int $replicas Number of replicas in the ring. + * @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes. + */ + public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null) + { + $this->replicas = $replicas; + $this->nodeHashCallback = $nodeHashCallback; + } + + /** + * Adds a node to the ring with an optional weight. + * + * @param mixed $node Node object. + * @param int $weight Weight for the node. + */ + public function add($node, $weight = null) + { + // In case of collisions in the hashes of the nodes, the node added + // last wins, thus the order in which nodes are added is significant. + $this->nodes[] = [ + 'object' => $node, + 'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT, + ]; + + $this->reset(); + } + + /** + * {@inheritdoc} + */ + public function remove($node) + { + // A node is removed by resetting the ring so that it's recreated from + // scratch, in order to reassign possible hashes with collisions to the + // right node according to the order in which they were added in the + // first place. + for ($i = 0; $i < count($this->nodes); ++$i) { + if ($this->nodes[$i]['object'] === $node) { + array_splice($this->nodes, $i, 1); + $this->reset(); + + break; + } + } + } + + /** + * Resets the distributor. + */ + private function reset() + { + unset( + $this->ring, + $this->ringKeys, + $this->ringKeysCount + ); + } + + /** + * Returns the initialization status of the distributor. + * + * @return bool + */ + private function isInitialized() + { + return isset($this->ringKeys); + } + + /** + * Calculates the total weight of all the nodes in the distributor. + * + * @return int + */ + private function computeTotalWeight() + { + $totalWeight = 0; + + foreach ($this->nodes as $node) { + $totalWeight += $node['weight']; + } + + return $totalWeight; + } + + /** + * Initializes the distributor. + */ + private function initialize() + { + if ($this->isInitialized()) { + return; + } + + if (!$this->nodes) { + throw new EmptyRingException('Cannot initialize an empty hashring.'); + } + + $this->ring = []; + $totalWeight = $this->computeTotalWeight(); + $nodesCount = count($this->nodes); + + foreach ($this->nodes as $node) { + $weightRatio = $node['weight'] / $totalWeight; + $this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio); + } + + ksort($this->ring, SORT_NUMERIC); + $this->ringKeys = array_keys($this->ring); + $this->ringKeysCount = count($this->ringKeys); + } + + /** + * Implements the logic needed to add a node to the hashring. + * + * @param array $ring Source hashring. + * @param mixed $node Node object to be added. + * @param int $totalNodes Total number of nodes. + * @param int $replicas Number of replicas in the ring. + * @param float $weightRatio Weight ratio for the node. + */ + protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio) + { + $nodeObject = $node['object']; + $nodeHash = $this->getNodeHash($nodeObject); + $replicas = (int) round($weightRatio * $totalNodes * $replicas); + + for ($i = 0; $i < $replicas; ++$i) { + $key = $this->hash("$nodeHash:$i"); + $ring[$key] = $nodeObject; + } + } + + /** + * {@inheritdoc} + */ + protected function getNodeHash($nodeObject) + { + if (!isset($this->nodeHashCallback)) { + return (string) $nodeObject; + } + + return call_user_func($this->nodeHashCallback, $nodeObject); + } + + /** + * {@inheritdoc} + */ + public function hash($value) + { + return crc32($value); + } + + /** + * {@inheritdoc} + */ + public function getByHash($hash) + { + return $this->ring[$this->getSlot($hash)]; + } + + /** + * {@inheritdoc} + */ + public function getBySlot($slot) + { + $this->initialize(); + + if (isset($this->ring[$slot])) { + return $this->ring[$slot]; + } + } + + /** + * {@inheritdoc} + */ + public function getSlot($hash) + { + $this->initialize(); + + $ringKeys = $this->ringKeys; + $upper = $this->ringKeysCount - 1; + $lower = 0; + + while ($lower <= $upper) { + $index = ($lower + $upper) >> 1; + $item = $ringKeys[$index]; + + if ($item > $hash) { + $upper = $index - 1; + } elseif ($item < $hash) { + $lower = $index + 1; + } else { + return $item; + } + } + + return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)]; + } + + /** + * {@inheritdoc} + */ + public function get($value) + { + $hash = $this->hash($value); + + return $this->getByHash($hash); + } + + /** + * Implements a strategy to deal with wrap-around errors during binary searches. + * + * @param int $upper + * @param int $lower + * @param int $ringKeysCount + * + * @return int + */ + protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) + { + // Binary search for the last item in ringkeys with a value less or + // equal to the key. If no such item exists, return the last item. + return $upper >= 0 ? $upper : $ringKeysCount - 1; + } + + /** + * {@inheritdoc} + */ + public function getHashGenerator() + { + return $this; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Cluster/Distributor/KetamaRing.php b/redis-cache/dependencies/predis/predis/src/Cluster/Distributor/KetamaRing.php new file mode 100644 index 0000000..af3b884 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Cluster/Distributor/KetamaRing.php @@ -0,0 +1,70 @@ + + */ +class KetamaRing extends HashRing +{ + public const DEFAULT_REPLICAS = 160; + + /** + * @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes. + */ + public function __construct($nodeHashCallback = null) + { + parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback); + } + + /** + * {@inheritdoc} + */ + protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio) + { + $nodeObject = $node['object']; + $nodeHash = $this->getNodeHash($nodeObject); + $replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4)); + + for ($i = 0; $i < $replicas; ++$i) { + $unpackedDigest = unpack('V4', md5("$nodeHash-$i", true)); + + foreach ($unpackedDigest as $key) { + $ring[$key] = $nodeObject; + } + } + } + + /** + * {@inheritdoc} + */ + public function hash($value) + { + $hash = unpack('V', md5($value, true)); + + return $hash[1]; + } + + /** + * {@inheritdoc} + */ + protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) + { + // Binary search for the first item in ringkeys with a value greater + // or equal to the key. If no such item exists, return the first item. + return $lower < $ringKeysCount ? $lower : 0; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Cluster/Hash/CRC16.php b/redis-cache/dependencies/predis/predis/src/Cluster/Hash/CRC16.php new file mode 100644 index 0000000..4b21d5d --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Cluster/Hash/CRC16.php @@ -0,0 +1,73 @@ +> 8) ^ ord($value[$i])]) & 0xFFFF; + } + + return $crc; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Cluster/Hash/HashGeneratorInterface.php b/redis-cache/dependencies/predis/predis/src/Cluster/Hash/HashGeneratorInterface.php new file mode 100644 index 0000000..c835c0e --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Cluster/Hash/HashGeneratorInterface.php @@ -0,0 +1,29 @@ +distributor = $distributor ?: new HashRing(); + } + + /** + * {@inheritdoc} + */ + public function getSlotByKey($key) + { + $key = $this->extractKeyTag($key); + $hash = $this->distributor->hash($key); + + return $this->distributor->getSlot($hash); + } + + /** + * {@inheritdoc} + */ + protected function checkSameSlotForKeys(array $keys) + { + if (!$count = count($keys)) { + return false; + } + + $currentKey = $this->extractKeyTag($keys[0]); + + for ($i = 1; $i < $count; ++$i) { + $nextKey = $this->extractKeyTag($keys[$i]); + + if ($currentKey !== $nextKey) { + return false; + } + + $currentKey = $nextKey; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getDistributor() + { + return $this->distributor; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Cluster/RedisStrategy.php b/redis-cache/dependencies/predis/predis/src/Cluster/RedisStrategy.php new file mode 100644 index 0000000..8ae5c0f --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Cluster/RedisStrategy.php @@ -0,0 +1,55 @@ +hashGenerator = $hashGenerator ?: new CRC16(); + } + + /** + * {@inheritdoc} + */ + public function getSlotByKey($key) + { + $key = $this->extractKeyTag($key); + + return $this->hashGenerator->hash($key) & 0x3FFF; + } + + /** + * {@inheritdoc} + */ + public function getDistributor() + { + $class = get_class($this); + throw new NotSupportedException("$class does not provide an external distributor"); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Cluster/SlotMap.php b/redis-cache/dependencies/predis/predis/src/Cluster/SlotMap.php new file mode 100644 index 0000000..0557c25 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Cluster/SlotMap.php @@ -0,0 +1,208 @@ += 0x0000 && $slot <= 0x3FFF; + } + + /** + * Checks if the given slot range is valid. + * + * @param int $first Initial slot of the range. + * @param int $last Last slot of the range. + * + * @return bool + */ + public static function isValidRange($first, $last) + { + return $first >= 0x0000 && $first <= 0x3FFF && $last >= 0x0000 && $last <= 0x3FFF && $first <= $last; + } + + /** + * Resets the slot map. + */ + public function reset() + { + $this->slots = []; + } + + /** + * Checks if the slot map is empty. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->slots); + } + + /** + * Returns the current slot map as a dictionary of $slot => $node. + * + * The order of the slots in the dictionary is not guaranteed. + * + * @return array + */ + public function toArray() + { + return $this->slots; + } + + /** + * Returns the list of unique nodes in the slot map. + * + * @return array + */ + public function getNodes() + { + return array_keys(array_flip($this->slots)); + } + + /** + * Assigns the specified slot range to a node. + * + * @param int $first Initial slot of the range. + * @param int $last Last slot of the range. + * @param NodeConnectionInterface|string $connection ID or connection instance. + * + * @throws OutOfBoundsException + */ + public function setSlots($first, $last, $connection) + { + if (!static::isValidRange($first, $last)) { + throw new OutOfBoundsException("Invalid slot range $first-$last for `$connection`"); + } + + $this->slots += array_fill($first, $last - $first + 1, (string) $connection); + } + + /** + * Returns the specified slot range. + * + * @param int $first Initial slot of the range. + * @param int $last Last slot of the range. + * + * @return array + */ + public function getSlots($first, $last) + { + if (!static::isValidRange($first, $last)) { + throw new OutOfBoundsException("Invalid slot range $first-$last"); + } + + return array_intersect_key($this->slots, array_fill($first, $last - $first + 1, null)); + } + + /** + * Checks if the specified slot is assigned. + * + * @param int $slot Slot index. + * + * @return bool + */ + #[ReturnTypeWillChange] + public function offsetExists($slot) + { + return isset($this->slots[$slot]); + } + + /** + * Returns the node assigned to the specified slot. + * + * @param int $slot Slot index. + * + * @return string|null + */ + #[ReturnTypeWillChange] + public function offsetGet($slot) + { + return $this->slots[$slot] ?? null; + } + + /** + * Assigns the specified slot to a node. + * + * @param int $slot Slot index. + * @param NodeConnectionInterface|string $connection ID or connection instance. + * + * @return void + */ + #[ReturnTypeWillChange] + public function offsetSet($slot, $connection) + { + if (!static::isValid($slot)) { + throw new OutOfBoundsException("Invalid slot $slot for `$connection`"); + } + + $this->slots[(int) $slot] = (string) $connection; + } + + /** + * Returns the node assigned to the specified slot. + * + * @param int $slot Slot index. + * + * @return void + */ + #[ReturnTypeWillChange] + public function offsetUnset($slot) + { + unset($this->slots[$slot]); + } + + /** + * Returns the current number of assigned slots. + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->slots); + } + + /** + * Returns an iterator over the slot map. + * + * @return ArrayIterator + */ + #[ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->slots); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Cluster/StrategyInterface.php b/redis-cache/dependencies/predis/predis/src/Cluster/StrategyInterface.php new file mode 100644 index 0000000..83801ae --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Cluster/StrategyInterface.php @@ -0,0 +1,52 @@ +client = $client; + $this->match = $match; + $this->count = $count; + + $this->reset(); + } + + /** + * Ensures that the client supports the specified Redis command required to + * fetch elements from the server to perform the iteration. + * + * @param ClientInterface $client Client connected to Redis. + * @param string $commandID Command ID. + * + * @throws NotSupportedException + */ + protected function requiredCommand(ClientInterface $client, $commandID) + { + if (!$client->getCommandFactory()->supports($commandID)) { + throw new NotSupportedException("'$commandID' is not supported by the current command factory."); + } + } + + /** + * Resets the inner state of the iterator. + */ + protected function reset() + { + $this->valid = true; + $this->fetchmore = true; + $this->elements = []; + $this->cursor = 0; + $this->position = -1; + $this->current = null; + } + + /** + * Returns an array of options for the `SCAN` command. + * + * @return array + */ + protected function getScanOptions() + { + $options = []; + + if (strlen(strval($this->match)) > 0) { + $options['MATCH'] = $this->match; + } + + if ($this->count > 0) { + $options['COUNT'] = $this->count; + } + + return $options; + } + + /** + * Fetches a new set of elements from the remote collection, effectively + * advancing the iteration process. + * + * @return array + */ + abstract protected function executeCommand(); + + /** + * Populates the local buffer of elements fetched from the server during + * the iteration. + */ + protected function fetch() + { + [$cursor, $elements] = $this->executeCommand(); + + if (!$cursor) { + $this->fetchmore = false; + } + + $this->cursor = $cursor; + $this->elements = $elements; + } + + /** + * Extracts next values for key() and current(). + */ + protected function extractNext() + { + ++$this->position; + $this->current = array_shift($this->elements); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->reset(); + $this->next(); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function current() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function next() + { + tryFetch: + if (!$this->elements && $this->fetchmore) { + $this->fetch(); + } + + if ($this->elements) { + $this->extractNext(); + } elseif ($this->cursor) { + goto tryFetch; + } else { + $this->valid = false; + } + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function valid() + { + return $this->valid; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Collection/Iterator/HashKey.php b/redis-cache/dependencies/predis/predis/src/Collection/Iterator/HashKey.php new file mode 100644 index 0000000..91b7d27 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Collection/Iterator/HashKey.php @@ -0,0 +1,57 @@ += 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @see http://redis.io/commands/scan + */ +class HashKey extends CursorBasedIterator +{ + protected $key; + + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $key, $match = null, $count = null) + { + $this->requiredCommand($client, 'HSCAN'); + + parent::__construct($client, $match, $count); + + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions()); + } + + /** + * {@inheritdoc} + */ + protected function extractNext() + { + $this->position = key($this->elements); + $this->current = current($this->elements); + + unset($this->elements[$this->position]); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Collection/Iterator/Keyspace.php b/redis-cache/dependencies/predis/predis/src/Collection/Iterator/Keyspace.php new file mode 100644 index 0000000..b5fa022 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Collection/Iterator/Keyspace.php @@ -0,0 +1,42 @@ += 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @see http://redis.io/commands/scan + */ +class Keyspace extends CursorBasedIterator +{ + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $match = null, $count = null) + { + $this->requiredCommand($client, 'SCAN'); + + parent::__construct($client, $match, $count); + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->scan($this->cursor, $this->getScanOptions()); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Collection/Iterator/ListKey.php b/redis-cache/dependencies/predis/predis/src/Collection/Iterator/ListKey.php new file mode 100644 index 0000000..9f4a21c --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Collection/Iterator/ListKey.php @@ -0,0 +1,183 @@ +requiredCommand($client, 'LRANGE'); + + if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) { + throw new InvalidArgumentException('The $count argument must be a positive integer.'); + } + + $this->client = $client; + $this->key = $key; + $this->count = $count; + + $this->reset(); + } + + /** + * Ensures that the client instance supports the specified Redis command + * required to fetch elements from the server to perform the iteration. + * + * @param ClientInterface $client Client connected to Redis. + * @param string $commandID Command ID. + * + * @throws NotSupportedException + */ + protected function requiredCommand(ClientInterface $client, $commandID) + { + if (!$client->getCommandFactory()->supports($commandID)) { + throw new NotSupportedException("'$commandID' is not supported by the current command factory."); + } + } + + /** + * Resets the inner state of the iterator. + */ + protected function reset() + { + $this->valid = true; + $this->fetchmore = true; + $this->elements = []; + $this->position = -1; + $this->current = null; + } + + /** + * Fetches a new set of elements from the remote collection, effectively + * advancing the iteration process. + * + * @return array + */ + protected function executeCommand() + { + return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count); + } + + /** + * Populates the local buffer of elements fetched from the server during the + * iteration. + */ + protected function fetch() + { + $elements = $this->executeCommand(); + + if (count($elements) < $this->count) { + $this->fetchmore = false; + } + + $this->elements = $elements; + } + + /** + * Extracts next values for key() and current(). + */ + protected function extractNext() + { + ++$this->position; + $this->current = array_shift($this->elements); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->reset(); + $this->next(); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function current() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function next() + { + if (!$this->elements && $this->fetchmore) { + $this->fetch(); + } + + if ($this->elements) { + $this->extractNext(); + } else { + $this->valid = false; + } + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function valid() + { + return $this->valid; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Collection/Iterator/SetKey.php b/redis-cache/dependencies/predis/predis/src/Collection/Iterator/SetKey.php new file mode 100644 index 0000000..74f9823 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Collection/Iterator/SetKey.php @@ -0,0 +1,46 @@ += 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @see http://redis.io/commands/scan + */ +class SetKey extends CursorBasedIterator +{ + protected $key; + + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $key, $match = null, $count = null) + { + $this->requiredCommand($client, 'SSCAN'); + + parent::__construct($client, $match, $count); + + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions()); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Collection/Iterator/SortedSetKey.php b/redis-cache/dependencies/predis/predis/src/Collection/Iterator/SortedSetKey.php new file mode 100644 index 0000000..abee8c2 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Collection/Iterator/SortedSetKey.php @@ -0,0 +1,57 @@ += 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @see http://redis.io/commands/scan + */ +class SortedSetKey extends CursorBasedIterator +{ + protected $key; + + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $key, $match = null, $count = null) + { + $this->requiredCommand($client, 'ZSCAN'); + + parent::__construct($client, $match, $count); + + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions()); + } + + /** + * {@inheritdoc} + */ + protected function extractNext() + { + $this->position = key($this->elements); + $this->current = current($this->elements); + + unset($this->elements[$this->position]); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Argument/ArrayableArgument.php b/redis-cache/dependencies/predis/predis/src/Command/Argument/ArrayableArgument.php new file mode 100644 index 0000000..11073c0 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Argument/ArrayableArgument.php @@ -0,0 +1,26 @@ +unit = $unit; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/ByBox.php b/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/ByBox.php new file mode 100644 index 0000000..7dd9f23 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/ByBox.php @@ -0,0 +1,43 @@ +width = $width; + $this->height = $height; + $this->setUnit($unit); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [self::KEYWORD, $this->width, $this->height, $this->unit]; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/ByInterface.php b/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/ByInterface.php new file mode 100644 index 0000000..767886c --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/ByInterface.php @@ -0,0 +1,19 @@ +radius = $radius; + $this->setUnit($unit); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [self::KEYWORD, $this->radius, $this->unit]; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/FromInterface.php b/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/FromInterface.php new file mode 100644 index 0000000..44700bd --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/FromInterface.php @@ -0,0 +1,19 @@ +longitude = $longitude; + $this->latitude = $latitude; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [self::KEYWORD, $this->longitude, $this->latitude]; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/FromMember.php b/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/FromMember.php new file mode 100644 index 0000000..9e24b2b --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Argument/Geospatial/FromMember.php @@ -0,0 +1,36 @@ +member = $member; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [self::KEYWORD, $this->member]; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Argument/Server/LimitInterface.php b/redis-cache/dependencies/predis/predis/src/Command/Argument/Server/LimitInterface.php new file mode 100644 index 0000000..95ebd64 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Argument/Server/LimitInterface.php @@ -0,0 +1,19 @@ +offset = $offset; + $this->count = $count; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [self::KEYWORD, $this->offset, $this->count]; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Argument/Server/To.php b/redis-cache/dependencies/predis/predis/src/Command/Argument/Server/To.php new file mode 100644 index 0000000..1d77ef6 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Argument/Server/To.php @@ -0,0 +1,57 @@ +host = $host; + $this->port = $port; + $this->isForce = $isForce; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + $arguments = [self::KEYWORD, $this->host, $this->port]; + + if ($this->isForce) { + $arguments[] = self::FORCE_KEYWORD; + } + + return $arguments; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Command.php b/redis-cache/dependencies/predis/predis/src/Command/Command.php new file mode 100644 index 0000000..68629c4 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Command.php @@ -0,0 +1,126 @@ +arguments = $arguments; + unset($this->slot); + } + + /** + * {@inheritdoc} + */ + public function setRawArguments(array $arguments) + { + $this->arguments = $arguments; + unset($this->slot); + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function getArgument($index) + { + if (isset($this->arguments[$index])) { + return $this->arguments[$index]; + } + } + + /** + * {@inheritdoc} + */ + public function setSlot($slot) + { + $this->slot = $slot; + } + + /** + * {@inheritdoc} + */ + public function getSlot() + { + return $this->slot ?? null; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data; + } + + /** + * Normalizes the arguments array passed to a Redis command. + * + * @param array $arguments Arguments for a command. + * + * @return array + */ + public static function normalizeArguments(array $arguments) + { + if (count($arguments) === 1 && isset($arguments[0]) && is_array($arguments[0])) { + return $arguments[0]; + } + + return $arguments; + } + + /** + * Normalizes the arguments array passed to a variadic Redis command. + * + * @param array $arguments Arguments for a command. + * + * @return array + */ + public static function normalizeVariadic(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + return array_merge([$arguments[0]], $arguments[1]); + } + + return $arguments; + } + + /** + * Remove all false values from arguments. + * + * @return void + */ + public function filterArguments(): void + { + $this->arguments = array_filter($this->arguments, static function ($argument) { + return $argument !== false && $argument !== null; + }); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/CommandInterface.php b/redis-cache/dependencies/predis/predis/src/Command/CommandInterface.php new file mode 100644 index 0000000..2048031 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/CommandInterface.php @@ -0,0 +1,80 @@ +getCommandClass($commandID) === null) { + return false; + } + } + + return true; + } + + /** + * Returns the FQCN of a class that represents the specified command ID. + * + * @codeCoverageIgnore + * + * @param string $commandID Command ID + * + * @return string|null + */ + public function getCommandClass(string $commandID): ?string + { + return $this->commands[strtoupper($commandID)] ?? null; + } + + /** + * {@inheritdoc} + */ + public function create(string $commandID, array $arguments = []): CommandInterface + { + if (!$commandClass = $this->getCommandClass($commandID)) { + $commandID = strtoupper($commandID); + + throw new ClientException("Command `$commandID` is not a registered Redis command."); + } + + $command = new $commandClass(); + $command->setArguments($arguments); + + if (isset($this->processor)) { + $this->processor->process($command); + } + + return $command; + } + + /** + * Defines a command in the factory. + * + * Only classes implementing Predis\Command\CommandInterface are allowed to + * handle a command. If the command specified by its ID is already handled + * by the factory, the underlying command class is replaced by the new one. + * + * @param string $commandID Command ID + * @param string $commandClass FQCN of a class implementing Predis\Command\CommandInterface + * + * @throws InvalidArgumentException + */ + public function define(string $commandID, string $commandClass): void + { + if (!is_a($commandClass, 'Predis\Command\CommandInterface', true)) { + throw new InvalidArgumentException( + "Class $commandClass must implement Predis\Command\CommandInterface" + ); + } + + $this->commands[strtoupper($commandID)] = $commandClass; + } + + /** + * Undefines a command in the factory. + * + * When the factory already has a class handler associated to the specified + * command ID it is removed from the map of known commands. Nothing happens + * when the command is not handled by the factory. + * + * @param string $commandID Command ID + */ + public function undefine(string $commandID): void + { + unset($this->commands[strtoupper($commandID)]); + } + + /** + * Sets a command processor for processing command arguments. + * + * Command processors are used to process and transform arguments of Redis + * commands before their newly created instances are returned to the caller + * of "create()". + * + * A NULL value can be used to effectively unset any processor if previously + * set for the command factory. + * + * @param ProcessorInterface|null $processor Command processor or NULL value. + */ + public function setProcessor(?ProcessorInterface $processor): void + { + $this->processor = $processor; + } + + /** + * Returns the current command processor. + * + * @return ProcessorInterface|null + */ + public function getProcessor(): ?ProcessorInterface + { + return $this->processor; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/FactoryInterface.php b/redis-cache/dependencies/predis/predis/src/Command/FactoryInterface.php new file mode 100644 index 0000000..e819510 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/FactoryInterface.php @@ -0,0 +1,42 @@ +prefix = $prefix; + + $prefixFirst = static::class . '::first'; + $prefixAll = static::class . '::all'; + $prefixInterleaved = static::class . '::interleaved'; + $prefixSkipFirst = static::class . '::skipFirst'; + $prefixSkipLast = static::class . '::skipLast'; + $prefixSort = static::class . '::sort'; + $prefixEvalKeys = static::class . '::evalKeys'; + $prefixZsetStore = static::class . '::zsetStore'; + $prefixMigrate = static::class . '::migrate'; + $prefixGeoradius = static::class . '::georadius'; + + $this->commands = [ + /* ---------------- Redis 1.2 ---------------- */ + 'EXISTS' => $prefixAll, + 'DEL' => $prefixAll, + 'TYPE' => $prefixFirst, + 'KEYS' => $prefixFirst, + 'RENAME' => $prefixAll, + 'RENAMENX' => $prefixAll, + 'EXPIRE' => $prefixFirst, + 'EXPIREAT' => $prefixFirst, + 'TTL' => $prefixFirst, + 'MOVE' => $prefixFirst, + 'SORT' => $prefixSort, + 'DUMP' => $prefixFirst, + 'RESTORE' => $prefixFirst, + 'SET' => $prefixFirst, + 'SETNX' => $prefixFirst, + 'MSET' => $prefixInterleaved, + 'MSETNX' => $prefixInterleaved, + 'GET' => $prefixFirst, + 'MGET' => $prefixAll, + 'GETSET' => $prefixFirst, + 'INCR' => $prefixFirst, + 'INCRBY' => $prefixFirst, + 'DECR' => $prefixFirst, + 'DECRBY' => $prefixFirst, + 'RPUSH' => $prefixFirst, + 'LPUSH' => $prefixFirst, + 'LLEN' => $prefixFirst, + 'LRANGE' => $prefixFirst, + 'LTRIM' => $prefixFirst, + 'LINDEX' => $prefixFirst, + 'LSET' => $prefixFirst, + 'LREM' => $prefixFirst, + 'LPOP' => $prefixFirst, + 'RPOP' => $prefixFirst, + 'RPOPLPUSH' => $prefixAll, + 'SADD' => $prefixFirst, + 'SREM' => $prefixFirst, + 'SPOP' => $prefixFirst, + 'SMOVE' => $prefixSkipLast, + 'SCARD' => $prefixFirst, + 'SISMEMBER' => $prefixFirst, + 'SINTER' => $prefixAll, + 'SINTERSTORE' => $prefixAll, + 'SUNION' => $prefixAll, + 'SUNIONSTORE' => $prefixAll, + 'SDIFF' => $prefixAll, + 'SDIFFSTORE' => $prefixAll, + 'SMEMBERS' => $prefixFirst, + 'SRANDMEMBER' => $prefixFirst, + 'ZADD' => $prefixFirst, + 'ZINCRBY' => $prefixFirst, + 'ZREM' => $prefixFirst, + 'ZRANGE' => $prefixFirst, + 'ZREVRANGE' => $prefixFirst, + 'ZRANGEBYSCORE' => $prefixFirst, + 'ZCARD' => $prefixFirst, + 'ZSCORE' => $prefixFirst, + 'ZREMRANGEBYSCORE' => $prefixFirst, + /* ---------------- Redis 2.0 ---------------- */ + 'SETEX' => $prefixFirst, + 'APPEND' => $prefixFirst, + 'SUBSTR' => $prefixFirst, + 'BLPOP' => $prefixSkipLast, + 'BRPOP' => $prefixSkipLast, + 'ZUNIONSTORE' => $prefixZsetStore, + 'ZINTERSTORE' => $prefixZsetStore, + 'ZCOUNT' => $prefixFirst, + 'ZRANK' => $prefixFirst, + 'ZREVRANK' => $prefixFirst, + 'ZREMRANGEBYRANK' => $prefixFirst, + 'HSET' => $prefixFirst, + 'HSETNX' => $prefixFirst, + 'HMSET' => $prefixFirst, + 'HINCRBY' => $prefixFirst, + 'HGET' => $prefixFirst, + 'HMGET' => $prefixFirst, + 'HDEL' => $prefixFirst, + 'HEXISTS' => $prefixFirst, + 'HLEN' => $prefixFirst, + 'HKEYS' => $prefixFirst, + 'HVALS' => $prefixFirst, + 'HGETALL' => $prefixFirst, + 'SUBSCRIBE' => $prefixAll, + 'UNSUBSCRIBE' => $prefixAll, + 'PSUBSCRIBE' => $prefixAll, + 'PUNSUBSCRIBE' => $prefixAll, + 'PUBLISH' => $prefixFirst, + /* ---------------- Redis 2.2 ---------------- */ + 'PERSIST' => $prefixFirst, + 'STRLEN' => $prefixFirst, + 'SETRANGE' => $prefixFirst, + 'GETRANGE' => $prefixFirst, + 'SETBIT' => $prefixFirst, + 'GETBIT' => $prefixFirst, + 'RPUSHX' => $prefixFirst, + 'LPUSHX' => $prefixFirst, + 'LINSERT' => $prefixFirst, + 'BRPOPLPUSH' => $prefixSkipLast, + 'ZREVRANGEBYSCORE' => $prefixFirst, + 'WATCH' => $prefixAll, + /* ---------------- Redis 2.6 ---------------- */ + 'PTTL' => $prefixFirst, + 'PEXPIRE' => $prefixFirst, + 'PEXPIREAT' => $prefixFirst, + 'PSETEX' => $prefixFirst, + 'INCRBYFLOAT' => $prefixFirst, + 'BITOP' => $prefixSkipFirst, + 'BITCOUNT' => $prefixFirst, + 'HINCRBYFLOAT' => $prefixFirst, + 'EVAL' => $prefixEvalKeys, + 'EVALSHA' => $prefixEvalKeys, + 'MIGRATE' => $prefixMigrate, + /* ---------------- Redis 2.8 ---------------- */ + 'SSCAN' => $prefixFirst, + 'ZSCAN' => $prefixFirst, + 'HSCAN' => $prefixFirst, + 'PFADD' => $prefixFirst, + 'PFCOUNT' => $prefixAll, + 'PFMERGE' => $prefixAll, + 'ZLEXCOUNT' => $prefixFirst, + 'ZRANGEBYLEX' => $prefixFirst, + 'ZREMRANGEBYLEX' => $prefixFirst, + 'ZREVRANGEBYLEX' => $prefixFirst, + 'BITPOS' => $prefixFirst, + /* ---------------- Redis 3.2 ---------------- */ + 'HSTRLEN' => $prefixFirst, + 'BITFIELD' => $prefixFirst, + 'GEOADD' => $prefixFirst, + 'GEOHASH' => $prefixFirst, + 'GEOPOS' => $prefixFirst, + 'GEODIST' => $prefixFirst, + 'GEORADIUS' => $prefixGeoradius, + 'GEORADIUSBYMEMBER' => $prefixGeoradius, + /* ---------------- Redis 5.0 ---------------- */ + 'XADD' => $prefixFirst, + 'XRANGE' => $prefixFirst, + 'XDEL' => $prefixFirst, + 'XLEN' => $prefixFirst, + 'XACK' => $prefixFirst, + ]; + } + + /** + * Sets a prefix that is applied to all the keys. + * + * @param string $prefix Prefix for the keys. + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * Gets the current prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * {@inheritdoc} + */ + public function process(CommandInterface $command) + { + if ($command instanceof PrefixableCommandInterface) { + $command->prefixKeys($this->prefix); + } elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) { + $this->commands[$commandID]($command, $this->prefix); + } + } + + /** + * Sets an handler for the specified command ID. + * + * The callback signature must have 2 parameters of the following types: + * + * - Predis\Command\CommandInterface (command instance) + * - String (prefix) + * + * When the callback argument is omitted or NULL, the previously + * associated handler for the specified command ID is removed. + * + * @param string $commandID The ID of the command to be handled. + * @param mixed $callback A valid callable object or NULL. + * + * @throws InvalidArgumentException + */ + public function setCommandHandler($commandID, $callback = null) + { + $commandID = strtoupper($commandID); + + if (!isset($callback)) { + unset($this->commands[$commandID]); + + return; + } + + if (!is_callable($callback)) { + throw new InvalidArgumentException( + 'Callback must be a valid callable object or NULL' + ); + } + + $this->commands[$commandID] = $callback; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->getPrefix(); + } + + /** + * Applies the specified prefix only the first argument. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function first(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to all the arguments. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function all(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + foreach ($arguments as &$key) { + $key = "$prefix$key"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix only to even arguments in the list. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function interleaved(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $length = count($arguments); + + for ($i = 0; $i < $length; $i += 2) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to all the arguments but the first one. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function skipFirst(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $length = count($arguments); + + for ($i = 1; $i < $length; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to all the arguments but the last one. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function skipLast(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $length = count($arguments); + + for ($i = 0; $i < $length - 1; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the keys of a SORT command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function sort(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + + if (($count = count($arguments)) > 1) { + for ($i = 1; $i < $count; ++$i) { + switch (strtoupper($arguments[$i])) { + case 'BY': + case 'STORE': + $arguments[$i] = "$prefix{$arguments[++$i]}"; + break; + + case 'GET': + $value = $arguments[++$i]; + if ($value !== '#') { + $arguments[$i] = "$prefix$value"; + } + break; + + case 'LIMIT': + $i += 2; + break; + } + } + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the keys of an EVAL-based command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function evalKeys(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + for ($i = 2; $i < $arguments[1] + 2; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the keys of Z[INTERSECTION|UNION]STORE. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function zsetStore(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + $length = ((int) $arguments[1]) + 2; + + for ($i = 2; $i < $length; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the key of a MIGRATE command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function migrate(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[2] = "$prefix{$arguments[2]}"; + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the key of a GEORADIUS command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function georadius(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; + + if (($count = count($arguments)) > $startIndex) { + for ($i = $startIndex; $i < $count; ++$i) { + switch (strtoupper($arguments[$i])) { + case 'STORE': + case 'STOREDIST': + $arguments[$i] = "$prefix{$arguments[++$i]}"; + break; + } + } + } + + $command->setRawArguments($arguments); + } + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Processor/ProcessorChain.php b/redis-cache/dependencies/predis/predis/src/Command/Processor/ProcessorChain.php new file mode 100644 index 0000000..8ecfc4d --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Processor/ProcessorChain.php @@ -0,0 +1,137 @@ +add($processor); + } + } + + /** + * {@inheritdoc} + */ + public function add(ProcessorInterface $processor) + { + $this->processors[] = $processor; + } + + /** + * {@inheritdoc} + */ + public function remove(ProcessorInterface $processor) + { + if (false !== $index = array_search($processor, $this->processors, true)) { + unset($this[$index]); + } + } + + /** + * {@inheritdoc} + */ + public function process(CommandInterface $command) + { + for ($i = 0; $i < $count = count($this->processors); ++$i) { + $this->processors[$i]->process($command); + } + } + + /** + * {@inheritdoc} + */ + public function getProcessors() + { + return $this->processors; + } + + /** + * Returns an iterator over the list of command processor in the chain. + * + * @return Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->processors); + } + + /** + * Returns the number of command processors in the chain. + * + * @return int + */ + public function count() + { + return count($this->processors); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function offsetExists($index) + { + return isset($this->processors[$index]); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function offsetGet($index) + { + return $this->processors[$index]; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function offsetSet($index, $processor) + { + if (!$processor instanceof ProcessorInterface) { + throw new InvalidArgumentException( + 'Processor chain accepts only instances of `Predis\Command\Processor\ProcessorInterface`' + ); + } + + $this->processors[$index] = $processor; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function offsetUnset($index) + { + unset($this->processors[$index]); + $this->processors = array_values($this->processors); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Processor/ProcessorInterface.php b/redis-cache/dependencies/predis/predis/src/Command/Processor/ProcessorInterface.php new file mode 100644 index 0000000..f915b9e --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Processor/ProcessorInterface.php @@ -0,0 +1,28 @@ +commandID = strtoupper($commandID); + $this->setArguments($arguments); + } + + /** + * Creates a new raw command using a variadic method. + * + * @param string $commandID Redis command ID + * @param string ...$args Arguments list for the command + * + * @return CommandInterface + */ + public static function create($commandID, ...$args) + { + $arguments = func_get_args(); + + return new static(array_shift($arguments), $arguments); + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->commandID; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + unset($this->slot); + } + + /** + * {@inheritdoc} + */ + public function setRawArguments(array $arguments) + { + $this->setArguments($arguments); + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function getArgument($index) + { + if (isset($this->arguments[$index])) { + return $this->arguments[$index]; + } + } + + /** + * {@inheritdoc} + */ + public function setSlot($slot) + { + $this->slot = $slot; + } + + /** + * {@inheritdoc} + */ + public function getSlot() + { + return $this->slot ?? null; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/RawFactory.php b/redis-cache/dependencies/predis/predis/src/Command/RawFactory.php new file mode 100644 index 0000000..2c0637f --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/RawFactory.php @@ -0,0 +1,43 @@ +setKeys($arguments, false); + } + + public function parseResponse($data) + { + $key = array_shift($data); + + if (null === $key) { + return [$key]; + } + + return array_combine([$key], [[$data[0] => $data[1]]]); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/BGREWRITEAOF.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/BGREWRITEAOF.php new file mode 100644 index 0000000..97d443d --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/BGREWRITEAOF.php @@ -0,0 +1,37 @@ +getArguments(), CASE_UPPER); + + switch (strtoupper($args[0])) { + case 'LIST': + return $this->parseClientList($data); + case 'KILL': + case 'GETNAME': + case 'SETNAME': + default: + return $data; + } // @codeCoverageIgnore + } + + /** + * Parses the response to CLIENT LIST and returns a structured list. + * + * @param string $data Response buffer. + * + * @return array + */ + protected function parseClientList($data) + { + $clients = []; + + foreach (explode("\n", $data, -1) as $clientData) { + $client = []; + + foreach (explode(' ', $clientData) as $kv) { + @[$k, $v] = explode('=', $kv); + $client[$k] = $v; + } + + $clients[] = $client; + } + + return $clients; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/COMMAND.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/COMMAND.php new file mode 100644 index 0000000..a215f47 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/COMMAND.php @@ -0,0 +1,29 @@ +setDB($arguments); + $arguments = $this->getArguments(); + + $this->setReplace($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/Container/AbstractContainer.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/Container/AbstractContainer.php new file mode 100644 index 0000000..6ba86d0 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/Container/AbstractContainer.php @@ -0,0 +1,42 @@ +client = $client; + } + + /** + * {@inheritDoc} + */ + public function __call($subcommandID, $arguments) + { + array_unshift($arguments, strtoupper($subcommandID)); + + return $this->client->executeCommand( + $this->client->createCommand($this->getContainerCommandId(), $arguments) + ); + } + + abstract public function getContainerCommandId(): string; +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/Container/ContainerFactory.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/Container/ContainerFactory.php new file mode 100644 index 0000000..c9a0665 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/Container/ContainerFactory.php @@ -0,0 +1,54 @@ + FunctionContainer::class, + ]; + + /** + * Creates container command. + * + * @param ClientInterface $client + * @param string $containerCommandID + * @return ContainerInterface + */ + public static function create(ClientInterface $client, string $containerCommandID): ContainerInterface + { + $containerCommandID = strtoupper($containerCommandID); + + if (class_exists($containerClass = self::CONTAINER_NAMESPACE . '\\' . $containerCommandID)) { + return new $containerClass($client); + } + + if (array_key_exists($containerCommandID, self::$specialMappings)) { + $containerClass = self::$specialMappings[$containerCommandID]; + + return new $containerClass($client); + } + + throw new UnexpectedValueException('Given command is not supported.'); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/Container/ContainerInterface.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/Container/ContainerInterface.php new file mode 100644 index 0000000..ce3989b --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/Container/ContainerInterface.php @@ -0,0 +1,33 @@ +getArgument(0); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/EVALSHA_RO.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/EVALSHA_RO.php new file mode 100644 index 0000000..809a087 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/EVALSHA_RO.php @@ -0,0 +1,27 @@ +getArgument(0)); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/EVAL_RO.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/EVAL_RO.php new file mode 100644 index 0000000..cef8bd3 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/EVAL_RO.php @@ -0,0 +1,34 @@ +setTimeout($arguments); + $arguments = $this->getArguments(); + + $this->setTo($arguments); + $this->filterArguments(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/FCALL.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/FCALL.php new file mode 100644 index 0000000..cc56393 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/FCALL.php @@ -0,0 +1,33 @@ +strategyResolver = new SubcommandStrategyResolver(); + } + + public function getId() + { + return 'FUNCTION'; + } + + public function setArguments(array $arguments) + { + $strategy = $this->strategyResolver->resolve('functions', $arguments[0]); + $arguments = $strategy->processArguments($arguments); + + parent::setArguments($arguments); + $this->filterArguments(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/GEOADD.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/GEOADD.php new file mode 100644 index 0000000..56156b7 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/GEOADD.php @@ -0,0 +1,43 @@ +setSorting($arguments); + $arguments = $this->getArguments(); + + $this->setWithCoord($arguments); + $arguments = $this->getArguments(); + + $this->setWithDist($arguments); + $arguments = $this->getArguments(); + + $this->setWithHash($arguments); + $arguments = $this->getArguments(); + + $this->setCount($arguments, $arguments[5] ?? false); + $arguments = $this->getArguments(); + + $this->setFrom($arguments); + $arguments = $this->getArguments(); + + $this->setBy($arguments); + $this->filterArguments(); + } + + public function parseResponse($data) + { + $parsedData = []; + $itemKey = ''; + + foreach ($data as $item) { + if (!is_array($item)) { + $parsedData[] = $item; + continue; + } + + foreach ($item as $key => $itemRow) { + if ($key === 0) { + $itemKey = $itemRow; + continue; + } + + if (is_string($itemRow)) { + $parsedData[$itemKey]['dist'] = round((float) $itemRow, 5); + } elseif (is_int($itemRow)) { + $parsedData[$itemKey]['hash'] = $itemRow; + } else { + $parsedData[$itemKey]['lng'] = round($itemRow[0], 5); + $parsedData[$itemKey]['lat'] = round($itemRow[1], 5); + } + } + } + + return $parsedData; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/GEOSEARCHSTORE.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/GEOSEARCHSTORE.php new file mode 100644 index 0000000..6798db7 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/GEOSEARCHSTORE.php @@ -0,0 +1,71 @@ +setStoreDist($arguments); + $arguments = $this->getArguments(); + + $this->setCount($arguments, $arguments[6] ?? false); + $arguments = $this->getArguments(); + + $this->setSorting($arguments); + $arguments = $this->getArguments(); + + $this->setFrom($arguments); + $arguments = $this->getArguments(); + + $this->setBy($arguments); + $this->filterArguments(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/GET.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/GET.php new file mode 100644 index 0000000..e9177ab --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/GET.php @@ -0,0 +1,29 @@ + 'EX', + 'px' => 'PX', + 'exat' => 'EXAT', + 'pxat' => 'PXAT', + 'persist' => 'PERSIST', + ]; + + public function getId() + { + return 'GETEX'; + } + + public function setArguments(array $arguments) + { + if (!array_key_exists(1, $arguments) || $arguments[1] === '') { + parent::setArguments([$arguments[0]]); + + return; + } + + if (!in_array(strtoupper($arguments[1]), self::$modifierEnum)) { + $enumValues = implode(', ', array_keys(self::$modifierEnum)); + throw new UnexpectedValueException("Modifier argument accepts only: {$enumValues} values"); + } + + if ($arguments[1] === 'persist') { + parent::setArguments([$arguments[0], self::$modifierEnum[$arguments[1]]]); + + return; + } + + $arguments[1] = self::$modifierEnum[$arguments[1]]; + + if (!array_key_exists(2, $arguments)) { + throw new UnexpectedValueException('You should provide value for current modifier'); + } + + parent::setArguments($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/GETRANGE.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/GETRANGE.php new file mode 100644 index 0000000..c6feb40 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/GETRANGE.php @@ -0,0 +1,29 @@ + $v) { + $flattenedKVs[] = $k; + $flattenedKVs[] = $v; + } + + $arguments = $flattenedKVs; + } + + parent::setArguments($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/HRANDFIELD.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/HRANDFIELD.php new file mode 100644 index 0000000..0d0fb75 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/HRANDFIELD.php @@ -0,0 +1,34 @@ +prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + parent::setArguments($arguments); + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = []; + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_array($data)) { + $fields = $data[1]; + $result = []; + + for ($i = 0; $i < count($fields); ++$i) { + $result[$fields[$i]] = $fields[++$i]; + } + + $data[1] = $result; + } + + return $data; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/HSET.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/HSET.php new file mode 100644 index 0000000..662094a --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/HSET.php @@ -0,0 +1,29 @@ +parseNewResponseFormat($lines); + } else { + return $this->parseOldResponseFormat($lines); + } + } + + /** + * {@inheritdoc} + */ + public function parseNewResponseFormat($lines) + { + $info = []; + $current = null; + + foreach ($lines as $row) { + if ($row === '') { + continue; + } + + if (preg_match('/^# (\w+)$/', $row, $matches)) { + $info[$matches[1]] = []; + $current = &$info[$matches[1]]; + continue; + } + + [$k, $v] = $this->parseRow($row); + $current[$k] = $v; + } + + return $info; + } + + /** + * {@inheritdoc} + */ + public function parseOldResponseFormat($lines) + { + $info = []; + + foreach ($lines as $row) { + if (strpos($row, ':') === false) { + continue; + } + + [$k, $v] = $this->parseRow($row); + $info[$k] = $v; + } + + return $info; + } + + /** + * Parses a single row of the response and returns the key-value pair. + * + * @param string $row Single row of the response. + * + * @return array + */ + protected function parseRow($row) + { + [$k, $v] = explode(':', $row, 2); + + if (preg_match('/^db\d+$/', $k)) { + $v = $this->parseDatabaseStats($v); + } + + return [$k, $v]; + } + + /** + * Extracts the statistics of each logical DB from the string buffer. + * + * @param string $str Response buffer. + * + * @return array + */ + protected function parseDatabaseStats($str) + { + $db = []; + + foreach (explode(',', $str) as $dbvar) { + [$dbvk, $dbvv] = explode('=', $dbvar); + $db[trim($dbvk)] = $dbvv; + } + + return $db; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/KEYS.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/KEYS.php new file mode 100644 index 0000000..fadb8d7 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/KEYS.php @@ -0,0 +1,29 @@ +filterArguments(); + } + + public function parseResponse($data) + { + if (is_array($data)) { + return [$data[0] => $data[1], $data[2] => $data[3]]; + } + + return $data; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/LINDEX.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/LINDEX.php new file mode 100644 index 0000000..80510e1 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/LINDEX.php @@ -0,0 +1,29 @@ +setCount($arguments); + $arguments = $this->getArguments(); + + $this->setLeftRight($arguments); + $arguments = $this->getArguments(); + + $this->setKeys($arguments); + $this->filterArguments(); + } + + public function parseResponse($data) + { + if (null === $data) { + return null; + } + + return [$data[0] => $data[1]]; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/LPOP.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/LPOP.php new file mode 100644 index 0000000..d375bac --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/LPOP.php @@ -0,0 +1,29 @@ + $value) { + $modifier = strtoupper($modifier); + + if ($modifier === 'COPY' && $value == true) { + $arguments[] = $modifier; + } + + if ($modifier === 'REPLACE' && $value == true) { + $arguments[] = $modifier; + } + } + } + + parent::setArguments($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/MONITOR.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/MONITOR.php new file mode 100644 index 0000000..06e9e59 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/MONITOR.php @@ -0,0 +1,29 @@ + $v) { + $flattenedKVs[] = $k; + $flattenedKVs[] = $v; + } + + $arguments = $flattenedKVs; + } + + parent::setArguments($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/MSETNX.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/MSETNX.php new file mode 100644 index 0000000..94d9ce2 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/MSETNX.php @@ -0,0 +1,27 @@ +getArgument(0))) { + case 'numsub': + return self::processNumsub($data); + + default: + return $data; + } + } + + /** + * Returns the processed response to PUBSUB NUMSUB. + * + * @param array $channels List of channels + * + * @return array + */ + protected static function processNumsub(array $channels) + { + $processed = []; + $count = count($channels); + + for ($i = 0; $i < $count; ++$i) { + $processed[$channels[$i]] = $channels[++$i]; + } + + return $processed; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/PUNSUBSCRIBE.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/PUNSUBSCRIBE.php new file mode 100644 index 0000000..f15a39f --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/PUNSUBSCRIBE.php @@ -0,0 +1,39 @@ +prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + parent::setArguments($arguments); + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = []; + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/SCARD.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/SCARD.php new file mode 100644 index 0000000..daf1393 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/SCARD.php @@ -0,0 +1,29 @@ +getArgument(0); + $argument = is_null($argument) ? null : strtolower($argument); + + switch ($argument) { + case 'masters': + case 'slaves': + return self::processMastersOrSlaves($data); + + default: + return $data; + } + } + + /** + * Returns a processed response to SENTINEL MASTERS or SENTINEL SLAVES. + * + * @param array $servers List of Redis servers. + * + * @return array + */ + protected static function processMastersOrSlaves(array $servers) + { + foreach ($servers as $idx => $node) { + $processed = []; + $count = count($node); + + for ($i = 0; $i < $count; ++$i) { + $processed[$node[$i]] = $node[++$i]; + } + + $servers[$idx] = $processed; + } + + return $servers; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/SET.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/SET.php new file mode 100644 index 0000000..f8956f4 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/SET.php @@ -0,0 +1,29 @@ +setLimit($arguments); + $arguments = $this->getArguments(); + + $this->setKeys($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/SINTERSTORE.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/SINTERSTORE.php new file mode 100644 index 0000000..144335a --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/SINTERSTORE.php @@ -0,0 +1,41 @@ + $entry) { + $log[$index] = [ + 'id' => $entry[0], + 'timestamp' => $entry[1], + 'duration' => $entry[2], + 'command' => $entry[3], + ]; + } + + return $log; + } + + return $data; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/SMEMBERS.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/SMEMBERS.php new file mode 100644 index 0000000..8f32be4 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/SMEMBERS.php @@ -0,0 +1,29 @@ +setSorting($arguments); + $arguments = $this->getArguments(); + + $this->setGetArgument($arguments); + $arguments = $this->getArguments(); + + $this->setLimit($arguments); + $arguments = $this->getArguments(); + + $this->setBy($arguments); + $this->filterArguments(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/SPOP.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/SPOP.php new file mode 100644 index 0000000..e09a3d5 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/SPOP.php @@ -0,0 +1,29 @@ +prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + parent::setArguments($arguments); + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = []; + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/STRLEN.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/STRLEN.php new file mode 100644 index 0000000..f517756 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/STRLEN.php @@ -0,0 +1,29 @@ + $val) { + $args[] = $key; + $args[] = $val; + } + } + + parent::setArguments($args); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/XDEL.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/XDEL.php new file mode 100644 index 0000000..f1c509e --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/XDEL.php @@ -0,0 +1,39 @@ + $score) { + $arguments[] = $score; + $arguments[] = $member; + } + } + + parent::setArguments($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/ZCARD.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZCARD.php new file mode 100644 index 0000000..7f6cfd4 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZCARD.php @@ -0,0 +1,29 @@ +setKeys($arguments); + $arguments = $this->getArguments(); + + $this->setWithScore($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/ZDIFFSTORE.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZDIFFSTORE.php new file mode 100644 index 0000000..7299883 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZDIFFSTORE.php @@ -0,0 +1,40 @@ +setLimit($arguments); + $arguments = $this->getArguments(); + + $this->setKeys($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/ZINTERSTORE.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZINTERSTORE.php new file mode 100644 index 0000000..7f0aa44 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZINTERSTORE.php @@ -0,0 +1,27 @@ +setCount($arguments); + $arguments = $this->getArguments(); + + $this->resolveModifier(static::$modifierArgumentPositionOffset, $arguments); + + $this->setKeys($arguments); + $arguments = $this->getArguments(); + + parent::setArguments($arguments); + } + + public function parseResponse($data) + { + $key = array_shift($data); + + if (null === $key) { + return [$key]; + } + + $data = $data[0]; + $parsedData = []; + + for ($i = 0, $iMax = count($data); $i < $iMax; $i++) { + for ($j = 0, $jMax = count($data[$i]); $j < $jMax; ++$j) { + if ($data[$i][$j + 1] ?? false) { + $parsedData[$data[$i][$j]] = $data[$i][++$j]; + } + } + } + + return array_combine([$key], [$parsedData]); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/ZMSCORE.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZMSCORE.php new file mode 100644 index 0000000..2dd76fd --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZMSCORE.php @@ -0,0 +1,34 @@ + true]; + $lastType = 'array'; + } + + if ($lastType === 'array') { + $options = $this->prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + } + + parent::setArguments($arguments); + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $opts = array_change_key_case($options, CASE_UPPER); + $finalizedOpts = []; + + if (!empty($opts['WITHSCORES'])) { + $finalizedOpts[] = 'WITHSCORES'; + } + + return $finalizedOpts; + } + + /** + * Checks for the presence of the WITHSCORES modifier. + * + * @return bool + */ + protected function withScores() + { + $arguments = $this->getArguments(); + + if (count($arguments) < 4) { + return false; + } + + return strtoupper($arguments[3]) === 'WITHSCORES'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if ($this->withScores()) { + $result = []; + + for ($i = 0; $i < count($data); ++$i) { + $result[$data[$i]] = $data[++$i]; + } + + return $result; + } + + return $data; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/ZRANGEBYLEX.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZRANGEBYLEX.php new file mode 100644 index 0000000..18b4a6d --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZRANGEBYLEX.php @@ -0,0 +1,54 @@ +getArguments(); + + for ($i = 3; $i < count($arguments); ++$i) { + switch (strtoupper($arguments[$i])) { + case 'WITHSCORES': + return true; + + case 'LIMIT': + $i += 2; + break; + } + } + + return false; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/ZRANGESTORE.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZRANGESTORE.php new file mode 100644 index 0000000..4f820b0 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZRANGESTORE.php @@ -0,0 +1,57 @@ +setByLexByScoreArgument($arguments); + $arguments = $this->getArguments(); + + $this->setReversedArgument($arguments); + $arguments = $this->getArguments(); + + $this->setLimitArguments($arguments); + $this->filterArguments(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/ZRANK.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZRANK.php new file mode 100644 index 0000000..3c6ac2a --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZRANK.php @@ -0,0 +1,29 @@ +prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + parent::setArguments($arguments); + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = []; + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_array($data)) { + $members = $data[1]; + $result = []; + + for ($i = 0; $i < count($members); ++$i) { + $result[$members[$i]] = (float) $members[++$i]; + } + + $data[1] = $result; + } + + return $data; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Redis/ZSCORE.php b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZSCORE.php new file mode 100644 index 0000000..978a172 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Redis/ZSCORE.php @@ -0,0 +1,29 @@ +setAggregate($arguments); + $arguments = $this->getArguments(); + + $this->setWeights($arguments); + $arguments = $this->getArguments(); + + $this->setKeys($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/RedisFactory.php b/redis-cache/dependencies/predis/predis/src/Command/RedisFactory.php new file mode 100644 index 0000000..ed845f3 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/RedisFactory.php @@ -0,0 +1,69 @@ +commands = [ + 'ECHO' => 'Predis\Command\Redis\ECHO_', + 'EVAL' => 'Predis\Command\Redis\EVAL_', + 'OBJECT' => 'Predis\Command\Redis\OBJECT_', + // Class name corresponds to PHP reserved word "function", added mapping to bypass restrictions + 'FUNCTION' => FUNCTIONS::class, + ]; + } + + /** + * {@inheritdoc} + */ + public function getCommandClass(string $commandID): ?string + { + $commandID = strtoupper($commandID); + + if (isset($this->commands[$commandID]) || array_key_exists($commandID, $this->commands)) { + $commandClass = $this->commands[$commandID]; + } elseif (class_exists($commandClass = "Predis\Command\Redis\\$commandID")) { + $this->commands[$commandID] = $commandClass; + } else { + return null; + } + + return $commandClass; + } + + /** + * {@inheritdoc} + */ + public function undefine(string $commandID): void + { + // NOTE: we explicitly associate `NULL` to the command ID in the map + // instead of the parent's `unset()` because our subclass tries to load + // a predefined class from the Predis\Command\Redis namespace when no + // explicit mapping is defined, see RedisFactory::getCommandClass() for + // details of the implementation of this mechanism. + $this->commands[strtoupper($commandID)] = null; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/ScriptCommand.php b/redis-cache/dependencies/predis/predis/src/Command/ScriptCommand.php new file mode 100644 index 0000000..330ee94 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/ScriptCommand.php @@ -0,0 +1,108 @@ +getScript()); + } + + /** + * Specifies the number of arguments that should be considered as keys. + * + * The default behaviour for the base class is to return 0 to indicate that + * all the elements of the arguments array should be considered as keys, but + * subclasses can enforce a static number of keys. + * + * @return int + */ + protected function getKeysCount() + { + return 0; + } + + /** + * Returns the elements from the arguments that are identified as keys. + * + * @return array + */ + public function getKeys() + { + return array_slice($this->getArguments(), 2, $this->getKeysCount()); + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments) + { + if (($numkeys = $this->getKeysCount()) && $numkeys < 0) { + $numkeys = count($arguments) + $numkeys; + } + + $arguments = array_merge([$this->getScriptHash(), (int) $numkeys], $arguments); + + parent::setArguments($arguments); + } + + /** + * Returns arguments for EVAL command. + * + * @return array + */ + public function getEvalArguments() + { + $arguments = $this->getArguments(); + $arguments[0] = $this->getScript(); + + return $arguments; + } + + /** + * Returns the equivalent EVAL command as a raw command instance. + * + * @return RawCommand + */ + public function getEvalCommand() + { + return new RawCommand('EVAL', $this->getEvalArguments()); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Strategy/ContainerCommands/Functions/DeleteStrategy.php b/redis-cache/dependencies/predis/predis/src/Command/Strategy/ContainerCommands/Functions/DeleteStrategy.php new file mode 100644 index 0000000..250ae74 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Strategy/ContainerCommands/Functions/DeleteStrategy.php @@ -0,0 +1,26 @@ + 'MIN', + 'max' => 'MAX', + 'sum' => 'SUM', + ]; + + /** + * @var string + */ + private static $aggregateModifier = 'AGGREGATE'; + + public function setArguments(array $arguments) + { + $argumentsLength = count($arguments); + + if (static::$aggregateArgumentPositionOffset >= $argumentsLength) { + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$aggregateArgumentPositionOffset]; + + if (is_string($argument) && in_array(strtoupper($argument), self::$aggregateValuesEnum)) { + $argument = self::$aggregateValuesEnum[$argument]; + } else { + $enumValues = implode(', ', array_keys(self::$aggregateValuesEnum)); + throw new UnexpectedValueException("Aggregate argument accepts only: {$enumValues} values"); + } + + $argumentsBefore = array_slice($arguments, 0, static::$aggregateArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$aggregateArgumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + [self::$aggregateModifier], + [$argument], + $argumentsAfter + )); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/BitByte.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/BitByte.php new file mode 100644 index 0000000..b7ce689 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/BitByte.php @@ -0,0 +1,34 @@ + 'BIT', + 'byte' => 'BYTE', + ]; + + public function setArguments(array $arguments) + { + $value = array_pop($arguments); + + if (in_array(strtoupper($value), self::$argumentEnum, true)) { + $arguments[] = self::$argumentEnum[$value]; + } else { + $arguments[] = $value; + } + + parent::setArguments($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/By/ByArgument.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/By/ByArgument.php new file mode 100644 index 0000000..99bd172 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/By/ByArgument.php @@ -0,0 +1,40 @@ += $argumentsLength || null === $arguments[static::$byArgumentPositionOffset]) { + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$byArgumentPositionOffset]; + $argumentsBefore = array_slice($arguments, 0, static::$byArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$byArgumentPositionOffset + 1); + + parent::setArguments(array_merge($argumentsBefore, [$this->byModifier, $argument], $argumentsAfter)); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/By/ByLexByScore.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/By/ByLexByScore.php new file mode 100644 index 0000000..66c5315 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/By/ByLexByScore.php @@ -0,0 +1,49 @@ + 'BYLEX', + 'byscore' => 'BYSCORE', + ]; + + public function setArguments(array $arguments) + { + $argument = $arguments[static::$byLexByScoreArgumentPositionOffset]; + + if (false === $argument) { + parent::setArguments($arguments); + + return; + } + + if (is_string($argument) && in_array(strtoupper($argument), self::$argumentsEnum)) { + $argument = self::$argumentsEnum[$argument]; + } else { + throw new UnexpectedValueException('By argument accepts only "bylex" and "byscore" values'); + } + + $argumentsBefore = array_slice($arguments, 0, static::$byLexByScoreArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$byLexByScoreArgumentPositionOffset + 1); + + parent::setArguments(array_merge($argumentsBefore, [$argument], $argumentsAfter)); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/By/GeoBy.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/By/GeoBy.php new file mode 100644 index 0000000..c5ee2b7 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/By/GeoBy.php @@ -0,0 +1,49 @@ +getByArgumentPositionOffset($arguments); + + if (null === $argumentPositionOffset) { + throw new InvalidArgumentException('Invalid BY argument value given'); + } + + $byArgumentObject = $arguments[$argumentPositionOffset]; + $argumentsBefore = array_slice($arguments, 0, $argumentPositionOffset); + $argumentsAfter = array_slice($arguments, $argumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + $byArgumentObject->toArray(), + $argumentsAfter + )); + } + + private function getByArgumentPositionOffset(array $arguments): ?int + { + foreach ($arguments as $i => $value) { + if ($value instanceof ByInterface) { + return $i; + } + } + + return null; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/Count.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/Count.php new file mode 100644 index 0000000..46ae489 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/Count.php @@ -0,0 +1,71 @@ += $argumentsLength) { + parent::setArguments($arguments); + + return; + } + + if ($arguments[static::$countArgumentPositionOffset] === -1) { + array_splice($arguments, static::$countArgumentPositionOffset, 1, [false]); + parent::setArguments($arguments); + + return; + } + + if ($arguments[static::$countArgumentPositionOffset] < 1) { + throw new UnexpectedValueException('Wrong count argument value or position offset'); + } + + $countArgument = $arguments[static::$countArgumentPositionOffset]; + $argumentsBefore = array_slice($arguments, 0, static::$countArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$countArgumentPositionOffset + 2); + + if (!$any) { + $argumentsAfter = array_slice($arguments, static::$countArgumentPositionOffset + 1); + parent::setArguments(array_merge( + $argumentsBefore, + [$this->countModifier], + [$countArgument], + $argumentsAfter + )); + + return; + } + + parent::setArguments(array_merge( + $argumentsBefore, + [$this->countModifier], + [$countArgument], + [$this->anyModifier], + $argumentsAfter + )); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/DB.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/DB.php new file mode 100644 index 0000000..cf494f9 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/DB.php @@ -0,0 +1,53 @@ += $argumentsLength) { + parent::setArguments($arguments); + + return; + } + + if (!is_numeric($arguments[static::$dbArgumentPositionOffset])) { + throw new UnexpectedValueException('DB argument should be a valid numeric value'); + } + + if ($arguments[static::$dbArgumentPositionOffset] < 0) { + array_splice($arguments, static::$dbArgumentPositionOffset, 1); + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$dbArgumentPositionOffset]; + $argumentsBefore = array_slice($arguments, 0, static::$dbArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$dbArgumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + [$this->dbModifier], + [$argument], + $argumentsAfter + )); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/Expire/ExpireOptions.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/Expire/ExpireOptions.php new file mode 100644 index 0000000..85cff7a --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/Expire/ExpireOptions.php @@ -0,0 +1,36 @@ + 'NX', + 'xx' => 'XX', + 'gt' => 'GT', + 'lt' => 'LT', + ]; + + public function setArguments(array $arguments) + { + $value = array_pop($arguments); + + if (in_array(strtoupper($value), self::$argumentEnum, true)) { + $arguments[] = self::$argumentEnum[strtolower($value)]; + } else { + $arguments[] = $value; + } + + parent::setArguments($arguments); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/From/GeoFrom.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/From/GeoFrom.php new file mode 100644 index 0000000..22688ad --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/From/GeoFrom.php @@ -0,0 +1,49 @@ +getFromArgumentPositionOffset($arguments); + + if (null === $argumentPositionOffset) { + throw new InvalidArgumentException('Invalid FROM argument value given'); + } + + $fromArgumentObject = $arguments[$argumentPositionOffset]; + $argumentsBefore = array_slice($arguments, 0, $argumentPositionOffset); + $argumentsAfter = array_slice($arguments, $argumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + $fromArgumentObject->toArray(), + $argumentsAfter + )); + } + + private function getFromArgumentPositionOffset(array $arguments): ?int + { + foreach ($arguments as $i => $value) { + if ($value instanceof FromInterface) { + return $i; + } + } + + return null; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/Get/Get.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/Get/Get.php new file mode 100644 index 0000000..256676e --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/Get/Get.php @@ -0,0 +1,47 @@ += $argumentsLength) { + parent::setArguments($arguments); + + return; + } + + if (!is_array($arguments[static::$getArgumentPositionOffset])) { + throw new UnexpectedValueException('Wrong get argument type'); + } + + $patterns = []; + + foreach ($arguments[static::$getArgumentPositionOffset] as $pattern) { + $patterns[] = self::$getModifier; + $patterns[] = $pattern; + } + + $argumentsBeforeKeys = array_slice($arguments, 0, static::$getArgumentPositionOffset); + $argumentsAfterKeys = array_slice($arguments, static::$getArgumentPositionOffset + 1); + + parent::setArguments(array_merge($argumentsBeforeKeys, $patterns, $argumentsAfterKeys)); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/Keys.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/Keys.php new file mode 100644 index 0000000..a623e9c --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/Keys.php @@ -0,0 +1,47 @@ + $argumentsLength || + !is_array($arguments[static::$keysArgumentPositionOffset]) + ) { + throw new UnexpectedValueException('Wrong keys argument type or position offset'); + } + + $keysArgument = $arguments[static::$keysArgumentPositionOffset]; + $argumentsBeforeKeys = array_slice($arguments, 0, static::$keysArgumentPositionOffset); + $argumentsAfterKeys = array_slice($arguments, static::$keysArgumentPositionOffset + 1); + + if ($withNumkeys) { + $numkeys = count($keysArgument); + parent::setArguments(array_merge($argumentsBeforeKeys, [$numkeys], $keysArgument, $argumentsAfterKeys)); + + return; + } + + parent::setArguments(array_merge($argumentsBeforeKeys, $keysArgument, $argumentsAfterKeys)); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/LeftRight.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/LeftRight.php new file mode 100644 index 0000000..181fbd1 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/LeftRight.php @@ -0,0 +1,60 @@ + 'LEFT', + 'right' => 'RIGHT', + ]; + + public function setArguments(array $arguments) + { + $argumentsLength = count($arguments); + + if (static::$leftRightArgumentPositionOffset >= $argumentsLength) { + $arguments[] = 'LEFT'; + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$leftRightArgumentPositionOffset]; + + if (is_string($argument) && in_array(strtoupper($argument), self::$leftRightEnum, true)) { + $argument = self::$leftRightEnum[$argument]; + } else { + $enumValues = implode(', ', array_keys(self::$leftRightEnum)); + throw new UnexpectedValueException("Left/Right argument accepts only: {$enumValues} values"); + } + + $argumentsBefore = array_slice($arguments, 0, static::$leftRightArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$leftRightArgumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + [$argument], + $argumentsAfter + )); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/Limit/Limit.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/Limit/Limit.php new file mode 100644 index 0000000..e244994 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/Limit/Limit.php @@ -0,0 +1,54 @@ += $argumentsLength + || false === $arguments[static::$limitArgumentPositionOffset] + ) { + parent::setArguments($argumentsBefore); + + return; + } + + $argument = $arguments[static::$limitArgumentPositionOffset]; + $argumentsAfter = array_slice($arguments, static::$limitArgumentPositionOffset + 1); + + if (true === $argument) { + parent::setArguments(array_merge($argumentsBefore, [self::$limitModifier], $argumentsAfter)); + + return; + } + + if (!is_int($argument)) { + throw new UnexpectedValueException('Wrong limit argument type'); + } + + parent::setArguments(array_merge($argumentsBefore, [self::$limitModifier], [$argument], $argumentsAfter)); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/Limit/LimitObject.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/Limit/LimitObject.php new file mode 100644 index 0000000..3e47de9 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/Limit/LimitObject.php @@ -0,0 +1,50 @@ +getLimitArgumentPositionOffset($arguments); + + if (null === $argumentPositionOffset) { + parent::setArguments($arguments); + + return; + } + + $limitObject = $arguments[$argumentPositionOffset]; + $argumentsBefore = array_slice($arguments, 0, $argumentPositionOffset); + $argumentsAfter = array_slice($arguments, $argumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + $limitObject->toArray(), + $argumentsAfter + )); + } + + private function getLimitArgumentPositionOffset(array $arguments): ?int + { + foreach ($arguments as $i => $value) { + if ($value instanceof LimitInterface) { + return $i; + } + } + + return null; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/MinMaxModifier.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/MinMaxModifier.php new file mode 100644 index 0000000..f48f4c1 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/MinMaxModifier.php @@ -0,0 +1,45 @@ + 'MIN', + 'max' => 'MAX', + ]; + + public function resolveModifier(int $offset, array &$arguments): void + { + if ($offset >= count($arguments)) { + $arguments[$offset] = $this->modifierEnum['min']; + + return; + } + + if (!is_string($arguments[$offset]) || !array_key_exists($arguments[$offset], $this->modifierEnum)) { + throw new UnexpectedValueException('Wrong type of modifier given'); + } + + $arguments[$offset] = $this->modifierEnum[$arguments[$offset]]; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/Replace.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/Replace.php new file mode 100644 index 0000000..d193d66 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/Replace.php @@ -0,0 +1,34 @@ + 'ASC', + 'desc' => 'DESC', + ]; + + public function setArguments(array $arguments) + { + $argumentsLength = count($arguments); + + if (static::$sortArgumentPositionOffset >= $argumentsLength) { + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$sortArgumentPositionOffset]; + + if (null === $argument) { + array_splice($arguments, static::$sortArgumentPositionOffset, 1, [false]); + parent::setArguments($arguments); + + return; + } + + if (!in_array(strtoupper($argument), self::$sortingEnum, true)) { + $enumValues = implode(', ', array_keys(self::$sortingEnum)); + throw new UnexpectedValueException("Sorting argument accepts only: {$enumValues} values"); + } + + $argumentsBefore = array_slice($arguments, 0, static::$sortArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$sortArgumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + [self::$sortingEnum[$argument]], + $argumentsAfter + )); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/Storedist.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/Storedist.php new file mode 100644 index 0000000..6feed1e --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/Storedist.php @@ -0,0 +1,49 @@ += $argumentsLength + || false === $arguments[static::$storeDistArgumentPositionOffset] + ) { + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$storeDistArgumentPositionOffset]; + + if (true === $argument) { + $argument = 'STOREDIST'; + } else { + throw new UnexpectedValueException('Wrong STOREDIST argument type'); + } + + $argumentsBefore = array_slice($arguments, 0, static::$storeDistArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$storeDistArgumentPositionOffset + 1); + + parent::setArguments(array_merge($argumentsBefore, [$argument], $argumentsAfter)); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/Timeout.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/Timeout.php new file mode 100644 index 0000000..fd33ea9 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/Timeout.php @@ -0,0 +1,53 @@ += $argumentsLength) { + parent::setArguments($arguments); + + return; + } + + if ($arguments[static::$timeoutArgumentPositionOffset] === -1) { + array_splice($arguments, static::$timeoutArgumentPositionOffset, 1, [false]); + parent::setArguments($arguments); + + return; + } + + if ($arguments[static::$timeoutArgumentPositionOffset] < 1) { + throw new UnexpectedValueException('Wrong timeout argument value or position offset'); + } + + $argument = $arguments[static::$timeoutArgumentPositionOffset]; + $argumentsBefore = array_slice($arguments, 0, static::$timeoutArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$timeoutArgumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + [self::$timeoutModifier], + [$argument], + $argumentsAfter + )); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/To/ServerTo.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/To/ServerTo.php new file mode 100644 index 0000000..1ab13ec --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/To/ServerTo.php @@ -0,0 +1,48 @@ += $argumentsLength) { + parent::setArguments($arguments); + + return; + } + + /** @var To|null $toArgument */ + $toArgument = $arguments[static::$toArgumentPositionOffset]; + + if (null === $toArgument) { + array_splice($arguments, static::$toArgumentPositionOffset, 1, [false]); + parent::setArguments($arguments); + + return; + } + + $argumentsBefore = array_slice($arguments, 0, static::$toArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$toArgumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + $toArgument->toArray(), + $argumentsAfter + )); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/Weights.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/Weights.php new file mode 100644 index 0000000..1f175ed --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/Weights.php @@ -0,0 +1,61 @@ += $argumentsLength) { + parent::setArguments($arguments); + + return; + } + + if (!is_array($arguments[static::$weightsArgumentPositionOffset])) { + throw new UnexpectedValueException('Wrong weights argument type'); + } + + $weightsArray = $arguments[static::$weightsArgumentPositionOffset]; + + if (empty($weightsArray)) { + unset($arguments[static::$weightsArgumentPositionOffset]); + parent::setArguments($arguments); + + return; + } + + $argumentsBefore = array_slice($arguments, 0, static::$weightsArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$weightsArgumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + [self::$weightsModifier], + $weightsArray, + $argumentsAfter + )); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithCoord.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithCoord.php new file mode 100644 index 0000000..797ae2e --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithCoord.php @@ -0,0 +1,49 @@ += $argumentsLength + || false === $arguments[static::$withCoordArgumentPositionOffset] + ) { + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$withCoordArgumentPositionOffset]; + + if (true === $argument) { + $argument = 'WITHCOORD'; + } else { + throw new UnexpectedValueException('Wrong WITHCOORD argument type'); + } + + $argumentsBefore = array_slice($arguments, 0, static::$withCoordArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$withCoordArgumentPositionOffset + 1); + + parent::setArguments(array_merge($argumentsBefore, [$argument], $argumentsAfter)); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithDist.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithDist.php new file mode 100644 index 0000000..479606d --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithDist.php @@ -0,0 +1,45 @@ += $argumentsLength + || false === $arguments[static::$withDistArgumentPositionOffset] + ) { + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$withDistArgumentPositionOffset]; + + if (true === $argument) { + $argument = 'WITHDIST'; + } else { + throw new UnexpectedValueException('Wrong WITHDIST argument type'); + } + + $argumentsBefore = array_slice($arguments, 0, static::$withDistArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$withDistArgumentPositionOffset + 1); + + parent::setArguments(array_merge($argumentsBefore, [$argument], $argumentsAfter)); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithHash.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithHash.php new file mode 100644 index 0000000..c00f680 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithHash.php @@ -0,0 +1,45 @@ += $argumentsLength + || false === $arguments[static::$withHashArgumentPositionOffset] + ) { + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$withHashArgumentPositionOffset]; + + if (true === $argument) { + $argument = 'WITHHASH'; + } else { + throw new UnexpectedValueException('Wrong WITHHASH argument type'); + } + + $argumentsBefore = array_slice($arguments, 0, static::$withHashArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$withHashArgumentPositionOffset + 1); + + parent::setArguments(array_merge($argumentsBefore, [$argument], $argumentsAfter)); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithScores.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithScores.php new file mode 100644 index 0000000..e98b4ea --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithScores.php @@ -0,0 +1,66 @@ +isWithScoreModifier()) { + $result = []; + + for ($i = 0, $iMax = count($data); $i < $iMax; ++$i) { + if ($data[$i + 1] ?? false) { + $result[$data[$i]] = $data[++$i]; + } + } + + return $result; + } + + return $data; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithValues.php b/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithValues.php new file mode 100644 index 0000000..4efb065 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Command/Traits/With/WithValues.php @@ -0,0 +1,34 @@ +connection = $connection; + } + + /** + * Gets the connection that generated the exception. + * + * @return NodeConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Indicates if the receiver should reset the underlying connection. + * + * @return bool + */ + public function shouldResetConnection() + { + return true; + } + + /** + * Helper method to handle exceptions generated by a connection object. + * + * @param CommunicationException $exception Exception. + * + * @throws CommunicationException + */ + public static function handle(CommunicationException $exception) + { + if ($exception->shouldResetConnection()) { + $connection = $exception->getConnection(); + + if ($connection->isConnected()) { + $connection->disconnect(); + } + } + + throw $exception; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Configuration/Option/Aggregate.php b/redis-cache/dependencies/predis/predis/src/Configuration/Option/Aggregate.php new file mode 100644 index 0000000..262f8d6 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Configuration/Option/Aggregate.php @@ -0,0 +1,114 @@ +getConnectionInitializer($options, $value); + } + + /** + * Wraps a user-supplied callable used to create a new aggregate connection. + * + * When the original callable acting as a connection initializer is executed + * by the client to create a new aggregate connection, it will receive the + * following arguments: + * + * - $parameters (same as passed to Predis\Client::__construct()) + * - $options (options container, Predis\Configuration\OptionsInterface) + * - $option (current option, Predis\Configuration\OptionInterface) + * + * The original callable must return a valid aggregation connection instance + * of type Predis\Connection\AggregateConnectionInterface, this is enforced + * by the wrapper returned by this method and an exception is thrown when + * invalid values are returned. + * + * @param OptionsInterface $options Client options + * @param callable $callable Callable initializer + * + * @return callable + * @throws InvalidArgumentException + */ + protected function getConnectionInitializer(OptionsInterface $options, callable $callable) + { + return function ($parameters = null, $autoaggregate = false) use ($callable, $options) { + $connection = call_user_func_array($callable, [&$parameters, $options, $this]); + + if (!$connection instanceof AggregateConnectionInterface) { + throw new InvalidArgumentException(sprintf( + '%s expects the supplied callable to return an instance of %s, but %s was returned', + static::class, + AggregateConnectionInterface::class, + is_object($connection) ? get_class($connection) : gettype($connection) + )); + } + + if ($parameters && $autoaggregate) { + static::aggregate($options, $connection, $parameters); + } + + return $connection; + }; + } + + /** + * Adds single connections to an aggregate connection instance. + * + * @param OptionsInterface $options Client options + * @param AggregateConnectionInterface $connection Target aggregate connection + * @param array $nodes List of nodes to be added to the target aggregate connection + */ + public static function aggregate(OptionsInterface $options, AggregateConnectionInterface $connection, array $nodes) + { + $connections = $options->connections; + + foreach ($nodes as $node) { + $connection->add($node instanceof NodeConnectionInterface ? $node : $connections->create($node)); + } + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Configuration/Option/CRC16.php b/redis-cache/dependencies/predis/predis/src/Configuration/Option/CRC16.php new file mode 100644 index 0000000..b171449 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Configuration/Option/CRC16.php @@ -0,0 +1,74 @@ +getHashGeneratorByDescription($options, $value); + } elseif ($value instanceof Hash\HashGeneratorInterface) { + return $value; + } else { + $class = get_class($this); + throw new InvalidArgumentException("$class expects a valid hash generator"); + } + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return function_exists('phpiredis_utils_crc16') + ? new Hash\PhpiredisCRC16() + : new Hash\CRC16(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Configuration/Option/Cluster.php b/redis-cache/dependencies/predis/predis/src/Configuration/Option/Cluster.php new file mode 100644 index 0000000..34b33de --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Configuration/Option/Cluster.php @@ -0,0 +1,99 @@ +getConnectionInitializerByString($options, $value); + } + + if (is_callable($value)) { + return $this->getConnectionInitializer($options, $value); + } else { + throw new InvalidArgumentException(sprintf( + '%s expects either a string or a callable value, %s given', + static::class, + is_object($value) ? get_class($value) : gettype($value) + )); + } + } + + /** + * Returns a connection initializer from a descriptive name. + * + * @param OptionsInterface $options Client options + * @param string $description Identifier of a replication backend (`predis`, `sentinel`) + * + * @return callable + */ + protected function getConnectionInitializerByString(OptionsInterface $options, string $description) + { + switch ($description) { + case 'redis': + case 'redis-cluster': + return function ($parameters, $options, $option) { + return new RedisCluster($options->connections, new RedisStrategy($options->crc16)); + }; + + case 'predis': + return $this->getDefaultConnectionInitializer(); + + default: + throw new InvalidArgumentException(sprintf( + '%s expects either `predis`, `redis` or `redis-cluster` as valid string values, `%s` given', + static::class, + $description + )); + } + } + + /** + * Returns the default connection initializer. + * + * @return callable + */ + protected function getDefaultConnectionInitializer() + { + return function ($parameters, $options, $option) { + return new PredisCluster(); + }; + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return $this->getConnectionInitializer( + $options, + $this->getDefaultConnectionInitializer() + ); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Configuration/Option/Commands.php b/redis-cache/dependencies/predis/predis/src/Configuration/Option/Commands.php new file mode 100644 index 0000000..2fbe00e --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Configuration/Option/Commands.php @@ -0,0 +1,146 @@ +createFactoryByArray($options, $value); + } elseif (is_string($value)) { + return $this->createFactoryByString($options, $value); + } else { + throw new InvalidArgumentException(sprintf( + '%s expects a valid command factory', + static::class + )); + } + } + + /** + * Creates a new default command factory from a named array. + * + * The factory instance is configured according to the supplied named array + * mapping command IDs (passed as keys) to the FCQN of classes implementing + * Predis\Command\CommandInterface. + * + * @param OptionsInterface $options Client options container + * @param array $value Named array mapping command IDs to classes + * + * @return FactoryInterface + */ + protected function createFactoryByArray(OptionsInterface $options, array $value) + { + /** + * @var FactoryInterface + */ + $commands = $this->getDefault($options); + + foreach ($value as $commandID => $commandClass) { + if ($commandClass === null) { + $commands->undefine($commandID); + } else { + $commands->define($commandID, $commandClass); + } + } + + return $commands; + } + + /** + * Creates a new command factory from a descriptive string. + * + * The factory instance is configured according to the supplied descriptive + * string that identifies specific configurations of schemes and connection + * classes. Supported configuration values are: + * + * - "predis" returns the default command factory used by Predis + * - "raw" returns a command factory that creates only raw commands + * - "default" is simply an alias of "predis" + * + * @param OptionsInterface $options Client options container + * @param string $value Descriptive string identifying the desired configuration + * + * @return FactoryInterface + */ + protected function createFactoryByString(OptionsInterface $options, string $value) + { + switch (strtolower($value)) { + case 'default': + case 'predis': + return $this->getDefault($options); + + case 'raw': + return $this->createRawFactory($options); + + default: + throw new InvalidArgumentException(sprintf( + '%s does not recognize `%s` as a supported configuration string', + static::class, + $value + )); + } + } + + /** + * Creates a new raw command factory instance. + * + * @param OptionsInterface $options Client options container + */ + protected function createRawFactory(OptionsInterface $options): FactoryInterface + { + $commands = new RawFactory(); + + if (isset($options->prefix)) { + throw new InvalidArgumentException(sprintf( + '%s does not support key prefixing', RawFactory::class + )); + } + + return $commands; + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + $commands = new RedisFactory(); + + if (isset($options->prefix)) { + $commands->setProcessor($options->prefix); + } + + return $commands; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Configuration/Option/Connections.php b/redis-cache/dependencies/predis/predis/src/Configuration/Option/Connections.php new file mode 100644 index 0000000..fdc6656 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Configuration/Option/Connections.php @@ -0,0 +1,144 @@ +createFactoryByArray($options, $value); + } elseif (is_string($value)) { + return $this->createFactoryByString($options, $value); + } else { + throw new InvalidArgumentException(sprintf( + '%s expects a valid connection factory', static::class + )); + } + } + + /** + * Creates a new connection factory from a named array. + * + * The factory instance is configured according to the supplied named array + * mapping URI schemes (passed as keys) to the FCQN of classes implementing + * Predis\Connection\NodeConnectionInterface, or callable objects acting as + * lazy initalizers and returning new instances of classes implementing + * Predis\Connection\NodeConnectionInterface. + * + * @param OptionsInterface $options Client options + * @param array $value Named array mapping URI schemes to classes or callables + * + * @return FactoryInterface + */ + protected function createFactoryByArray(OptionsInterface $options, array $value) + { + /** + * @var FactoryInterface + */ + $factory = $this->getDefault($options); + + foreach ($value as $scheme => $initializer) { + $factory->define($scheme, $initializer); + } + + return $factory; + } + + /** + * Creates a new connection factory from a descriptive string. + * + * The factory instance is configured according to the supplied descriptive + * string that identifies specific configurations of schemes and connection + * classes. Supported configuration values are: + * + * - "phpiredis-stream" maps tcp, redis, unix to PhpiredisStreamConnection + * - "phpiredis-socket" maps tcp, redis, unix to PhpiredisSocketConnection + * - "phpiredis" is an alias of "phpiredis-stream" + * + * @param OptionsInterface $options Client options + * @param string $value Descriptive string identifying the desired configuration + * + * @return FactoryInterface + */ + protected function createFactoryByString(OptionsInterface $options, string $value) + { + /** + * @var FactoryInterface + */ + $factory = $this->getDefault($options); + + switch (strtolower($value)) { + case 'phpiredis': + case 'phpiredis-stream': + $factory->define('tcp', PhpiredisStreamConnection::class); + $factory->define('redis', PhpiredisStreamConnection::class); + $factory->define('unix', PhpiredisStreamConnection::class); + break; + + case 'phpiredis-socket': + $factory->define('tcp', PhpiredisSocketConnection::class); + $factory->define('redis', PhpiredisSocketConnection::class); + $factory->define('unix', PhpiredisSocketConnection::class); + break; + + case 'default': + return $factory; + + default: + throw new InvalidArgumentException(sprintf( + '%s does not recognize `%s` as a supported configuration string', static::class, $value + )); + } + + return $factory; + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + $factory = new Factory(); + + if ($options->defined('parameters')) { + $factory->setDefaultParameters($options->parameters); + } + + return $factory; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Configuration/Option/Exceptions.php b/redis-cache/dependencies/predis/predis/src/Configuration/Option/Exceptions.php new file mode 100644 index 0000000..6834272 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Configuration/Option/Exceptions.php @@ -0,0 +1,39 @@ +getConnectionInitializerByString($options, $value); + } + + if (is_callable($value)) { + return $this->getConnectionInitializer($options, $value); + } else { + throw new InvalidArgumentException(sprintf( + '%s expects either a string or a callable value, %s given', + static::class, + is_object($value) ? get_class($value) : gettype($value) + )); + } + } + + /** + * Returns a connection initializer (callable) from a descriptive string. + * + * Each connection initializer is specialized for the specified replication + * backend so that all the necessary steps for the configuration of the new + * aggregate connection are performed inside the initializer and the client + * receives a ready-to-use connection. + * + * Supported configuration values are: + * + * - `predis` for unmanaged replication setups + * - `redis-sentinel` for replication setups managed by redis-sentinel + * - `sentinel` is an alias of `redis-sentinel` + * + * @param OptionsInterface $options Client options + * @param string $description Identifier of a replication backend + * + * @return callable + */ + protected function getConnectionInitializerByString(OptionsInterface $options, string $description) + { + switch ($description) { + case 'sentinel': + case 'redis-sentinel': + return function ($parameters, $options) { + return new SentinelReplication($options->service, $parameters, $options->connections); + }; + + case 'predis': + return $this->getDefaultConnectionInitializer(); + + default: + throw new InvalidArgumentException(sprintf( + '%s expects either `predis`, `sentinel` or `redis-sentinel` as valid string values, `%s` given', + static::class, + $description + )); + } + } + + /** + * Returns the default connection initializer. + * + * @return callable + */ + protected function getDefaultConnectionInitializer() + { + return function ($parameters, $options) { + $connection = new MasterSlaveReplication(); + + if ($options->autodiscovery) { + $connection->setConnectionFactory($options->connections); + $connection->setAutoDiscovery(true); + } + + return $connection; + }; + } + + /** + * {@inheritdoc} + */ + public static function aggregate(OptionsInterface $options, AggregateConnectionInterface $connection, array $nodes) + { + if (!$connection instanceof SentinelReplication) { + parent::aggregate($options, $connection, $nodes); + } + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return $this->getConnectionInitializer( + $options, + $this->getDefaultConnectionInitializer() + ); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Configuration/OptionInterface.php b/redis-cache/dependencies/predis/predis/src/Configuration/OptionInterface.php new file mode 100644 index 0000000..538fc0b --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Configuration/OptionInterface.php @@ -0,0 +1,39 @@ + Option\Aggregate::class, + 'cluster' => Option\Cluster::class, + 'replication' => Option\Replication::class, + 'connections' => Option\Connections::class, + 'commands' => Option\Commands::class, + 'exceptions' => Option\Exceptions::class, + 'prefix' => Option\Prefix::class, + 'crc16' => Option\CRC16::class, + ]; + + /** @var array */ + protected $options = []; + + /** @var array */ + protected $input; + + /** + * @param array $options Named array of client options + */ + public function __construct(array $options = null) + { + $this->input = $options ?? []; + } + + /** + * {@inheritdoc} + */ + public function getDefault($option) + { + if (isset($this->handlers[$option])) { + $handler = $this->handlers[$option]; + $handler = new $handler(); + + return $handler->getDefault($this); + } + } + + /** + * {@inheritdoc} + */ + public function defined($option) + { + return + array_key_exists($option, $this->options) || + array_key_exists($option, $this->input) + ; + } + + /** + * {@inheritdoc} + */ + public function __isset($option) + { + return ( + array_key_exists($option, $this->options) || + array_key_exists($option, $this->input) + ) && $this->__get($option) !== null; + } + + /** + * {@inheritdoc} + */ + public function __get($option) + { + if (isset($this->options[$option]) || array_key_exists($option, $this->options)) { + return $this->options[$option]; + } + + if (isset($this->input[$option]) || array_key_exists($option, $this->input)) { + $value = $this->input[$option]; + unset($this->input[$option]); + + if (isset($this->handlers[$option])) { + $handler = $this->handlers[$option]; + $handler = new $handler(); + $value = $handler->filter($this, $value); + } elseif (is_object($value) && method_exists($value, '__invoke')) { + $value = $value($this); + } + + return $this->options[$option] = $value; + } + + if (isset($this->handlers[$option])) { + return $this->options[$option] = $this->getDefault($option); + } + + return; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Configuration/OptionsInterface.php b/redis-cache/dependencies/predis/predis/src/Configuration/OptionsInterface.php new file mode 100644 index 0000000..597a057 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Configuration/OptionsInterface.php @@ -0,0 +1,63 @@ +parameters = $this->assertParameters($parameters); + } + + /** + * Disconnects from the server and destroys the underlying resource when + * PHP's garbage collector kicks in. + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * Checks some of the parameters used to initialize the connection. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @return ParametersInterface + * @throws InvalidArgumentException + */ + abstract protected function assertParameters(ParametersInterface $parameters); + + /** + * Creates the underlying resource used to communicate with Redis. + * + * @return mixed + */ + abstract protected function createResource(); + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return isset($this->resource); + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (!$this->isConnected()) { + $this->resource = $this->createResource(); + + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + unset($this->resource); + } + + /** + * {@inheritdoc} + */ + public function addConnectCommand(CommandInterface $command) + { + $this->initCommands[] = $command; + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $this->writeRequest($command); + + return $this->readResponse($command); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->read(); + } + + /** + * Helper method to handle connection errors. + * + * @param string $message Error message. + * @param int $code Error code. + */ + protected function onConnectionError($message, $code = 0) + { + CommunicationException::handle( + new ConnectionException($this, "$message [{$this->getParameters()}]", $code) + ); + } + + /** + * Helper method to handle protocol errors. + * + * @param string $message Error message. + */ + protected function onProtocolError($message) + { + CommunicationException::handle( + new ProtocolException($this, "$message [{$this->getParameters()}]") + ); + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + if (isset($this->resource)) { + return $this->resource; + } + + $this->connect(); + + return $this->resource; + } + + /** + * {@inheritdoc} + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Gets an identifier for the connection. + * + * @return string + */ + protected function getIdentifier() + { + if ($this->parameters->scheme === 'unix') { + return $this->parameters->path; + } + + return "{$this->parameters->host}:{$this->parameters->port}"; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + if (!isset($this->cachedId)) { + $this->cachedId = $this->getIdentifier(); + } + + return $this->cachedId; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return ['parameters', 'initCommands']; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/AggregateConnectionInterface.php b/redis-cache/dependencies/predis/predis/src/Connection/AggregateConnectionInterface.php new file mode 100644 index 0000000..8864bba --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/AggregateConnectionInterface.php @@ -0,0 +1,56 @@ +strategy = $strategy ?: new PredisStrategy(); + $this->distributor = $this->strategy->getDistributor(); + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + foreach ($this->pool as $connection) { + if ($connection->isConnected()) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + foreach ($this->pool as $connection) { + $connection->connect(); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + foreach ($this->pool as $connection) { + $connection->disconnect(); + } + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $parameters = $connection->getParameters(); + + $this->pool[(string) $connection] = $connection; + + if (isset($parameters->alias)) { + $this->aliases[$parameters->alias] = $connection; + } + + $this->distributor->add($connection, $parameters->weight); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if (false !== $id = array_search($connection, $this->pool, true)) { + unset($this->pool[$id]); + $this->distributor->remove($connection); + + if ($this->aliases && $alias = $connection->getParameters()->alias) { + unset($this->aliases[$alias]); + } + + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getConnectionByCommand(CommandInterface $command) + { + $slot = $this->strategy->getSlot($command); + + if (!isset($slot)) { + throw new NotSupportedException( + "Cannot use '{$command->getId()}' over clusters of connections." + ); + } + + return $this->distributor->getBySlot($slot); + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($id) + { + return $this->pool[$id] ?? null; + } + + /** + * Returns a connection instance by its alias. + * + * @param string $alias Connection alias. + * + * @return NodeConnectionInterface|null + */ + public function getConnectionByAlias($alias) + { + return $this->aliases[$alias] ?? null; + } + + /** + * Retrieves a connection instance by slot. + * + * @param string $slot Slot name. + * + * @return NodeConnectionInterface|null + */ + public function getConnectionBySlot($slot) + { + return $this->distributor->getBySlot($slot); + } + + /** + * Retrieves a connection instance from the cluster using a key. + * + * @param string $key Key string. + * + * @return NodeConnectionInterface + */ + public function getConnectionByKey($key) + { + $hash = $this->strategy->getSlotByKey($key); + + return $this->distributor->getBySlot($hash); + } + + /** + * Returns the underlying command hash strategy used to hash commands by + * using keys found in their arguments. + * + * @return StrategyInterface + */ + public function getClusterStrategy() + { + return $this->strategy; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->pool); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->pool); + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->getConnectionByCommand($command)->writeRequest($command); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->getConnectionByCommand($command)->readResponse($command); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + return $this->getConnectionByCommand($command)->executeCommand($command); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/Cluster/RedisCluster.php b/redis-cache/dependencies/predis/predis/src/Connection/Cluster/RedisCluster.php new file mode 100644 index 0000000..e037694 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/Cluster/RedisCluster.php @@ -0,0 +1,672 @@ += 3.0.0). + * + * This connection backend offers smart support for redis-cluster by handling + * automatic slots map (re)generation upon -MOVED or -ASK responses returned by + * Redis when redirecting a client to a different node. + * + * The cluster can be pre-initialized using only a subset of the actual nodes in + * the cluster, Predis will do the rest by adjusting the slots map and creating + * the missing underlying connection instances on the fly. + * + * It is possible to pre-associate connections to a slots range with the "slots" + * parameter in the form "$first-$last". This can greatly reduce runtime node + * guessing and redirections. + * + * It is also possible to ask for the full and updated slots map directly to one + * of the nodes and optionally enable such a behaviour upon -MOVED redirections. + * Asking for the cluster configuration to Redis is actually done by issuing a + * CLUSTER SLOTS command to a random node in the pool. + */ +class RedisCluster implements ClusterInterface, IteratorAggregate, Countable +{ + private $useClusterSlots = true; + private $pool = []; + private $slots = []; + private $slotmap; + private $strategy; + private $connections; + private $retryLimit = 5; + private $retryInterval = 10; + + /** + * @param FactoryInterface $connections Optional connection factory. + * @param StrategyInterface $strategy Optional cluster strategy. + */ + public function __construct( + FactoryInterface $connections, + StrategyInterface $strategy = null + ) { + $this->connections = $connections; + $this->strategy = $strategy ?: new RedisClusterStrategy(); + $this->slotmap = new SlotMap(); + } + + /** + * Sets the maximum number of retries for commands upon server failure. + * + * -1 = unlimited retry attempts + * 0 = no retry attempts (fails immediately) + * n = fail only after n retry attempts + * + * @param int $retry Number of retry attempts. + */ + public function setRetryLimit($retry) + { + $this->retryLimit = (int) $retry; + } + + /** + * Sets the initial retry interval (milliseconds). + * + * @param int $retryInterval Milliseconds between retries. + */ + public function setRetryInterval($retryInterval) + { + $this->retryInterval = (int) $retryInterval; + } + + /** + * Returns the retry interval (milliseconds). + * + * @return int Milliseconds between retries. + */ + public function getRetryInterval() + { + return (int) $this->retryInterval; + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + foreach ($this->pool as $connection) { + if ($connection->isConnected()) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if ($connection = $this->getRandomConnection()) { + $connection->connect(); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + foreach ($this->pool as $connection) { + $connection->disconnect(); + } + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $this->pool[(string) $connection] = $connection; + $this->slotmap->reset(); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if (false !== $id = array_search($connection, $this->pool, true)) { + $this->slotmap->reset(); + $this->slots = array_diff($this->slots, [$connection]); + unset($this->pool[$id]); + + return true; + } + + return false; + } + + /** + * Removes a connection instance by using its identifier. + * + * @param string $connectionID Connection identifier. + * + * @return bool True if the connection was in the pool. + */ + public function removeById($connectionID) + { + if (isset($this->pool[$connectionID])) { + $this->slotmap->reset(); + $this->slots = array_diff($this->slots, [$connectionID]); + unset($this->pool[$connectionID]); + + return true; + } + + return false; + } + + /** + * Generates the current slots map by guessing the cluster configuration out + * of the connection parameters of the connections in the pool. + * + * Generation is based on the same algorithm used by Redis to generate the + * cluster, so it is most effective when all of the connections supplied on + * initialization have the "slots" parameter properly set accordingly to the + * current cluster configuration. + */ + public function buildSlotMap() + { + $this->slotmap->reset(); + + foreach ($this->pool as $connectionID => $connection) { + $parameters = $connection->getParameters(); + + if (!isset($parameters->slots)) { + continue; + } + + foreach (explode(',', $parameters->slots) as $slotRange) { + $slots = explode('-', $slotRange, 2); + + if (!isset($slots[1])) { + $slots[1] = $slots[0]; + } + + $this->slotmap->setSlots($slots[0], $slots[1], $connectionID); + } + } + } + + /** + * Queries the specified node of the cluster to fetch the updated slots map. + * + * When the connection fails, this method tries to execute the same command + * on a different connection picked at random from the pool of known nodes, + * up until the retry limit is reached. + * + * @param NodeConnectionInterface $connection Connection to a node of the cluster. + * + * @return mixed + */ + private function queryClusterNodeForSlotMap(NodeConnectionInterface $connection) + { + $retries = 0; + $retryAfter = $this->retryInterval; + $command = RawCommand::create('CLUSTER', 'SLOTS'); + + while ($retries <= $this->retryLimit) { + try { + $response = $connection->executeCommand($command); + break; + } catch (ConnectionException $exception) { + $connection = $exception->getConnection(); + $connection->disconnect(); + + $this->remove($connection); + + if ($retries === $this->retryLimit) { + throw $exception; + } + + if (!$connection = $this->getRandomConnection()) { + throw new ClientException('No connections left in the pool for `CLUSTER SLOTS`'); + } + + usleep($retryAfter * 1000); + $retryAfter = $retryAfter * 2; + ++$retries; + } + } + + return $response; + } + + /** + * Generates an updated slots map fetching the cluster configuration using + * the CLUSTER SLOTS command against the specified node or a random one from + * the pool. + * + * @param NodeConnectionInterface $connection Optional connection instance. + */ + public function askSlotMap(NodeConnectionInterface $connection = null) + { + if (!$connection && !$connection = $this->getRandomConnection()) { + return; + } + + $this->slotmap->reset(); + + $response = $this->queryClusterNodeForSlotMap($connection); + + foreach ($response as $slots) { + // We only support master servers for now, so we ignore subsequent + // elements in the $slots array identifying slaves. + [$start, $end, $master] = $slots; + + if ($master[0] === '') { + $this->slotmap->setSlots($start, $end, (string) $connection); + } else { + $this->slotmap->setSlots($start, $end, "{$master[0]}:{$master[1]}"); + } + } + } + + /** + * Guesses the correct node associated to a given slot using a precalculated + * slots map, falling back to the same logic used by Redis to initialize a + * cluster (best-effort). + * + * @param int $slot Slot index. + * + * @return string Connection ID. + */ + protected function guessNode($slot) + { + if (!$this->pool) { + throw new ClientException('No connections available in the pool'); + } + + if ($this->slotmap->isEmpty()) { + $this->buildSlotMap(); + } + + if ($node = $this->slotmap[$slot]) { + return $node; + } + + $count = count($this->pool); + $index = min((int) ($slot / (int) (16384 / $count)), $count - 1); + $nodes = array_keys($this->pool); + + return $nodes[$index]; + } + + /** + * Creates a new connection instance from the given connection ID. + * + * @param string $connectionID Identifier for the connection. + * + * @return NodeConnectionInterface + */ + protected function createConnection($connectionID) + { + $separator = strrpos($connectionID, ':'); + + return $this->connections->create([ + 'host' => substr($connectionID, 0, $separator), + 'port' => substr($connectionID, $separator + 1), + ]); + } + + /** + * {@inheritdoc} + */ + public function getConnectionByCommand(CommandInterface $command) + { + $slot = $this->strategy->getSlot($command); + + if (!isset($slot)) { + throw new NotSupportedException( + "Cannot use '{$command->getId()}' with redis-cluster." + ); + } + + if (isset($this->slots[$slot])) { + return $this->slots[$slot]; + } else { + return $this->getConnectionBySlot($slot); + } + } + + /** + * Returns the connection currently associated to a given slot. + * + * @param int $slot Slot index. + * + * @return NodeConnectionInterface + * @throws OutOfBoundsException + */ + public function getConnectionBySlot($slot) + { + if (!SlotMap::isValid($slot)) { + throw new OutOfBoundsException("Invalid slot [$slot]."); + } + + if (isset($this->slots[$slot])) { + return $this->slots[$slot]; + } + + $connectionID = $this->guessNode($slot); + + if (!$connection = $this->getConnectionById($connectionID)) { + $connection = $this->createConnection($connectionID); + $this->pool[$connectionID] = $connection; + } + + return $this->slots[$slot] = $connection; + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($connectionID) + { + return $this->pool[$connectionID] ?? null; + } + + /** + * Returns a random connection from the pool. + * + * @return NodeConnectionInterface|null + */ + protected function getRandomConnection() + { + if (!$this->pool) { + return null; + } + + return $this->pool[array_rand($this->pool)]; + } + + /** + * Permanently associates the connection instance to a new slot. + * The connection is added to the connections pool if not yet included. + * + * @param NodeConnectionInterface $connection Connection instance. + * @param int $slot Target slot index. + */ + protected function move(NodeConnectionInterface $connection, $slot) + { + $this->pool[(string) $connection] = $connection; + $this->slots[(int) $slot] = $connection; + $this->slotmap[(int) $slot] = $connection; + } + + /** + * Handles -ERR responses returned by Redis. + * + * @param CommandInterface $command Command that generated the -ERR response. + * @param ErrorResponseInterface $error Redis error response object. + * + * @return mixed + */ + protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $error) + { + $details = explode(' ', $error->getMessage(), 2); + + switch ($details[0]) { + case 'MOVED': + return $this->onMovedResponse($command, $details[1]); + + case 'ASK': + return $this->onAskResponse($command, $details[1]); + + default: + return $error; + } + } + + /** + * Handles -MOVED responses by executing again the command against the node + * indicated by the Redis response. + * + * @param CommandInterface $command Command that generated the -MOVED response. + * @param string $details Parameters of the -MOVED response. + * + * @return mixed + */ + protected function onMovedResponse(CommandInterface $command, $details) + { + [$slot, $connectionID] = explode(' ', $details, 2); + + if (!$connection = $this->getConnectionById($connectionID)) { + $connection = $this->createConnection($connectionID); + } + + if ($this->useClusterSlots) { + $this->askSlotMap($connection); + } + + $this->move($connection, $slot); + + return $this->executeCommand($command); + } + + /** + * Handles -ASK responses by executing again the command against the node + * indicated by the Redis response. + * + * @param CommandInterface $command Command that generated the -ASK response. + * @param string $details Parameters of the -ASK response. + * + * @return mixed + */ + protected function onAskResponse(CommandInterface $command, $details) + { + [$slot, $connectionID] = explode(' ', $details, 2); + + if (!$connection = $this->getConnectionById($connectionID)) { + $connection = $this->createConnection($connectionID); + } + + $connection->executeCommand(RawCommand::create('ASKING')); + + return $connection->executeCommand($command); + } + + /** + * Ensures that a command is executed one more time on connection failure. + * + * The connection to the node that generated the error is evicted from the + * pool before trying to fetch an updated slots map from another node. If + * the new slots map points to an unreachable server the client gives up and + * throws the exception as the nodes participating in the cluster may still + * have to agree that something changed in the configuration of the cluster. + * + * @param CommandInterface $command Command instance. + * @param string $method Actual method. + * + * @return mixed + */ + private function retryCommandOnFailure(CommandInterface $command, $method) + { + $retries = 0; + $retryAfter = $this->retryInterval; + + while ($retries <= $this->retryLimit) { + try { + $response = $this->getConnectionByCommand($command)->$method($command); + + if ($response instanceof ErrorResponse) { + $message = $response->getMessage(); + + if (strpos($message, 'CLUSTERDOWN') !== false) { + throw new ServerException($message); + } + } + + break; + } catch (Throwable $exception) { + usleep($retryAfter * 1000); + $retryAfter = $retryAfter * 2; + + if ($exception instanceof ConnectionException) { + $connection = $exception->getConnection(); + + if ($connection) { + $connection->disconnect(); + $this->remove($connection); + } + } + + if ($retries === $this->retryLimit) { + throw $exception; + } + + if ($this->useClusterSlots) { + $this->askSlotMap(); + } + + ++$retries; + } + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $response = $this->retryCommandOnFailure($command, __FUNCTION__); + + if ($response instanceof ErrorResponseInterface) { + return $this->onErrorResponse($command, $response); + } + + return $response; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->pool); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function getIterator() + { + if ($this->slotmap->isEmpty()) { + $this->useClusterSlots ? $this->askSlotMap() : $this->buildSlotMap(); + } + + $connections = []; + + foreach ($this->slotmap->getNodes() as $node) { + if (!$connection = $this->getConnectionById($node)) { + $this->add($connection = $this->createConnection($node)); + } + + $connections[] = $connection; + } + + return new ArrayIterator($connections); + } + + /** + * Returns the underlying slot map. + * + * @return SlotMap + */ + public function getSlotMap() + { + return $this->slotmap; + } + + /** + * Returns the underlying command hash strategy used to hash commands by + * using keys found in their arguments. + * + * @return StrategyInterface + */ + public function getClusterStrategy() + { + return $this->strategy; + } + + /** + * Returns the underlying connection factory used to create new connection + * instances to Redis nodes indicated by redis-cluster. + * + * @return FactoryInterface + */ + public function getConnectionFactory() + { + return $this->connections; + } + + /** + * Enables automatic fetching of the current slots map from one of the nodes + * using the CLUSTER SLOTS command. This option is enabled by default as + * asking the current slots map to Redis upon -MOVED responses may reduce + * overhead by eliminating the trial-and-error nature of the node guessing + * procedure, mostly when targeting many keys that would end up in a lot of + * redirections. + * + * The slots map can still be manually fetched using the askSlotMap() + * method whether or not this option is enabled. + * + * @param bool $value Enable or disable the use of CLUSTER SLOTS. + */ + public function useClusterSlots($value) + { + $this->useClusterSlots = (bool) $value; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/CompositeConnectionInterface.php b/redis-cache/dependencies/predis/predis/src/Connection/CompositeConnectionInterface.php new file mode 100644 index 0000000..22b8c5f --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/CompositeConnectionInterface.php @@ -0,0 +1,48 @@ +parameters = $this->assertParameters($parameters); + $this->protocol = $protocol ?: new TextProtocolProcessor(); + } + + /** + * {@inheritdoc} + */ + public function getProtocol() + { + return $this->protocol; + } + + /** + * {@inheritdoc} + */ + public function writeBuffer($buffer) + { + $this->write($buffer); + } + + /** + * {@inheritdoc} + */ + public function readBuffer($length) + { + if ($length <= 0) { + throw new InvalidArgumentException('Length parameter must be greater than 0.'); + } + + $value = ''; + $socket = $this->getResource(); + + do { + $chunk = fread($socket, $length); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading bytes from the server.'); + } + + $value .= $chunk; + } while (($length -= strlen($chunk)) > 0); + + return $value; + } + + /** + * {@inheritdoc} + */ + public function readLine() + { + $value = ''; + $socket = $this->getResource(); + + do { + $chunk = fgets($socket); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading line from the server.'); + } + + $value .= $chunk; + } while (substr($value, -2) !== "\r\n"); + + return substr($value, 0, -2); + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->protocol->write($this, $command); + } + + /** + * {@inheritdoc} + */ + public function read() + { + return $this->protocol->read($this); + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array_merge(parent::__sleep(), ['protocol']); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/ConnectionException.php b/redis-cache/dependencies/predis/predis/src/Connection/ConnectionException.php new file mode 100644 index 0000000..77e7a15 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/ConnectionException.php @@ -0,0 +1,22 @@ + 'Predis\Connection\StreamConnection', + 'unix' => 'Predis\Connection\StreamConnection', + 'tls' => 'Predis\Connection\StreamConnection', + 'redis' => 'Predis\Connection\StreamConnection', + 'rediss' => 'Predis\Connection\StreamConnection', + 'http' => 'Predis\Connection\WebdisConnection', + ]; + + /** + * Checks if the provided argument represents a valid connection class + * implementing Predis\Connection\NodeConnectionInterface. Optionally, + * callable objects are used for lazy initialization of connection objects. + * + * @param mixed $initializer FQN of a connection class or a callable for lazy initialization. + * + * @return mixed + * @throws InvalidArgumentException + */ + protected function checkInitializer($initializer) + { + if (is_callable($initializer)) { + return $initializer; + } + + $class = new ReflectionClass($initializer); + + if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) { + throw new InvalidArgumentException( + 'A connection initializer must be a valid connection class or a callable object.' + ); + } + + return $initializer; + } + + /** + * {@inheritdoc} + */ + public function define($scheme, $initializer) + { + $this->schemes[$scheme] = $this->checkInitializer($initializer); + } + + /** + * {@inheritdoc} + */ + public function undefine($scheme) + { + unset($this->schemes[$scheme]); + } + + /** + * {@inheritdoc} + */ + public function create($parameters) + { + if (!$parameters instanceof ParametersInterface) { + $parameters = $this->createParameters($parameters); + } + + $scheme = $parameters->scheme; + + if (!isset($this->schemes[$scheme])) { + throw new InvalidArgumentException("Unknown connection scheme: '$scheme'."); + } + + $initializer = $this->schemes[$scheme]; + + if (is_callable($initializer)) { + $connection = call_user_func($initializer, $parameters, $this); + } else { + $connection = new $initializer($parameters); + $this->prepareConnection($connection); + } + + if (!$connection instanceof NodeConnectionInterface) { + throw new UnexpectedValueException( + 'Objects returned by connection initializers must implement ' . + "'Predis\Connection\NodeConnectionInterface'." + ); + } + + return $connection; + } + + /** + * Assigns a default set of parameters applied to new connections. + * + * The set of parameters passed to create a new connection have precedence + * over the default values set for the connection factory. + * + * @param array $parameters Set of connection parameters. + */ + public function setDefaultParameters(array $parameters) + { + $this->defaults = $parameters; + } + + /** + * Returns the default set of parameters applied to new connections. + * + * @return array + */ + public function getDefaultParameters() + { + return $this->defaults; + } + + /** + * Creates a connection parameters instance from the supplied argument. + * + * @param mixed $parameters Original connection parameters. + * + * @return ParametersInterface + */ + protected function createParameters($parameters) + { + if (is_string($parameters)) { + $parameters = Parameters::parse($parameters); + } else { + $parameters = $parameters ?: []; + } + + if ($this->defaults) { + $parameters += $this->defaults; + } + + return new Parameters($parameters); + } + + /** + * Prepares a connection instance after its initialization. + * + * @param NodeConnectionInterface $connection Connection instance. + */ + protected function prepareConnection(NodeConnectionInterface $connection) + { + $parameters = $connection->getParameters(); + + if (isset($parameters->password) && strlen($parameters->password)) { + $cmdAuthArgs = isset($parameters->username) && strlen($parameters->username) + ? [$parameters->username, $parameters->password] + : [$parameters->password]; + + $connection->addConnectCommand( + new RawCommand('AUTH', $cmdAuthArgs) + ); + } + + if (isset($parameters->database) && strlen($parameters->database)) { + $connection->addConnectCommand( + new RawCommand('SELECT', [$parameters->database]) + ); + } + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/FactoryInterface.php b/redis-cache/dependencies/predis/predis/src/Connection/FactoryInterface.php new file mode 100644 index 0000000..24dc782 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/FactoryInterface.php @@ -0,0 +1,43 @@ + 'tcp', + 'host' => '127.0.0.1', + 'port' => 6379, + ]; + + /** + * Set of connection parameters already filtered + * for NULL or 0-length string values. + * + * @var array + */ + protected $parameters; + + /** + * @param array $parameters Named array of connection parameters. + */ + public function __construct(array $parameters = []) + { + $this->parameters = $this->filter($parameters + static::$defaults); + } + + /** + * Filters parameters removing entries with NULL or 0-length string values. + * + * @params array $parameters Array of parameters to be filtered + * + * @return array + */ + protected function filter(array $parameters) + { + return array_filter($parameters, function ($value) { + return $value !== null && $value !== ''; + }); + } + + /** + * Creates a new instance by supplying the initial parameters either in the + * form of an URI string or a named array. + * + * @param array|string $parameters Set of connection parameters. + * + * @return Parameters + */ + public static function create($parameters) + { + if (is_string($parameters)) { + $parameters = static::parse($parameters); + } + + return new static($parameters ?: []); + } + + /** + * Parses an URI string returning an array of connection parameters. + * + * When using the "redis" and "rediss" schemes the URI is parsed according + * to the rules defined by the provisional registration documents approved + * by IANA. If the URI has a password in its "user-information" part or a + * database number in the "path" part these values override the values of + * "password" and "database" if they are present in the "query" part. + * + * @see http://www.iana.org/assignments/uri-schemes/prov/redis + * @see http://www.iana.org/assignments/uri-schemes/prov/rediss + * + * @param string $uri URI string. + * + * @return array + * @throws InvalidArgumentException + */ + public static function parse($uri) + { + if (stripos($uri, 'unix://') === 0) { + // parse_url() can parse unix:/path/to/sock so we do not need the + // unix:///path/to/sock hack, we will support it anyway until 2.0. + $uri = str_ireplace('unix://', 'unix:', $uri); + } + + if (!$parsed = parse_url($uri)) { + throw new InvalidArgumentException("Invalid parameters URI: $uri"); + } + + if ( + isset($parsed['host']) + && false !== strpos($parsed['host'], '[') + && false !== strpos($parsed['host'], ']') + ) { + $parsed['host'] = substr($parsed['host'], 1, -1); + } + + if (isset($parsed['query'])) { + parse_str($parsed['query'], $queryarray); + unset($parsed['query']); + + $parsed = array_merge($parsed, $queryarray); + } + + if (stripos($uri, 'redis') === 0) { + if (isset($parsed['user'])) { + if (strlen($parsed['user'])) { + $parsed['username'] = $parsed['user']; + } + unset($parsed['user']); + } + + if (isset($parsed['pass'])) { + if (strlen($parsed['pass'])) { + $parsed['password'] = $parsed['pass']; + } + unset($parsed['pass']); + } + + if (isset($parsed['path']) && preg_match('/^\/(\d+)(\/.*)?/', $parsed['path'], $path)) { + $parsed['database'] = $path[1]; + + if (isset($path[2])) { + $parsed['path'] = $path[2]; + } else { + unset($parsed['path']); + } + } + } + + return $parsed; + } + + /** + * {@inheritdoc} + */ + public function toArray() + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function __get($parameter) + { + if (isset($this->parameters[$parameter])) { + return $this->parameters[$parameter]; + } + } + + /** + * {@inheritdoc} + */ + public function __isset($parameter) + { + return isset($this->parameters[$parameter]); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + if ($this->scheme === 'unix') { + return "$this->scheme:$this->path"; + } + + if (filter_var($this->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + return "$this->scheme://[$this->host]:$this->port"; + } + + return "$this->scheme://$this->host:$this->port"; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return ['parameters']; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/ParametersInterface.php b/redis-cache/dependencies/predis/predis/src/Connection/ParametersInterface.php new file mode 100644 index 0000000..25c7e1a --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/ParametersInterface.php @@ -0,0 +1,68 @@ +assertExtensions(); + + parent::__construct($parameters); + + $this->reader = $this->createReader(); + } + + /** + * Disconnects from the server and destroys the underlying resource and the + * protocol reader resource when PHP's garbage collector kicks in. + */ + public function __destruct() + { + parent::__destruct(); + + phpiredis_reader_destroy($this->reader); + } + + /** + * Checks if the socket and phpiredis extensions are loaded in PHP. + */ + protected function assertExtensions() + { + if (!extension_loaded('sockets')) { + throw new NotSupportedException( + 'The "sockets" extension is required by this connection backend.' + ); + } + + if (!extension_loaded('phpiredis')) { + throw new NotSupportedException( + 'The "phpiredis" extension is required by this connection backend.' + ); + } + } + + /** + * {@inheritdoc} + */ + protected function assertParameters(ParametersInterface $parameters) + { + switch ($parameters->scheme) { + case 'tcp': + case 'redis': + case 'unix': + break; + + default: + throw new InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); + } + + if (isset($parameters->persistent)) { + throw new NotSupportedException( + 'Persistent connections are not supported by this connection backend.' + ); + } + + return $parameters; + } + + /** + * Creates a new instance of the protocol reader resource. + * + * @return resource + */ + private function createReader() + { + $reader = phpiredis_reader_create(); + + phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); + phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); + + return $reader; + } + + /** + * Returns the underlying protocol reader resource. + * + * @return resource + */ + protected function getReader() + { + return $this->reader; + } + + /** + * Returns the handler used by the protocol reader for inline responses. + * + * @return Closure + */ + protected function getStatusHandler() + { + static $statusHandler; + + if (!$statusHandler) { + $statusHandler = function ($payload) { + return StatusResponse::get($payload); + }; + } + + return $statusHandler; + } + + /** + * Returns the handler used by the protocol reader for error responses. + * + * @return Closure + */ + protected function getErrorHandler() + { + static $errorHandler; + + if (!$errorHandler) { + $errorHandler = function ($errorMessage) { + return new ErrorResponse($errorMessage); + }; + } + + return $errorHandler; + } + + /** + * Helper method used to throw exceptions on socket errors. + */ + private function emitSocketError() + { + $errno = socket_last_error(); + $errstr = socket_strerror($errno); + + $this->disconnect(); + + $this->onConnectionError(trim($errstr), $errno); + } + + /** + * Gets the address of an host from connection parameters. + * + * @param ParametersInterface $parameters Parameters used to initialize the connection. + * + * @return string + */ + protected static function getAddress(ParametersInterface $parameters) + { + if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP)) { + return $host; + } + + if ($host === $address = gethostbyname($host)) { + return false; + } + + return $address; + } + + /** + * {@inheritdoc} + */ + protected function createResource() + { + $parameters = $this->parameters; + + if ($parameters->scheme === 'unix') { + $address = $parameters->path; + $domain = AF_UNIX; + $protocol = 0; + } else { + if (false === $address = self::getAddress($parameters)) { + $this->onConnectionError("Cannot resolve the address of '$parameters->host'."); + } + + $domain = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? AF_INET6 : AF_INET; + $protocol = SOL_TCP; + } + + if (false === $socket = @socket_create($domain, SOCK_STREAM, $protocol)) { + $this->emitSocketError(); + } + + $this->setSocketOptions($socket, $parameters); + $this->connectWithTimeout($socket, $address, $parameters); + + return $socket; + } + + /** + * Sets options on the socket resource from the connection parameters. + * + * @param resource $socket Socket resource. + * @param ParametersInterface $parameters Parameters used to initialize the connection. + */ + private function setSocketOptions($socket, ParametersInterface $parameters) + { + if ($parameters->scheme !== 'unix') { + if (!socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1)) { + $this->emitSocketError(); + } + + if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) { + $this->emitSocketError(); + } + } + + if (isset($parameters->read_write_timeout)) { + $rwtimeout = (float) $parameters->read_write_timeout; + $timeoutSec = floor($rwtimeout); + $timeoutUsec = ($rwtimeout - $timeoutSec) * 1000000; + + $timeout = [ + 'sec' => $timeoutSec, + 'usec' => $timeoutUsec, + ]; + + if (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout)) { + $this->emitSocketError(); + } + + if (!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout)) { + $this->emitSocketError(); + } + } + } + + /** + * Opens the actual connection to the server with a timeout. + * + * @param resource $socket Socket resource. + * @param string $address IP address (DNS-resolved from hostname) + * @param ParametersInterface $parameters Parameters used to initialize the connection. + * + * @return void + */ + private function connectWithTimeout($socket, $address, ParametersInterface $parameters) + { + socket_set_nonblock($socket); + + if (@socket_connect($socket, $address, (int) $parameters->port) === false) { + $error = socket_last_error(); + + if ($error != SOCKET_EINPROGRESS && $error != SOCKET_EALREADY) { + $this->emitSocketError(); + } + } + + socket_set_block($socket); + + $null = null; + $selectable = [$socket]; + + $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); + $timeoutSecs = floor($timeout); + $timeoutUSecs = ($timeout - $timeoutSecs) * 1000000; + + $selected = socket_select($selectable, $selectable, $null, $timeoutSecs, $timeoutUSecs); + + if ($selected === 2) { + $this->onConnectionError('Connection refused.', SOCKET_ECONNREFUSED); + } + + if ($selected === 0) { + $this->onConnectionError('Connection timed out.', SOCKET_ETIMEDOUT); + } + + if ($selected === false) { + $this->emitSocketError(); + } + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (parent::connect() && $this->initCommands) { + foreach ($this->initCommands as $command) { + $response = $this->executeCommand($command); + + if ($response instanceof ErrorResponseInterface) { + $this->onConnectionError("`{$command->getId()}` failed: {$response->getMessage()}", 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + if ($this->isConnected()) { + phpiredis_reader_reset($this->reader); + socket_close($this->getResource()); + + parent::disconnect(); + } + } + + /** + * {@inheritdoc} + */ + protected function write($buffer) + { + $socket = $this->getResource(); + + while (($length = strlen($buffer)) > 0) { + $written = socket_write($socket, $buffer, $length); + + if ($length === $written) { + return; + } + + if ($written === false) { + $this->onConnectionError('Error while writing bytes to the server.'); + } + + $buffer = substr($buffer, $written); + } + } + + /** + * {@inheritdoc} + */ + public function read() + { + $socket = $this->getResource(); + $reader = $this->reader; + + while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) { + if (@socket_recv($socket, $buffer, 4096, 0) === false || $buffer === '' || $buffer === null) { + $this->emitSocketError(); + } + + phpiredis_reader_feed($reader, $buffer); + } + + if ($state === PHPIREDIS_READER_STATE_COMPLETE) { + return phpiredis_reader_get_reply($reader); + } else { + $this->onProtocolError(phpiredis_reader_get_error($reader)); + + return; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $arguments = $command->getArguments(); + array_unshift($arguments, $command->getId()); + + $this->write(phpiredis_format_command($arguments)); + } + + /** + * {@inheritdoc} + */ + public function __wakeup() + { + $this->assertExtensions(); + $this->reader = $this->createReader(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/PhpiredisStreamConnection.php b/redis-cache/dependencies/predis/predis/src/Connection/PhpiredisStreamConnection.php new file mode 100644 index 0000000..e3dbfd8 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/PhpiredisStreamConnection.php @@ -0,0 +1,262 @@ +assertExtensions(); + + parent::__construct($parameters); + + $this->reader = $this->createReader(); + } + + /** + * {@inheritdoc} + */ + public function __destruct() + { + parent::__destruct(); + + phpiredis_reader_destroy($this->reader); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + phpiredis_reader_reset($this->reader); + + parent::disconnect(); + } + + /** + * Checks if the phpiredis extension is loaded in PHP. + */ + private function assertExtensions() + { + if (!extension_loaded('phpiredis')) { + throw new NotSupportedException( + 'The "phpiredis" extension is required by this connection backend.' + ); + } + } + + /** + * {@inheritdoc} + */ + protected function assertParameters(ParametersInterface $parameters) + { + switch ($parameters->scheme) { + case 'tcp': + case 'redis': + case 'unix': + break; + + case 'tls': + case 'rediss': + throw new InvalidArgumentException('SSL encryption is not supported by this connection backend.'); + default: + throw new InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); + } + + return $parameters; + } + + /** + * {@inheritdoc} + */ + protected function createStreamSocket(ParametersInterface $parameters, $address, $flags) + { + $socket = null; + $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); + $context = stream_context_create(['socket' => ['tcp_nodelay' => (bool) $parameters->tcp_nodelay]]); + + if (!$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags, $context)) { + $this->onConnectionError(trim($errstr), $errno); + } + + if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) { + $rwtimeout = (float) $parameters->read_write_timeout; + $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; + + $timeout = [ + 'sec' => $timeoutSeconds = floor($rwtimeout), + 'usec' => ($rwtimeout - $timeoutSeconds) * 1000000, + ]; + + $socket = $socket ?: socket_import_stream($resource); + @socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout); + @socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout); + } + + if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) { + $socket = $socket ?: socket_import_stream($resource); + socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay); + } + + return $resource; + } + + /** + * Creates a new instance of the protocol reader resource. + * + * @return resource + */ + private function createReader() + { + $reader = phpiredis_reader_create(); + + phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); + phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); + + return $reader; + } + + /** + * Returns the underlying protocol reader resource. + * + * @return resource + */ + protected function getReader() + { + return $this->reader; + } + + /** + * Returns the handler used by the protocol reader for inline responses. + * + * @return Closure + */ + protected function getStatusHandler() + { + static $statusHandler; + + if (!$statusHandler) { + $statusHandler = function ($payload) { + return StatusResponse::get($payload); + }; + } + + return $statusHandler; + } + + /** + * Returns the handler used by the protocol reader for error responses. + * + * @return Closure + */ + protected function getErrorHandler() + { + static $errorHandler; + + if (!$errorHandler) { + $errorHandler = function ($errorMessage) { + return new ErrorResponse($errorMessage); + }; + } + + return $errorHandler; + } + + /** + * {@inheritdoc} + */ + public function read() + { + $socket = $this->getResource(); + $reader = $this->reader; + + while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) { + $buffer = stream_socket_recvfrom($socket, 4096); + + if ($buffer === false || $buffer === '') { + $this->onConnectionError('Error while reading bytes from the server.'); + } + + phpiredis_reader_feed($reader, $buffer); + } + + if ($state === PHPIREDIS_READER_STATE_COMPLETE) { + return phpiredis_reader_get_reply($reader); + } else { + $this->onProtocolError(phpiredis_reader_get_error($reader)); + + return; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $arguments = $command->getArguments(); + array_unshift($arguments, $command->getId()); + + $this->write(phpiredis_format_command($arguments)); + } + + /** + * {@inheritdoc} + */ + public function __wakeup() + { + $this->assertExtensions(); + $this->reader = $this->createReader(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/Replication/MasterSlaveReplication.php b/redis-cache/dependencies/predis/predis/src/Connection/Replication/MasterSlaveReplication.php new file mode 100644 index 0000000..9d57a5a --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/Replication/MasterSlaveReplication.php @@ -0,0 +1,553 @@ +strategy = $strategy ?: new ReplicationStrategy(); + } + + /** + * Configures the automatic discovery of the replication configuration on failure. + * + * @param bool $value Enable or disable auto discovery. + */ + public function setAutoDiscovery($value) + { + if (!$this->connectionFactory) { + throw new ClientException('Automatic discovery requires a connection factory'); + } + + $this->autoDiscovery = (bool) $value; + } + + /** + * Sets the connection factory used to create the connections by the auto + * discovery procedure. + * + * @param FactoryInterface $connectionFactory Connection factory instance. + */ + public function setConnectionFactory(FactoryInterface $connectionFactory) + { + $this->connectionFactory = $connectionFactory; + } + + /** + * Resets the connection state. + */ + protected function reset() + { + $this->current = null; + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $parameters = $connection->getParameters(); + + if ('master' === $parameters->role) { + $this->master = $connection; + } else { + // everything else is considered a slvave. + $this->slaves[] = $connection; + } + + if (isset($parameters->alias)) { + $this->aliases[$parameters->alias] = $connection; + } + + $this->pool[(string) $connection] = $connection; + + $this->reset(); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if ($connection === $this->master) { + $this->master = null; + } elseif (false !== $id = array_search($connection, $this->slaves, true)) { + unset($this->slaves[$id]); + } else { + return false; + } + + unset($this->pool[(string) $connection]); + + if ($this->aliases && $alias = $connection->getParameters()->alias) { + unset($this->aliases[$alias]); + } + + $this->reset(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getConnectionByCommand(CommandInterface $command) + { + if (!$this->current) { + if ($this->strategy->isReadOperation($command) && $slave = $this->pickSlave()) { + $this->current = $slave; + } else { + $this->current = $this->getMasterOrDie(); + } + + return $this->current; + } + + if ($this->current === $master = $this->getMasterOrDie()) { + return $master; + } + + if (!$this->strategy->isReadOperation($command) || !$this->slaves) { + $this->current = $master; + } + + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($id) + { + return $this->pool[$id] ?? null; + } + + /** + * Returns a connection instance by its alias. + * + * @param string $alias Connection alias. + * + * @return NodeConnectionInterface|null + */ + public function getConnectionByAlias($alias) + { + return $this->aliases[$alias] ?? null; + } + + /** + * Returns a connection by its role. + * + * @param string $role Connection role (`master` or `slave`) + * + * @return NodeConnectionInterface|null + */ + public function getConnectionByRole($role) + { + if ($role === 'master') { + return $this->getMaster(); + } elseif ($role === 'slave') { + return $this->pickSlave(); + } + + return null; + } + + /** + * Switches the internal connection in use by the backend. + * + * @param NodeConnectionInterface $connection Connection instance in the pool. + */ + public function switchTo(NodeConnectionInterface $connection) + { + if ($connection && $connection === $this->current) { + return; + } + + if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) { + throw new InvalidArgumentException('Invalid connection or connection not found.'); + } + + $this->current = $connection; + } + + /** + * {@inheritdoc} + */ + public function switchToMaster() + { + if (!$connection = $this->getConnectionByRole('master')) { + throw new InvalidArgumentException('Invalid connection or connection not found.'); + } + + $this->switchTo($connection); + } + + /** + * {@inheritdoc} + */ + public function switchToSlave() + { + if (!$connection = $this->getConnectionByRole('slave')) { + throw new InvalidArgumentException('Invalid connection or connection not found.'); + } + + $this->switchTo($connection); + } + + /** + * {@inheritdoc} + */ + public function getCurrent() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function getMaster() + { + return $this->master; + } + + /** + * Returns the connection associated to the master server. + * + * @return NodeConnectionInterface + */ + private function getMasterOrDie() + { + if (!$connection = $this->getMaster()) { + throw new MissingMasterException('No master server available for replication'); + } + + return $connection; + } + + /** + * {@inheritdoc} + */ + public function getSlaves() + { + return $this->slaves; + } + + /** + * Returns the underlying replication strategy. + * + * @return ReplicationStrategy + */ + public function getReplicationStrategy() + { + return $this->strategy; + } + + /** + * Returns a random slave. + * + * @return NodeConnectionInterface|null + */ + protected function pickSlave() + { + if (!$this->slaves) { + return null; + } + + return $this->slaves[array_rand($this->slaves)]; + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return $this->current ? $this->current->isConnected() : false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (!$this->current) { + if (!$this->current = $this->pickSlave()) { + if (!$this->current = $this->getMaster()) { + throw new ClientException('No available connection for replication'); + } + } + } + + $this->current->connect(); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + foreach ($this->pool as $connection) { + $connection->disconnect(); + } + } + + /** + * Handles response from INFO. + * + * @param string $response + * + * @return array + */ + private function handleInfoResponse($response) + { + $info = []; + + foreach (preg_split('/\r?\n/', $response) as $row) { + if (strpos($row, ':') === false) { + continue; + } + + [$k, $v] = explode(':', $row, 2); + $info[$k] = $v; + } + + return $info; + } + + /** + * Fetches the replication configuration from one of the servers. + */ + public function discover() + { + if (!$this->connectionFactory) { + throw new ClientException('Discovery requires a connection factory'); + } + + while (true) { + try { + if ($connection = $this->getMaster()) { + $this->discoverFromMaster($connection, $this->connectionFactory); + break; + } elseif ($connection = $this->pickSlave()) { + $this->discoverFromSlave($connection, $this->connectionFactory); + break; + } else { + throw new ClientException('No connection available for discovery'); + } + } catch (ConnectionException $exception) { + $this->remove($connection); + } + } + } + + /** + * Discovers the replication configuration by contacting the master node. + * + * @param NodeConnectionInterface $connection Connection to the master node. + * @param FactoryInterface $connectionFactory Connection factory instance. + */ + protected function discoverFromMaster(NodeConnectionInterface $connection, FactoryInterface $connectionFactory) + { + $response = $connection->executeCommand(RawCommand::create('INFO', 'REPLICATION')); + $replication = $this->handleInfoResponse($response); + + if ($replication['role'] !== 'master') { + throw new ClientException("Role mismatch (expected master, got slave) [$connection]"); + } + + $this->slaves = []; + + foreach ($replication as $k => $v) { + $parameters = null; + + if (strpos($k, 'slave') === 0 && preg_match('/ip=(?P.*),port=(?P\d+)/', $v, $parameters)) { + $slaveConnection = $connectionFactory->create([ + 'host' => $parameters['host'], + 'port' => $parameters['port'], + 'role' => 'slave', + ]); + + $this->add($slaveConnection); + } + } + } + + /** + * Discovers the replication configuration by contacting one of the slaves. + * + * @param NodeConnectionInterface $connection Connection to one of the slaves. + * @param FactoryInterface $connectionFactory Connection factory instance. + */ + protected function discoverFromSlave(NodeConnectionInterface $connection, FactoryInterface $connectionFactory) + { + $response = $connection->executeCommand(RawCommand::create('INFO', 'REPLICATION')); + $replication = $this->handleInfoResponse($response); + + if ($replication['role'] !== 'slave') { + throw new ClientException("Role mismatch (expected slave, got master) [$connection]"); + } + + $masterConnection = $connectionFactory->create([ + 'host' => $replication['master_host'], + 'port' => $replication['master_port'], + 'role' => 'master', + ]); + + $this->add($masterConnection); + + $this->discoverFromMaster($masterConnection, $connectionFactory); + } + + /** + * Retries the execution of a command upon slave failure. + * + * @param CommandInterface $command Command instance. + * @param string $method Actual method. + * + * @return mixed + */ + private function retryCommandOnFailure(CommandInterface $command, $method) + { + while (true) { + try { + $connection = $this->getConnectionByCommand($command); + $response = $connection->$method($command); + + if ($response instanceof ResponseErrorInterface && $response->getErrorType() === 'LOADING') { + throw new ConnectionException($connection, "Redis is loading the dataset in memory [$connection]"); + } + + break; + } catch (ConnectionException $exception) { + $connection = $exception->getConnection(); + $connection->disconnect(); + + if ($connection === $this->master && !$this->autoDiscovery) { + // Throw immediately when master connection is failing, even + // when the command represents a read-only operation, unless + // automatic discovery has been enabled. + throw $exception; + } else { + // Otherwise remove the failing slave and attempt to execute + // the command again on one of the remaining slaves... + $this->remove($connection); + } + + // ... that is, unless we have no more connections to use. + if (!$this->slaves && !$this->master) { + throw $exception; + } elseif ($this->autoDiscovery) { + $this->discover(); + } + } catch (MissingMasterException $exception) { + if ($this->autoDiscovery) { + $this->discover(); + } else { + throw $exception; + } + } + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + return $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return ['master', 'slaves', 'pool', 'aliases', 'strategy']; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/Replication/ReplicationInterface.php b/redis-cache/dependencies/predis/predis/src/Connection/Replication/ReplicationInterface.php new file mode 100644 index 0000000..14fd249 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/Replication/ReplicationInterface.php @@ -0,0 +1,53 @@ + + * @author Ville Mattila + */ +class SentinelReplication implements ReplicationInterface +{ + /** + * @var NodeConnectionInterface + */ + protected $master; + + /** + * @var NodeConnectionInterface[] + */ + protected $slaves = []; + + /** + * @var NodeConnectionInterface[] + */ + protected $pool = []; + + /** + * @var NodeConnectionInterface + */ + protected $current; + + /** + * @var string + */ + protected $service; + + /** + * @var ConnectionFactoryInterface + */ + protected $connectionFactory; + + /** + * @var ReplicationStrategy + */ + protected $strategy; + + /** + * @var NodeConnectionInterface[] + */ + protected $sentinels = []; + + /** + * @var int + */ + protected $sentinelIndex = 0; + + /** + * @var NodeConnectionInterface + */ + protected $sentinelConnection; + + /** + * @var float + */ + protected $sentinelTimeout = 0.100; + + /** + * Max number of automatic retries of commands upon server failure. + * + * -1 = unlimited retry attempts + * 0 = no retry attempts (fails immediately) + * n = fail only after n retry attempts + * + * @var int + */ + protected $retryLimit = 20; + + /** + * Time to wait in milliseconds before fetching a new configuration from one + * of the sentinel servers. + * + * @var int + */ + protected $retryWait = 1000; + + /** + * Flag for automatic fetching of available sentinels. + * + * @var bool + */ + protected $updateSentinels = false; + + /** + * @param string $service Name of the service for autodiscovery. + * @param array $sentinels Sentinel servers connection parameters. + * @param ConnectionFactoryInterface $connectionFactory Connection factory instance. + * @param ReplicationStrategy $strategy Replication strategy instance. + */ + public function __construct( + $service, + array $sentinels, + ConnectionFactoryInterface $connectionFactory, + ReplicationStrategy $strategy = null + ) { + $this->sentinels = $sentinels; + $this->service = $service; + $this->connectionFactory = $connectionFactory; + $this->strategy = $strategy ?: new ReplicationStrategy(); + } + + /** + * Sets a default timeout for connections to sentinels. + * + * When "timeout" is present in the connection parameters of sentinels, its + * value overrides the default sentinel timeout. + * + * @param float $timeout Timeout value. + */ + public function setSentinelTimeout($timeout) + { + $this->sentinelTimeout = (float) $timeout; + } + + /** + * Sets the maximum number of retries for commands upon server failure. + * + * -1 = unlimited retry attempts + * 0 = no retry attempts (fails immediately) + * n = fail only after n retry attempts + * + * @param int $retry Number of retry attempts. + */ + public function setRetryLimit($retry) + { + $this->retryLimit = (int) $retry; + } + + /** + * Sets the time to wait (in milliseconds) before fetching a new configuration + * from one of the sentinels. + * + * @param float $milliseconds Time to wait before the next attempt. + */ + public function setRetryWait($milliseconds) + { + $this->retryWait = (float) $milliseconds; + } + + /** + * Set automatic fetching of available sentinels. + * + * @param bool $update Enable or disable automatic updates. + */ + public function setUpdateSentinels($update) + { + $this->updateSentinels = (bool) $update; + } + + /** + * Resets the current connection. + */ + protected function reset() + { + $this->current = null; + } + + /** + * Wipes the current list of master and slaves nodes. + */ + protected function wipeServerList() + { + $this->reset(); + + $this->master = null; + $this->slaves = []; + $this->pool = []; + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $parameters = $connection->getParameters(); + $role = $parameters->role; + + if ('master' === $role) { + $this->master = $connection; + } elseif ('sentinel' === $role) { + $this->sentinels[] = $connection; + // sentinels are not considered part of the pool. + return; + } else { + // everything else is considered a slave. + $this->slaves[] = $connection; + } + + $this->pool[(string) $connection] = $connection; + + $this->reset(); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if ($connection === $this->master) { + $this->master = null; + } elseif (false !== $id = array_search($connection, $this->slaves, true)) { + unset($this->slaves[$id]); + } elseif (false !== $id = array_search($connection, $this->sentinels, true)) { + unset($this->sentinels[$id]); + + return true; + } else { + return false; + } + + unset($this->pool[(string) $connection]); + + $this->reset(); + + return true; + } + + /** + * Creates a new connection to a sentinel server. + * + * @return NodeConnectionInterface + */ + protected function createSentinelConnection($parameters) + { + if ($parameters instanceof NodeConnectionInterface) { + return $parameters; + } + + if (is_string($parameters)) { + $parameters = Parameters::parse($parameters); + } + + if (is_array($parameters)) { + // NOTE: sentinels do not accept AUTH and SELECT commands so we must + // explicitly set them to NULL to avoid problems when using default + // parameters set via client options. Actually AUTH is supported for + // sentinels starting with Redis 5 but we have to differentiate from + // sentinels passwords and nodes passwords, this will be implemented + // in a later release. + $parameters['database'] = null; + $parameters['username'] = null; + + // don't leak password from between configurations + // https://github.com/predis/predis/pull/807/#discussion_r985764770 + if (!isset($parameters['password'])) { + $parameters['password'] = null; + } + + if (!isset($parameters['timeout'])) { + $parameters['timeout'] = $this->sentinelTimeout; + } + } + + return $this->connectionFactory->create($parameters); + } + + /** + * Returns the current sentinel connection. + * + * If there is no active sentinel connection, a new connection is created. + * + * @return NodeConnectionInterface + */ + public function getSentinelConnection() + { + if (!$this->sentinelConnection) { + if ($this->sentinelIndex >= count($this->sentinels)) { + $this->sentinelIndex = 0; + throw new \Predis\ClientException('No sentinel server available for autodiscovery.'); + } + + $sentinel = $this->sentinels[$this->sentinelIndex]; + ++$this->sentinelIndex; + $this->sentinelConnection = $this->createSentinelConnection($sentinel); + } + + return $this->sentinelConnection; + } + + /** + * Fetches an updated list of sentinels from a sentinel. + */ + public function updateSentinels() + { + SENTINEL_QUERY: { + $sentinel = $this->getSentinelConnection(); + + try { + $payload = $sentinel->executeCommand( + RawCommand::create('SENTINEL', 'sentinels', $this->service) + ); + + $this->sentinels = []; + $this->sentinelIndex = 0; + // NOTE: sentinel server does not return itself, so we add it back. + $this->sentinels[] = $sentinel->getParameters()->toArray(); + + foreach ($payload as $sentinel) { + $this->sentinels[] = [ + 'host' => $sentinel[3], + 'port' => $sentinel[5], + 'role' => 'sentinel', + ]; + } + } catch (ConnectionException $exception) { + $this->sentinelConnection = null; + + goto SENTINEL_QUERY; + } + } + } + + /** + * Fetches the details for the master and slave servers from a sentinel. + */ + public function querySentinel() + { + $this->wipeServerList(); + + $this->updateSentinels(); + $this->getMaster(); + $this->getSlaves(); + } + + /** + * Handles error responses returned by redis-sentinel. + * + * @param NodeConnectionInterface $sentinel Connection to a sentinel server. + * @param ErrorResponseInterface $error Error response. + */ + private function handleSentinelErrorResponse(NodeConnectionInterface $sentinel, ErrorResponseInterface $error) + { + if ($error->getErrorType() === 'IDONTKNOW') { + throw new ConnectionException($sentinel, $error->getMessage()); + } else { + throw new ServerException($error->getMessage()); + } + } + + /** + * Fetches the details for the master server from a sentinel. + * + * @param NodeConnectionInterface $sentinel Connection to a sentinel server. + * @param string $service Name of the service. + * + * @return array + */ + protected function querySentinelForMaster(NodeConnectionInterface $sentinel, $service) + { + $payload = $sentinel->executeCommand( + RawCommand::create('SENTINEL', 'get-master-addr-by-name', $service) + ); + + if ($payload === null) { + throw new ServerException('ERR No such master with that name'); + } + + if ($payload instanceof ErrorResponseInterface) { + $this->handleSentinelErrorResponse($sentinel, $payload); + } + + return [ + 'host' => $payload[0], + 'port' => $payload[1], + 'role' => 'master', + ]; + } + + /** + * Fetches the details for the slave servers from a sentinel. + * + * @param NodeConnectionInterface $sentinel Connection to a sentinel server. + * @param string $service Name of the service. + * + * @return array + */ + protected function querySentinelForSlaves(NodeConnectionInterface $sentinel, $service) + { + $slaves = []; + + $payload = $sentinel->executeCommand( + RawCommand::create('SENTINEL', 'slaves', $service) + ); + + if ($payload instanceof ErrorResponseInterface) { + $this->handleSentinelErrorResponse($sentinel, $payload); + } + + foreach ($payload as $slave) { + $flags = explode(',', $slave[9]); + + if (array_intersect($flags, ['s_down', 'o_down', 'disconnected'])) { + continue; + } + + $slaves[] = [ + 'host' => $slave[3], + 'port' => $slave[5], + 'role' => 'slave', + ]; + } + + return $slaves; + } + + /** + * {@inheritdoc} + */ + public function getCurrent() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function getMaster() + { + if ($this->master) { + return $this->master; + } + + if ($this->updateSentinels) { + $this->updateSentinels(); + } + + SENTINEL_QUERY: { + $sentinel = $this->getSentinelConnection(); + + try { + $masterParameters = $this->querySentinelForMaster($sentinel, $this->service); + $masterConnection = $this->connectionFactory->create($masterParameters); + + $this->add($masterConnection); + } catch (ConnectionException $exception) { + $this->sentinelConnection = null; + + goto SENTINEL_QUERY; + } + } + + return $masterConnection; + } + + /** + * {@inheritdoc} + */ + public function getSlaves() + { + if ($this->slaves) { + return array_values($this->slaves); + } + + if ($this->updateSentinels) { + $this->updateSentinels(); + } + + SENTINEL_QUERY: { + $sentinel = $this->getSentinelConnection(); + + try { + $slavesParameters = $this->querySentinelForSlaves($sentinel, $this->service); + + foreach ($slavesParameters as $slaveParameters) { + $this->add($this->connectionFactory->create($slaveParameters)); + } + } catch (ConnectionException $exception) { + $this->sentinelConnection = null; + + goto SENTINEL_QUERY; + } + } + + return array_values($this->slaves); + } + + /** + * Returns a random slave. + * + * @return NodeConnectionInterface|null + */ + protected function pickSlave() + { + $slaves = $this->getSlaves(); + + return $slaves + ? $slaves[rand(1, count($slaves)) - 1] + : null; + } + + /** + * Returns the connection instance in charge for the given command. + * + * @param CommandInterface $command Command instance. + * + * @return NodeConnectionInterface + */ + private function getConnectionInternal(CommandInterface $command) + { + if (!$this->current) { + if ($this->strategy->isReadOperation($command) && $slave = $this->pickSlave()) { + $this->current = $slave; + } else { + $this->current = $this->getMaster(); + } + + return $this->current; + } + + if ($this->current === $this->master) { + return $this->current; + } + + if (!$this->strategy->isReadOperation($command)) { + $this->current = $this->getMaster(); + } + + return $this->current; + } + + /** + * Asserts that the specified connection matches an expected role. + * + * @param NodeConnectionInterface $connection Connection to a redis server. + * @param string $role Expected role of the server ("master", "slave" or "sentinel"). + * + * @throws RoleException|ConnectionException + */ + protected function assertConnectionRole(NodeConnectionInterface $connection, $role) + { + $role = strtolower($role); + $actualRole = $connection->executeCommand(RawCommand::create('ROLE')); + + if ($actualRole instanceof Error) { + throw new ConnectionException($connection, $actualRole->getMessage()); + } + + if ($role !== $actualRole[0]) { + throw new RoleException($connection, "Expected $role but got $actualRole[0] [$connection]"); + } + } + + /** + * {@inheritdoc} + */ + public function getConnectionByCommand(CommandInterface $command) + { + $connection = $this->getConnectionInternal($command); + + if (!$connection->isConnected()) { + // When we do not have any available slave in the pool we can expect + // read-only operations to hit the master server. + $expectedRole = $this->strategy->isReadOperation($command) && $this->slaves ? 'slave' : 'master'; + $this->assertConnectionRole($connection, $expectedRole); + } + + return $connection; + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($id) + { + return $this->pool[$id] ?? null; + } + + /** + * Returns a connection by its role. + * + * @param string $role Connection role (`master`, `slave` or `sentinel`) + * + * @return NodeConnectionInterface|null + */ + public function getConnectionByRole($role) + { + if ($role === 'master') { + return $this->getMaster(); + } elseif ($role === 'slave') { + return $this->pickSlave(); + } elseif ($role === 'sentinel') { + return $this->getSentinelConnection(); + } else { + return null; + } + } + + /** + * Switches the internal connection in use by the backend. + * + * Sentinel connections are not considered as part of the pool, meaning that + * trying to switch to a sentinel will throw an exception. + * + * @param NodeConnectionInterface $connection Connection instance in the pool. + */ + public function switchTo(NodeConnectionInterface $connection) + { + if ($connection && $connection === $this->current) { + return; + } + + if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) { + throw new InvalidArgumentException('Invalid connection or connection not found.'); + } + + $connection->connect(); + + if ($this->current) { + $this->current->disconnect(); + } + + $this->current = $connection; + } + + /** + * {@inheritdoc} + */ + public function switchToMaster() + { + $connection = $this->getConnectionByRole('master'); + $this->switchTo($connection); + } + + /** + * {@inheritdoc} + */ + public function switchToSlave() + { + $connection = $this->getConnectionByRole('slave'); + $this->switchTo($connection); + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return $this->current ? $this->current->isConnected() : false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (!$this->current) { + if (!$this->current = $this->pickSlave()) { + $this->current = $this->getMaster(); + } + } + + $this->current->connect(); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + foreach ($this->pool as $connection) { + $connection->disconnect(); + } + } + + /** + * Retries the execution of a command upon server failure after asking a new + * configuration to one of the sentinels. + * + * @param CommandInterface $command Command instance. + * @param string $method Actual method. + * + * @return mixed + */ + private function retryCommandOnFailure(CommandInterface $command, $method) + { + $retries = 0; + + while ($retries <= $this->retryLimit) { + try { + $response = $this->getConnectionByCommand($command)->$method($command); + break; + } catch (CommunicationException $exception) { + $this->wipeServerList(); + $exception->getConnection()->disconnect(); + + if ($retries === $this->retryLimit) { + throw $exception; + } + + usleep($this->retryWait * 1000); + + ++$retries; + } + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + return $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * Returns the underlying replication strategy. + * + * @return ReplicationStrategy + */ + public function getReplicationStrategy() + { + return $this->strategy; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return [ + 'master', 'slaves', 'pool', 'service', 'sentinels', 'connectionFactory', 'strategy', + ]; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/StreamConnection.php b/redis-cache/dependencies/predis/predis/src/Connection/StreamConnection.php new file mode 100644 index 0000000..4402530 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/StreamConnection.php @@ -0,0 +1,369 @@ +parameters->persistent) && $this->parameters->persistent) { + return; + } + + $this->disconnect(); + } + + /** + * {@inheritdoc} + */ + protected function assertParameters(ParametersInterface $parameters) + { + switch ($parameters->scheme) { + case 'tcp': + case 'redis': + case 'unix': + case 'tls': + case 'rediss': + break; + + default: + throw new InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); + } + + return $parameters; + } + + /** + * {@inheritdoc} + */ + protected function createResource() + { + switch ($this->parameters->scheme) { + case 'tcp': + case 'redis': + return $this->tcpStreamInitializer($this->parameters); + + case 'unix': + return $this->unixStreamInitializer($this->parameters); + + case 'tls': + case 'rediss': + return $this->tlsStreamInitializer($this->parameters); + + default: + throw new InvalidArgumentException("Invalid scheme: '{$this->parameters->scheme}'."); + } + } + + /** + * Creates a connected stream socket resource. + * + * @param ParametersInterface $parameters Connection parameters. + * @param string $address Address for stream_socket_client(). + * @param int $flags Flags for stream_socket_client(). + * + * @return resource + */ + protected function createStreamSocket(ParametersInterface $parameters, $address, $flags) + { + $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); + $context = stream_context_create(['socket' => ['tcp_nodelay' => (bool) $parameters->tcp_nodelay]]); + + if (!$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags, $context)) { + $this->onConnectionError(trim($errstr), $errno); + } + + if (isset($parameters->read_write_timeout)) { + $rwtimeout = (float) $parameters->read_write_timeout; + $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; + $timeoutSeconds = floor($rwtimeout); + $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000; + stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds); + } + + return $resource; + } + + /** + * Initializes a TCP stream resource. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @return resource + */ + protected function tcpStreamInitializer(ParametersInterface $parameters) + { + if (!filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $address = "tcp://$parameters->host:$parameters->port"; + } else { + $address = "tcp://[$parameters->host]:$parameters->port"; + } + + $flags = STREAM_CLIENT_CONNECT; + + if (isset($parameters->async_connect) && $parameters->async_connect) { + $flags |= STREAM_CLIENT_ASYNC_CONNECT; + } + + if (isset($parameters->persistent)) { + if (false !== $persistent = filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { + $flags |= STREAM_CLIENT_PERSISTENT; + + if ($persistent === null) { + $address = "{$address}/{$parameters->persistent}"; + } + } + } + + return $this->createStreamSocket($parameters, $address, $flags); + } + + /** + * Initializes a UNIX stream resource. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @return resource + */ + protected function unixStreamInitializer(ParametersInterface $parameters) + { + if (!isset($parameters->path)) { + throw new InvalidArgumentException('Missing UNIX domain socket path.'); + } + + $flags = STREAM_CLIENT_CONNECT; + + if (isset($parameters->persistent)) { + if (false !== $persistent = filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { + $flags |= STREAM_CLIENT_PERSISTENT; + + if ($persistent === null) { + throw new InvalidArgumentException( + 'Persistent connection IDs are not supported when using UNIX domain sockets.' + ); + } + } + } + + return $this->createStreamSocket($parameters, "unix://{$parameters->path}", $flags); + } + + /** + * Initializes a SSL-encrypted TCP stream resource. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @return resource + */ + protected function tlsStreamInitializer(ParametersInterface $parameters) + { + $resource = $this->tcpStreamInitializer($parameters); + $metadata = stream_get_meta_data($resource); + + // Detect if crypto mode is already enabled for this stream (PHP >= 7.0.0). + if (isset($metadata['crypto'])) { + return $resource; + } + + if (isset($parameters->ssl) && is_array($parameters->ssl)) { + $options = $parameters->ssl; + } else { + $options = []; + } + + if (!isset($options['crypto_type'])) { + $options['crypto_type'] = STREAM_CRYPTO_METHOD_TLS_CLIENT; + } + + if (!stream_context_set_option($resource, ['ssl' => $options])) { + $this->onConnectionError('Error while setting SSL context options'); + } + + if (!stream_socket_enable_crypto($resource, true, $options['crypto_type'])) { + $this->onConnectionError('Error while switching to encrypted communication'); + } + + return $resource; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (parent::connect() && $this->initCommands) { + foreach ($this->initCommands as $command) { + $response = $this->executeCommand($command); + + if ($response instanceof ErrorResponseInterface) { + $this->onConnectionError("`{$command->getId()}` failed: {$response->getMessage()}", 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + if ($this->isConnected()) { + fclose($this->getResource()); + parent::disconnect(); + } + } + + /** + * Performs a write operation over the stream of the buffer containing a + * command serialized with the Redis wire protocol. + * + * @param string $buffer Representation of a command in the Redis wire protocol. + */ + protected function write($buffer) + { + $socket = $this->getResource(); + + while (($length = strlen($buffer)) > 0) { + $written = is_resource($socket) ? @fwrite($socket, $buffer) : false; + + if ($length === $written) { + return; + } + + if ($written === false || $written === 0) { + $this->onConnectionError('Error while writing bytes to the server.'); + } + + $buffer = substr($buffer, $written); + } + } + + /** + * {@inheritdoc} + */ + public function read() + { + $socket = $this->getResource(); + $chunk = fgets($socket); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading line from the server.'); + } + + $prefix = $chunk[0]; + $payload = substr($chunk, 1, -2); + + switch ($prefix) { + case '+': + return StatusResponse::get($payload); + + case '$': + $size = (int) $payload; + + if ($size === -1) { + return; + } + + $bulkData = ''; + $bytesLeft = ($size += 2); + + do { + $chunk = is_resource($socket) ? fread($socket, min($bytesLeft, 4096)) : false; + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading bytes from the server.'); + } + + $bulkData .= $chunk; + $bytesLeft = $size - strlen($bulkData); + } while ($bytesLeft > 0); + + return substr($bulkData, 0, -2); + + case '*': + $count = (int) $payload; + + if ($count === -1) { + return; + } + + $multibulk = []; + + for ($i = 0; $i < $count; ++$i) { + $multibulk[$i] = $this->read(); + } + + return $multibulk; + + case ':': + $integer = (int) $payload; + + return $integer == $payload ? $integer : $payload; + + case '-': + return new ErrorResponse($payload); + + default: + $this->onProtocolError("Unknown response prefix: '$prefix'."); + + return; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $commandID = $command->getId(); + $arguments = $command->getArguments(); + + $cmdlen = strlen($commandID); + $reqlen = count($arguments) + 1; + + $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; + + foreach ($arguments as $argument) { + $arglen = strlen(strval($argument)); + $buffer .= "\${$arglen}\r\n{$argument}\r\n"; + } + + $this->write($buffer); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Connection/WebdisConnection.php b/redis-cache/dependencies/predis/predis/src/Connection/WebdisConnection.php new file mode 100644 index 0000000..bd53378 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Connection/WebdisConnection.php @@ -0,0 +1,366 @@ +assertExtensions(); + + if ($parameters->scheme !== 'http') { + throw new InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'."); + } + + $this->parameters = $parameters; + + $this->resource = $this->createCurl(); + $this->reader = $this->createReader(); + } + + /** + * Frees the underlying cURL and protocol reader resources when the garbage + * collector kicks in. + */ + public function __destruct() + { + curl_close($this->resource); + phpiredis_reader_destroy($this->reader); + } + + /** + * Helper method used to throw on unsupported methods. + * + * @param string $method Name of the unsupported method. + * + * @throws NotSupportedException + */ + private function throwNotSupportedException($method) + { + $class = __CLASS__; + throw new NotSupportedException("The method $class::$method() is not supported."); + } + + /** + * Checks if the cURL and phpiredis extensions are loaded in PHP. + */ + private function assertExtensions() + { + if (!extension_loaded('curl')) { + throw new NotSupportedException( + 'The "curl" extension is required by this connection backend.' + ); + } + + if (!extension_loaded('phpiredis')) { + throw new NotSupportedException( + 'The "phpiredis" extension is required by this connection backend.' + ); + } + } + + /** + * Initializes cURL. + * + * @return resource + */ + private function createCurl() + { + $parameters = $this->getParameters(); + $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0) * 1000; + + if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $host = "[$host]"; + } + + $options = [ + CURLOPT_FAILONERROR => true, + CURLOPT_CONNECTTIMEOUT_MS => $timeout, + CURLOPT_URL => "$parameters->scheme://$host:$parameters->port", + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_POST => true, + CURLOPT_WRITEFUNCTION => [$this, 'feedReader'], + ]; + + if (isset($parameters->user, $parameters->pass)) { + $options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}"; + } + + curl_setopt_array($resource = curl_init(), $options); + + return $resource; + } + + /** + * Initializes the phpiredis protocol reader. + * + * @return resource + */ + private function createReader() + { + $reader = phpiredis_reader_create(); + + phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); + phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); + + return $reader; + } + + /** + * Returns the handler used by the protocol reader for inline responses. + * + * @return Closure + */ + protected function getStatusHandler() + { + static $statusHandler; + + if (!$statusHandler) { + $statusHandler = function ($payload) { + return StatusResponse::get($payload); + }; + } + + return $statusHandler; + } + + /** + * Returns the handler used by the protocol reader for error responses. + * + * @return Closure + */ + protected function getErrorHandler() + { + static $errorHandler; + + if (!$errorHandler) { + $errorHandler = function ($errorMessage) { + return new ErrorResponse($errorMessage); + }; + } + + return $errorHandler; + } + + /** + * Feeds the phpredis reader resource with the data read from the network. + * + * @param resource $resource Reader resource. + * @param string $buffer Buffer of data read from a connection. + * + * @return int + */ + protected function feedReader($resource, $buffer) + { + phpiredis_reader_feed($this->reader, $buffer); + + return strlen($buffer); + } + + /** + * {@inheritdoc} + */ + public function connect() + { + // NOOP + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + // NOOP + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return true; + } + + /** + * Checks if the specified command is supported by this connection class. + * + * @param CommandInterface $command Command instance. + * + * @return string + * @throws NotSupportedException + */ + protected function getCommandId(CommandInterface $command) + { + switch ($commandID = $command->getId()) { + case 'AUTH': + case 'SELECT': + case 'MULTI': + case 'EXEC': + case 'WATCH': + case 'UNWATCH': + case 'DISCARD': + case 'MONITOR': + throw new NotSupportedException("Command '$commandID' is not allowed by Webdis."); + default: + return $commandID; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $resource = $this->resource; + $commandId = $this->getCommandId($command); + + if ($arguments = $command->getArguments()) { + $arguments = implode('/', array_map('urlencode', $arguments)); + $serializedCommand = "$commandId/$arguments.raw"; + } else { + $serializedCommand = "$commandId.raw"; + } + + curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand); + + if (curl_exec($resource) === false) { + $error = trim(curl_error($resource)); + $errno = curl_errno($resource); + + throw new ConnectionException($this, "$error{$this->getParameters()}]", $errno); + } + + if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) { + throw new ProtocolException($this, phpiredis_reader_get_error($this->reader)); + } + + return phpiredis_reader_get_reply($this->reader); + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + return $this->resource; + } + + /** + * {@inheritdoc} + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function addConnectCommand(CommandInterface $command) + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function read() + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return "{$this->parameters->host}:{$this->parameters->port}"; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return ['parameters']; + } + + /** + * {@inheritdoc} + */ + public function __wakeup() + { + $this->assertExtensions(); + + $this->resource = $this->createCurl(); + $this->reader = $this->createReader(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Monitor/Consumer.php b/redis-cache/dependencies/predis/predis/src/Monitor/Consumer.php new file mode 100644 index 0000000..eb46b04 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Monitor/Consumer.php @@ -0,0 +1,179 @@ +assertClient($client); + + $this->client = $client; + + $this->start(); + } + + /** + * Automatically stops the consumer when the garbage collector kicks in. + */ + public function __destruct() + { + $this->stop(); + } + + /** + * Checks if the passed client instance satisfies the required conditions + * needed to initialize a monitor consumer. + * + * @param ClientInterface $client Client instance used by the consumer. + * + * @throws NotSupportedException + */ + private function assertClient(ClientInterface $client) + { + if ($client->getConnection() instanceof ClusterInterface) { + throw new NotSupportedException( + 'Cannot initialize a monitor consumer over cluster connections.' + ); + } + + if (!$client->getCommandFactory()->supports('MONITOR')) { + throw new NotSupportedException("'MONITOR' is not supported by the current command factory."); + } + } + + /** + * Initializes the consumer and sends the MONITOR command to the server. + */ + protected function start() + { + $this->client->executeCommand( + $this->client->createCommand('MONITOR') + ); + $this->valid = true; + } + + /** + * Stops the consumer. Internally this is done by disconnecting from server + * since there is no way to terminate the stream initialized by MONITOR. + */ + public function stop() + { + $this->client->disconnect(); + $this->valid = false; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function rewind() + { + // NOOP + } + + /** + * Returns the last message payload retrieved from the server. + * + * @return object + */ + #[ReturnTypeWillChange] + public function current() + { + return $this->getValue(); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function next() + { + ++$this->position; + } + + /** + * Checks if the the consumer is still in a valid state to continue. + * + * @return bool + */ + #[ReturnTypeWillChange] + public function valid() + { + return $this->valid; + } + + /** + * Waits for a new message from the server generated by MONITOR and returns + * it when available. + * + * @return object + */ + private function getValue() + { + $database = 0; + $client = null; + $event = $this->client->getConnection()->read(); + + $callback = function ($matches) use (&$database, &$client) { + if (2 === $count = count($matches)) { + // Redis <= 2.4 + $database = (int) $matches[1]; + } + + if (4 === $count) { + // Redis >= 2.6 + $database = (int) $matches[2]; + $client = $matches[3]; + } + + return ' '; + }; + + $event = preg_replace_callback('/ \(db (\d+)\) | \[(\d+) (.*?)\] /', $callback, $event, 1); + @[$timestamp, $command, $arguments] = explode(' ', $event, 3); + + return (object) [ + 'timestamp' => (float) $timestamp, + 'database' => $database, + 'client' => $client, + 'command' => substr($command, 1, -1), + 'arguments' => $arguments, + ]; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/NotSupportedException.php b/redis-cache/dependencies/predis/predis/src/NotSupportedException.php new file mode 100644 index 0000000..037696b --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/NotSupportedException.php @@ -0,0 +1,21 @@ +getCommandFactory()->supports('multi', 'exec', 'discard')) { + throw new ClientException( + "'MULTI', 'EXEC' and 'DISCARD' are not supported by the current command factory." + ); + } + + parent::__construct($client); + } + + /** + * {@inheritdoc} + */ + protected function getConnection() + { + $connection = $this->getClient()->getConnection(); + + if (!$connection instanceof NodeConnectionInterface) { + $class = __CLASS__; + + throw new ClientException("The class '$class' does not support aggregate connections."); + } + + return $connection; + } + + /** + * {@inheritdoc} + */ + protected function executePipeline(ConnectionInterface $connection, SplQueue $commands) + { + $commandFactory = $this->getClient()->getCommandFactory(); + $connection->executeCommand($commandFactory->create('multi')); + + foreach ($commands as $command) { + $connection->writeRequest($command); + } + + foreach ($commands as $command) { + $response = $connection->readResponse($command); + + if ($response instanceof ErrorResponseInterface) { + $connection->executeCommand($commandFactory->create('discard')); + throw new ServerException($response->getMessage()); + } + } + + $executed = $connection->executeCommand($commandFactory->create('exec')); + + if (!isset($executed)) { + throw new ClientException( + 'The underlying transaction has been aborted by the server.' + ); + } + + if (count($executed) !== count($commands)) { + $expected = count($commands); + $received = count($executed); + + throw new ClientException( + "Invalid number of responses [expected $expected, received $received]." + ); + } + + $responses = []; + $sizeOfPipe = count($commands); + $exceptions = $this->throwServerExceptions(); + + for ($i = 0; $i < $sizeOfPipe; ++$i) { + $command = $commands->dequeue(); + $response = $executed[$i]; + + if (!$response instanceof ResponseInterface) { + $responses[] = $command->parseResponse($response); + } elseif ($response instanceof ErrorResponseInterface && $exceptions) { + $this->exception($connection, $response); + } else { + $responses[] = $response; + } + + unset($executed[$i]); + } + + return $responses; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Pipeline/ConnectionErrorProof.php b/redis-cache/dependencies/predis/predis/src/Pipeline/ConnectionErrorProof.php new file mode 100644 index 0000000..8f995cf --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Pipeline/ConnectionErrorProof.php @@ -0,0 +1,128 @@ +getClient()->getConnection(); + } + + /** + * {@inheritdoc} + */ + protected function executePipeline(ConnectionInterface $connection, SplQueue $commands) + { + if ($connection instanceof NodeConnectionInterface) { + return $this->executeSingleNode($connection, $commands); + } elseif ($connection instanceof ClusterInterface) { + return $this->executeCluster($connection, $commands); + } else { + $class = get_class($connection); + + throw new NotSupportedException("The connection class '$class' is not supported."); + } + } + + /** + * {@inheritdoc} + */ + protected function executeSingleNode(NodeConnectionInterface $connection, SplQueue $commands) + { + $responses = []; + $sizeOfPipe = count($commands); + + foreach ($commands as $command) { + try { + $connection->writeRequest($command); + } catch (CommunicationException $exception) { + return array_fill(0, $sizeOfPipe, $exception); + } + } + + for ($i = 0; $i < $sizeOfPipe; ++$i) { + $command = $commands->dequeue(); + + try { + $responses[$i] = $connection->readResponse($command); + } catch (CommunicationException $exception) { + $add = count($commands) - count($responses); + $responses = array_merge($responses, array_fill(0, $add, $exception)); + + break; + } + } + + return $responses; + } + + /** + * {@inheritdoc} + */ + protected function executeCluster(ClusterInterface $connection, SplQueue $commands) + { + $responses = []; + $sizeOfPipe = count($commands); + $exceptions = []; + + foreach ($commands as $command) { + $cmdConnection = $connection->getConnectionByCommand($command); + + if (isset($exceptions[spl_object_hash($cmdConnection)])) { + continue; + } + + try { + $cmdConnection->writeRequest($command); + } catch (CommunicationException $exception) { + $exceptions[spl_object_hash($cmdConnection)] = $exception; + } + } + + for ($i = 0; $i < $sizeOfPipe; ++$i) { + $command = $commands->dequeue(); + + $cmdConnection = $connection->getConnectionByCommand($command); + $connectionHash = spl_object_hash($cmdConnection); + + if (isset($exceptions[$connectionHash])) { + $responses[$i] = $exceptions[$connectionHash]; + continue; + } + + try { + $responses[$i] = $cmdConnection->readResponse($command); + } catch (CommunicationException $exception) { + $responses[$i] = $exception; + $exceptions[$connectionHash] = $exception; + } + } + + return $responses; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Pipeline/FireAndForget.php b/redis-cache/dependencies/predis/predis/src/Pipeline/FireAndForget.php new file mode 100644 index 0000000..75ee88e --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Pipeline/FireAndForget.php @@ -0,0 +1,36 @@ +isEmpty()) { + $connection->writeRequest($commands->dequeue()); + } + + $connection->disconnect(); + + return []; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Pipeline/Pipeline.php b/redis-cache/dependencies/predis/predis/src/Pipeline/Pipeline.php new file mode 100644 index 0000000..3e1011f --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Pipeline/Pipeline.php @@ -0,0 +1,248 @@ +client = $client; + $this->pipeline = new SplQueue(); + } + + /** + * Queues a command into the pipeline buffer. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return $this + */ + public function __call($method, $arguments) + { + $command = $this->client->createCommand($method, $arguments); + $this->recordCommand($command); + + return $this; + } + + /** + * Queues a command instance into the pipeline buffer. + * + * @param CommandInterface $command Command to be queued in the buffer. + */ + protected function recordCommand(CommandInterface $command) + { + $this->pipeline->enqueue($command); + } + + /** + * Queues a command instance into the pipeline buffer. + * + * @param CommandInterface $command Command instance to be queued in the buffer. + * + * @return $this + */ + public function executeCommand(CommandInterface $command) + { + $this->recordCommand($command); + + return $this; + } + + /** + * Throws an exception on -ERR responses returned by Redis. + * + * @param ConnectionInterface $connection Redis connection that returned the error. + * @param ErrorResponseInterface $response Instance of the error response. + * + * @throws ServerException + */ + protected function exception(ConnectionInterface $connection, ErrorResponseInterface $response) + { + $connection->disconnect(); + $message = $response->getMessage(); + + throw new ServerException($message); + } + + /** + * Returns the underlying connection to be used by the pipeline. + * + * @return ConnectionInterface + */ + protected function getConnection() + { + $connection = $this->getClient()->getConnection(); + + if ($connection instanceof ReplicationInterface) { + $connection->switchToMaster(); + } + + return $connection; + } + + /** + * Implements the logic to flush the queued commands and read the responses + * from the current connection. + * + * @param ConnectionInterface $connection Current connection instance. + * @param SplQueue $commands Queued commands. + * + * @return array + */ + protected function executePipeline(ConnectionInterface $connection, SplQueue $commands) + { + foreach ($commands as $command) { + $connection->writeRequest($command); + } + + $responses = []; + $exceptions = $this->throwServerExceptions(); + + while (!$commands->isEmpty()) { + $command = $commands->dequeue(); + $response = $connection->readResponse($command); + + if (!$response instanceof ResponseInterface) { + $responses[] = $command->parseResponse($response); + } elseif ($response instanceof ErrorResponseInterface && $exceptions) { + $this->exception($connection, $response); + } else { + $responses[] = $response; + } + } + + return $responses; + } + + /** + * Flushes the buffer holding all of the commands queued so far. + * + * @param bool $send Specifies if the commands in the buffer should be sent to Redis. + * + * @return $this + */ + public function flushPipeline($send = true) + { + if ($send && !$this->pipeline->isEmpty()) { + $responses = $this->executePipeline($this->getConnection(), $this->pipeline); + $this->responses = array_merge($this->responses, $responses); + } else { + $this->pipeline = new SplQueue(); + } + + return $this; + } + + /** + * Marks the running status of the pipeline. + * + * @param bool $bool Sets the running status of the pipeline. + * + * @throws ClientException + */ + private function setRunning($bool) + { + if ($bool && $this->running) { + throw new ClientException('The current pipeline context is already being executed.'); + } + + $this->running = $bool; + } + + /** + * Handles the actual execution of the whole pipeline. + * + * @param mixed $callable Optional callback for execution. + * + * @return array + * @throws Exception + * @throws InvalidArgumentException + */ + public function execute($callable = null) + { + if ($callable && !is_callable($callable)) { + throw new InvalidArgumentException('The argument must be a callable object.'); + } + + $exception = null; + $this->setRunning(true); + + try { + if ($callable) { + call_user_func($callable, $this); + } + + $this->flushPipeline(); + } catch (Exception $exception) { + // NOOP + } + + $this->setRunning(false); + + if ($exception) { + throw $exception; + } + + return $this->responses; + } + + /** + * Returns if the pipeline should throw exceptions on server errors. + * + * @return bool + */ + protected function throwServerExceptions() + { + return (bool) $this->client->getOptions()->exceptions; + } + + /** + * Returns the underlying client instance used by the pipeline object. + * + * @return ClientInterface + */ + public function getClient() + { + return $this->client; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/PredisException.php b/redis-cache/dependencies/predis/predis/src/PredisException.php new file mode 100644 index 0000000..8e12422 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/PredisException.php @@ -0,0 +1,22 @@ +setRequestSerializer($serializer ?: new RequestSerializer()); + $this->setResponseReader($reader ?: new ResponseReader()); + } + + /** + * {@inheritdoc} + */ + public function write(CompositeConnectionInterface $connection, CommandInterface $command) + { + $connection->writeBuffer($this->serializer->serialize($command)); + } + + /** + * {@inheritdoc} + */ + public function read(CompositeConnectionInterface $connection) + { + return $this->reader->read($connection); + } + + /** + * Sets the request serializer used by the protocol processor. + * + * @param RequestSerializerInterface $serializer Request serializer. + */ + public function setRequestSerializer(RequestSerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + /** + * Returns the request serializer used by the protocol processor. + * + * @return RequestSerializerInterface + */ + public function getRequestSerializer() + { + return $this->serializer; + } + + /** + * Sets the response reader used by the protocol processor. + * + * @param ResponseReaderInterface $reader Response reader. + */ + public function setResponseReader(ResponseReaderInterface $reader) + { + $this->reader = $reader; + } + + /** + * Returns the Response reader used by the protocol processor. + * + * @return ResponseReaderInterface + */ + public function getResponseReader() + { + return $this->reader; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/BulkResponse.php b/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/BulkResponse.php new file mode 100644 index 0000000..961c011 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/BulkResponse.php @@ -0,0 +1,54 @@ +getParameters()}]" + )); + } + + if ($length >= 0) { + return substr($connection->readBuffer($length + 2), 0, -2); + } + + if ($length == -1) { + return; + } + + CommunicationException::handle(new ProtocolException( + $connection, "Value '$payload' is not a valid length for a bulk response [{$connection->getParameters()}]" + )); + + return; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/ErrorResponse.php b/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/ErrorResponse.php new file mode 100644 index 0000000..aa400a4 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/ErrorResponse.php @@ -0,0 +1,33 @@ +getParameters()}]" + )); + } + + return; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/MultiBulkResponse.php b/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/MultiBulkResponse.php new file mode 100644 index 0000000..d9c5142 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/MultiBulkResponse.php @@ -0,0 +1,67 @@ +getParameters()}]" + )); + } + + if ($length === -1) { + return; + } + + $list = []; + + if ($length > 0) { + $handlersCache = []; + $reader = $connection->getProtocol()->getResponseReader(); + + for ($i = 0; $i < $length; ++$i) { + $header = $connection->readLine(); + $prefix = $header[0]; + + if (isset($handlersCache[$prefix])) { + $handler = $handlersCache[$prefix]; + } else { + $handler = $reader->getHandler($prefix); + $handlersCache[$prefix] = $handler; + } + + $list[$i] = $handler->handle($connection, substr($header, 1)); + } + } + + return $list; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/ResponseHandlerInterface.php b/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/ResponseHandlerInterface.php new file mode 100644 index 0000000..b1c9066 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Protocol/Text/Handler/ResponseHandlerInterface.php @@ -0,0 +1,32 @@ +getParameters()}]" + )); + } + + return new MultiBulkIterator($connection, $length); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Protocol/Text/ProtocolProcessor.php b/redis-cache/dependencies/predis/predis/src/Protocol/Text/ProtocolProcessor.php new file mode 100644 index 0000000..02e0c5d --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Protocol/Text/ProtocolProcessor.php @@ -0,0 +1,120 @@ +mbiterable = false; + $this->serializer = new RequestSerializer(); + } + + /** + * {@inheritdoc} + */ + public function write(CompositeConnectionInterface $connection, CommandInterface $command) + { + $request = $this->serializer->serialize($command); + $connection->writeBuffer($request); + } + + /** + * {@inheritdoc} + */ + public function read(CompositeConnectionInterface $connection) + { + $chunk = $connection->readLine(); + $prefix = $chunk[0]; + $payload = substr($chunk, 1); + + switch ($prefix) { + case '+': + return new StatusResponse($payload); + + case '$': + $size = (int) $payload; + if ($size === -1) { + return; + } + + return substr($connection->readBuffer($size + 2), 0, -2); + + case '*': + $count = (int) $payload; + + if ($count === -1) { + return; + } + if ($this->mbiterable) { + return new MultiBulkIterator($connection, $count); + } + + $multibulk = []; + + for ($i = 0; $i < $count; ++$i) { + $multibulk[$i] = $this->read($connection); + } + + return $multibulk; + + case ':': + $integer = (int) $payload; + + return $integer == $payload ? $integer : $payload; + + case '-': + return new ErrorResponse($payload); + + default: + CommunicationException::handle(new ProtocolException( + $connection, "Unknown response prefix: '$prefix' [{$connection->getParameters()}]" + )); + + return; + } + } + + /** + * Enables or disables returning multibulk responses as specialized PHP + * iterators used to stream bulk elements of a multibulk response instead + * returning a plain array. + * + * Streamable multibulk responses are not globally supported by the + * abstractions built-in into Predis, such as transactions or pipelines. + * Use them with care! + * + * @param bool $value Enable or disable streamable multibulk responses. + */ + public function useIterableMultibulk($value) + { + $this->mbiterable = (bool) $value; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Protocol/Text/RequestSerializer.php b/redis-cache/dependencies/predis/predis/src/Protocol/Text/RequestSerializer.php new file mode 100644 index 0000000..853bae0 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Protocol/Text/RequestSerializer.php @@ -0,0 +1,45 @@ +getId(); + $arguments = $command->getArguments(); + + $cmdlen = strlen($commandID); + $reqlen = count($arguments) + 1; + + $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; + + foreach ($arguments as $argument) { + $arglen = strlen($argument); + $buffer .= "\${$arglen}\r\n{$argument}\r\n"; + } + + return $buffer; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Protocol/Text/ResponseReader.php b/redis-cache/dependencies/predis/predis/src/Protocol/Text/ResponseReader.php new file mode 100644 index 0000000..f49c96d --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Protocol/Text/ResponseReader.php @@ -0,0 +1,110 @@ +handlers = $this->getDefaultHandlers(); + } + + /** + * Returns the default handlers for the supported type of responses. + * + * @return array + */ + protected function getDefaultHandlers() + { + return [ + '+' => new Handler\StatusResponse(), + '-' => new Handler\ErrorResponse(), + ':' => new Handler\IntegerResponse(), + '$' => new Handler\BulkResponse(), + '*' => new Handler\MultiBulkResponse(), + ]; + } + + /** + * Sets the handler for the specified prefix identifying the response type. + * + * @param string $prefix Identifier of the type of response. + * @param Handler\ResponseHandlerInterface $handler Response handler. + */ + public function setHandler($prefix, Handler\ResponseHandlerInterface $handler) + { + $this->handlers[$prefix] = $handler; + } + + /** + * Returns the response handler associated to a certain type of response. + * + * @param string $prefix Identifier of the type of response. + * + * @return Handler\ResponseHandlerInterface + */ + public function getHandler($prefix) + { + if (isset($this->handlers[$prefix])) { + return $this->handlers[$prefix]; + } + + return; + } + + /** + * {@inheritdoc} + */ + public function read(CompositeConnectionInterface $connection) + { + $header = $connection->readLine(); + + if ($header === '') { + $this->onProtocolError($connection, 'Unexpected empty response header'); + } + + $prefix = $header[0]; + + if (!isset($this->handlers[$prefix])) { + $this->onProtocolError($connection, "Unknown response prefix: '$prefix'"); + } + + return $this->handlers[$prefix]->handle($connection, substr($header, 1)); + } + + /** + * Handles protocol errors generated while reading responses from a + * connection. + * + * @param CompositeConnectionInterface $connection Redis connection that generated the error. + * @param string $message Error message. + */ + protected function onProtocolError(CompositeConnectionInterface $connection, $message) + { + CommunicationException::handle( + new ProtocolException($connection, "$message [{$connection->getParameters()}]") + ); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/PubSub/AbstractConsumer.php b/redis-cache/dependencies/predis/predis/src/PubSub/AbstractConsumer.php new file mode 100644 index 0000000..f764693 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/PubSub/AbstractConsumer.php @@ -0,0 +1,226 @@ +stop(true); + } + + /** + * Checks if the specified flag is valid based on the state of the consumer. + * + * @param int $value Flag. + * + * @return bool + */ + protected function isFlagSet($value) + { + return ($this->statusFlags & $value) === $value; + } + + /** + * Subscribes to the specified channels. + * + * @param string ...$channel One or more channel names. + */ + public function subscribe($channel /* , ... */) + { + $this->writeRequest(self::SUBSCRIBE, func_get_args()); + $this->statusFlags |= self::STATUS_SUBSCRIBED; + } + + /** + * Unsubscribes from the specified channels. + * + * @param string ...$channel One or more channel names. + */ + public function unsubscribe(...$channel) + { + $this->writeRequest(self::UNSUBSCRIBE, func_get_args()); + } + + /** + * Subscribes to the specified channels using a pattern. + * + * @param string ...$pattern One or more channel name patterns. + */ + public function psubscribe(...$pattern) + { + $this->writeRequest(self::PSUBSCRIBE, func_get_args()); + $this->statusFlags |= self::STATUS_PSUBSCRIBED; + } + + /** + * Unsubscribes from the specified channels using a pattern. + * + * @param string ...$pattern One or more channel name patterns. + */ + public function punsubscribe(...$pattern) + { + $this->writeRequest(self::PUNSUBSCRIBE, func_get_args()); + } + + /** + * PING the server with an optional payload that will be echoed as a + * PONG message in the pub/sub loop. + * + * @param string $payload Optional PING payload. + */ + public function ping($payload = null) + { + $this->writeRequest('PING', [$payload]); + } + + /** + * Closes the context by unsubscribing from all the subscribed channels. The + * context can be forcefully closed by dropping the underlying connection. + * + * @param bool $drop Indicates if the context should be closed by dropping the connection. + * + * @return bool Returns false when there are no pending messages. + */ + public function stop($drop = false) + { + if (!$this->valid()) { + return false; + } + + if ($drop) { + $this->invalidate(); + $this->disconnect(); + } else { + if ($this->isFlagSet(self::STATUS_SUBSCRIBED)) { + $this->unsubscribe(); + } + if ($this->isFlagSet(self::STATUS_PSUBSCRIBED)) { + $this->punsubscribe(); + } + } + + return !$drop; + } + + /** + * Closes the underlying connection when forcing a disconnection. + */ + abstract protected function disconnect(); + + /** + * Writes a Redis command on the underlying connection. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + */ + abstract protected function writeRequest($method, $arguments); + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function rewind() + { + // NOOP + } + + /** + * Returns the last message payload retrieved from the server and generated + * by one of the active subscriptions. + * + * @return array + */ + #[ReturnTypeWillChange] + public function current() + { + return $this->getValue(); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function next() + { + if ($this->valid()) { + ++$this->position; + } + + return $this->position; + } + + /** + * Checks if the the consumer is still in a valid state to continue. + * + * @return bool + */ + #[ReturnTypeWillChange] + public function valid() + { + $isValid = $this->isFlagSet(self::STATUS_VALID); + $subscriptionFlags = self::STATUS_SUBSCRIBED | self::STATUS_PSUBSCRIBED; + $hasSubscriptions = ($this->statusFlags & $subscriptionFlags) > 0; + + return $isValid && $hasSubscriptions; + } + + /** + * Resets the state of the consumer. + */ + protected function invalidate() + { + $this->statusFlags = 0; // 0b0000; + } + + /** + * Waits for a new message from the server generated by one of the active + * subscriptions and returns it when available. + * + * @return array + */ + abstract protected function getValue(); +} diff --git a/redis-cache/dependencies/predis/predis/src/PubSub/Consumer.php b/redis-cache/dependencies/predis/predis/src/PubSub/Consumer.php new file mode 100644 index 0000000..253c33f --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/PubSub/Consumer.php @@ -0,0 +1,157 @@ +checkCapabilities($client); + + $this->options = $options ?: []; + $this->client = $client; + + $this->genericSubscribeInit('subscribe'); + $this->genericSubscribeInit('psubscribe'); + } + + /** + * Returns the underlying client instance used by the pub/sub iterator. + * + * @return ClientInterface + */ + public function getClient() + { + return $this->client; + } + + /** + * Checks if the client instance satisfies the required conditions needed to + * initialize a PUB/SUB consumer. + * + * @param ClientInterface $client Client instance used by the consumer. + * + * @throws NotSupportedException + */ + private function checkCapabilities(ClientInterface $client) + { + if ($client->getConnection() instanceof ClusterInterface) { + throw new NotSupportedException( + 'Cannot initialize a PUB/SUB consumer over cluster connections.' + ); + } + + $commands = ['publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe']; + + if (!$client->getCommandFactory()->supports(...$commands)) { + throw new NotSupportedException( + 'PUB/SUB commands are not supported by the current command factory.' + ); + } + } + + /** + * This method shares the logic to handle both SUBSCRIBE and PSUBSCRIBE. + * + * @param string $subscribeAction Type of subscription. + */ + private function genericSubscribeInit($subscribeAction) + { + if (isset($this->options[$subscribeAction])) { + $this->$subscribeAction($this->options[$subscribeAction]); + } + } + + /** + * {@inheritdoc} + */ + protected function writeRequest($method, $arguments) + { + $this->client->getConnection()->writeRequest( + $this->client->createCommand($method, + Command::normalizeArguments($arguments) + ) + ); + } + + /** + * {@inheritdoc} + */ + protected function disconnect() + { + $this->client->disconnect(); + } + + /** + * {@inheritdoc} + */ + protected function getValue() + { + $response = $this->client->getConnection()->read(); + + switch ($response[0]) { + case self::SUBSCRIBE: + case self::UNSUBSCRIBE: + case self::PSUBSCRIBE: + case self::PUNSUBSCRIBE: + if ($response[2] === 0) { + $this->invalidate(); + } + // The missing break here is intentional as we must process + // subscriptions and unsubscriptions as standard messages. + // no break + + case self::MESSAGE: + return (object) [ + 'kind' => $response[0], + 'channel' => $response[1], + 'payload' => $response[2], + ]; + + case self::PMESSAGE: + return (object) [ + 'kind' => $response[0], + 'pattern' => $response[1], + 'channel' => $response[2], + 'payload' => $response[3], + ]; + + case self::PONG: + return (object) [ + 'kind' => $response[0], + 'payload' => $response[1], + ]; + + default: + throw new ClientException( + "Unknown message type '{$response[0]}' received in the PUB/SUB context." + ); + } + } +} diff --git a/redis-cache/dependencies/predis/predis/src/PubSub/DispatcherLoop.php b/redis-cache/dependencies/predis/predis/src/PubSub/DispatcherLoop.php new file mode 100644 index 0000000..b6f79cd --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/PubSub/DispatcherLoop.php @@ -0,0 +1,171 @@ +callbacks = []; + $this->pubsub = $pubsub; + } + + /** + * Checks if the passed argument is a valid callback. + * + * @param mixed $callable A callback. + * + * @throws InvalidArgumentException + */ + protected function assertCallback($callable) + { + if (!is_callable($callable)) { + throw new InvalidArgumentException('The given argument must be a callable object.'); + } + } + + /** + * Returns the underlying PUB / SUB context. + * + * @return Consumer + */ + public function getPubSubConsumer() + { + return $this->pubsub; + } + + /** + * Sets a callback that gets invoked upon new subscriptions. + * + * @param mixed $callable A callback. + */ + public function subscriptionCallback($callable = null) + { + if (isset($callable)) { + $this->assertCallback($callable); + } + + $this->subscriptionCallback = $callable; + } + + /** + * Sets a callback that gets invoked when a message is received on a + * channel that does not have an associated callback. + * + * @param mixed $callable A callback. + */ + public function defaultCallback($callable = null) + { + if (isset($callable)) { + $this->assertCallback($callable); + } + + $this->subscriptionCallback = $callable; + } + + /** + * Binds a callback to a channel. + * + * @param string $channel Channel name. + * @param callable $callback A callback. + */ + public function attachCallback($channel, $callback) + { + $callbackName = $this->getPrefixKeys() . $channel; + + $this->assertCallback($callback); + $this->callbacks[$callbackName] = $callback; + $this->pubsub->subscribe($channel); + } + + /** + * Stops listening to a channel and removes the associated callback. + * + * @param string $channel Redis channel. + */ + public function detachCallback($channel) + { + $callbackName = $this->getPrefixKeys() . $channel; + + if (isset($this->callbacks[$callbackName])) { + unset($this->callbacks[$callbackName]); + $this->pubsub->unsubscribe($channel); + } + } + + /** + * Starts the dispatcher loop. + */ + public function run() + { + foreach ($this->pubsub as $message) { + $kind = $message->kind; + + if ($kind !== Consumer::MESSAGE && $kind !== Consumer::PMESSAGE) { + if (isset($this->subscriptionCallback)) { + $callback = $this->subscriptionCallback; + call_user_func($callback, $message, $this); + } + + continue; + } + + if (isset($this->callbacks[$message->channel])) { + $callback = $this->callbacks[$message->channel]; + call_user_func($callback, $message->payload, $this); + } elseif (isset($this->defaultCallback)) { + $callback = $this->defaultCallback; + call_user_func($callback, $message, $this); + } + } + } + + /** + * Terminates the dispatcher loop. + */ + public function stop() + { + $this->pubsub->stop(); + } + + /** + * Return the prefix used for keys. + * + * @return string + */ + protected function getPrefixKeys() + { + $options = $this->pubsub->getClient()->getOptions(); + + if (isset($options->prefix)) { + return $options->prefix->getPrefix(); + } + + return ''; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Replication/MissingMasterException.php b/redis-cache/dependencies/predis/predis/src/Replication/MissingMasterException.php new file mode 100644 index 0000000..d30c259 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Replication/MissingMasterException.php @@ -0,0 +1,22 @@ +disallowed = $this->getDisallowedOperations(); + $this->readonly = $this->getReadOnlyOperations(); + $this->readonlySHA1 = []; + } + + /** + * Returns if the specified command will perform a read-only operation + * on Redis or not. + * + * @param CommandInterface $command Command instance. + * + * @return bool + * @throws NotSupportedException + */ + public function isReadOperation(CommandInterface $command) + { + if (!$this->loadBalancing) { + return false; + } + + if (isset($this->disallowed[$id = $command->getId()])) { + throw new NotSupportedException( + "The command '$id' is not allowed in replication mode." + ); + } + + if (isset($this->readonly[$id])) { + if (true === $readonly = $this->readonly[$id]) { + return true; + } + + return call_user_func($readonly, $command); + } + + if (($eval = $id === 'EVAL') || $id === 'EVALSHA') { + $argument = $command->getArgument(0); + $sha1 = $eval ? sha1(strval($argument)) : $argument; + + if (isset($this->readonlySHA1[$sha1])) { + if (true === $readonly = $this->readonlySHA1[$sha1]) { + return true; + } + + return call_user_func($readonly, $command); + } + } + + return false; + } + + /** + * Returns if the specified command is not allowed for execution in a master + * / slave replication context. + * + * @param CommandInterface $command Command instance. + * + * @return bool + */ + public function isDisallowedOperation(CommandInterface $command) + { + return isset($this->disallowed[$command->getId()]); + } + + /** + * Checks if BITFIELD performs a read-only operation by looking for certain + * SET and INCRYBY modifiers in the arguments array of the command. + * + * @param CommandInterface $command Command instance. + * + * @return bool + */ + protected function isBitfieldReadOnly(CommandInterface $command) + { + $arguments = $command->getArguments(); + $argc = count($arguments); + + if ($argc >= 2) { + for ($i = 1; $i < $argc; ++$i) { + $argument = strtoupper($arguments[$i]); + if ($argument === 'SET' || $argument === 'INCRBY') { + return false; + } + } + } + + return true; + } + + /** + * Checks if a GEORADIUS command is a readable operation by parsing the + * arguments array of the specified command instance. + * + * @param CommandInterface $command Command instance. + * + * @return bool + */ + protected function isGeoradiusReadOnly(CommandInterface $command) + { + $arguments = $command->getArguments(); + $argc = count($arguments); + $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; + + if ($argc > $startIndex) { + for ($i = $startIndex; $i < $argc; ++$i) { + $argument = strtoupper($arguments[$i]); + if ($argument === 'STORE' || $argument === 'STOREDIST') { + return false; + } + } + } + + return true; + } + + /** + * Marks a command as a read-only operation. + * + * When the behavior of a command can be decided only at runtime depending + * on its arguments, a callable object can be provided to dynamically check + * if the specified command performs a read or a write operation. + * + * @param string $commandID Command ID. + * @param mixed $readonly A boolean value or a callable object. + */ + public function setCommandReadOnly($commandID, $readonly = true) + { + $commandID = strtoupper($commandID); + + if ($readonly) { + $this->readonly[$commandID] = $readonly; + } else { + unset($this->readonly[$commandID]); + } + } + + /** + * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When + * the behaviour of a script can be decided only at runtime depending on + * its arguments, a callable object can be provided to dynamically check + * if the passed instance of EVAL or EVALSHA performs write operations or + * not. + * + * @param string $script Body of the Lua script. + * @param mixed $readonly A boolean value or a callable object. + */ + public function setScriptReadOnly($script, $readonly = true) + { + $sha1 = sha1($script); + + if ($readonly) { + $this->readonlySHA1[$sha1] = $readonly; + } else { + unset($this->readonlySHA1[$sha1]); + } + } + + /** + * Returns the default list of disallowed commands. + * + * @return array + */ + protected function getDisallowedOperations() + { + return [ + 'SHUTDOWN' => true, + 'INFO' => true, + 'DBSIZE' => true, + 'LASTSAVE' => true, + 'CONFIG' => true, + 'MONITOR' => true, + 'SLAVEOF' => true, + 'SAVE' => true, + 'BGSAVE' => true, + 'BGREWRITEAOF' => true, + 'SLOWLOG' => true, + ]; + } + + /** + * Returns the default list of commands performing read-only operations. + * + * @return array + */ + protected function getReadOnlyOperations() + { + return [ + 'EXISTS' => true, + 'TYPE' => true, + 'KEYS' => true, + 'SCAN' => true, + 'RANDOMKEY' => true, + 'TTL' => true, + 'GET' => true, + 'MGET' => true, + 'SUBSTR' => true, + 'STRLEN' => true, + 'GETRANGE' => true, + 'GETBIT' => true, + 'LLEN' => true, + 'LRANGE' => true, + 'LINDEX' => true, + 'SCARD' => true, + 'SISMEMBER' => true, + 'SINTER' => true, + 'SUNION' => true, + 'SDIFF' => true, + 'SMEMBERS' => true, + 'SSCAN' => true, + 'SRANDMEMBER' => true, + 'ZRANGE' => true, + 'ZREVRANGE' => true, + 'ZRANGEBYSCORE' => true, + 'ZREVRANGEBYSCORE' => true, + 'ZCARD' => true, + 'ZSCORE' => true, + 'ZCOUNT' => true, + 'ZRANK' => true, + 'ZREVRANK' => true, + 'ZSCAN' => true, + 'ZLEXCOUNT' => true, + 'ZRANGEBYLEX' => true, + 'ZREVRANGEBYLEX' => true, + 'HGET' => true, + 'HMGET' => true, + 'HEXISTS' => true, + 'HLEN' => true, + 'HKEYS' => true, + 'HVALS' => true, + 'HGETALL' => true, + 'HSCAN' => true, + 'HSTRLEN' => true, + 'PING' => true, + 'AUTH' => true, + 'SELECT' => true, + 'ECHO' => true, + 'QUIT' => true, + 'OBJECT' => true, + 'BITCOUNT' => true, + 'BITPOS' => true, + 'TIME' => true, + 'PFCOUNT' => true, + 'BITFIELD' => [$this, 'isBitfieldReadOnly'], + 'GEOHASH' => true, + 'GEOPOS' => true, + 'GEODIST' => true, + 'GEORADIUS' => [$this, 'isGeoradiusReadOnly'], + 'GEORADIUSBYMEMBER' => [$this, 'isGeoradiusReadOnly'], + ]; + } + + /** + * Disables reads to slaves when using + * a replication topology. + * + * @return self + */ + public function disableLoadBalancing(): self + { + $this->loadBalancing = false; + + return $this; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Replication/RoleException.php b/redis-cache/dependencies/predis/predis/src/Replication/RoleException.php new file mode 100644 index 0000000..968b7e2 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Replication/RoleException.php @@ -0,0 +1,23 @@ +message = $message; + } + + /** + * {@inheritdoc} + */ + public function getMessage() + { + return $this->message; + } + + /** + * {@inheritdoc} + */ + public function getErrorType() + { + [$errorType] = explode(' ', $this->getMessage(), 2); + + return $errorType; + } + + /** + * Converts the object to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->getMessage(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Response/ErrorInterface.php b/redis-cache/dependencies/predis/predis/src/Response/ErrorInterface.php new file mode 100644 index 0000000..ac3bb16 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Response/ErrorInterface.php @@ -0,0 +1,34 @@ +connection = $connection; + $this->size = $size; + $this->position = 0; + $this->current = $size > 0 ? $this->getValue() : null; + } + + /** + * Handles the synchronization of the client with the Redis protocol when + * the garbage collector kicks in (e.g. when the iterator goes out of the + * scope of a foreach or it is unset). + */ + public function __destruct() + { + $this->drop(true); + } + + /** + * Drop queued elements that have not been read from the connection either + * by consuming the rest of the multibulk response or quickly by closing the + * underlying connection. + * + * @param bool $disconnect Consume the iterator or drop the connection. + */ + public function drop($disconnect = false) + { + if ($disconnect) { + if ($this->valid()) { + $this->position = $this->size; + $this->connection->disconnect(); + } + } else { + while ($this->valid()) { + $this->next(); + } + } + } + + /** + * Reads the next item of the multibulk response from the connection. + * + * @return mixed + */ + protected function getValue() + { + return $this->connection->read(); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Response/Iterator/MultiBulkIterator.php b/redis-cache/dependencies/predis/predis/src/Response/Iterator/MultiBulkIterator.php new file mode 100644 index 0000000..cbc74a1 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Response/Iterator/MultiBulkIterator.php @@ -0,0 +1,112 @@ +current; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function next() + { + if (++$this->position < $this->size) { + $this->current = $this->getValue(); + } + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function valid() + { + return $this->position < $this->size; + } + + /** + * Returns the number of items comprising the whole multibulk response. + * + * This method should be used instead of iterator_count() to get the size of + * the current multibulk response since the former consumes the iteration to + * count the number of elements, but our iterators do not support rewinding. + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return $this->size; + } + + /** + * Returns the current position of the iterator. + * + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + abstract protected function getValue(); +} diff --git a/redis-cache/dependencies/predis/predis/src/Response/Iterator/MultiBulkTuple.php b/redis-cache/dependencies/predis/predis/src/Response/Iterator/MultiBulkTuple.php new file mode 100644 index 0000000..77019aa --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Response/Iterator/MultiBulkTuple.php @@ -0,0 +1,95 @@ + $value pairs. + */ +class MultiBulkTuple extends MultiBulk implements OuterIterator +{ + private $iterator; + + /** + * @param MultiBulk $iterator Inner multibulk response iterator. + */ + public function __construct(MultiBulk $iterator) + { + $this->checkPreconditions($iterator); + + $this->size = count($iterator) / 2; + $this->iterator = $iterator; + $this->position = $iterator->getPosition(); + $this->current = $this->size > 0 ? $this->getValue() : null; + } + + /** + * Checks for valid preconditions. + * + * @param MultiBulk $iterator Inner multibulk response iterator. + * + * @throws InvalidArgumentException + * @throws UnexpectedValueException + */ + protected function checkPreconditions(MultiBulk $iterator) + { + if ($iterator->getPosition() !== 0) { + throw new InvalidArgumentException( + 'Cannot initialize a tuple iterator using an already initiated iterator.' + ); + } + + if (($size = count($iterator)) % 2 !== 0) { + throw new UnexpectedValueException('Invalid response size for a tuple iterator.'); + } + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function getInnerIterator() + { + return $this->iterator; + } + + /** + * {@inheritdoc} + */ + public function __destruct() + { + $this->iterator->drop(true); + } + + /** + * {@inheritdoc} + */ + protected function getValue() + { + $k = $this->iterator->current(); + $this->iterator->next(); + + $v = $this->iterator->current(); + $this->iterator->next(); + + return [$k, $v]; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Response/ResponseInterface.php b/redis-cache/dependencies/predis/predis/src/Response/ResponseInterface.php new file mode 100644 index 0000000..d2f6828 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Response/ResponseInterface.php @@ -0,0 +1,20 @@ +getMessage(), 2); + + return $errorType; + } + + /** + * Converts the exception to an instance of Predis\Response\Error. + * + * @return Error + */ + public function toErrorResponse() + { + return new Error($this->getMessage()); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Response/Status.php b/redis-cache/dependencies/predis/predis/src/Response/Status.php new file mode 100644 index 0000000..80cab93 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Response/Status.php @@ -0,0 +1,78 @@ +payload = $payload; + } + + /** + * Converts the response object to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->payload; + } + + /** + * Returns the payload of status response. + * + * @return string + */ + public function getPayload() + { + return $this->payload; + } + + /** + * Returns an instance of a status response object. + * + * Common status responses such as OK or QUEUED are cached in order to lower + * the global memory usage especially when using pipelines. + * + * @param string $payload Status response payload. + * + * @return self + */ + public static function get($payload) + { + switch ($payload) { + case 'OK': + case 'QUEUED': + if (isset(self::$$payload)) { + return self::$$payload; + } + + return self::$$payload = new self($payload); + + default: + return new self($payload); + } + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Session/Handler.php b/redis-cache/dependencies/predis/predis/src/Session/Handler.php new file mode 100644 index 0000000..2e22a18 --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Session/Handler.php @@ -0,0 +1,139 @@ +client = $client; + + if (isset($options['gc_maxlifetime'])) { + $this->ttl = (int) $options['gc_maxlifetime']; + } else { + $this->ttl = ini_get('session.gc_maxlifetime'); + } + } + + /** + * Registers this instance as the current session handler. + */ + public function register() + { + session_set_save_handler($this, true); + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function open($save_path, $session_id) + { + // NOOP + return true; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function close() + { + // NOOP + return true; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function gc($maxlifetime) + { + // NOOP + return true; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function read($session_id) + { + if ($data = $this->client->get($session_id)) { + return $data; + } + + return ''; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function write($session_id, $session_data) + { + $this->client->setex($session_id, $this->ttl, $session_data); + + return true; + } + + /** + * {@inheritdoc} + */ + #[ReturnTypeWillChange] + public function destroy($session_id) + { + $this->client->del($session_id); + + return true; + } + + /** + * Returns the underlying client instance. + * + * @return ClientInterface + */ + public function getClient() + { + return $this->client; + } + + /** + * Returns the session max lifetime value. + * + * @return int + */ + public function getMaxLifeTime() + { + return $this->ttl; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Transaction/AbortedMultiExecException.php b/redis-cache/dependencies/predis/predis/src/Transaction/AbortedMultiExecException.php new file mode 100644 index 0000000..75fc0bb --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Transaction/AbortedMultiExecException.php @@ -0,0 +1,45 @@ +transaction = $transaction; + } + + /** + * Returns the transaction that generated the exception. + * + * @return MultiExec + */ + public function getTransaction() + { + return $this->transaction; + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Transaction/MultiExec.php b/redis-cache/dependencies/predis/predis/src/Transaction/MultiExec.php new file mode 100644 index 0000000..1f99d2a --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Transaction/MultiExec.php @@ -0,0 +1,458 @@ +assertClient($client); + + $this->client = $client; + $this->state = new MultiExecState(); + + $this->configure($client, $options ?: []); + $this->reset(); + } + + /** + * Checks if the passed client instance satisfies the required conditions + * needed to initialize the transaction object. + * + * @param ClientInterface $client Client instance used by the transaction object. + * + * @throws NotSupportedException + */ + private function assertClient(ClientInterface $client) + { + if ($client->getConnection() instanceof ClusterInterface) { + throw new NotSupportedException( + 'Cannot initialize a MULTI/EXEC transaction over cluster connections.' + ); + } + + if (!$client->getCommandFactory()->supports('MULTI', 'EXEC', 'DISCARD')) { + throw new NotSupportedException( + 'MULTI, EXEC and DISCARD are not supported by the current command factory.' + ); + } + } + + /** + * Configures the transaction using the provided options. + * + * @param ClientInterface $client Underlying client instance. + * @param array $options Array of options for the transaction. + **/ + protected function configure(ClientInterface $client, array $options) + { + if (isset($options['exceptions'])) { + $this->exceptions = (bool) $options['exceptions']; + } else { + $this->exceptions = $client->getOptions()->exceptions; + } + + if (isset($options['cas'])) { + $this->modeCAS = (bool) $options['cas']; + } + + if (isset($options['watch']) && $keys = $options['watch']) { + $this->watchKeys = $keys; + } + + if (isset($options['retry'])) { + $this->attempts = (int) $options['retry']; + } + } + + /** + * Resets the state of the transaction. + */ + protected function reset() + { + $this->state->reset(); + $this->commands = new SplQueue(); + } + + /** + * Initializes the transaction context. + */ + protected function initialize() + { + if ($this->state->isInitialized()) { + return; + } + + if ($this->modeCAS) { + $this->state->flag(MultiExecState::CAS); + } + + if ($this->watchKeys) { + $this->watch($this->watchKeys); + } + + $cas = $this->state->isCAS(); + $discarded = $this->state->isDiscarded(); + + if (!$cas || ($cas && $discarded)) { + $this->call('MULTI'); + + if ($discarded) { + $this->state->unflag(MultiExecState::CAS); + } + } + + $this->state->unflag(MultiExecState::DISCARDED); + $this->state->flag(MultiExecState::INITIALIZED); + } + + /** + * Dynamically invokes a Redis command with the specified arguments. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return mixed + */ + public function __call($method, $arguments) + { + return $this->executeCommand( + $this->client->createCommand($method, $arguments) + ); + } + + /** + * Executes a Redis command bypassing the transaction logic. + * + * @param string $commandID Command ID. + * @param array $arguments Arguments for the command. + * + * @return mixed + * @throws ServerException + */ + protected function call($commandID, array $arguments = []) + { + $response = $this->client->executeCommand( + $this->client->createCommand($commandID, $arguments) + ); + + if ($response instanceof ErrorResponseInterface) { + throw new ServerException($response->getMessage()); + } + + return $response; + } + + /** + * Executes the specified Redis command. + * + * @param CommandInterface $command Command instance. + * + * @return $this|mixed + * @throws AbortedMultiExecException + * @throws CommunicationException + */ + public function executeCommand(CommandInterface $command) + { + $this->initialize(); + + if ($this->state->isCAS()) { + return $this->client->executeCommand($command); + } + + $response = $this->client->getConnection()->executeCommand($command); + + if ($response instanceof StatusResponse && $response == 'QUEUED') { + $this->commands->enqueue($command); + } elseif ($response instanceof ErrorResponseInterface) { + throw new AbortedMultiExecException($this, $response->getMessage()); + } else { + $this->onProtocolError('The server did not return a +QUEUED status response.'); + } + + return $this; + } + + /** + * Executes WATCH against one or more keys. + * + * @param string|array $keys One or more keys. + * + * @return mixed + * @throws NotSupportedException + * @throws ClientException + */ + public function watch($keys) + { + if (!$this->client->getCommandFactory()->supports('WATCH')) { + throw new NotSupportedException('WATCH is not supported by the current command factory.'); + } + + if ($this->state->isWatchAllowed()) { + throw new ClientException('Sending WATCH after MULTI is not allowed.'); + } + + $response = $this->call('WATCH', is_array($keys) ? $keys : [$keys]); + $this->state->flag(MultiExecState::WATCH); + + return $response; + } + + /** + * Finalizes the transaction by executing MULTI on the server. + * + * @return MultiExec + */ + public function multi() + { + if ($this->state->check(MultiExecState::INITIALIZED | MultiExecState::CAS)) { + $this->state->unflag(MultiExecState::CAS); + $this->call('MULTI'); + } else { + $this->initialize(); + } + + return $this; + } + + /** + * Executes UNWATCH. + * + * @return MultiExec + * @throws NotSupportedException + */ + public function unwatch() + { + if (!$this->client->getCommandFactory()->supports('UNWATCH')) { + throw new NotSupportedException( + 'UNWATCH is not supported by the current command factory.' + ); + } + + $this->state->unflag(MultiExecState::WATCH); + $this->__call('UNWATCH', []); + + return $this; + } + + /** + * Resets the transaction by UNWATCH-ing the keys that are being WATCHed and + * DISCARD-ing pending commands that have been already sent to the server. + * + * @return MultiExec + */ + public function discard() + { + if ($this->state->isInitialized()) { + $this->call($this->state->isCAS() ? 'UNWATCH' : 'DISCARD'); + + $this->reset(); + $this->state->flag(MultiExecState::DISCARDED); + } + + return $this; + } + + /** + * Executes the whole transaction. + * + * @return mixed + */ + public function exec() + { + return $this->execute(); + } + + /** + * Checks the state of the transaction before execution. + * + * @param mixed $callable Callback for execution. + * + * @throws InvalidArgumentException + * @throws ClientException + */ + private function checkBeforeExecution($callable) + { + if ($this->state->isExecuting()) { + throw new ClientException( + 'Cannot invoke "execute" or "exec" inside an active transaction context.' + ); + } + + if ($callable) { + if (!is_callable($callable)) { + throw new InvalidArgumentException('The argument must be a callable object.'); + } + + if (!$this->commands->isEmpty()) { + $this->discard(); + + throw new ClientException( + 'Cannot execute a transaction block after using fluent interface.' + ); + } + } elseif ($this->attempts) { + $this->discard(); + + throw new ClientException( + 'Automatic retries are supported only when a callable block is provided.' + ); + } + } + + /** + * Handles the actual execution of the whole transaction. + * + * @param mixed $callable Optional callback for execution. + * + * @return array + * @throws CommunicationException + * @throws AbortedMultiExecException + * @throws ServerException + */ + public function execute($callable = null) + { + $this->checkBeforeExecution($callable); + + $execResponse = null; + $attempts = $this->attempts; + + do { + if ($callable) { + $this->executeTransactionBlock($callable); + } + + if ($this->commands->isEmpty()) { + if ($this->state->isWatching()) { + $this->discard(); + } + + return; + } + + $execResponse = $this->call('EXEC'); + + if ($execResponse === null) { + if ($attempts === 0) { + throw new AbortedMultiExecException( + $this, 'The current transaction has been aborted by the server.' + ); + } + + $this->reset(); + + continue; + } + + break; + } while ($attempts-- > 0); + + $response = []; + $commands = $this->commands; + $size = count($execResponse); + + if ($size !== count($commands)) { + $this->onProtocolError('EXEC returned an unexpected number of response items.'); + } + + for ($i = 0; $i < $size; ++$i) { + $cmdResponse = $execResponse[$i]; + + if ($cmdResponse instanceof ErrorResponseInterface && $this->exceptions) { + throw new ServerException($cmdResponse->getMessage()); + } + + $response[$i] = $commands->dequeue()->parseResponse($cmdResponse); + } + + return $response; + } + + /** + * Passes the current transaction object to a callable block for execution. + * + * @param mixed $callable Callback. + * + * @throws CommunicationException + * @throws ServerException + */ + protected function executeTransactionBlock($callable) + { + $exception = null; + $this->state->flag(MultiExecState::INSIDEBLOCK); + + try { + call_user_func($callable, $this); + } catch (CommunicationException $exception) { + // NOOP + } catch (ServerException $exception) { + // NOOP + } catch (Exception $exception) { + $this->discard(); + } + + $this->state->unflag(MultiExecState::INSIDEBLOCK); + + if ($exception) { + throw $exception; + } + } + + /** + * Helper method for protocol errors encountered inside the transaction. + * + * @param string $message Error message. + */ + private function onProtocolError($message) + { + // Since a MULTI/EXEC block cannot be initialized when using aggregate + // connections we can safely assume that Predis\Client::getConnection() + // will return a Predis\Connection\NodeConnectionInterface instance. + CommunicationException::handle(new ProtocolException( + $this->client->getConnection(), $message + )); + } +} diff --git a/redis-cache/dependencies/predis/predis/src/Transaction/MultiExecState.php b/redis-cache/dependencies/predis/predis/src/Transaction/MultiExecState.php new file mode 100644 index 0000000..c9be15c --- /dev/null +++ b/redis-cache/dependencies/predis/predis/src/Transaction/MultiExecState.php @@ -0,0 +1,162 @@ +flags = 0; + } + + /** + * Sets the internal state flags. + * + * @param int $flags Set of flags + */ + public function set($flags) + { + $this->flags = $flags; + } + + /** + * Gets the internal state flags. + * + * @return int + */ + public function get() + { + return $this->flags; + } + + /** + * Sets one or more flags. + * + * @param int $flags Set of flags + */ + public function flag($flags) + { + $this->flags |= $flags; + } + + /** + * Resets one or more flags. + * + * @param int $flags Set of flags + */ + public function unflag($flags) + { + $this->flags &= ~$flags; + } + + /** + * Returns if the specified flag or set of flags is set. + * + * @param int $flags Flag + * + * @return bool + */ + public function check($flags) + { + return ($this->flags & $flags) === $flags; + } + + /** + * Resets the state of a transaction. + */ + public function reset() + { + $this->flags = 0; + } + + /** + * Returns the state of the RESET flag. + * + * @return bool + */ + public function isReset() + { + return $this->flags === 0; + } + + /** + * Returns the state of the INITIALIZED flag. + * + * @return bool + */ + public function isInitialized() + { + return $this->check(self::INITIALIZED); + } + + /** + * Returns the state of the INSIDEBLOCK flag. + * + * @return bool + */ + public function isExecuting() + { + return $this->check(self::INSIDEBLOCK); + } + + /** + * Returns the state of the CAS flag. + * + * @return bool + */ + public function isCAS() + { + return $this->check(self::CAS); + } + + /** + * Returns if WATCH is allowed in the current state. + * + * @return bool + */ + public function isWatchAllowed() + { + return $this->check(self::INITIALIZED) && !$this->check(self::CAS); + } + + /** + * Returns the state of the WATCH flag. + * + * @return bool + */ + public function isWatching() + { + return $this->check(self::WATCH); + } + + /** + * Returns the state of the DISCARDED flag. + * + * @return bool + */ + public function isDiscarded() + { + return $this->check(self::DISCARDED); + } +} diff --git a/redis-cache/dependencies/vendor/predis/predis/autoload.php b/redis-cache/dependencies/vendor/predis/predis/autoload.php new file mode 100644 index 0000000..35340fe --- /dev/null +++ b/redis-cache/dependencies/vendor/predis/predis/autoload.php @@ -0,0 +1,8 @@ +prefixes[ $prefix ] ) ) { + $this->prefixes[ $prefix ] = []; + } + + if ( $prepend ) { + array_unshift( $this->prefixes[ $prefix ], $base_dir ); + } else { + array_push( $this->prefixes[ $prefix ], $base_dir ); + } + } + + /** + * Loads the class file for a given class name. + * + * @since 2.0.0 + * @param string $class The fully-qualified class name. + * @return string|null The mapped file name on success, or null on failure. + */ + public function load_class( $class ) { + $prefix = $class; + + while ( false !== ( $pos = strrpos( $prefix, '\\' ) ) ) { // phpcs:ignore + $prefix = substr( $class, 0, $pos + 1 ); + + $relative_class = substr( $class, $pos + 1 ); + + $mapped_file = $this->load_mapped_file( $prefix, $relative_class ); + if ( $mapped_file ) { + return $mapped_file; + } + + $prefix = rtrim( $prefix, '\\' ); + } + + return null; + } + + /** + * Load the mapped file for a namespace prefix and relative class. + * + * @since 2.0.0 + * @param string $prefix The namespace prefix. + * @param string $relative_class The relative class name. + * @return string|null Null if no mapped file can be loaded, or + * the name of the loaded mapped file. + */ + private function load_mapped_file( $prefix, $relative_class ) { + if ( false === isset( $this->prefixes[ $prefix ] ) ) { + return null; + } + + foreach ( $this->prefixes[ $prefix ] as $base_dir ) { + $relative_class = strtolower( $relative_class ); + $relative_class = strtr( $relative_class, '_', '-' ); + + $file = $base_dir + . str_replace( '\\', '/', $relative_class ) + . '.php'; + + if ( $this->class_file_prefix ) { + $pos = strrpos( $file, '/' ); + $filename = $this->class_file_prefix . substr( $file, $pos + 1 ); + $file = substr_replace( $file, $filename, $pos + 1 ); + } + + if ( $this->require_file( $file ) ) { + return $file; + } + } + + return null; + } + + /** + * If a file exists, require it from the file system. + * + * @since 2.0.0 + * @param string $file The file to require. + * @return bool True if the file exists, false if not. + */ + private function require_file( $file ) { + if ( file_exists( $file ) ) { + require $file; + return true; + } + return false; + } +} diff --git a/redis-cache/includes/class-metrics.php b/redis-cache/includes/class-metrics.php new file mode 100644 index 0000000..29bdaf3 --- /dev/null +++ b/redis-cache/includes/class-metrics.php @@ -0,0 +1,276 @@ +get_redis_status() + && method_exists( $wp_object_cache, 'info' ) + && method_exists( $wp_object_cache, 'redis_instance' ); + } + + /** + * Retrieves metrics max time + * + * @return int + */ + public static function max_time() { + if ( defined( 'WP_REDIS_METRICS_MAX_TIME' ) ) { + return (int) WP_REDIS_METRICS_MAX_TIME; + } + + return HOUR_IN_SECONDS; + } + + /** + * Records metrics and adds them to redis + * + * @return void + */ + public static function record() { + if ( ! self::is_active() ) { + return; + } + + $metrics = new self(); + $metrics->collect(); + $metrics->save(); + } + + /** + * Collect metrics from object cache instance. + */ + public function collect() { + global $wp_object_cache; + + $info = $wp_object_cache->info(); + + $this->id = substr( md5( uniqid( strval( mt_rand() ), true ) ), 12 ); + $this->hits = $info->hits; + $this->misses = $info->misses; + $this->ratio = $info->ratio; + $this->bytes = $info->bytes; + $this->time = round( $info->time, 5 ); + $this->calls = $info->calls; + $this->timestamp = time(); + } + + /** + * Retrieves metrics from redis + * + * @param int $seconds Number of seconds of the oldest entry to retrieve. + * @return Metrics[] + */ + public static function get( $seconds = null ) { + global $wp_object_cache; + + if ( ! self::is_active() ) { + return []; + } + + if ( null === $seconds ) { + $seconds = self::max_time(); + } + + try { + $raw_metrics = $wp_object_cache->redis_instance()->zrangebyscore( + $wp_object_cache->build_key( 'metrics', 'redis-cache' ), + time() - $seconds, + time() - MINUTE_IN_SECONDS, + [ 'withscores' => true ] + ); + } catch ( Exception $exception ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( $exception ); + + return []; + } + + $metrics = []; + $prefix = sprintf( 'O:%d:"%s', strlen( self::class ), self::class ); + + foreach ( $raw_metrics as $serialized => $timestamp ) { + // Compatibility: Ignore all non serialized entries as they were used by prior versions. + if ( strpos( $serialized, $prefix ) !== 0 ) { + continue; + } + + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize + $metrics[] = unserialize( $serialized ); + } + + return $metrics; + } + + /** + * Saves the current metrics to redis + * + * @return void + */ + public function save() { + global $wp_object_cache; + + try { + $wp_object_cache->redis_instance()->zadd( + $wp_object_cache->build_key( 'metrics', 'redis-cache' ), + $this->timestamp, + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize + serialize( $this ) + ); + } catch ( Exception $exception ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( $exception ); + } + } + + /** + * Removes recorded metrics after an hour + * + * @return void + */ + public static function discard() { + global $wp_object_cache; + + if ( ! self::is_active() ) { + return; + } + + try { + $wp_object_cache->redis_instance()->zremrangebyscore( + $wp_object_cache->build_key( 'metrics', 'redis-cache' ), + 0, + time() - self::max_time() + ); + } catch ( Exception $exception ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( $exception ); + } + } + + /** + * Counts the recorded metrics + * + * @return int + */ + public static function count() { + global $wp_object_cache; + + if ( ! self::is_active() ) { + return 0; + } + + try { + return $wp_object_cache->redis_instance()->zcount( + $wp_object_cache->build_key( 'metrics', 'redis-cache' ), + '-inf', + '+inf' + ); + } catch ( Exception $exception ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( $exception ); + return 0; + } + } + +} diff --git a/redis-cache/includes/class-plugin.php b/redis-cache/includes/class-plugin.php new file mode 100644 index 0000000..80a79dc --- /dev/null +++ b/redis-cache/includes/class-plugin.php @@ -0,0 +1,1599 @@ +page = 'settings.php?page=redis-cache'; + $this->screen = 'settings_page_redis-cache-network'; + } else { + $this->page = 'options-general.php?page=redis-cache'; + $this->screen = 'settings_page_redis-cache'; + } + + Metrics::init(); + + $this->add_actions_and_filters(); + } + + /** + * Adds all necessary hooks + * + * @return void + */ + public function add_actions_and_filters() { + add_action( 'deactivate_plugin', [ $this, 'on_deactivation' ] ); + add_action( 'admin_init', [ $this, 'maybe_update_dropin' ] ); + add_action( 'admin_init', [ $this, 'maybe_redirect' ] ); + add_action( 'init', [ $this, 'init' ] ); + + add_action( is_multisite() ? 'network_admin_menu' : 'admin_menu', [ $this, 'add_admin_menu_page' ] ); + + add_action( 'admin_notices', [ $this, 'show_admin_notices' ] ); + add_action( 'network_admin_notices', [ $this, 'show_admin_notices' ] ); + + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_styles' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_redis_metrics' ] ); + + add_action( 'admin_bar_menu', [ $this, 'render_admin_bar' ], 998 ); + + add_action( 'load-settings_page_redis-cache', [ $this, 'do_admin_actions' ] ); + + add_action( 'wp_dashboard_setup', [ $this, 'setup_dashboard_widget' ] ); + add_action( 'wp_network_dashboard_setup', [ $this, 'setup_dashboard_widget' ] ); + + add_action( 'wp_ajax_roc_dismiss_notice', [ $this, 'ajax_dismiss_notice' ] ); + add_action( 'wp_ajax_roc_flush_cache', [ $this, 'ajax_flush_cache' ] ); + + add_filter( 'gettext_redis-cache', [ $this, 'get_text' ], 10, 2 ); + + add_filter( 'plugin_row_meta', [ $this, 'add_plugin_row_meta' ], 10, 2 ); + add_filter( sprintf( '%splugin_action_links_%s', is_multisite() ? 'network_admin_' : '', WP_REDIS_BASENAME ), [ $this, 'add_plugin_actions_links' ] ); + + add_action( 'wp_head', [ $this, 'register_shutdown_hooks' ] ); + + add_filter( 'qm/collectors', [ $this, 'register_qm_collector' ], 25 ); + add_filter( 'qm/outputter/html', [ $this, 'register_qm_output' ] ); + + add_filter( 'perflab_disable_object_cache_dropin', '__return_true' ); + add_filter( 'w3tc_config_item_objectcache.enabled', '__return_false' ); + add_action( 'litespeed_init', [ $this, 'litespeed_disable_objectcache' ] ); + } + + /** + * Callback of the `init` hook. + * + * @return void + */ + public function init() { + load_plugin_textdomain( 'redis-cache', false, 'redis-cache/languages' ); + + if ( is_admin() && ! wp_next_scheduled( 'rediscache_discard_metrics' ) ) { + wp_schedule_event( time(), 'hourly', 'rediscache_discard_metrics' ); + } + } + + /** + * Adds a submenu page to "Settings" + * + * @return void + */ + public function add_admin_menu_page() { + add_submenu_page( + is_multisite() ? 'settings.php' : 'options-general.php', + __( 'Redis Object Cache', 'redis-cache' ), + __( 'Redis', 'redis-cache' ), + $this->manage_redis_capability(), + 'redis-cache', + [ $this, 'show_admin_page' ] + ); + } + + /** + * Displays the settings page + * + * @return void + */ + public function show_admin_page() { + // Request filesystem credentials? + if ( isset( $_GET['_wpnonce'], $_GET['action'] ) ) { + $action = sanitize_key( $_GET['action'] ); + $nonce = sanitize_key( $_GET['_wpnonce'] ); + + foreach ( $this->actions as $name ) { + // Nonce verification. + if ( $action === $name && wp_verify_nonce( $nonce, $action ) ) { + $url = $this->action_link( $action ); + + if ( $this->initialize_filesystem( $url ) === false ) { + return; // Request filesystem credentials. + } + } + } + } + + if ( wp_next_scheduled( 'redis_gather_metrics' ) ) { + wp_clear_scheduled_hook( 'redis_gather_metrics' ); + } + + UI::register_tab( + 'overview', + __( 'Overview', 'redis-cache' ), + [ 'default' => true ] + ); + + UI::register_tab( + 'metrics', + __( 'Metrics', 'redis-cache' ), + [ 'disabled' => ! Metrics::is_enabled() ] + ); + + UI::register_tab( + 'diagnostics', + __( 'Diagnostics', 'redis-cache' ) + ); + + // Show the admin page. + require_once WP_REDIS_PLUGIN_PATH . '/includes/ui/settings.php'; + } + + /** + * Adds the dashboard metrics widget + * + * @return void + */ + public function setup_dashboard_widget() { + if ( ! $this->current_user_can_manage_redis() ) { + return; + } + + if ( ! Metrics::is_enabled() ) { + return; + } + + wp_add_dashboard_widget( + 'dashboard_rediscache', + __( 'Redis Object Cache', 'redis-cache' ), + [ $this, 'show_dashboard_widget' ], + null, + null, + 'normal', + 'high' + ); + } + + /** + * Displays the dashboard widget + * + * @return void + */ + public function show_dashboard_widget() { + require_once WP_REDIS_PLUGIN_PATH . '/includes/ui/widget.php'; + } + + /** + * Adds the settings page to the plugin action links on the plugin page + * + * @param string[] $links The current plugin action links. + * @return string[] + */ + public function add_plugin_actions_links( $links ) { + $settings = sprintf( + '%s', + network_admin_url( $this->page ), + esc_html__( 'Settings', 'redis-cache' ) + ); + + return array_merge( [ $settings ], $links ); + } + + /** + * Ensure Author URI has UTM parameters. + * + * @param string $translation Translated text. + * @param string $text Text to translate. + * @return string + */ + public function get_text( $translation, $text ) { + if ( $text === 'https://objectcache.pro' ) { + return $this->link_to_ocp( 'author', false ); + } + + return $translation; + } + + /** + * Adds plugin meta links on the plugin page + * + * @param string[] $plugin_meta An array of the plugin's metadata. + * @param string $plugin_file Path to the plugin file relative to the plugins directory. + * @return string[] An array of the plugin's metadata. + */ + public function add_plugin_row_meta( array $plugin_meta, $plugin_file ) { + if ( strpos( $plugin_file, 'redis-cache.php' ) === false ) { + return $plugin_meta; + } + + if ( self::acceleratewp_install() ) { + return $plugin_meta; + } + + $plugin_meta[] = sprintf( + '%2$s', + $this->link_to_ocp( 'meta-row' ), + esc_html_x( 'Upgrade to Pro', 'verb', 'redis-cache' ) + ); + + return $plugin_meta; + } + + /** + * Returns the link to Object Cache Pro. + * + * @param string $medium + * @param bool $as_html + * @return string + */ + public function link_to_ocp($medium, $as_html = true) + { + $ref = 'oss'; + + if ( self::acceleratewp_install( true ) ) { + $ref = 'oss-cl'; + } + + $url = "https://objectcache.pro/?ref={$ref}&utm_source=wp-plugin&utm_medium={$medium}"; + + return $as_html ? str_replace('&', '&', $url) : $url; + } + + /** + * Enqueues admin style resources + * + * @return void + */ + public function enqueue_admin_styles() { + $screen = get_current_screen(); + + if ( ! isset( $screen->id ) ) { + return; + } + + $screens = [ + $this->screen, + 'dashboard', + 'dashboard-network', + ]; + + if ( ! in_array( $screen->id, $screens, true ) ) { + return; + } + + wp_enqueue_style( 'redis-cache', WP_REDIS_PLUGIN_DIR . '/assets/css/admin.css', [], WP_REDIS_VERSION ); + } + + /** + * Enqueues admin script resources + * + * @return void + */ + public function enqueue_admin_scripts() { + $screen = get_current_screen(); + + if ( ! isset( $screen->id ) ) { + return; + } + + $screens = [ + $this->screen, + 'dashboard', + 'dashboard-network', + 'edit-shop_order', + 'edit-product', + 'woocommerce_page_wc-admin', + ]; + + if ( ! in_array( $screen->id, $screens, true ) ) { + return; + } + + $clipboard = file_exists( ABSPATH . WPINC . '/js/clipboard.min.js' ); + + wp_enqueue_script( + 'redis-cache', + plugins_url( 'assets/js/admin.js', WP_REDIS_FILE ), + array_merge( [ 'jquery', 'underscore' ], $clipboard ? [ 'clipboard' ] : [] ), + WP_REDIS_VERSION, + true + ); + + wp_localize_script( + 'redis-cache', + 'rediscache', + [ + 'jQuery' => 'jQuery', + 'disable_pro' => $screen->id !== $this->screen + || ( defined( 'WP_REDIS_DISABLE_BANNERS' ) && WP_REDIS_DISABLE_BANNERS ) + || self::acceleratewp_install(), + 'l10n' => [ + 'time' => __( 'Time', 'redis-cache' ), + 'bytes' => __( 'Bytes', 'redis-cache' ), + 'ratio' => __( 'Ratio', 'redis-cache' ), + 'calls' => __( 'Calls', 'redis-cache' ), + 'no_data' => __( 'Not enough data collected, yet.', 'redis-cache' ), + 'no_cache' => __( 'Enable object cache to collect data.', 'redis-cache' ), + 'pro' => 'Object Cache Pro', + ], + ] + ); + } + + /** + * Enqueues scripts to display recorded metrics + * + * @return void + */ + public function enqueue_redis_metrics() { + if ( ! Metrics::is_enabled() ) { + return; + } + + $screen = get_current_screen(); + + if ( ! isset( $screen->id ) ) { + return; + } + + if ( ! in_array( $screen->id, [ $this->screen, 'dashboard', 'dashboard-network' ], true ) ) { + return; + } + + wp_enqueue_script( + 'redis-cache-charts', + plugins_url( 'assets/js/apexcharts.min.js', WP_REDIS_FILE ), + [], + WP_REDIS_VERSION, + true + ); + + if ( ! $this->get_redis_status() ) { + return; + } + + $min_time = $screen->id === $this->screen + ? Metrics::max_time() + : MINUTE_IN_SECONDS * 30; + + $metrics = Metrics::get( $min_time ); + + wp_localize_script( 'redis-cache', 'rediscache_metrics', $metrics ); + } + + /** + * Registers a new cache collector for the Query Monitor plugin + * + * @param array $collectors Array of currently registered collectors. + * @return array + */ + public function register_qm_collector( array $collectors ) { + $collectors['cache'] = new QM_Collector(); + + return $collectors; + } + + /** + * Registers a new cache output using our collector for the Query Monitor plugin + * + * @param array $output Array of current QM_Output handlers. + * @return array + */ + public function register_qm_output( $output ) { + $collector = \QM_Collectors::get( 'cache' ); + + if ( + $collector instanceof QM_Collector && + method_exists( 'QM_Output_Html', 'before_non_tabular_output' ) + ) { + $output['cache'] = new QM_Output( $collector ); + } + + return $output; + } + + /** + * Checks if the `object-cache.php` drop-in exists + * + * @return bool + */ + public function object_cache_dropin_exists() { + return file_exists( WP_CONTENT_DIR . '/object-cache.php' ); + } + + /** + * Validates the `object-cache.php` drop-in + * + * @return bool + */ + public function validate_object_cache_dropin() { + if ( ! $this->object_cache_dropin_exists() ) { + return false; + } + + $dropin = get_plugin_data( WP_CONTENT_DIR . '/object-cache.php' ); + $plugin = get_plugin_data( WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php' ); + + /** + * Filters the drop-in validation state + * + * @since 2.0.16 + * @param bool $state The validation state of the drop-in. + * @param string $dropin The `PluginURI` of the drop-in. + * @param string $plugin The `PluginURI` of the plugin. + */ + return apply_filters( + 'redis_cache_validate_dropin', + $dropin['PluginURI'] === $plugin['PluginURI'], + $dropin['PluginURI'], + $plugin['PluginURI'] + ); + } + + /** + * Checks if the `object-cache.php` drop-in is outdated + * + * @return bool + */ + public function object_cache_dropin_outdated() { + if ( ! $this->object_cache_dropin_exists() ) { + return false; + } + + $dropin = get_plugin_data( WP_CONTENT_DIR . '/object-cache.php' ); + $plugin = get_plugin_data( WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php' ); + + if ( $dropin['PluginURI'] === $plugin['PluginURI'] ) { + return version_compare( $dropin['Version'], $plugin['Version'], '<' ); + } + + return false; + } + + /** + * Retrieves the current human-readable status + * + * @return string + */ + public function get_status() { + global $wp_object_cache; + + if ( defined( 'WP_REDIS_DISABLED' ) && WP_REDIS_DISABLED ) { + return __( 'Disabled', 'redis-cache' ); + } + + if ( ! $this->object_cache_dropin_exists() ) { + return __( 'Not enabled', 'redis-cache' ); + } + + if ( $this->object_cache_dropin_outdated() ) { + return __( 'Drop-in is outdated', 'redis-cache' ); + } + + if ( ! $this->validate_object_cache_dropin() ) { + return __( 'Drop-in is invalid', 'redis-cache' ); + } + + if ( method_exists( $wp_object_cache, 'redis_status' ) ) { + return $wp_object_cache->redis_status() + ? __( 'Connected', 'redis-cache' ) + : __( 'Not connected', 'redis-cache' ); + } + + return __( 'Unknown', 'redis-cache' ); + } + + /** + * Retrieves the Redis connection status + * + * @return bool|null Boolean Redis connection status if available, null otherwise. + */ + public function get_redis_status() { + global $wp_object_cache; + + if ( defined( 'WP_REDIS_DISABLED' ) && WP_REDIS_DISABLED ) { + return null; + } + + if ( $this->object_cache_dropin_outdated() ) { + return null; + } + + if ( ! $this->validate_object_cache_dropin() ) { + return null; + } + + if ( ! method_exists( $wp_object_cache, 'redis_status' ) ) { + return null; + } + + return $wp_object_cache->redis_status(); + } + + /** + * Check whether we can connect to Redis via Predis. + * + * @return bool|string + */ + public function check_redis_connection() { + try { + $predis = new Predis(); + $predis->connect(); + } catch ( Exception $error ) { + return $error->getMessage(); + } + + return true; + } + + /** + * Returns the redis version if possible + * + * @see WP_Object_Cache::redis_version() + * @return null|string + */ + public function get_redis_version() { + global $wp_object_cache; + + if ( defined( 'WP_REDIS_DISABLED' ) && WP_REDIS_DISABLED ) { + return null; + } + + if ( ! $this->validate_object_cache_dropin() || ! method_exists( $wp_object_cache, 'redis_version' ) ) { + return null; + } + + return $wp_object_cache->redis_version(); + } + + /** + * Returns the currently used redis client (if any) + * + * @return null|string + */ + public function get_redis_client_name() { + global $wp_object_cache; + + if ( isset( $wp_object_cache->diagnostics[ 'client' ] ) ) { + return $wp_object_cache->diagnostics[ 'client' ]; + } + + if ( ! defined( 'WP_REDIS_CLIENT' ) ) { + return null; + } + + return WP_REDIS_CLIENT; + } + + /** + * Fetches the redis diagnostics data + * + * @return null|array + */ + public function get_diagnostics() { + global $wp_object_cache; + + if ( ! $this->validate_object_cache_dropin() || ! property_exists( $wp_object_cache, 'diagnostics' ) ) { + return null; + } + + return $wp_object_cache->diagnostics; + } + + /** + * Retrieves the redis prefix + * + * @return null|mixed + */ + public function get_redis_prefix() { + return defined( 'WP_REDIS_PREFIX' ) ? WP_REDIS_PREFIX : null; + } + + /** + * Retrieves the redis maximum time to live + * + * @return null|mixed + */ + public function get_redis_maxttl() { + return defined( 'WP_REDIS_MAXTTL' ) ? WP_REDIS_MAXTTL : null; + } + + /** + * Displays admin notices + * + * @return void + */ + public function show_admin_notices() { + $this->pro_notice(); + $this->wc_pro_notice(); + + // Only show admin notices to users with the right capability. + if ( ! $this->current_user_can_manage_redis() ) { + return; + } + + // Do not display the dropin message if you want + if ( defined( 'WP_REDIS_DISABLE_DROPIN_BANNERS' ) && WP_REDIS_DISABLE_DROPIN_BANNERS ) { + return; + } + + if ( defined( 'RedisCachePro\Version' ) || defined( 'ObjectCachePro\Version' ) ) { + return; + } + + if ( $this->object_cache_dropin_exists() ) { + if ( $this->validate_object_cache_dropin() ) { + if ( $this->object_cache_dropin_outdated() ) { + $message = sprintf( + // translators: %s = Action link to update the drop-in. + __( 'The Redis object cache drop-in is outdated. Please update the drop-in.', 'redis-cache' ), + $this->action_link( 'update-dropin' ) + ); + } + } else { + $message = sprintf( + // translators: %s = Link to settings page. + __( 'A foreign object cache drop-in was found. To use Redis for object caching, please enable the drop-in.', 'redis-cache' ), + esc_url( network_admin_url( $this->page ) ) + ); + } + + if ( isset( $message ) ) { + printf( '
%s
', wp_kses_post( $message ) ); + } + } + } + + /** + * Display the admin bar menu item. + * + * @param \WP_Admin_Bar $wp_admin_bar + * + * @return void + */ + public function render_admin_bar( $wp_admin_bar ) { + if ( defined( 'WP_REDIS_DISABLE_ADMINBAR' ) && WP_REDIS_DISABLE_ADMINBAR ) { + return; + } + + if ( ! $this->current_user_can_manage_redis() ) { + return; + } + + $nodeTitle = __( 'Object Cache', 'redis-cache' ); + + $style = preg_replace( '/\s+/', ' ', $this->admin_bar_style() ); + $script = preg_replace( '/\s+/', ' ', $this->admin_bar_script() ); + $html = "\n{$style}\n{$script}\n"; + + $redis_status = $this->get_redis_status(); + + $wp_admin_bar->add_node([ + 'id' => 'redis-cache', + 'title' => $nodeTitle, + 'meta' => [ + 'html' => $html, + 'class' => $redis_status === false ? 'redis-cache-error' : '', + ], + ]); + + if ( $redis_status ) { + $wp_admin_bar->add_node([ + 'parent' => 'redis-cache', + 'id' => 'redis-cache-flush', + 'title' => __( 'Flush Cache', 'redis-cache' ), + 'href' => $this->action_link( 'flush-cache' ), + ]); + } + + $wp_admin_bar->add_node([ + 'parent' => 'redis-cache', + 'id' => 'redis-cache-metrics', + 'title' => __( 'Settings', 'redis-cache' ), + 'href' => network_admin_url( $this->page ), + ]); + + $wp_admin_bar->add_group( + [ + 'id' => 'redis-cache-info', + 'parent' => 'redis-cache', + 'meta' => [ + 'class' => 'ab-sub-secondary', + ], + ] + ); + + $value = $this->get_status(); + // translators: %s = The status of the Redis connection. + $title = sprintf( __( 'Status: %s', 'redis-cache' ), $value ); + + if ( $redis_status ) { + global $wp_object_cache; + + $info = $wp_object_cache->info(); + $hits = number_format_i18n( $info->hits ); + $misses = number_format_i18n( $info->misses ); + $size = size_format( $info->bytes ); + + $value = sprintf( + '%s%%  %s/%s  %s', + $info->ratio, + $hits, + $misses, + $size + ); + + $title = sprintf( + // translators: 1: Hit ratio, 2: Hits, 3: Misses. 4: Human-readable size of cache. + __( '(Current page) Hit Ratio: %1$s%%, Hits %2$s, Misses: %3$s, Size: %4$s', 'redis-cache' ), + $info->ratio, + $hits, + $misses, + $size + ); + } + + $wp_admin_bar->add_node([ + 'parent' => 'redis-cache-info', + 'id' => 'redis-cache-info-details', + 'title' => $value, + 'href' => $redis_status && Metrics::is_enabled() ? network_admin_url( $this->page . '#metrics' ) : '', + 'meta' => [ + 'title' => $title, + ], + ]); + } + + /** + * Returns the admin-bar +HTML; + } + + /** + * Returns the admin-bar +HTML; + } + + /** + * Executes admin actions + * + * @return void + */ + public function do_admin_actions() { + global $wp_filesystem; + + if ( isset( $_GET['_wpnonce'], $_GET['action'] ) ) { + $action = sanitize_key( $_GET['action'] ); + $nonce = sanitize_key( $_GET['_wpnonce'] ); + + // Nonce verification. + foreach ( $this->actions as $name ) { + if ( $action === $name && ! wp_verify_nonce( $nonce, $action ) ) { + return; + } + } + + if ( in_array( $action, $this->actions, true ) ) { + $url = $this->action_link( $action ); + + if ( $action === 'flush-cache' ) { + wp_cache_flush() + ? add_settings_error( + 'redis-cache', + 'flush', + __( 'Object cache flushed.', 'redis-cache' ), + 'updated' + ) + : add_settings_error( + 'redis-cache', + 'flush', + __( 'Object cache could not be flushed.', 'redis-cache' ), + 'error' + ); + } + + // do we have filesystem credentials? + if ( $this->initialize_filesystem( $url, true ) ) { + + if ( $action === 'enable-cache' ) { + $result = $wp_filesystem->copy( + WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php', + WP_CONTENT_DIR . '/object-cache.php', + true, + FS_CHMOD_FILE + ); + + if ( $result ) { + try { + $predis = new Predis(); + $predis->flush(); + } catch ( Exception $exception ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( $exception ); + } + } + + /** + * Fires on cache enable event + * + * @since 1.3.5 + * @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful. + */ + do_action( 'redis_object_cache_enable', $result ); + + $result + ? add_settings_error( + 'redis-cache', + 'dropin', + __( 'Object cache enabled.', 'redis-cache' ), + 'updated' + ) + : add_settings_error( + 'redis-cache', + 'dropin', + __( 'Object cache could not be enabled.', 'redis-cache' ), + 'error' + ); + } + + if ( $action === 'disable-cache' ) { + $result = $wp_filesystem->delete( WP_CONTENT_DIR . '/object-cache.php' ); + + if ( $result ) { + try { + $predis = new Predis(); + $predis->flush(); + } catch ( Exception $exception ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( $exception ); + } + } + + /** + * Fires on cache enable event + * + * @since 1.3.5 + * @param bool $result Whether the filesystem event (deletion of the `object-cache.php` file) was successful. + */ + do_action( 'redis_object_cache_disable', $result ); + + $result + ? add_settings_error( + 'redis-cache', + 'dropin', + __( 'Object cache disabled.', 'redis-cache' ), + 'updated' + ) + : add_settings_error( + 'redis-cache', + 'dropin', + __( 'Object cache could not be disabled.', 'redis-cache' ), + 'error' + ); + } + + if ( $action === 'update-dropin' ) { + $result = $wp_filesystem->copy( + WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php', + WP_CONTENT_DIR . '/object-cache.php', + true, + FS_CHMOD_FILE + ); + + /** + * Fires on cache enable event + * + * @since 1.3.5 + * @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful. + */ + do_action( 'redis_object_cache_update_dropin', $result ); + + $result + ? add_settings_error( + 'redis-cache', + 'dropin', + __( 'Updated object cache drop-in and enabled Redis object cache.', 'redis-cache' ), + 'updated' + ) + : add_settings_error( + 'redis-cache', + 'dropin', + __( 'Object cache drop-in could not be updated.', 'redis-cache' ), + 'error' + ); + } + } + + $messages = get_settings_errors( 'redis-cache' ); + + if ( count( $messages ) !== 0 ) { + set_transient( 'settings_errors', $messages, 30 ); + + wp_safe_redirect( + network_admin_url( add_query_arg( 'settings-updated', 1, $this->page ) ) + ); + exit; + } + } + } + } + + /** + * Dismisses the admin notice for the current user + * + * @return void + */ + public function ajax_dismiss_notice() { + if ( isset( $_POST['notice'] ) ) { + check_ajax_referer( 'roc_dismiss_notice' ); + + $notice = sprintf( + 'roc_dismissed_%s', + sanitize_key( $_POST['notice'] ) + ); + + update_user_meta( get_current_user_id(), $notice, '1' ); + } + + wp_die(); + } + + /** + * Flushes object cache + * + * @return void + */ + public function ajax_flush_cache() { + if ( ! wp_verify_nonce( $_POST['nonce'] ) ) { + $message = 'Invalid Nonce.'; + } else if ( wp_cache_flush() ) { + $message = 'Object cache flushed.'; + } else { + $message = 'Object cache could not be flushed.'; + } + + wp_die( __( $message , 'redis-cache' ) ); + } + + /** + * Displays a redis cache pro admin notice + * + * @return void + */ + public function pro_notice() { + $screen = get_current_screen(); + + if ( ! isset( $screen->id ) ) { + return; + } + + if ( $screen->id === $this->screen && ( defined( 'RedisCachePro\Version' ) || defined( 'ObjectCachePro\Version' ) ) ) { + printf( + '

%s

', + wp_kses_post( + sprintf( + // translators: %s = Action link to update the drop-in. + __( 'The Object Cache Pro plugin appears to be installed and should be used. You can safely uninstall Redis Object Cache.', 'redis-cache' ), + esc_url( network_admin_url( 'plugins.php?plugin_status=all&s=redis%20object%20cache' ) ) + ) + ) + ); + + return; + } + + if ( defined( 'RedisCachePro\Version' ) || defined( 'ObjectCachePro\Version' ) ) { + return; + } + + if ( defined( 'WP_REDIS_DISABLE_BANNERS' ) && WP_REDIS_DISABLE_BANNERS ) { + return; + } + + if ( ! in_array( $screen->id, [ 'dashboard', 'dashboard-network' ], true ) ) { + return; + } + + if ( ! $this->current_user_can_manage_redis() ) { + return; + } + + if ( intval( get_user_meta( get_current_user_id(), 'roc_dismissed_pro_release_notice', true ) ) === 1 ) { + return; + } + + printf( + '

%s %s

', + esc_attr( wp_create_nonce( 'roc_dismiss_notice' ) ), + esc_html__( 'Object Cache Pro!', 'redis-cache' ), + sprintf( + // translators: %s = Link to the plugin setting screen. + wp_kses_post( __( 'A business class object cache backend. Truly reliable, highly-optimized and fully customizable, with a dedicated engineer when you most need it. Learn more »', 'redis-cache' ) ), + esc_url( network_admin_url( $this->page ) ) + ) + ); + } + + /** + * Displays a redis cache pro admin notice specifically for WooCommerce + * + * @return void + */ + public function wc_pro_notice() { + if ( defined( 'RedisCachePro\Version' ) || defined( 'ObjectCachePro\Version' ) ) { + return; + } + + if ( defined( 'WP_REDIS_DISABLE_BANNERS' ) && WP_REDIS_DISABLE_BANNERS ) { + return; + } + + if ( ! class_exists( 'WooCommerce' ) ) { + return; + } + + $screen = get_current_screen(); + + if ( ! isset( $screen->id ) ) { + return; + } + + if ( ! in_array( $screen->id, [ 'edit-shop_order', 'edit-product', 'woocommerce_page_wc-admin' ], true ) ) { + return; + } + + if ( ! $this->current_user_can_manage_redis() ) { + return; + } + + if ( intval( get_user_meta( get_current_user_id(), 'roc_dismissed_wc_pro_notice', true ) ) === 1 ) { + return; + } + + printf( + '

%s

%s

', + esc_attr( wp_create_nonce( 'roc_dismiss_notice' ) ), + esc_html__( 'Object Cache Pro + WooCommerce = ❤️', 'redis-cache' ), + sprintf( + // translators: %s = Link to the plugin's settings screen. + wp_kses_post( __( 'Object Cache Pro is a business class object cache that’s highly-optimized for WooCommerce to provide true reliability, peace of mind and faster load times for your store. Learn more »', 'redis-cache' ) ), + esc_url( network_admin_url( $this->page ) ) + ) + ); + } + + /** + * Registers all hooks associated with the shutdown hook + * + * @return void + */ + public function register_shutdown_hooks() { + if ( ! defined( 'WP_REDIS_DISABLE_COMMENT' ) || ! WP_REDIS_DISABLE_COMMENT ) { + add_action( 'shutdown', [ $this, 'maybe_print_comment' ], 0 ); + } + } + + /** + * Displays the redis cache html comment + * + * @return void + */ + public function maybe_print_comment() { + /** @var \WP_Object_Cache $wp_object_cache */ + global $wp_object_cache; + + if ( + ( defined( 'WP_CLI' ) && WP_CLI ) || + ( defined( 'DOING_CRON' ) && DOING_CRON ) || + ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || + ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || + ( defined( 'JSON_REQUEST' ) && JSON_REQUEST ) || + ( defined( 'IFRAME_REQUEST' ) && IFRAME_REQUEST ) || + ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || + ( defined( 'WC_API_REQUEST' ) && WC_API_REQUEST ) + ) { + return; + } + + if ( function_exists( 'wp_is_json_request' ) && wp_is_json_request() ) { + return; + } + + if ( + ! isset( $wp_object_cache->cache, $wp_object_cache->diagnostics ) || + ! is_array( $wp_object_cache->cache ) + ) { + return; + } + + if ($this->incompatible_content_type()) { + return; + } + + $message = sprintf( + 'Performance optimized by Redis Object Cache. Learn more: %s', + 'https://wprediscache.com' + ); + + if ( ! WP_DEBUG_DISPLAY ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + printf( "\n\n", $message ); + + return; + } + + $bytes = strlen( serialize( $wp_object_cache->cache ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize + + $debug = sprintf( + // translators: %1$d = number of objects. %2$s = human-readable size of cache. %3$s = name of the used client. + __( 'Retrieved %1$d objects (%2$s) from Redis using %3$s.', 'redis-cache' ), + $wp_object_cache->cache_hits, + function_exists( 'size_format' ) ? size_format( $bytes ) : "{$bytes} bytes", + $wp_object_cache->diagnostics['client'] + ); + + printf( + "\n", + $message, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + esc_html( $debug ) + ); + } + + /** + * Whether incompatible content type headers were sent. + * + * @return bool + */ + protected function incompatible_content_type() + { + $jsonContentType = static function ($headers) { + foreach ($headers as $header => $value) { + if (stripos((string) $header, 'content-type') === false) { + continue; + } + + if (stripos((string) $value, '/json') === false) { + continue; + } + + return true; + } + + return false; + }; + + if (function_exists('headers_list')) { + $headers = []; + + foreach (headers_list() as $header) { + [$name, $value] = explode(':', $header); + $headers[$name] = $value; + } + + if ($jsonContentType($headers)) { + return true; + } + } + + if (function_exists('apache_response_headers')) { + if ($headers = apache_response_headers()) { + return $jsonContentType($headers); + } + } + + return false; + } + + /** + * Initializes the WP filesystem API to be ready for use + * + * @param string $url The URL to post the form to. + * @param bool $silent Whether to ask the user for credentials if necessary or not. + * @return bool + */ + public function initialize_filesystem( $url, $silent = false ) { + if ( $silent ) { + ob_start(); + } + + $credentials = request_filesystem_credentials( $url ); + + if ( false === $credentials ) { + if ( $silent ) { + ob_end_clean(); + } + + return false; + } + + if ( ! WP_Filesystem( $credentials ) ) { + request_filesystem_credentials( $url ); + + if ( $silent ) { + ob_end_clean(); + } + + return false; + } + + return true; + } + + /** + * Test if we can write in the WP_CONTENT_DIR and modify the `object-cache.php` drop-in + * + * @return true|WP_Error + */ + public function test_filesystem_writing() { + /** @var \WP_Filesystem_Base $wp_filesystem */ + global $wp_filesystem; + + if ( ! $this->initialize_filesystem( '', true ) ) { + return new WP_Error( 'fs', __( 'Could not initialize filesystem.', 'redis-cache' ) ); + } + + $cachefile = WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php'; + $testfile = WP_CONTENT_DIR . '/.redis-write-test.tmp'; + + if ( ! $wp_filesystem->exists( $cachefile ) ) { + return new WP_Error( 'exists', __( 'Object cache file doesn’t exist.', 'redis-cache' ) ); + } + + if ( $wp_filesystem->exists( $testfile ) ) { + if ( ! $wp_filesystem->delete( $testfile ) ) { + return new WP_Error( 'delete', __( 'Test file exists, but couldn’t be deleted.', 'redis-cache' ) ); + } + } + + if ( ! $wp_filesystem->is_writable( WP_CONTENT_DIR ) ) { + return new WP_Error( 'copy', __( 'Content directory is not writable.', 'redis-cache' ) ); + } + + if ( ! $wp_filesystem->copy( $cachefile, $testfile, true, FS_CHMOD_FILE ) ) { + return new WP_Error( 'copy', __( 'Failed to copy test file.', 'redis-cache' ) ); + } + + if ( ! $wp_filesystem->exists( $testfile ) ) { + return new WP_Error( 'exists', __( 'Copied test file doesn’t exist.', 'redis-cache' ) ); + } + + $meta = get_file_data( $testfile, [ 'Version' => 'Version' ] ); + + if ( $meta['Version'] !== WP_REDIS_VERSION ) { + return new WP_Error( 'version', __( 'Couldn’t verify test file contents.', 'redis-cache' ) ); + } + + if ( ! $wp_filesystem->delete( $testfile ) ) { + return new WP_Error( 'delete', __( 'Copied test file couldn’t be deleted.', 'redis-cache' ) ); + } + + return true; + } + + /** + * Calls the drop-in update method if necessary + * + * @return void + */ + public function maybe_update_dropin() { + if ( defined( 'WP_REDIS_DISABLE_DROPIN_AUTOUPDATE' ) && WP_REDIS_DISABLE_DROPIN_AUTOUPDATE ) { + return; + } + + if ( $this->object_cache_dropin_outdated() ) { + add_action( 'shutdown', [ $this, 'update_dropin' ] ); + } + } + + /** + * Redirects to the plugin settings if the plugin was just activated + * + * @return void + */ + public function maybe_redirect() { + if ( ! get_transient( '_rediscache_activation_redirect' ) ) { + return; + } + + if ( ! $this->current_user_can_manage_redis() ) { + return; + } + + delete_transient( '_rediscache_activation_redirect' ); + + wp_safe_redirect( network_admin_url( $this->page ) ); + } + + /** + * Updates the `object-cache.php` drop-in + * + * @return void + */ + public function update_dropin() { + global $wp_filesystem; + + if ( ! $this->validate_object_cache_dropin() ) { + return; + } + + if ( $this->initialize_filesystem( '', true ) ) { + $result = $wp_filesystem->copy( + WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php', + WP_CONTENT_DIR . '/object-cache.php', + true, + FS_CHMOD_FILE + ); + + /** + * Fires on cache enable event + * + * @since 1.3.5 + * @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful. + */ + do_action( 'redis_object_cache_update_dropin', $result ); + } + } + + /** + * Plugin activation hook + * + * @return void + */ + public static function on_activation() { + set_transient( '_rediscache_activation_redirect', 1, 30 ); + } + + /** + * Plugin deactivation hook + * + * @param string $plugin Plugin basename. + * @return void + */ + public function on_deactivation( $plugin ) { + global $wp_filesystem; + + ob_start(); + + if ( $plugin === WP_REDIS_BASENAME ) { + $timestamp = wp_next_scheduled( 'rediscache_discard_metrics' ); + + if ( $timestamp ) { + wp_unschedule_event( $timestamp, 'rediscache_discard_metrics' ); + } + + wp_cache_flush(); + + if ( $this->validate_object_cache_dropin() && $this->initialize_filesystem( '', true ) ) { + $wp_filesystem->delete( WP_CONTENT_DIR . '/object-cache.php' ); + } + } + + ob_end_clean(); + } + + /** + * Helper method to retrieve a nonced plugin action link + * + * @param string $action The action to be executed once the link is followed. + * @return string + */ + public function action_link( $action ) { + if ( ! in_array( $action, $this->actions, true ) ) { + return ''; + } + + return wp_nonce_url( + network_admin_url( add_query_arg( 'action', $action, $this->page ) ), + $action + ); + } + + /** + * Obscure `password` URL parameter. + * + * @param string $url + * @return string + */ + public function obscure_url_secrets( $url ) { + return preg_replace( + '/password=[^&]+/', + 'password=*****', + (string) $url + ); + } + + /** + * The capability required to manage Redis. + * + * @return string + */ + public function manage_redis_capability() { + return is_multisite() ? 'manage_network_options' : 'manage_options'; + } + + /** + * Does the current user have the capability to manage Redis? + * + * @return bool + */ + public function current_user_can_manage_redis() { + return current_user_can( $this->manage_redis_capability() ); + } + + /** + * Prevent LiteSpeed Cache from overwriting the `object-cache.php` drop-in. + * + * @return void + */ + public function litespeed_disable_objectcache() + { + if ( isset( $_POST['LSCWP_CTRL'], $_POST['LSCWP_NONCE'], $_POST['object'] ) ) { + $_POST['object'] = '0'; + } + } + + /** + * Returns `true` if the plugin was installed by AccelerateWP from CloudLinux. + * + * @param bool $ignore_banner_constant + * @return bool + */ + public static function acceleratewp_install( $ignore_banner_constant = false ) { + $path = defined( 'WP_REDIS_PATH' ) ? WP_REDIS_PATH : null; + $scheme = defined( 'WP_REDIS_SCHEME' ) ? WP_REDIS_SCHEME : null; + + if ( $scheme === 'unix' && strpos( (string) $path, '.clwpos/redis.sock' ) !== false ) { + if ( $ignore_banner_constant ) { + return true; + } else { + return defined( 'WP_REDIS_DISABLE_BANNERS' ) && WP_REDIS_DISABLE_BANNERS; + } + } + + return false; + } +} diff --git a/redis-cache/includes/class-predis.php b/redis-cache/includes/class-predis.php new file mode 100644 index 0000000..2fe11c5 --- /dev/null +++ b/redis-cache/includes/class-predis.php @@ -0,0 +1,179 @@ + 'tcp', + 'host' => '127.0.0.1', + 'port' => 6379, + 'database' => 0, + 'timeout' => 1, + 'read_timeout' => 1, + ]; + + $settings = [ + 'scheme', + 'host', + 'port', + 'path', + 'password', + 'database', + 'timeout', + 'read_timeout', + ]; + + foreach ( $settings as $setting ) { + $constant = sprintf( 'WP_REDIS_%s', strtoupper( $setting ) ); + + if ( defined( $constant ) ) { + $parameters[ $setting ] = constant( $constant ); + } + } + + if ( isset( $parameters['password'] ) && $parameters['password'] === '' ) { + unset( $parameters['password'] ); + } + + if ( defined( 'WP_REDIS_SHARDS' ) ) { + $servers = WP_REDIS_SHARDS; + $parameters['shards'] = $servers; + } elseif ( defined( 'WP_REDIS_SENTINEL' ) ) { + $servers = WP_REDIS_SERVERS; + $parameters['servers'] = $servers; + $options['replication'] = 'sentinel'; + $options['service'] = WP_REDIS_SENTINEL; + } elseif ( defined( 'WP_REDIS_SERVERS' ) ) { + $servers = WP_REDIS_SERVERS; + $parameters['servers'] = $servers; + $options['replication'] = 'predis'; + } elseif ( defined( 'WP_REDIS_CLUSTER' ) ) { + $servers = $this->build_cluster_connection_array(); + $parameters['cluster'] = $servers; + $options['cluster'] = 'redis'; + } + + if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) { + unset( $parameters['host'], $parameters['port'] ); + } + + if ( isset( $parameters['read_timeout'] ) && $parameters['read_timeout'] ) { + $parameters['read_write_timeout'] = $parameters['read_timeout']; + } + + foreach ( [ 'WP_REDIS_SERVERS', 'WP_REDIS_SHARDS', 'WP_REDIS_CLUSTER' ] as $constant ) { + if ( defined( $constant ) ) { + if ( $parameters['database'] ) { + $options['parameters']['database'] = $parameters['database']; + } + + if ( isset( $parameters['password'] ) ) { + if ( is_array( $parameters['password'] ) ) { + $options['parameters']['username'] = WP_REDIS_PASSWORD[0]; + $options['parameters']['password'] = WP_REDIS_PASSWORD[1]; + } else { + $options['parameters']['password'] = WP_REDIS_PASSWORD; + } + } + } + } + + if ( isset( $parameters['password'] ) ) { + if ( is_array( $parameters['password'] ) ) { + $parameters['username'] = array_shift( $parameters['password'] ); + $parameters['password'] = implode( '', $parameters['password'] ); + } + + if ( defined( 'WP_REDIS_USERNAME' ) ) { + $parameters['username'] = WP_REDIS_USERNAME; + } + } + + if ( defined( 'WP_REDIS_SSL_CONTEXT' ) && ! empty( WP_REDIS_SSL_CONTEXT ) ) { + $parameters['ssl'] = WP_REDIS_SSL_CONTEXT; + } + + $this->redis = new \Predis\Client( $servers ?: $parameters, $options ); + $this->redis->connect(); + } + + /** + * Invalidate all items in the cache. + * + * @return bool True on success, false on failure. + */ + public function flush() { + if ( is_null( $this->redis ) ) { + $this->connect(); + } + + if ( defined( 'WP_REDIS_CLUSTER' ) ) { + try { + foreach ( $this->redis->_masters() as $master ) { + $this->redis->flushdb( $master ); + } + } catch ( Exception $exception ) { + return false; + } + } else { + try { + $this->redis->flushdb(); + } catch ( Exception $exception ) { + return false; + } + } + + return true; + } + + /** + * Builds a clean connection array out of redis clusters array. + * + * @return array + */ + protected function build_cluster_connection_array() { + $cluster = array_values( WP_REDIS_CLUSTER ); + + foreach ( $cluster as $key => $server ) { + $connection_string = parse_url( $server ); + + $cluster[ $key ] = sprintf( + "%s:%s", + $connection_string['host'], + $connection_string['port'] + ); + } + + return $cluster; + } +} diff --git a/redis-cache/includes/class-qm-collector.php b/redis-cache/includes/class-qm-collector.php new file mode 100644 index 0000000..d9ec5d7 --- /dev/null +++ b/redis-cache/includes/class-qm-collector.php @@ -0,0 +1,141 @@ +process_defaults(); + + $roc = Plugin::instance(); + + $this->data['status'] = $roc->get_status(); + $this->data['has_dropin'] = $roc->object_cache_dropin_exists(); + $this->data['valid_dropin'] = $roc->validate_object_cache_dropin(); + + if ( ! method_exists( $wp_object_cache, 'info' ) ) { + return; + } + + $info = $wp_object_cache->info(); + + $this->data['hits'] = $info->hits; + $this->data['misses'] = $info->misses; + $this->data['ratio'] = $info->ratio; + $this->data['bytes'] = $info->bytes; + + $this->data['errors'] = $info->errors; + $this->data['meta'] = $info->meta; + + if ( defined( 'WP_REDIS_DISABLED' ) && WP_REDIS_DISABLED ) { + $this->data['meta'][ __( 'Disabled', 'redis-cache' ) ] = __( 'Yes', 'redis-cache' ); + } + + $this->data['groups'] = [ + 'global' => $info->groups->global, + 'non_persistent' => $info->groups->non_persistent, + 'unflushable' => $info->groups->unflushable, + ]; + + // These are used by Query Monitor + $this->data['cache_hit_percentage'] = $info->ratio; + + if ( $this->data instanceof QM_Data_Cache ) { + $this->data->stats['cache_hits'] = $info->hits; + $this->data->stats['cache_misses'] = $info->misses; + } else { + $this->data['stats']['cache_hits'] = $info->hits; + $this->data['stats']['cache_misses'] = $info->misses; + } + } + + /** + * Sets collector defaults + * + * @return void + */ + public function process_defaults() { + $this->data['hits'] = 0; + $this->data['misses'] = 0; + $this->data['ratio'] = 0; + $this->data['bytes'] = 0; + + $this->data['cache_hit_percentage'] = 0; + + $this->data['object_cache_extensions'] = []; + $this->data['opcode_cache_extensions'] = []; + + if ( function_exists( 'extension_loaded' ) ) { + $this->data['object_cache_extensions'] = array_map( + 'extension_loaded', + [ + 'APCu' => 'apcu', + 'Redis' => 'redis', + 'Relay' => 'relay', + 'Memcache' => 'memcache', + 'Memcached' => 'memcached', + ] + ); + + $this->data['opcode_cache_extensions'] = array_map( + 'extension_loaded', + [ + 'APC' => 'APC', + 'Zend OPcache' => 'Zend OPcache', + ] + ); + } + + $this->data['has_object_cache'] = (bool) wp_using_ext_object_cache(); + $this->data['has_opcode_cache'] = array_filter( $this->data['opcode_cache_extensions'] ) ? true : false; + + $this->data['display_hit_rate_warning'] = false; + $this->data['ext_object_cache'] = $this->data['has_object_cache']; + } +} diff --git a/redis-cache/includes/class-qm-output.php b/redis-cache/includes/class-qm-output.php new file mode 100644 index 0000000..366879a --- /dev/null +++ b/redis-cache/includes/class-qm-output.php @@ -0,0 +1,122 @@ +collector->get_data(); + + $title = $data['ratio'] + ? sprintf( '%s (%s%%)', $this->name(), $data['ratio'] ) + : $this->name(); + + $args = [ + 'title' => esc_html( $title ), + ]; + + if ( empty( $data['status'] ) ) { + $args['meta']['classname'] = 'qm-alert'; + } + + if ( ! empty( $data['errors'] ) ) { + $args['meta']['classname'] = 'qm-warning'; + } + + $menu[ $this->collector->id() ] = $this->menu( $args ); + + return $menu; + } + + /** + * Adds a menu item in the panel navigation menu in Query Monitor's output. + * + * @param array $menu Array of menus. + */ + public function panel_menu( array $menu ) { + $ids = array_keys( $menu ); + $request = array_search( 'qm-request', $ids, true ); + $position = false === $request ? count( $menu ) : $request; + + $item = [ + $this->collector->id() => $this->menu( [ 'title' => $this->name() ] ), + ]; + + return array_merge( + array_slice( $menu, 0, $position ), + $item, + array_slice( $menu, $position ) + ); + } + + /** + * Renders the output + * + * @return void + */ + public function output() { + $data = $this->collector->get_data(); + + if ( ! $data['has_dropin'] ) { + $this->before_non_tabular_output(); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->build_notice( + esc_html__( 'The Redis Object Cache drop-in is not installed. Use WP CLI or go to "Settings -> Redis" to enable drop-in.', 'redis-cache' ) + ); + $this->after_non_tabular_output(); + + return; + } + + if ( ! $data['valid_dropin'] ) { + $this->before_non_tabular_output(); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->build_notice( + esc_html__( 'WordPress is using a foreign object cache drop-in and Redis Object Cache is not being used. Use WP CLI or go to "Settings -> Redis" to enable drop-in.', 'redis-cache' ) + ); + $this->after_non_tabular_output(); + + return; + } + + require_once WP_REDIS_PLUGIN_PATH . '/includes/ui/query-monitor.php'; + } +} diff --git a/redis-cache/includes/class-ui.php b/redis-cache/includes/class-ui.php new file mode 100644 index 0000000..b251dcc --- /dev/null +++ b/redis-cache/includes/class-ui.php @@ -0,0 +1,45 @@ +object_cache_dropin_exists() ) { + + if ( $plugin->validate_object_cache_dropin() ) { + WP_CLI::line( __( 'Redis object cache already enabled.', 'redis-cache' ) ); + } else { + WP_CLI::error( __( 'A foreign object cache drop-in was found. To use Redis for object caching, run: `wp redis update-dropin`.', 'redis-cache' ) ); + } + } else { + $flush = $this->flush_redis(); + + if ( is_string( $flush ) ) { + // translators: %s = The Redis connection error message. + WP_CLI::error( sprintf( __( "Object cache could not be enabled. Redis server is unreachable: %s", 'redis-cache' ), $flush ) ); + } + + WP_Filesystem(); + + $copy = $wp_filesystem->copy( + WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php', + WP_CONTENT_DIR . '/object-cache.php', + true, + FS_CHMOD_FILE + ); + + if ( $copy ) { + WP_CLI::success( __( 'Object cache enabled.', 'redis-cache' ) ); + } else { + WP_CLI::error( __( 'Object cache could not be enabled.', 'redis-cache' ) ); + } + } + + } + + /** + * Disables the Redis object cache. + * + * Default behavior is to delete the object cache drop-in, + * unless an unknown object cache drop-in is present. + * + * ## EXAMPLES + * + * wp redis disable + */ + public function disable() { + + global $wp_filesystem; + + $plugin = Plugin::instance(); + + if ( ! $plugin->object_cache_dropin_exists() ) { + + WP_CLI::error( __( 'No object cache drop-in found.', 'redis-cache' ) ); + + } else { + + if ( ! $plugin->validate_object_cache_dropin() ) { + + WP_CLI::error( __( 'A foreign object cache drop-in was found. To use Redis for object caching, run: `wp redis update-dropin`.', 'redis-cache' ) ); + + } else { + + WP_Filesystem(); + + if ( $wp_filesystem->delete( WP_CONTENT_DIR . '/object-cache.php' ) ) { + $this->flush_redis(); + + WP_CLI::success( __( 'Object cache disabled.', 'redis-cache' ) ); + } else { + WP_CLI::error( __( 'Object cache could not be disabled.', 'redis-cache' ) ); + } + } + } + + } + + /** + * Updates the Redis object cache drop-in. + * + * Default behavior is to overwrite any existing object cache drop-in. + * + * ## EXAMPLES + * + * wp redis update-dropin + * + * @subcommand update-dropin + */ + public function update_dropin() { + + global $wp_filesystem; + + WP_Filesystem(); + + $copy = $wp_filesystem->copy( + WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php', + WP_CONTENT_DIR . '/object-cache.php', + true, + FS_CHMOD_FILE + ); + + if ( $copy ) { + $flush = $this->flush_redis(); + + if ( is_string( $flush ) ) { + // translators: %s = The Redis connection error message. + WP_CLI::error( sprintf( __( "Object cache drop-in could not be updated. Redis server is unreachable: %s", 'redis-cache' ), $flush ) ); + } + + WP_CLI::success( __( 'Updated object cache drop-in and enabled Redis object cache.', 'redis-cache' ) ); + } else { + WP_CLI::error( __( 'Object cache drop-in could not be updated.', 'redis-cache' ) ); + } + + } + + /** + * Flush the Redis cache via Predis. + * + * @return bool|string + */ + protected function flush_redis() { + try { + $predis = new Predis(); + $predis->connect(); + } catch ( Exception $exception ) { + return $exception->getMessage(); + } + + try { + $predis->flush(); + } catch ( Exception $exception ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( $exception ); + } + + return true; + } + +} diff --git a/redis-cache/includes/diagnostics.php b/redis-cache/includes/diagnostics.php new file mode 100644 index 0000000..cd08e91 --- /dev/null +++ b/redis-cache/includes/diagnostics.php @@ -0,0 +1,163 @@ +test_filesystem_writing(); +$dropin = $roc->validate_object_cache_dropin(); +$disabled = defined( 'WP_REDIS_DISABLED' ) && WP_REDIS_DISABLED; + +$info['Status'] = $roc->get_status(); +$info['Client'] = $roc->get_redis_client_name(); + +$info['Drop-in'] = $roc->object_cache_dropin_exists() + ? ( $dropin ? 'Valid' : 'Invalid' ) + : 'Not installed'; + +$info['Disabled'] = $disabled ? 'Yes' : 'No'; + +if ( $dropin && ! $disabled ) { + $info[ 'Ping' ] = $wp_object_cache->diagnostics['ping'] ?? false; + + try { + $cache = new WP_Object_Cache( false ); + } catch ( Exception $exception ) { + $info[ 'Connection Exception' ] = sprintf( '%s (%s)', $exception->getMessage(), get_class( $exception ) ); + } + + $errors = is_array( $wp_object_cache->errors ) ? $wp_object_cache->errors : []; + $info[ 'Errors' ] = wp_json_encode( array_values( $errors ), JSON_PRETTY_PRINT ); +} + +$info['PhpRedis'] = class_exists( 'Redis' ) ? phpversion( 'redis' ) : 'Not loaded'; +$info['Relay'] = class_exists( 'Relay\Relay' ) ? phpversion( 'relay' ) : 'Not loaded'; +$info['Predis'] = class_exists( 'Predis\Client' ) ? Predis\Client::VERSION : 'Not loaded'; +$info['Credis'] = class_exists( 'Credis_Client' ) ? 'v1.14.0' : 'Not loaded'; + +if ( defined( 'PHP_VERSION' ) ) { + $info['PHP Version'] = PHP_VERSION; +} + +if ( defined( 'HHVM_VERSION' ) ) { + $info['HHVM Version'] = HHVM_VERSION; +} + +$info['Plugin Version'] = WP_REDIS_VERSION; +$info['Redis Version'] = $roc->get_redis_version() ?: 'Unknown'; + +$info['Multisite'] = is_multisite() ? 'Yes' : 'No'; + +$info['Metrics'] = \Rhubarb\RedisCache\Metrics::is_active() ? 'Enabled' : 'Disabled'; +$info['Metrics recorded'] = wp_json_encode( \Rhubarb\RedisCache\Metrics::count() ); + +$info['Filesystem'] = is_wp_error( $filesystem ) ? $filesystem->get_error_message() : 'Working'; + +if ( $dropin && ! $disabled ) { + $info['Global Prefix'] = wp_json_encode( $wp_object_cache->global_prefix ); + $info['Blog Prefix'] = wp_json_encode( $wp_object_cache->blog_prefix ); +} + +$constants = [ + 'WP_REDIS_DISABLED', + 'WP_REDIS_CLIENT', + 'WP_REDIS_SCHEME', + 'WP_REDIS_SSL_CONTEXT', + 'WP_REDIS_PATH', + 'WP_REDIS_HOST', + 'WP_REDIS_PORT', + 'WP_REDIS_DATABASE', + 'WP_REDIS_TIMEOUT', + 'WP_REDIS_READ_TIMEOUT', + 'WP_REDIS_RETRY_INTERVAL', + 'WP_REDIS_SERVERS', + 'WP_REDIS_CLUSTER', + 'WP_REDIS_SHARDS', + 'WP_REDIS_SENTINEL', + 'WP_REDIS_IGBINARY', + 'WP_REDIS_MAXTTL', + 'WP_REDIS_PREFIX', + 'WP_CACHE_KEY_SALT', + 'WP_REDIS_PLUGIN_PATH', + 'WP_REDIS_METRICS_MAX_TIME', + 'WP_REDIS_GLOBAL_GROUPS', + 'WP_REDIS_IGNORED_GROUPS', + 'WP_REDIS_UNFLUSHABLE_GROUPS', + 'WP_REDIS_SELECTIVE_FLUSH', +]; + +foreach ( $constants as $constant ) { + if ( defined( $constant ) ) { + $info[ $constant ] = wp_json_encode( + constant( $constant ), + JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT + ); + } +} + +if ( defined( 'WP_REDIS_PASSWORD' ) ) { + /** @var string|array|null $password */ + $password = WP_REDIS_PASSWORD; + + if ( is_array( $password ) ) { + if ( isset( $password[1] ) && '' !== $password[1] ) { + $password[1] = str_repeat( '•', 8 ); + } + + $info['WP_REDIS_PASSWORD'] = wp_json_encode( $password, JSON_UNESCAPED_UNICODE ); + } elseif ( is_string( $password ) && '' !== $password ) { + $info['WP_REDIS_PASSWORD'] = str_repeat( '•', 8 ); + } +} + +if ( isset( $info['WP_REDIS_SERVERS'] ) ) { + $info['WP_REDIS_SERVERS'] = $roc->obscure_url_secrets( $info['WP_REDIS_SERVERS'] ); +} + +if ( $dropin && ! $disabled ) { + $info['Global Groups'] = wp_json_encode( + array_values( $wp_object_cache->global_groups ?? [] ), + JSON_PRETTY_PRINT + ); + + $info['Ignored Groups'] = wp_json_encode( + array_values( $wp_object_cache->ignored_groups ?? [] ), + JSON_PRETTY_PRINT + ); + + $info['Unflushable Groups'] = wp_json_encode( + array_values( $wp_object_cache->unflushable_groups ?? [] ), + JSON_PRETTY_PRINT + ); + + $info['Groups Types'] = wp_json_encode( + $wp_object_cache->group_type ?? null, + JSON_PRETTY_PRINT + ); +} + +$dropins = []; + +foreach ( get_dropins() as $file => $details ) { + $dropins[ $file ] = sprintf( '%s v%s by %s', $details['Name'], $details['Version'], $details['Author'] ); +} + +$info['Drop-ins'] = wp_json_encode( + array_values( $dropins ), + JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE +); + +foreach ( $info as $name => $value ) { + if ( defined( 'WP_CLI' ) && WP_CLI ) { + WP_CLI::line( "{$name}: $value" ); + } else { + echo esc_html( "{$name}: {$value}\r\n" ); + } +} diff --git a/redis-cache/includes/object-cache.php b/redis-cache/includes/object-cache.php new file mode 100644 index 0000000..001776e --- /dev/null +++ b/redis-cache/includes/object-cache.php @@ -0,0 +1,2992 @@ +add( $key, $value, $group, $expiration ); +} + +/** + * Adds multiple values to the cache in one call. + * + * @param array $data Array of keys and values to be set. + * @param string $group Optional. Where the cache contents are grouped. Default empty. + * @param int $expire Optional. When to expire the cache contents, in seconds. + * Default 0 (no expiration). + * @return bool[] Array of return values, grouped by key. Each value is either + * true on success, or false if cache key and group already exist. + */ +function wp_cache_add_multiple( array $data, $group = '', $expire = 0 ) { + global $wp_object_cache; + + return $wp_object_cache->add_multiple( $data, $group, $expire ); +} + +/** + * Closes the cache. + * + * This function has ceased to do anything since WordPress 2.5. The + * functionality was removed along with the rest of the persistent cache. This + * does not mean that plugins can't implement this function when they need to + * make sure that the cache is cleaned up after WordPress no longer needs it. + * + * @return bool Always returns True + */ +function wp_cache_close() { + return true; +} + +/** + * Decrement a numeric item's value. + * + * @param string $key The key under which to store the value. + * @param int $offset The amount by which to decrement the item's value. + * @param string $group The group value appended to the $key. + * + * @return int|bool Returns item's new value on success or FALSE on failure. + */ +function wp_cache_decr( $key, $offset = 1, $group = '' ) { + global $wp_object_cache; + + return $wp_object_cache->decrement( $key, $offset, $group ); +} + +/** + * Remove the item from the cache. + * + * @param string $key The key under which to store the value. + * @param string $group The group value appended to the $key. + * @param int $time The amount of time the server will wait to delete the item in seconds. + * + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wp_cache_delete( $key, $group = '', $time = 0 ) { + global $wp_object_cache; + + return $wp_object_cache->delete( $key, $group, $time ); +} + +/** + * Deletes multiple values from the cache in one call. + * + * @param array $keys Array of keys under which the cache to deleted. + * @param string $group Optional. Where the cache contents are grouped. Default empty. + * @return bool[] Array of return values, grouped by key. Each value is either + * true on success, or false if the contents were not deleted. + */ +function wp_cache_delete_multiple( array $keys, $group = '' ) { + global $wp_object_cache; + + return $wp_object_cache->delete_multiple( $keys, $group ); +} + +/** + * Invalidate all items in the cache. If `WP_REDIS_SELECTIVE_FLUSH` is `true`, + * only keys prefixed with the `WP_REDIS_PREFIX` are flushed. + * + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wp_cache_flush() { + global $wp_object_cache; + + return $wp_object_cache->flush(); +} + +/** + * Removes all cache items in a group. + * + * @param string $group Name of group to remove from cache. + * @return true Returns TRUE on success or FALSE on failure. + */ +function wp_cache_flush_group( $group ) +{ + global $wp_object_cache; + + return $wp_object_cache->flush_group( $group ); +} + +/** + * Removes all cache items from the in-memory runtime cache. + * + * @return bool True on success, false on failure. + */ +function wp_cache_flush_runtime() { + global $wp_object_cache; + + return $wp_object_cache->flush_runtime(); +} + +/** + * Retrieve object from cache. + * + * Gets an object from cache based on $key and $group. + * + * @param string $key The key under which to store the value. + * @param string $group The group value appended to the $key. + * @param bool $force Optional. Whether to force an update of the local cache from the persistent + * cache. Default false. + * @param bool $found Optional. Whether the key was found in the cache. Disambiguates a return of false, + * a storable value. Passed by reference. Default null. + * + * @return bool|mixed Cached object value. + */ +function wp_cache_get( $key, $group = '', $force = false, &$found = null ) { + global $wp_object_cache; + + return $wp_object_cache->get( $key, $group, $force, $found ); +} + +/** + * Retrieves multiple values from the cache in one call. + * + * @param array $keys Array of keys under which the cache contents are stored. + * @param string $group Optional. Where the cache contents are grouped. Default empty. + * @param bool $force Optional. Whether to force an update of the local cache + * from the persistent cache. Default false. + * @return array Array of values organized into groups. + */ +function wp_cache_get_multiple( $keys, $group = '', $force = false ) { + global $wp_object_cache; + + return $wp_object_cache->get_multiple( $keys, $group, $force ); +} + +/** + * Increment a numeric item's value. + * + * @param string $key The key under which to store the value. + * @param int $offset The amount by which to increment the item's value. + * @param string $group The group value appended to the $key. + * + * @return int|bool Returns item's new value on success or FALSE on failure. + */ +function wp_cache_incr( $key, $offset = 1, $group = '' ) { + global $wp_object_cache; + + return $wp_object_cache->increment( $key, $offset, $group ); +} + +/** + * Sets up Object Cache Global and assigns it. + * + * @return void + */ +function wp_cache_init() { + global $wp_object_cache; + + if ( ! defined( 'WP_REDIS_PREFIX' ) && getenv( 'WP_REDIS_PREFIX' ) ) { + define( 'WP_REDIS_PREFIX', getenv( 'WP_REDIS_PREFIX' ) ); + } + + if ( ! defined( 'WP_REDIS_SELECTIVE_FLUSH' ) && getenv( 'WP_REDIS_SELECTIVE_FLUSH' ) ) { + define( 'WP_REDIS_SELECTIVE_FLUSH', (bool) getenv( 'WP_REDIS_SELECTIVE_FLUSH' ) ); + } + + // Backwards compatibility: map `WP_CACHE_KEY_SALT` constant to `WP_REDIS_PREFIX`. + if ( defined( 'WP_CACHE_KEY_SALT' ) && ! defined( 'WP_REDIS_PREFIX' ) ) { + define( 'WP_REDIS_PREFIX', WP_CACHE_KEY_SALT ); + } + + // Set unique prefix for sites hosted on Cloudways + if ( ! defined( 'WP_REDIS_PREFIX' ) && isset( $_SERVER['cw_allowed_ip'] ) ) { + define( 'WP_REDIS_PREFIX', getenv( 'HTTP_X_APP_USER' ) ); + } + + if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) { + $fail_gracefully = defined( 'WP_REDIS_GRACEFUL' ) && WP_REDIS_GRACEFUL; + + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $wp_object_cache = new WP_Object_Cache( $fail_gracefully ); + } +} + +/** + * Replaces a value in cache. + * + * This method is similar to "add"; however, is does not successfully set a value if + * the object's key is not already set in cache. + * + * @param string $key The key under which to store the value. + * @param mixed $value The value to store. + * @param string $group The group value appended to the $key. + * @param int $expiration The expiration time, defaults to 0. + * + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wp_cache_replace( $key, $value, $group = '', $expiration = 0 ) { + global $wp_object_cache; + + return $wp_object_cache->replace( $key, $value, $group, $expiration ); +} + +/** + * Sets a value in cache. + * + * The value is set whether or not this key already exists in Redis. + * + * @param string $key The key under which to store the value. + * @param mixed $value The value to store. + * @param string $group The group value appended to the $key. + * @param int $expiration The expiration time, defaults to 0. + * + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wp_cache_set( $key, $value, $group = '', $expiration = 0 ) { + global $wp_object_cache; + + return $wp_object_cache->set( $key, $value, $group, $expiration ); +} + +/** + * Sets multiple values to the cache in one call. + * + * @param array $data Array of keys and values to be set. + * @param string $group Optional. Where the cache contents are grouped. Default empty. + * @param int $expire Optional. When to expire the cache contents, in seconds. + * Default 0 (no expiration). + * @return bool[] Array of return values, grouped by key. Each value is either + * true on success, or false on failure. + */ +function wp_cache_set_multiple( array $data, $group = '', $expire = 0 ) { + global $wp_object_cache; + + return $wp_object_cache->set_multiple( $data, $group, $expire ); +} + +/** + * Switch the internal blog id. + * + * This changes the blog id used to create keys in blog specific groups. + * + * @param int $_blog_id The blog ID. + * + * @return bool + */ +function wp_cache_switch_to_blog( $_blog_id ) { + global $wp_object_cache; + + return $wp_object_cache->switch_to_blog( $_blog_id ); +} + +/** + * Adds a group or set of groups to the list of Redis groups. + * + * @param string|array $groups A group or an array of groups to add. + * + * @return void + */ +function wp_cache_add_global_groups( $groups ) { + global $wp_object_cache; + + $wp_object_cache->add_global_groups( $groups ); +} + +/** + * Adds a group or set of groups to the list of non-Redis groups. + * + * @param string|array $groups A group or an array of groups to add. + * + * @return void + */ +function wp_cache_add_non_persistent_groups( $groups ) { + global $wp_object_cache; + + $wp_object_cache->add_non_persistent_groups( $groups ); +} + +/** + * Object cache class definition + */ +class WP_Object_Cache { + /** + * The Redis client. + * + * @var mixed + */ + private $redis; + + /** + * The Redis server version. + * + * @var null|string + */ + private $redis_version = null; + + /** + * Track if Redis is available. + * + * @var bool + */ + private $redis_connected = false; + + /** + * Check to fail gracefully or throw an exception. + * + * @var bool + */ + private $fail_gracefully = true; + + /** + * Holds the non-Redis objects. + * + * @var array + */ + public $cache = []; + + /** + * Holds the diagnostics values. + * + * @var array + */ + public $diagnostics = null; + + /** + * Holds the error messages. + * + * @var array + */ + public $errors = []; + + /** + * List of global groups. + * + * @var array + */ + public $global_groups = [ + 'blog-details', + 'blog-id-cache', + 'blog-lookup', + 'global-posts', + 'networks', + 'rss', + 'sites', + 'site-details', + 'site-lookup', + 'site-options', + 'site-transient', + 'users', + 'useremail', + 'userlogins', + 'usermeta', + 'user_meta', + 'userslugs', + ]; + + /** + * List of groups that will not be flushed. + * + * @var array + */ + public $unflushable_groups = []; + + /** + * List of groups not saved to Redis. + * + * @var array + */ + public $ignored_groups = [ + 'counts', + 'plugins', + 'themes', + ]; + + /** + * List of groups and their types. + * + * @var array + */ + public $group_type = []; + + /** + * Prefix used for global groups. + * + * @var string + */ + public $global_prefix = ''; + + /** + * Prefix used for non-global groups. + * + * @var int + */ + public $blog_prefix = 0; + + /** + * Track how many requests were found in cache. + * + * @var int + */ + public $cache_hits = 0; + + /** + * Track how may requests were not cached. + * + * @var int + */ + public $cache_misses = 0; + + /** + * The amount of Redis commands made. + * + * @var int + */ + public $cache_calls = 0; + + /** + * The amount of microseconds (μs) waited for Redis commands. + * + * @var float + */ + public $cache_time = 0; + + /** + * Instantiate the Redis class. + * + * @param bool $fail_gracefully Handles and logs errors if true throws exceptions otherwise. + */ + public function __construct( $fail_gracefully = false ) { + global $blog_id, $table_prefix; + + $this->fail_gracefully = $fail_gracefully; + + if ( defined( 'WP_REDIS_GLOBAL_GROUPS' ) && is_array( WP_REDIS_GLOBAL_GROUPS ) ) { + $this->global_groups = array_map( [ $this, 'sanitize_key_part' ], WP_REDIS_GLOBAL_GROUPS ); + } + + $this->global_groups[] = 'redis-cache'; + + if ( defined( 'WP_REDIS_IGNORED_GROUPS' ) && is_array( WP_REDIS_IGNORED_GROUPS ) ) { + $this->ignored_groups = array_map( [ $this, 'sanitize_key_part' ], WP_REDIS_IGNORED_GROUPS ); + } + + if ( defined( 'WP_REDIS_UNFLUSHABLE_GROUPS' ) && is_array( WP_REDIS_UNFLUSHABLE_GROUPS ) ) { + $this->unflushable_groups = array_map( [ $this, 'sanitize_key_part' ], WP_REDIS_UNFLUSHABLE_GROUPS ); + } + + $this->cache_group_types(); + + if ( function_exists( '_doing_it_wrong' ) ) { + if ( defined( 'WP_REDIS_TRACE' ) && WP_REDIS_TRACE ) { + _doing_it_wrong( __FUNCTION__ , 'Tracing feature was removed.' , '2.1.2' ); + } + } + + $client = $this->determine_client(); + $parameters = $this->build_parameters(); + + try { + switch ( $client ) { + case 'hhvm': + $this->connect_using_hhvm( $parameters ); + break; + case 'phpredis': + $this->connect_using_phpredis( $parameters ); + break; + case 'relay': + $this->connect_using_relay( $parameters ); + break; + case 'credis': + $this->connect_using_credis( $parameters ); + break; + case 'predis': + default: + $this->connect_using_predis( $parameters ); + break; + } + + if ( defined( 'WP_REDIS_CLUSTER' ) ) { + $connectionId = is_string( WP_REDIS_CLUSTER ) + ? WP_REDIS_CLUSTER + : current( $this->build_cluster_connection_array() ); + + $this->diagnostics[ 'ping' ] = $client === 'predis' + ? $this->redis->getClientBy( 'id', $connectionId )->ping() + : $this->redis->ping( $connectionId ); + } else { + $this->diagnostics[ 'ping' ] = $this->redis->ping(); + } + + $this->fetch_info(); + + $this->redis_connected = true; + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + } + + // Assign global and blog prefixes for use with keys. + if ( function_exists( 'is_multisite' ) ) { + $this->global_prefix = is_multisite() ? '' : $table_prefix; + $this->blog_prefix = is_multisite() ? $blog_id : $table_prefix; + } + } + + /** + * Set group type array + * + * @return void + */ + protected function cache_group_types() { + foreach ( $this->global_groups as $group ) { + $this->group_type[ $group ] = 'global'; + } + + foreach ( $this->unflushable_groups as $group ) { + $this->group_type[ $group ] = 'unflushable'; + } + + foreach ( $this->ignored_groups as $group ) { + $this->group_type[ $group ] = 'ignored'; + } + } + + /** + * Determine the Redis client. + * + * @return string + */ + protected function determine_client() { + $client = 'predis'; + + if ( class_exists( 'Redis' ) ) { + $client = defined( 'HHVM_VERSION' ) ? 'hhvm' : 'phpredis'; + } + + if ( defined( 'WP_REDIS_CLIENT' ) ) { + $client = (string) WP_REDIS_CLIENT; + $client = str_replace( 'pecl', 'phpredis', $client ); + } + + return trim( strtolower( $client ) ); + } + + /** + * Build the connection parameters from config constants. + * + * @return array + */ + protected function build_parameters() { + $parameters = [ + 'scheme' => 'tcp', + 'host' => '127.0.0.1', + 'port' => 6379, + 'database' => 0, + 'timeout' => 1, + 'read_timeout' => 1, + 'retry_interval' => null, + 'persistent' => false, + ]; + + $settings = [ + 'scheme', + 'host', + 'port', + 'path', + 'password', + 'database', + 'timeout', + 'read_timeout', + 'retry_interval', + ]; + + foreach ( $settings as $setting ) { + $constant = sprintf( 'WP_REDIS_%s', strtoupper( $setting ) ); + + if ( defined( $constant ) ) { + $parameters[ $setting ] = constant( $constant ); + } + } + + if ( isset( $parameters[ 'password' ] ) && $parameters[ 'password' ] === '' ) { + unset( $parameters[ 'password' ] ); + } + + return $parameters; + } + + /** + * Connect to Redis using the PhpRedis (PECL) extension. + * + * @param array $parameters Connection parameters built by the `build_parameters` method. + * @return void + */ + protected function connect_using_phpredis( $parameters ) { + $version = phpversion( 'redis' ); + + $this->diagnostics[ 'client' ] = sprintf( 'PhpRedis (v%s)', $version ); + + if ( defined( 'WP_REDIS_SHARDS' ) ) { + $this->redis = new RedisArray( array_values( WP_REDIS_SHARDS ) ); + + $this->diagnostics[ 'shards' ] = WP_REDIS_SHARDS; + } elseif ( defined( 'WP_REDIS_CLUSTER' ) ) { + if ( is_string( WP_REDIS_CLUSTER ) ) { + $this->redis = new RedisCluster( WP_REDIS_CLUSTER ); + } else { + $args = [ + 'cluster' => $this->build_cluster_connection_array(), + 'timeout' => $parameters['timeout'], + 'read_timeout' => $parameters['read_timeout'], + 'persistent' => $parameters['persistent'], + ]; + + if ( isset( $parameters['password'] ) && version_compare( $version, '4.3.0', '>=' ) ) { + $args['password'] = $parameters['password']; + } + + $this->redis = new RedisCluster( null, ...array_values( $args ) ); + $this->diagnostics += $args; + } + } else { + $this->redis = new Redis(); + + $args = [ + 'host' => $parameters['host'], + 'port' => $parameters['port'], + 'timeout' => $parameters['timeout'], + '', + 'retry_interval' => (int) $parameters['retry_interval'], + ]; + + if ( version_compare( $version, '3.1.3', '>=' ) ) { + $args['read_timeout'] = $parameters['read_timeout']; + } + + if ( strcasecmp( 'tls', $parameters['scheme'] ) === 0 ) { + $args['host'] = sprintf( + '%s://%s', + $parameters['scheme'], + str_replace( 'tls://', '', $parameters['host'] ) + ); + + if ( version_compare( $version, '5.3.0', '>=' ) && defined( 'WP_REDIS_SSL_CONTEXT' ) && ! empty( WP_REDIS_SSL_CONTEXT ) ) { + $args['others']['stream'] = WP_REDIS_SSL_CONTEXT; + } + } + + if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) { + $args['host'] = $parameters['path']; + $args['port'] = -1; + } + + call_user_func_array( [ $this->redis, 'connect' ], array_values( $args ) ); + + if ( isset( $parameters['password'] ) ) { + $args['password'] = $parameters['password']; + $this->redis->auth( $parameters['password'] ); + } + + if ( isset( $parameters['database'] ) ) { + if ( ctype_digit( (string) $parameters['database'] ) ) { + $parameters['database'] = (int) $parameters['database']; + } + + $args['database'] = $parameters['database']; + + if ( $parameters['database'] ) { + $this->redis->select( $parameters['database'] ); + } + } + + $this->diagnostics += $args; + } + + if ( defined( 'WP_REDIS_SERIALIZER' ) && ! empty( WP_REDIS_SERIALIZER ) ) { + $this->redis->setOption( Redis::OPT_SERIALIZER, WP_REDIS_SERIALIZER ); + + if ( function_exists( '_doing_it_wrong' ) ) { + _doing_it_wrong( __FUNCTION__ , 'The `WP_REDIS_SERIALIZER` configuration constant has been deprecated, use `WP_REDIS_IGBINARY` instead.', '2.3.1' ); + } + } + } + + /** + * Connect to Redis using the Relay extension. + * + * @param array $parameters Connection parameters built by the `build_parameters` method. + * @return void + */ + protected function connect_using_relay( $parameters ) { + $version = phpversion( 'relay' ); + + $this->diagnostics[ 'client' ] = sprintf( 'Relay (v%s)', $version ); + + if ( defined( 'WP_REDIS_SHARDS' ) ) { + throw new Exception('Relay does not support sharding.'); + } elseif ( defined( 'WP_REDIS_CLUSTER' ) ) { + throw new Exception('Relay does not cluster connections.'); + } else { + $this->redis = new Relay\Relay; + + $args = [ + 'host' => $parameters['host'], + 'port' => $parameters['port'], + 'timeout' => $parameters['timeout'], + '', + 'retry_interval' => (int) $parameters['retry_interval'], + ]; + + $args['read_timeout'] = $parameters['read_timeout']; + + if ( strcasecmp( 'tls', $parameters['scheme'] ) === 0 ) { + $args['host'] = sprintf( + '%s://%s', + $parameters['scheme'], + str_replace( 'tls://', '', $parameters['host'] ) + ); + + if ( defined( 'WP_REDIS_SSL_CONTEXT' ) && ! empty( WP_REDIS_SSL_CONTEXT ) ) { + $args['others']['stream'] = WP_REDIS_SSL_CONTEXT; + } + } + + if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) { + $args['host'] = $parameters['path']; + $args['port'] = -1; + } + + call_user_func_array( [ $this->redis, 'connect' ], array_values( $args ) ); + + if ( isset( $parameters['password'] ) ) { + $args['password'] = $parameters['password']; + $this->redis->auth( $parameters['password'] ); + } + + if ( isset( $parameters['database'] ) ) { + if ( ctype_digit( (string) $parameters['database'] ) ) { + $parameters['database'] = (int) $parameters['database']; + } + + $args['database'] = $parameters['database']; + + if ( $parameters['database'] ) { + $this->redis->select( $parameters['database'] ); + } + } + + $this->diagnostics += $args; + } + + if ( defined( 'WP_REDIS_SERIALIZER' ) && ! empty( WP_REDIS_SERIALIZER ) ) { + $this->redis->setOption( Relay\Relay::OPT_SERIALIZER, WP_REDIS_SERIALIZER ); + + if ( function_exists( '_doing_it_wrong' ) ) { + _doing_it_wrong( __FUNCTION__ , 'The `WP_REDIS_SERIALIZER` configuration constant has been deprecated, use `WP_REDIS_IGBINARY` instead.', '2.3.1' ); + } + } + } + + /** + * Connect to Redis using the Predis library. + * + * @param array $parameters Connection parameters built by the `build_parameters` method. + * @throws \Exception If the Predis library was not found or is unreadable. + * @return void + */ + protected function connect_using_predis( $parameters ) { + $client = 'Predis'; + + // Load bundled Predis library. + if ( ! class_exists( 'Predis\Client' ) ) { + $predis = '/dependencies/predis/predis/autoload.php'; + + $pluginDir = defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR . '/redis-cache' : null; + $contentDir = defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR . '/plugins/redis-cache' : null; + $pluginPath = defined( 'WP_REDIS_PLUGIN_PATH' ) ? WP_REDIS_PLUGIN_PATH : null; + + if ( $pluginDir && is_readable( $pluginDir . $predis ) ) { + require_once $pluginDir . $predis; + } elseif ( $contentDir && is_readable( $contentDir . $predis ) ) { + require_once $contentDir . $predis; + } elseif ( $pluginPath && is_readable( $pluginPath . $predis ) ) { + require_once $pluginPath . $predis; + } else { + throw new Exception( + 'Predis library not found. Re-install Redis Cache plugin or delete the object-cache.php.' + ); + } + } + + $servers = false; + $options = []; + + if ( defined( 'WP_REDIS_SHARDS' ) ) { + $servers = WP_REDIS_SHARDS; + $parameters['shards'] = $servers; + } elseif ( defined( 'WP_REDIS_SENTINEL' ) ) { + $servers = WP_REDIS_SERVERS; + $parameters['servers'] = $servers; + $options['replication'] = 'sentinel'; + $options['service'] = WP_REDIS_SENTINEL; + } elseif ( defined( 'WP_REDIS_SERVERS' ) ) { + $servers = WP_REDIS_SERVERS; + $parameters['servers'] = $servers; + $options['replication'] = 'predis'; + } elseif ( defined( 'WP_REDIS_CLUSTER' ) ) { + $servers = $this->build_cluster_connection_array(); + $parameters['cluster'] = $servers; + $options['cluster'] = 'redis'; + } + + if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) { + unset($parameters['host'], $parameters['port']); + } + + if ( isset( $parameters['read_timeout'] ) && $parameters['read_timeout'] ) { + $parameters['read_write_timeout'] = $parameters['read_timeout']; + } + + foreach ( [ 'WP_REDIS_SERVERS', 'WP_REDIS_SHARDS', 'WP_REDIS_CLUSTER' ] as $constant ) { + if ( defined( $constant ) ) { + if ( $parameters['database'] ) { + $options['parameters']['database'] = $parameters['database']; + } + + if ( isset( $parameters['password'] ) ) { + if ( is_array( $parameters['password'] ) ) { + $options['parameters']['username'] = WP_REDIS_PASSWORD[0]; + $options['parameters']['password'] = WP_REDIS_PASSWORD[1]; + } else { + $options['parameters']['password'] = WP_REDIS_PASSWORD; + } + } + } + } + + if ( isset( $parameters['password'] ) ) { + if ( is_array( $parameters['password'] ) ) { + $parameters['username'] = array_shift( $parameters['password'] ); + $parameters['password'] = implode( '', $parameters['password'] ); + } + + if ( defined( 'WP_REDIS_USERNAME' ) ) { + $parameters['username'] = WP_REDIS_USERNAME; + } + } + + if ( defined( 'WP_REDIS_SSL_CONTEXT' ) && ! empty( WP_REDIS_SSL_CONTEXT ) ) { + $parameters['ssl'] = WP_REDIS_SSL_CONTEXT; + } + + $this->redis = new Predis\Client( $servers ?: $parameters, $options ); + $this->redis->connect(); + + $this->diagnostics = array_merge( + [ 'client' => sprintf( '%s (v%s)', $client, Predis\Client::VERSION ) ], + $parameters, + $options + ); + } + + /** + * Connect to Redis using the Credis library. + * + * @param array $parameters Connection parameters built by the `build_parameters` method. + * @throws \Exception If the Credis library was not found or is unreadable. + * @throws \Exception If redis sharding should be configured as Credis does not support sharding. + * @throws \Exception If more than one seninel is configured as Credis does not support multiple sentinel servers. + * @return void + */ + protected function connect_using_credis( $parameters ) { + _doing_it_wrong( __FUNCTION__ , 'Credis support will be removed in future versions.' , '2.0.26' ); + + $client = 'Credis'; + + $creds_path = sprintf( + '%s/redis-cache/dependencies/colinmollenhour/credis/', + defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins' + ); + + $to_load = []; + + if ( ! class_exists( 'Credis_Client' ) ) { + $to_load[] = 'Client.php'; + } + + $has_shards = defined( 'WP_REDIS_SHARDS' ); + $has_sentinel = defined( 'WP_REDIS_SENTINEL' ); + $has_servers = defined( 'WP_REDIS_SERVERS' ); + $has_cluster = defined( 'WP_REDIS_CLUSTER' ); + + if ( ( $has_shards || $has_sentinel || $has_servers || $has_cluster ) && ! class_exists( 'Credis_Cluster' ) ) { + $to_load[] = 'Cluster.php'; + + if ( defined( 'WP_REDIS_SENTINEL' ) && ! class_exists( 'Credis_Sentinel' ) ) { + $to_load[] = 'Sentinel.php'; + } + } + + foreach ( $to_load as $sub_path ) { + $path = $creds_path . $sub_path; + + if ( file_exists( $path ) ) { + require_once $path; + } else { + throw new Exception( + 'Credis library not found. Re-install Redis Cache plugin or delete object-cache.php.' + ); + } + } + + if ( defined( 'WP_REDIS_SHARDS' ) ) { + throw new Exception( + 'Sharding not supported by bundled Credis library. Please review your Redis Cache configuration.' + ); + } + + if ( defined( 'WP_REDIS_SENTINEL' ) ) { + if ( is_array( WP_REDIS_SERVERS ) && count( WP_REDIS_SERVERS ) > 1 ) { + throw new Exception( + 'Multipe sentinel servers are not supported by the bundled Credis library. Please review your Redis Cache configuration.' + ); + } + + $connection_string = array_values( WP_REDIS_SERVERS )[0]; + $sentinel = new Credis_Sentinel( new Credis_Client( $connection_string ) ); + $this->redis = $sentinel->getCluster( WP_REDIS_SENTINEL ); + $args['servers'] = WP_REDIS_SERVERS; + } elseif ( defined( 'WP_REDIS_CLUSTER' ) || defined( 'WP_REDIS_SERVERS' ) ) { + $parameters['db'] = $parameters['database']; + + $is_cluster = defined( 'WP_REDIS_CLUSTER' ); + $clients = $is_cluster ? WP_REDIS_CLUSTER : WP_REDIS_SERVERS; + + foreach ( $clients as $index => $connection_string ) { + // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url + $url_components = parse_url( $connection_string ); + + if ( isset( $url_components['query'] ) ) { + parse_str( $url_components['query'], $add_params ); + } + + if ( ! $is_cluster && isset( $add_params['alias'] ) ) { + $add_params['master'] = 'master' === $add_params['alias']; + } + + $add_params['host'] = $url_components['host']; + $add_params['port'] = $url_components['port']; + + if ( ! isset( $add_params['alias'] ) ) { + $add_params['alias'] = "redis-$index"; + } + + $clients[ $index ] = array_merge( $parameters, $add_params ); + + unset($add_params); + } + + $this->redis = new Credis_Cluster( $clients ); + + foreach ( $clients as $index => $_client ) { + $connection_string = "{$_client['scheme']}://{$_client['host']}:{$_client['port']}"; + unset( $_client['scheme'], $_client['host'], $_client['port'] ); + + $params = array_filter( $_client ); + + if ( $params ) { + $connection_string .= '?' . http_build_query( $params, '', '&' ); + } + + $clients[ $index ] = $connection_string; + } + + $args['servers'] = $clients; + } else { + $args = [ + 'host' => $parameters['scheme'] === 'unix' ? $parameters['path'] : $parameters['host'], + 'port' => $parameters['port'], + 'timeout' => $parameters['timeout'], + 'persistent' => '', + 'database' => $parameters['database'], + 'password' => isset( $parameters['password'] ) ? $parameters['password'] : null, + ]; + + $this->redis = new Credis_Client( ...array_values( $args ) ); + } + + // Don't use PhpRedis if it is available. + $this->redis->forceStandalone(); + + $this->redis->connect(); + + if ( $parameters['read_timeout'] ) { + $args['read_timeout'] = $parameters['read_timeout']; + $this->redis->setReadTimeout( $parameters['read_timeout'] ); + } + + $this->diagnostics = array_merge( + [ 'client' => sprintf( '%s (v%s)', $client, Credis_Client::VERSION ) ], + $args + ); + } + + /** + * Connect to Redis using HHVM's Redis extension. + * + * @param array $parameters Connection parameters built by the `build_parameters` method. + * @return void + */ + protected function connect_using_hhvm( $parameters ) { + _doing_it_wrong( __FUNCTION__ , 'HHVM support will be removed in future versions.' , '2.0.26' ); + + $this->redis = new Redis(); + + // Adjust host and port if the scheme is `unix`. + if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) { + $parameters['host'] = 'unix://' . $parameters['path']; + $parameters['port'] = 0; + } + + $this->redis->connect( + $parameters['host'], + $parameters['port'], + $parameters['timeout'], + null, + $parameters['retry_interval'] + ); + + if ( $parameters['read_timeout'] ) { + $this->redis->setOption( Redis::OPT_READ_TIMEOUT, $parameters['read_timeout'] ); + } + + if ( isset( $parameters['password'] ) ) { + $this->redis->auth( $parameters['password'] ); + } + + if ( isset( $parameters['database'] ) ) { + if ( ctype_digit( (string) $parameters['database'] ) ) { + $parameters['database'] = (int) $parameters['database']; + } + + if ( $parameters['database'] ) { + $this->redis->select( $parameters['database'] ); + } + } + + $this->diagnostics = array_merge( + [ 'client' => sprintf( 'HHVM Extension (v%s)', HHVM_VERSION ) ], + $parameters + ); + } + + /** + * Fetches Redis `INFO` mostly for server version. + * + * @return void + */ + public function fetch_info() { + $options = method_exists( $this->redis, 'getOptions' ) + ? $this->redis->getOptions() + : new stdClass(); + + if ( isset( $options->replication ) && $options->replication ) { + return; + } + + if ( defined( 'WP_REDIS_CLUSTER' ) ) { + $connectionId = is_string( WP_REDIS_CLUSTER ) + ? 'SERVER' + : current( $this->build_cluster_connection_array() ); + + $info = $this->determine_client() === 'predis' + ? $this->redis->getClientBy( 'id', $connectionId )->info() + : $this->redis->info( $connectionId ); + } else { + $info = $this->redis->info(); + } + + if ( isset( $info['redis_version'] ) ) { + $this->redis_version = $info['redis_version']; + } elseif ( isset( $info['Server']['redis_version'] ) ) { + $this->redis_version = $info['Server']['redis_version']; + } + } + + /** + * Is Redis available? + * + * @return bool + */ + public function redis_status() { + return (bool) $this->redis_connected; + } + + /** + * Returns the Redis instance. + * + * @return mixed + */ + public function redis_instance() { + return $this->redis; + } + + /** + * Returns the Redis server version. + * + * @return null|string + */ + public function redis_version() { + return $this->redis_version; + } + + /** + * Adds a value to cache. + * + * If the specified key already exists, the value is not stored and the function + * returns false. + * + * @param string $key The key under which to store the value. + * @param mixed $value The value to store. + * @param string $group The group value appended to the $key. + * @param int $expiration The expiration time, defaults to 0. + * @return bool Returns TRUE on success or FALSE on failure. + */ + public function add( $key, $value, $group = 'default', $expiration = 0 ) { + return $this->add_or_replace( true, $key, $value, $group, $expiration ); + } + + /** + * Adds multiple values to the cache in one call. + * + * @param array $data Array of keys and values to be added. + * @param string $group Optional. Where the cache contents are grouped. + * @param int $expire Optional. When to expire the cache contents, in seconds. + * Default 0 (no expiration). + * @return bool[] Array of return values, grouped by key. Each value is either + * true on success, or false if cache key and group already exist. + */ + public function add_multiple( array $data, $group = 'default', $expire = 0 ) { + if ( function_exists( 'wp_suspend_cache_addition' ) && wp_suspend_cache_addition() ) { + return array_combine( array_keys( $data ), array_fill( 0, count( $data ), false ) ); + } + + if ( + $this->redis_status() && + method_exists( $this->redis, 'pipeline' ) && + ! $this->is_ignored_group( $group ) + ) { + return $this->add_multiple_at_once( $data, $group, $expire ); + } + + $values = []; + + foreach ( $data as $key => $value ) { + $values[ $key ] = $this->add( $key, $value, $group, $expire ); + } + + return $values; + } + + /** + * Adds multiple values to the cache in one call. + * + * @param array $data Array of keys and values to be added. + * @param string $group Optional. Where the cache contents are grouped. + * @param int $expire Optional. When to expire the cache contents, in seconds. + * Default 0 (no expiration). + * @return bool[] Array of return values, grouped by key. Each value is either + * true on success, or false if cache key and group already exist. + */ + protected function add_multiple_at_once( array $data, $group = 'default', $expire = 0 ) + { + $keys = array_keys( $data ); + + $san_group = $this->sanitize_key_part( $group ); + + $tx = $this->redis->pipeline(); + + $orig_exp = $expire; + $expire = $this->validate_expiration( $expire ); + $derived_keys = []; + + foreach ( $data as $key => $value ) { + /** + * Filters the cache expiration time + * + * @param int $expiration The time in seconds the entry expires. 0 for no expiry. + * @param string $key The cache key. + * @param string $group The cache group. + * @param mixed $orig_exp The original expiration value before validation. + */ + $expire = apply_filters( 'redis_cache_expiration', $expire, $key, $group, $orig_exp ); + + $san_key = $this->sanitize_key_part( $key ); + $derived_key = $derived_keys[ $key ] = $this->fast_build_key( $san_key, $san_group ); + + $args = [ $derived_key, $this->maybe_serialize( $value ) ]; + + if ( $this->is_predis() ) { + $args[] = 'nx'; + + if ( $expire ) { + $args[] = 'ex'; + $args[] = $expire; + } + } else { + if ( $expire ) { + $args[] = [ 'nx', 'ex' => $expire ]; + } else { + $args[] = [ 'nx' ]; + } + } + + $tx->set( ...$args ); + } + + try { + $start_time = microtime( true ); + + $method = $this->is_predis() ? 'execute' : 'exec'; + + $results = array_map( function ( $response ) { + return (bool) $this->parse_redis_response( $response ); + }, $tx->{$method}() ?: [] ); + + if ( count( $results ) !== count( $keys ) ) { + $tx->discard(); + + return array_fill_keys( $keys, false ); + } + + $results = array_combine( $keys, $results ); + + foreach ( $results as $key => $result ) { + if ( $result ) { + $this->add_to_internal_cache( $derived_keys[ $key ], $data[ $key ] ); + } + } + + $execute_time = microtime( true ) - $start_time; + + $this->cache_calls++; + $this->cache_time += $execute_time; + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return array_combine( $keys, array_fill( 0, count( $keys ), false ) ); + } + + return $results; + } + + /** + * Replace a value in the cache. + * + * If the specified key doesn't exist, the value is not stored and the function + * returns false. + * + * @param string $key The key under which to store the value. + * @param mixed $value The value to store. + * @param string $group The group value appended to the $key. + * @param int $expiration The expiration time, defaults to 0. + * @return bool Returns TRUE on success or FALSE on failure. + */ + public function replace( $key, $value, $group = 'default', $expiration = 0 ) { + return $this->add_or_replace( false, $key, $value, $group, $expiration ); + } + + /** + * Add or replace a value in the cache. + * + * Add does not set the value if the key exists; replace does not replace if the value doesn't exist. + * + * @param bool $add True if should only add if value doesn't exist, false to only add when value already exists. + * @param string $key The key under which to store the value. + * @param mixed $value The value to store. + * @param string $group The group value appended to the $key. + * @param int $expiration The expiration time, defaults to 0. + * @return bool Returns TRUE on success or FALSE on failure. + */ + protected function add_or_replace( $add, $key, $value, $group = 'default', $expiration = 0 ) { + $cache_addition_suspended = function_exists( 'wp_suspend_cache_addition' ) && wp_suspend_cache_addition(); + + if ( $add && $cache_addition_suspended ) { + return false; + } + + $result = true; + + $san_key = $this->sanitize_key_part( $key ); + $san_group = $this->sanitize_key_part( $group ); + + $derived_key = $this->fast_build_key( $san_key, $san_group ); + + // Save if group not excluded and redis is up. + if ( ! $this->is_ignored_group( $san_group ) && $this->redis_status() ) { + try { + $orig_exp = $expiration; + $expiration = $this->validate_expiration( $expiration ); + + /** + * Filters the cache expiration time + * + * @since 1.4.2 + * @param int $expiration The time in seconds the entry expires. 0 for no expiry. + * @param string $key The cache key. + * @param string $group The cache group. + * @param mixed $orig_exp The original expiration value before validation. + */ + $expiration = apply_filters( 'redis_cache_expiration', $expiration, $key, $group, $orig_exp ); + $start_time = microtime( true ); + + if ( $add ) { + $args = [ $derived_key, $this->maybe_serialize( $value ) ]; + + if ( $this->is_predis() ) { + $args[] = 'nx'; + + if ( $expiration ) { + $args[] = 'ex'; + $args[] = $expiration; + } + } else { + if ( $expiration ) { + $args[] = [ + 'nx', + 'ex' => $expiration, + ]; + } else { + $args[] = [ 'nx' ]; + } + } + + $result = $this->parse_redis_response( + $this->redis->set( ...$args ) + ); + + if ( ! $result ) { + return false; + } + } elseif ( $expiration ) { + $result = $this->parse_redis_response( $this->redis->setex( $derived_key, $expiration, $this->maybe_serialize( $value ) ) ); + } else { + $result = $this->parse_redis_response( $this->redis->set( $derived_key, $this->maybe_serialize( $value ) ) ); + } + + $execute_time = microtime( true ) - $start_time; + + $this->cache_calls++; + $this->cache_time += $execute_time; + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + } + + $exists = array_key_exists( $derived_key, $this->cache ); + + if ( (bool) $add === $exists ) { + return false; + } + + if ( $result ) { + $this->add_to_internal_cache( $derived_key, $value ); + } + + return $result; + } + + /** + * Remove the item from the cache. + * + * @param string $key The key under which to store the value. + * @param string $group The group value appended to the $key. + * @return bool Returns TRUE on success or FALSE on failure. + */ + public function delete( $key, $group = 'default', $deprecated = false ) { + $result = false; + + $san_key = $this->sanitize_key_part( $key ); + $san_group = $this->sanitize_key_part( $group ); + + $derived_key = $this->fast_build_key( $san_key, $san_group ); + + if ( array_key_exists( $derived_key, $this->cache ) ) { + unset( $this->cache[ $derived_key ] ); + $result = true; + } + + $start_time = microtime( true ); + + if ( $this->redis_status() && ! $this->is_ignored_group( $san_group ) ) { + try { + $result = $this->parse_redis_response( $this->redis->del( $derived_key ) ); + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + } + + $execute_time = microtime( true ) - $start_time; + + $this->cache_calls++; + $this->cache_time += $execute_time; + + if ( function_exists( 'do_action' ) ) { + /** + * Fires on every cache key deletion + * + * @since 1.3.3 + * @param string $key The cache key. + * @param string $group The group value appended to the $key. + * @param float $execute_time Execution time for the request in seconds. + */ + do_action( 'redis_object_cache_delete', $key, $group, $execute_time ); + } + + return (bool) $result; + } + + /** + * Deletes multiple values from the cache in one call. + * + * @param array $keys Array of keys to be deleted. + * @param string $group Optional. Where the cache contents are grouped. + * @return bool[] Array of return values, grouped by key. Each value is either + * true on success, or false if the contents were not deleted. + */ + public function delete_multiple( array $keys, $group = 'default' ) { + if ( + $this->redis_status() && + method_exists( $this->redis, 'pipeline' ) && + ! $this->is_ignored_group( $group ) + ) { + return $this->delete_multiple_at_once( $keys, $group ); + } + + $values = []; + + foreach ( $keys as $key ) { + $values[ $key ] = $this->delete( $key, $group ); + } + + return $values; + } + + /** + * Deletes multiple values from the cache in one call. + * + * @param array $keys Array of keys to be deleted. + * @param string $group Optional. Where the cache contents are grouped. + * @return bool[] Array of return values, grouped by key. Each value is either + * true on success, or false if the contents were not deleted. + */ + protected function delete_multiple_at_once( array $keys, $group = 'default' ) { + $start_time = microtime( true ); + + try { + $tx = $this->redis->pipeline(); + + foreach ( $keys as $key ) { + $derived_key = $this->build_key( (string) $key, $group ); + + $tx->del( $derived_key ); + + unset( $this->cache[ $derived_key ] ); + } + + $method = $this->is_predis() ? 'execute' : 'exec'; + + $results = array_map( function ( $response ) { + return (bool) $this->parse_redis_response( $response ); + }, $tx->{$method}() ?: [] ); + + if ( count( $results ) !== count( $keys ) ) { + $tx->discard(); + + return array_fill_keys( $keys, false ); + } + + $execute_time = microtime( true ) - $start_time; + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return array_combine( $keys, array_fill( 0, count( $keys ), false ) ); + } + + if ( function_exists( 'do_action' ) ) { + foreach ( $keys as $key ) { + /** + * Fires on every cache key deletion + * + * @since 1.3.3 + * @param string $key The cache key. + * @param string $group The group value appended to the $key. + * @param float $execute_time Execution time for the request in seconds. + */ + do_action( 'redis_object_cache_delete', $key, $group, $execute_time ); + } + } + + return array_combine( $keys, $results ); + } + + /** + * Removes all cache items from the in-memory runtime cache. + * + * @return bool True on success, false on failure. + */ + public function flush_runtime() { + $this->cache = []; + + return true; + } + + /** + * Invalidate all items in the cache. If `WP_REDIS_SELECTIVE_FLUSH` is `true`, + * only keys prefixed with the `WP_REDIS_PREFIX` are flushed. + * + * @return bool True on success, false on failure. + */ + public function flush() { + $results = []; + $this->cache = []; + + if ( $this->redis_status() ) { + $salt = defined( 'WP_REDIS_PREFIX' ) ? trim( WP_REDIS_PREFIX ) : null; + $selective = defined( 'WP_REDIS_SELECTIVE_FLUSH' ) ? WP_REDIS_SELECTIVE_FLUSH : null; + + $start_time = microtime( true ); + + if ( $salt && $selective ) { + $script = $this->get_flush_closure( $salt ); + + if ( defined( 'WP_REDIS_CLUSTER' ) ) { + try { + foreach ( $this->redis->_masters() as $master ) { + $redis = new Redis(); + $redis->connect( $master[0], $master[1] ); + $results[] = $this->parse_redis_response( $script() ); + unset( $redis ); + } + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + } else { + try { + $results[] = $this->parse_redis_response( $script() ); + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + } + } else { + if ( defined( 'WP_REDIS_CLUSTER' ) ) { + try { + foreach ( $this->redis->_masters() as $master ) { + $results[] = $this->parse_redis_response( $this->redis->flushdb( $master ) ); + } + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + } else { + try { + $results[] = $this->parse_redis_response( $this->redis->flushdb() ); + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + } + } + + if ( function_exists( 'do_action' ) ) { + $execute_time = microtime( true ) - $start_time; + + /** + * Fires on every cache flush + * + * @since 1.3.5 + * @param null|array $results Array of flush results. + * @param int $deprecated Unused. Default 0. + * @param bool $seletive Whether a selective flush took place. + * @param string $salt The defined key prefix. + * @param float $execute_time Execution time for the request in seconds. + */ + do_action( 'redis_object_cache_flush', $results, 0, $selective, $salt, $execute_time ); + } + } + + if ( empty( $results ) ) { + return false; + } + + foreach ( $results as $result ) { + if ( ! $result ) { + return false; + } + } + + return true; + } + + /** + * Removes all cache items in a group. + * + * @param string $group Name of group to remove from cache. + * @return bool Returns TRUE on success or FALSE on failure. + */ + public function flush_group( $group ) + { + $san_group = $this->sanitize_key_part( $group ); + + if ( is_multisite() && ! $this->is_global_group( $san_group ) ) { + $salt = str_replace( "{$this->blog_prefix}:{$san_group}", "*:{$san_group}", $this->fast_build_key( '*', $san_group ) ); + } else { + $salt = $this->fast_build_key( '*', $san_group ); + } + + foreach ( $this->cache as $key => $value ) { + if ( strpos( $key, "{$san_group}:" ) === 0 || strpos( $key, ":{$san_group}:" ) !== false ) { + unset( $this->cache[ $key ] ); + } + } + + if ( in_array( $san_group, $this->unflushable_groups ) ) { + return false; + } + + if ( ! $this->redis_status() ) { + return false; + } + + $results = []; + + $start_time = microtime( true ); + $script = $this->lua_flush_closure( $salt, false ); + + try { + if ( defined( 'WP_REDIS_CLUSTER' ) ) { + foreach ( $this->redis->_masters() as $master ) { + $redis = new Redis; + $redis->connect( $master[0], $master[1] ); + $results[] = $this->parse_redis_response( $script() ); + unset( $redis ); + } + } else { + $results[] = $this->parse_redis_response( $script() ); + } + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + + if ( function_exists( 'do_action' ) ) { + $execute_time = microtime( true ) - $start_time; + + /** + * Fires on every group cache flush + * + * @param null|array $results Array of flush results. + * @param string $salt The defined key prefix. + * @param float $execute_time Execution time for the request in seconds. + * @since 2.2.3 + */ + do_action( 'redis_object_cache_flush_group', $results, $salt, $execute_time ); + } + + foreach ( $results as $result ) { + if ( ! $result ) { + return false; + } + } + + return true; + } + + /** + * Returns a closure to flush selectively. + * + * @param string $salt The salt to be used to differentiate. + * @return callable Generated callable executing the lua script. + */ + protected function get_flush_closure( $salt ) { + if ( $this->unflushable_groups ) { + return $this->lua_flush_extended_closure( $salt ); + } else { + return $this->lua_flush_closure( $salt ); + } + } + + /** + * Quotes a string for usage in the `glob` function + * + * @param string $string The string to quote. + * @return string + */ + protected function glob_quote( $string ) { + $characters = [ '*', '+', '?', '!', '{', '}', '[', ']', '(', ')', '|', '@' ]; + + return str_replace( + $characters, + array_map( + function ( $character ) { + return "[{$character}]"; + }, + $characters + ), + $string + ); + } + + /** + * Returns a closure ready to be called to flush selectively ignoring unflushable groups. + * + * @param string $salt The salt to be used to differentiate. + * @param bool $escape ... + * @return callable Generated callable executing the lua script. + */ + protected function lua_flush_closure( $salt, $escape = true ) { + $salt = $escape ? $this->glob_quote( $salt ) : $salt; + + return function () use ( $salt ) { + $script = <<redis_version(), '5', '<' ) && version_compare( $this->redis_version(), '3.2', '>=' ) ) { + $script = 'redis.replicate_commands()' . "\n" . $script; + } + + $args = $this->is_predis() ? [ $script, 0 ] : [ $script ]; + + return call_user_func_array( [ $this->redis, 'eval' ], $args ); + }; + } + + /** + * Returns a closure ready to be called to flush selectively. + * + * @param string $salt The salt to be used to differentiate. + * @return callable Generated callable executing the lua script. + */ + protected function lua_flush_extended_closure( $salt ) { + $salt = $this->glob_quote( $salt ); + + return function () use ( $salt ) { + $salt_length = strlen( $salt ); + + $unflushable = array_map( + function ( $group ) { + return ":{$group}:"; + }, + $this->unflushable_groups + ); + + $script = <<redis_version(), '5', '<' ) && version_compare( $this->redis_version(), '3.2', '>=' ) ) { + $script = 'redis.replicate_commands()' . "\n" . $script; + } + + $args = $this->is_predis() + ? array_merge( [ $script, count( $unflushable ) ], $unflushable ) + : [ $script, $unflushable, count( $unflushable ) ]; + + return call_user_func_array( [ $this->redis, 'eval' ], $args ); + }; + } + + /** + * Retrieve object from cache. + * + * Gets an object from cache based on $key and $group. + * + * @param string $key The key under which to store the value. + * @param string $group The group value appended to the $key. + * @param bool $force Optional. Whether to force a refetch rather than relying on the local + * cache. Default false. + * @param bool $found Optional. Whether the key was found in the cache. Disambiguates a return of + * false, a storable value. Passed by reference. Default null. + * @return bool|mixed Cached object value. + */ + public function get( $key, $group = 'default', $force = false, &$found = null ) { + $san_key = $this->sanitize_key_part( $key ); + $san_group = $this->sanitize_key_part( $group ); + $derived_key = $this->fast_build_key( $san_key, $san_group ); + + if ( array_key_exists( $derived_key, $this->cache ) && ! $force ) { + $found = true; + $this->cache_hits++; + $value = $this->get_from_internal_cache( $derived_key ); + + return $value; + } elseif ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) { + $found = false; + $this->cache_misses++; + + return false; + } + + $start_time = microtime( true ); + + try { + $result = $this->redis->get( $derived_key ); + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + + $execute_time = microtime( true ) - $start_time; + + $this->cache_calls++; + $this->cache_time += $execute_time; + + if ( $result === null || $result === false ) { + $found = false; + $this->cache_misses++; + + return false; + } else { + $found = true; + $this->cache_hits++; + $value = $this->maybe_unserialize( $result ); + } + + $this->add_to_internal_cache( $derived_key, $value ); + + if ( function_exists( 'do_action' ) ) { + /** + * Fires on every cache get request + * + * @since 1.2.2 + * @param mixed $value Value of the cache entry. + * @param string $key The cache key. + * @param string $group The group value appended to the $key. + * @param bool $force Whether a forced refetch has taken place rather than relying on the local cache. + * @param bool $found Whether the key was found in the cache. + * @param float $execute_time Execution time for the request in seconds. + */ + do_action( 'redis_object_cache_get', $key, $value, $group, $force, $found, $execute_time ); + } + + if ( function_exists( 'apply_filters' ) && function_exists( 'has_filter' ) ) { + if ( has_filter( 'redis_object_cache_get_value' ) ) { + /** + * Filters the return value + * + * @since 1.4.2 + * @param mixed $value Value of the cache entry. + * @param string $key The cache key. + * @param string $group The group value appended to the $key. + * @param bool $force Whether a forced refetch has taken place rather than relying on the local cache. + * @param bool $found Whether the key was found in the cache. + */ + return apply_filters( 'redis_object_cache_get_value', $value, $key, $group, $force, $found ); + } + } + + return $value; + } + + /** + * Retrieves multiple values from the cache in one call. + * + * @param array $keys Array of keys under which the cache contents are stored. + * @param string $group Optional. Where the cache contents are grouped. Default empty. + * @param bool $force Optional. Whether to force an update of the local cache + * from the persistent cache. Default false. + * @return array|false Array of values organized into groups. + */ + public function get_multiple( $keys, $group = 'default', $force = false ) { + if ( ! is_array( $keys ) ) { + return false; + } + + $cache = []; + $derived_keys = []; + $start_time = microtime( true ); + + $san_group = $this->sanitize_key_part( $group ); + + foreach ( $keys as $key ) { + $san_key = $this->sanitize_key_part( $key ); + $derived_keys[ $key ] = $this->fast_build_key( $san_key, $san_group ); + } + + if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) { + foreach ( $keys as $key ) { + $value = $this->get_from_internal_cache( $derived_keys[ $key ] ); + $cache[ $key ] = $value; + + if ($value === false) { + $this->cache_misses++; + } else { + $this->cache_hits++; + } + } + + return $cache; + } + + if ( ! $force ) { + foreach ( $keys as $key ) { + $value = $this->get_from_internal_cache( $derived_keys[ $key ] ); + + if ( $value === false ) { + $this->cache_misses++; + + } else { + $cache[ $key ] = $value; + $this->cache_hits++; + } + } + } + + $remaining_keys = array_filter( + $keys, + function ( $key ) use ( $cache ) { + return ! array_key_exists( $key, $cache ); + } + ); + + if ( empty( $remaining_keys ) ) { + return $cache; + } + + $start_time = microtime( true ); + $results = []; + + $remaining_ids = array_map( + function ( $key ) use ( $derived_keys ) { + return $derived_keys[ $key ]; + }, + $remaining_keys + ); + + try { + $results = array_combine( + $remaining_keys, + $this->redis->mget( $remaining_ids ) + ?: array_fill( 0, count( $remaining_ids ), false ) + ); + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + $results = array_combine( + $remaining_keys, + array_fill( 0, count( $remaining_ids ), false ) + ); + } + + $execute_time = microtime( true ) - $start_time; + + $this->cache_calls++; + $this->cache_time += $execute_time; + + foreach ( $results as $key => $value ) { + if ( $value === null || $value === false ) { + $cache[ $key ] = false; + $this->cache_misses++; + } else { + $cache[ $key ] = $this->maybe_unserialize( $value ); + $this->add_to_internal_cache( $derived_keys[ $key ], $cache[ $key ] ); + $this->cache_hits++; + } + } + + if ( function_exists( 'do_action' ) ) { + /** + * Fires on every cache get multiple request + * + * @since 2.0.6 + * @param array $keys Array of keys under which the cache contents are stored. + * @param array $cache Cache items. + * @param string $group The group value appended to the $key. + * @param bool $force Whether a forced refetch has taken place rather than relying on the local cache. + * @param float $execute_time Execution time for the request in seconds. + */ + do_action( 'redis_object_cache_get_multiple', $keys, $cache, $group, $force, $execute_time ); + } + + if ( function_exists( 'apply_filters' ) && function_exists( 'has_filter' ) ) { + if ( has_filter( 'redis_object_cache_get_value' ) ) { + foreach ( $cache as $key => $value ) { + /** + * Filters the return value + * + * @since 1.4.2 + * @param mixed $value Value of the cache entry. + * @param string $key The cache key. + * @param string $group The group value appended to the $key. + * @param bool $force Whether a forced refetch has taken place rather than relying on the local cache. + */ + $cache[ $key ] = apply_filters( 'redis_object_cache_get_value', $value, $key, $group, $force ); + } + } + } + + return $cache; + } + + /** + * Sets a value in cache. + * + * The value is set whether or not this key already exists in Redis. + * + * @param string $key The key under which to store the value. + * @param mixed $value The value to store. + * @param string $group The group value appended to the $key. + * @param int $expiration The expiration time, defaults to 0. + * @return bool Returns TRUE on success or FALSE on failure. + */ + public function set( $key, $value, $group = 'default', $expiration = 0 ) { + $result = true; + $start_time = microtime( true ); + + $san_key = $this->sanitize_key_part( $key ); + $san_group = $this->sanitize_key_part( $group ); + + $derived_key = $this->fast_build_key( $san_key, $san_group ); + + // Save if group not excluded from redis and redis is up. + if ( ! $this->is_ignored_group( $group ) && $this->redis_status() ) { + $orig_exp = $expiration; + $expiration = $this->validate_expiration( $expiration ); + + /** + * Filters the cache expiration time + * + * @since 1.4.2 + * @param int $expiration The time in seconds the entry expires. 0 for no expiry. + * @param string $key The cache key. + * @param string $group The cache group. + * @param mixed $orig_exp The original expiration value before validation. + */ + $expiration = apply_filters( 'redis_cache_expiration', $expiration, $key, $group, $orig_exp ); + + try { + if ( $expiration ) { + $result = $this->parse_redis_response( $this->redis->setex( $derived_key, $expiration, $this->maybe_serialize( $value ) ) ); + } else { + $result = $this->parse_redis_response( $this->redis->set( $derived_key, $this->maybe_serialize( $value ) ) ); + } + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + + $execute_time = microtime( true ) - $start_time; + $this->cache_calls++; + $this->cache_time += $execute_time; + } + + // If the set was successful, or we didn't go to redis. + if ( $result ) { + $this->add_to_internal_cache( $derived_key, $value ); + } + + if ( function_exists( 'do_action' ) ) { + $execute_time = microtime( true ) - $start_time; + + /** + * Fires on every cache set + * + * @since 1.2.2 + * @param string $key The cache key. + * @param mixed $value Value of the cache entry. + * @param string $group The group value appended to the $key. + * @param int $expiration The time in seconds the entry expires. 0 for no expiry. + * @param float $execute_time Execution time for the request in seconds. + */ + do_action( 'redis_object_cache_set', $key, $value, $group, $expiration, $execute_time ); + } + + return $result; + } + + /** + * Sets multiple values to the cache in one call. + * + * @param array $data Array of key and value to be set. + * @param string $group Optional. Where the cache contents are grouped. + * @param int $expire Optional. When to expire the cache contents, in seconds. + * Default 0 (no expiration). + * @return bool[] Array of return values, grouped by key. Each value is always true. + */ + public function set_multiple( array $data, $group = 'default', $expire = 0 ) { + if ( + $this->redis_status() && + method_exists( $this->redis, 'pipeline' ) && + ! $this->is_ignored_group( $group ) + ) { + return $this->set_multiple_at_once( $data, $group, $expire ); + } + + $values = []; + + foreach ( $data as $key => $value ) { + $values[ $key ] = $this->set( $key, $value, $group, $expire ); + } + + return $values; + } + + /** + * Sets multiple values to the cache in one call. + * + * @param array $data Array of key and value to be set. + * @param string $group Optional. Where the cache contents are grouped. + * @param int $expiration Optional. When to expire the cache contents, in seconds. + * Default 0 (no expiration). + * @return bool[] Array of return values, grouped by key. Each value is always true. + */ + protected function set_multiple_at_once( array $data, $group = 'default', $expiration = 0 ) + { + $start_time = microtime( true ); + + $san_group = $this->sanitize_key_part( $group ); + $derived_keys = []; + + $orig_exp = $expiration; + $expiration = $this->validate_expiration( $expiration ); + $expirations = []; + + $tx = $this->redis->pipeline(); + $keys = array_keys( $data ); + + foreach ( $data as $key => $value ) { + $san_key = $this->sanitize_key_part( $key ); + $derived_key = $derived_keys[ $key ] = $this->fast_build_key( $san_key, $san_group ); + + /** + * Filters the cache expiration time + * + * @param int $expiration The time in seconds the entry expires. 0 for no expiry. + * @param string $key The cache key. + * @param string $group The cache group. + * @param mixed $orig_exp The original expiration value before validation. + */ + $expiration = $expirations[ $key ] = apply_filters( 'redis_cache_expiration', $expiration, $key, $group, $orig_exp ); + + if ( $expiration ) { + $tx->setex( $derived_key, $expiration, $this->maybe_serialize( $value ) ); + } else { + $tx->set( $derived_key, $this->maybe_serialize( $value ) ); + } + } + + try { + $method = $this->is_predis() ? 'execute' : 'exec'; + + $results = array_map( function ( $response ) { + return (bool) $this->parse_redis_response( $response ); + }, $tx->{$method}() ?: [] ); + + if ( count( $results ) !== count( $keys ) ) { + $tx->discard(); + + return array_fill_keys( $keys, false ); + } + + $results = array_combine( $keys, $results ); + + foreach ( $results as $key => $result ) { + if ( $result ) { + $this->add_to_internal_cache( $derived_keys[ $key ], $data[ $key ] ); + } + } + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return array_combine( $keys, array_fill( 0, count( $keys ), false ) ); + } + + $execute_time = microtime( true ) - $start_time; + + $this->cache_calls++; + $this->cache_time += $execute_time; + + if ( function_exists( 'do_action' ) ) { + foreach ( $data as $key => $value ) { + /** + * Fires on every cache set + * + * @param string $key The cache key. + * @param mixed $value Value of the cache entry. + * @param string $group The group value appended to the $key. + * @param int $expiration The time in seconds the entry expires. 0 for no expiry. + * @param float $execute_time Execution time for the request in seconds. + */ + do_action( 'redis_object_cache_set', $key, $value, $group, $expirations[ $key ], $execute_time ); + } + } + + return $results; + } + + /** + * Increment a Redis counter by the amount specified + * + * @param string $key The key name. + * @param int $offset Optional. The increment. Defaults to 1. + * @param string $group Optional. The key group. Default is 'default'. + * @return int|bool + */ + public function increment( $key, $offset = 1, $group = 'default' ) { + $offset = (int) $offset; + $start_time = microtime( true ); + + $san_key = $this->sanitize_key_part( $key ); + $san_group = $this->sanitize_key_part( $group ); + + $derived_key = $this->fast_build_key( $san_key, $san_group ); + + // If group is a non-Redis group, save to internal cache, not Redis. + if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) { + $value = $this->get_from_internal_cache( $derived_key ); + $value += $offset; + $this->add_to_internal_cache( $derived_key, $value ); + + return $value; + } + + try { + $result = $this->parse_redis_response( $this->redis->incrBy( $derived_key, $offset ) ); + + $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) ); + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + + $execute_time = microtime( true ) - $start_time; + + $this->cache_calls += 2; + $this->cache_time += $execute_time; + + return $result; + } + + /** + * Alias of `increment()`. + * + * @see self::increment() + * @param string $key The key name. + * @param int $offset Optional. The increment. Defaults to 1. + * @param string $group Optional. The key group. Default is 'default'. + * @return int|bool + */ + public function incr( $key, $offset = 1, $group = 'default' ) { + return $this->increment( $key, $offset, $group ); + } + + /** + * Decrement a Redis counter by the amount specified + * + * @param string $key The key name. + * @param int $offset Optional. The decrement. Defaults to 1. + * @param string $group Optional. The key group. Default is 'default'. + * @return int|bool + */ + public function decrement( $key, $offset = 1, $group = 'default' ) { + $offset = (int) $offset; + $start_time = microtime( true ); + + $san_key = $this->sanitize_key_part( $key ); + $san_group = $this->sanitize_key_part( $group ); + + $derived_key = $this->fast_build_key( $san_key, $san_group ); + + // If group is a non-Redis group, save to internal cache, not Redis. + if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) { + $value = $this->get_from_internal_cache( $derived_key ); + $value -= $offset; + $this->add_to_internal_cache( $derived_key, $value ); + + return $value; + } + + try { + $result = $this->parse_redis_response( $this->redis->decrBy( $derived_key, $offset ) ); + + $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) ); + } catch ( Exception $exception ) { + $this->handle_exception( $exception ); + + return false; + } + + $execute_time = microtime( true ) - $start_time; + + $this->cache_calls += 2; + $this->cache_time += $execute_time; + + return $result; + } + + /** + * Alias of `decrement()`. + * + * @see self::decrement() + * @param string $key The key name. + * @param int $offset Optional. The decrement. Defaults to 1. + * @param string $group Optional. The key group. Default is 'default'. + * @return int|bool + */ + public function decr( $key, $offset = 1, $group = 'default' ) { + return $this->decrement( $key, $offset, $group ); + } + + /** + * Render data about current cache requests + * Used by the Debug bar plugin + * + * @return void + */ + public function stats() { + ?> +

+ Redis Status: + redis_status() ? 'Connected' : 'Not connected'; ?> +
+ Redis Client: + diagnostics['client'] ?: 'Unknown'; ?> +
+ Cache Hits: + cache_hits; ?> +
+ Cache Misses: + cache_misses; ?> +
+ Cache Size: + cache ) ) / 1024, 2 ); ?> KB +

+ cache_hits + $this->cache_misses; + + $bytes = array_map( + function ( $keys ) { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize + return strlen( serialize( $keys ) ); + }, + $this->cache + ); + + return (object) [ + 'hits' => $this->cache_hits, + 'misses' => $this->cache_misses, + 'ratio' => $total > 0 ? round( $this->cache_hits / ( $total / 100 ), 1 ) : 100, + 'bytes' => array_sum( $bytes ), + 'time' => $this->cache_time, + 'calls' => $this->cache_calls, + 'groups' => (object) [ + 'global' => $this->global_groups, + 'non_persistent' => $this->ignored_groups, + 'unflushable' => $this->unflushable_groups, + ], + 'errors' => empty( $this->errors ) ? null : $this->errors, + 'meta' => [ + 'Client' => $this->diagnostics['client'] ?? 'Unknown', + 'Redis Version' => $this->redis_version, + ], + ]; + } + + /** + * Builds a key for the cached object using the prefix, group and key. + * + * @param string $key The key under which to store the value, pre-sanitized. + * @param string $group The group value appended to the $key, pre-sanitized. + * + * @return string + */ + public function build_key( $key, $group = 'default' ) { + if ( empty( $group ) ) { + $group = 'default'; + } + + $san_key = $this->sanitize_key_part( $key ); + $san_group = $this->sanitize_key_part( $group ); + + return $this->fast_build_key($san_key, $san_group); + } + + /** + * Builds a key for the cached object using the prefix, group and key. + * + * @param string $key The key under which to store the value, pre-sanitized. + * @param string $group The group value appended to the $key, pre-sanitized. + * + * @return string + */ + public function fast_build_key( $key, $group = 'default' ) { + if ( empty( $group ) ) { + $group = 'default'; + } + + $salt = defined( 'WP_REDIS_PREFIX' ) ? trim( WP_REDIS_PREFIX ) : ''; + + $prefix = $this->is_global_group( $group ) ? $this->global_prefix : $this->blog_prefix; + $prefix = trim( $prefix, '_-:$' ); + + return "{$salt}{$prefix}:{$group}:{$key}"; + } + + /** + * Replaces the set group separator by another one + * + * @param string $part The string to sanitize. + * @return string Sanitized string. + */ + protected function sanitize_key_part( $part ) { + return str_replace( ':', '-', $part ); + } + + /** + * Checks if the given group is part the ignored group array + * + * @param string $group Name of the group to check, pre-sanitized. + * @return bool + */ + protected function is_ignored_group( $group ) { + return $this->is_group_of_type( $group, 'ignored' ); + } + + /** + * Checks if the given group is part the global group array + * + * @param string $group Name of the group to check, pre-sanitized. + * @return bool + */ + protected function is_global_group( $group ) { + return $this->is_group_of_type( $group, 'global' ); + } + + /** + * Checks if the given group is part the unflushable group array + * + * @param string $group Name of the group to check, pre-sanitized. + * @return bool + */ + protected function is_unflushable_group( $group ) { + return $this->is_group_of_type( $group, 'unflushable' ); + } + + /** + * Checks the type of the given group + * + * @param string $group Name of the group to check, pre-sanitized. + * @param string $type Type of the group to check. + * @return bool + */ + private function is_group_of_type( $group, $type ) { + return isset( $this->group_type[ $group ] ) + && $this->group_type[ $group ] == $type; + } + + /** + * Convert Redis responses into something meaningful + * + * @param mixed $response Response sent from the redis instance. + * @return mixed + */ + protected function parse_redis_response( $response ) { + if ( is_bool( $response ) ) { + return $response; + } + + if ( is_numeric( $response ) ) { + return $response; + } + + if ( is_object( $response ) && method_exists( $response, 'getPayload' ) ) { + return $response->getPayload() === 'OK'; + } + + return false; + } + + /** + * Simple wrapper for saving object to the internal cache. + * + * @param string $derived_key Key to save value under. + * @param mixed $value Object value. + */ + public function add_to_internal_cache( $derived_key, $value ) { + if ( is_object( $value ) ) { + $value = clone $value; + } + + $this->cache[ $derived_key ] = $value; + } + + /** + * Get a value specifically from the internal, run-time cache, not Redis. + * + * @param int|string $derived_key Key value. + * + * @return bool|mixed Value on success; false on failure. + */ + public function get_from_internal_cache( $derived_key ) { + if ( ! array_key_exists( $derived_key, $this->cache ) ) { + return false; + } + + if ( is_object( $this->cache[ $derived_key ] ) ) { + return clone $this->cache[ $derived_key ]; + } + + return $this->cache[ $derived_key ]; + } + + /** + * In multisite, switch blog prefix when switching blogs + * + * @param int $_blog_id Blog ID. + * @return bool + */ + public function switch_to_blog( $_blog_id ) { + if ( ! function_exists( 'is_multisite' ) || ! is_multisite() ) { + return false; + } + + $this->blog_prefix = (int) $_blog_id; + + return true; + } + + /** + * Sets the list of global groups. + * + * @param array $groups List of groups that are global. + */ + public function add_global_groups( $groups ) { + $groups = (array) $groups; + + if ( $this->redis_status() ) { + $this->global_groups = array_unique( array_merge( $this->global_groups, $groups ) ); + } else { + $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) ); + } + + $this->cache_group_types(); + } + + /** + * Sets the list of groups not to be cached by Redis. + * + * @param array $groups List of groups that are to be ignored. + */ + public function add_non_persistent_groups( $groups ) { + /** + * Filters list of groups to be added to {@see self::$ignored_groups} + * + * @since 2.1.7 + * @param string[] $groups List of groups to be ignored. + */ + $groups = apply_filters( 'redis_cache_add_non_persistent_groups', (array) $groups ); + + $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) ); + $this->cache_group_types(); + } + + /** + * Sets the list of groups not to flushed cached. + * + * @param array $groups List of groups that are unflushable. + */ + public function add_unflushable_groups( $groups ) { + $groups = (array) $groups; + + $this->unflushable_groups = array_unique( array_merge( $this->unflushable_groups, $groups ) ); + $this->cache_group_types(); + } + + /** + * Wrapper to validate the cache keys expiration value + * + * @param mixed $expiration Incoming expiration value (whatever it is). + */ + protected function validate_expiration( $expiration ) { + $expiration = is_int( $expiration ) || ctype_digit( (string) $expiration ) ? (int) $expiration : 0; + + if ( defined( 'WP_REDIS_MAXTTL' ) ) { + $max = (int) WP_REDIS_MAXTTL; + + if ( $expiration === 0 || $expiration > $max ) { + $expiration = $max; + } + } + + return $expiration; + } + + /** + * Unserialize value only if it was serialized. + * + * @param string $original Maybe unserialized original, if is needed. + * @return mixed Unserialized data can be any type. + */ + protected function maybe_unserialize( $original ) { + if ( defined( 'WP_REDIS_SERIALIZER' ) && ! empty( WP_REDIS_SERIALIZER ) ) { + return $original; + } + + if ( defined( 'WP_REDIS_IGBINARY' ) && WP_REDIS_IGBINARY && function_exists( 'igbinary_unserialize' ) ) { + return igbinary_unserialize( $original ); + } + + // Don't attempt to unserialize data that wasn't serialized going in. + if ( $this->is_serialized( $original ) ) { + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize + $value = @unserialize( $original ); + + return is_object( $value ) ? clone $value : $value; + } + + return $original; + } + + /** + * Serialize data, if needed. + * + * @param mixed $data Data that might be serialized. + * @return mixed A scalar data + */ + protected function maybe_serialize( $data ) { + if ( is_object( $data ) ) { + $data = clone $data; + } + + if ( defined( 'WP_REDIS_SERIALIZER' ) && ! empty( WP_REDIS_SERIALIZER ) ) { + return $data; + } + + if ( defined( 'WP_REDIS_IGBINARY' ) && WP_REDIS_IGBINARY && function_exists( 'igbinary_serialize' ) ) { + return igbinary_serialize( $data ); + } + + if ( is_array( $data ) || is_object( $data ) ) { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize + return serialize( $data ); + } + + if ( $this->is_serialized( $data, false ) ) { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize + return serialize( $data ); + } + + return $data; + } + + /** + * Check value to find if it was serialized. + * + * If $data is not an string, then returned value will always be false. + * Serialized data is always a string. + * + * @param string $data Value to check to see if was serialized. + * @param bool $strict Optional. Whether to be strict about the end of the string. Default true. + * @return bool False if not serialized and true if it was. + */ + protected function is_serialized( $data, $strict = true ) { + // if it isn't a string, it isn't serialized. + if ( ! is_string( $data ) ) { + return false; + } + + $data = trim( $data ); + + if ( 'N;' === $data ) { + return true; + } + + if ( strlen( $data ) < 4 ) { + return false; + } + + if ( ':' !== $data[1] ) { + return false; + } + + if ( $strict ) { + $lastc = substr( $data, -1 ); + + if ( ';' !== $lastc && '}' !== $lastc ) { + return false; + } + } else { + $semicolon = strpos( $data, ';' ); + $brace = strpos( $data, '}' ); + + // Either ; or } must exist. + if ( false === $semicolon && false === $brace ) { + return false; + } + + // But neither must be in the first X characters. + if ( false !== $semicolon && $semicolon < 3 ) { + return false; + } + + if ( false !== $brace && $brace < 4 ) { + return false; + } + } + $token = $data[0]; + + switch ( $token ) { + case 's': + if ( $strict ) { + if ( '"' !== substr( $data, -2, 1 ) ) { + return false; + } + } elseif ( false === strpos( $data, '"' ) ) { + return false; + } + // Or else fall through. + // No break! + case 'a': + case 'O': + return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data ); + case 'b': + case 'i': + case 'd': + $end = $strict ? '$' : ''; + + return (bool) preg_match( "/^{$token}:[0-9.E-]+;$end/", $data ); + } + + return false; + } + + /** + * Handle the redis failure gracefully or throw an exception. + * + * @param \Exception $exception Exception thrown. + * @throws \Exception If `fail_gracefully` flag is set to a falsy value. + * @return void + */ + protected function handle_exception( $exception ) { + $this->redis_connected = false; + + // When Redis is unavailable, fall back to the internal cache by forcing all groups to be "no redis" groups. + $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $this->global_groups ) ); + + error_log( $exception ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + + if ( ! $this->fail_gracefully ) { + $this->show_error_and_die( $exception ); + } + + $this->errors[] = $exception->getMessage(); + + if ( function_exists( 'do_action' ) ) { + /** + * Fires on every cache error + * + * @since 1.5.0 + * @param \Exception $exception The exception triggered. + */ + do_action( 'redis_object_cache_error', $exception ); + } + } + + /** + * Show Redis connection error screen, or load custom `/redis-error.php`. + * + * @return void + */ + protected function show_error_and_die( Exception $exception ) { + wp_load_translations_early(); + + add_filter( 'pre_determine_locale', function () { + return defined( 'WPLANG' ) ? WPLANG : 'en_US'; + } ); + + // Load custom DB error template, if present. + if ( file_exists( WP_CONTENT_DIR . '/redis-error.php' ) ) { + require_once WP_CONTENT_DIR . '/redis-error.php'; + die(); + } + + $verbose = wp_installing() + || defined( 'WP_ADMIN' ) + || ( defined( 'WP_DEBUG' ) && WP_DEBUG ); + + $message = '

' . __( 'Error establishing a Redis connection', 'redis-cache' ) . "

\n"; + + if ( $verbose ) { + $message .= "

" . $exception->getMessage() . "

\n"; + + $message .= '

' . sprintf( + // translators: %s = Formatted wp-config.php file name. + __( 'WordPress is unable to establish a connection to Redis. This means that the connection information in your %s file are incorrect, or that the Redis server is not reachable.', 'redis-cache' ), + 'wp-config.php' + ) . "

\n"; + + $message .= "
    \n"; + $message .= '
  • ' . __( 'Is the correct Redis host and port set?', 'redis-cache' ) . "
  • \n"; + $message .= '
  • ' . __( 'Is the Redis server running?', 'redis-cache' ) . "
  • \n"; + $message .= "
\n"; + + $message .= '

' . sprintf( + // translators: %s = Link to installation instructions. + __( 'If you need help, please read the installation instructions.', 'redis-cache' ), + 'https://github.com/rhubarbgroup/redis-cache/blob/develop/INSTALL.md' + ) . "

\n"; + } + + $message .= '

' . sprintf( + // translators: %1$s = Formatted object-cache.php file name, %2$s = Formatted wp-content directory name. + __( 'To disable Redis, delete the %1$s file in the %2$s directory.', 'redis-cache' ), + 'object-cache.php', + '/wp-content/' + ) . "

\n"; + + wp_die( $message ); + } + + /** + * Builds a clean connection array out of redis clusters array. + * + * @return array + */ + protected function build_cluster_connection_array() { + $cluster = array_values( WP_REDIS_CLUSTER ); + + foreach ( $cluster as $key => $server ) { + $connection_string = parse_url( $server ); + + $cluster[ $key ] = sprintf( + "%s:%s", + $connection_string['host'], + $connection_string['port'] + ); + } + + return $cluster; + } + + /** + * Check whether Predis client is in use. + * + * @return bool + */ + protected function is_predis() { + return $this->redis instanceof Predis\Client; + } + + /** + * Allows access to private properties for backwards compatibility. + * + * @param string $name Name of the property. + * @return mixed + */ + public function __get( $name ) { + return isset( $this->{$name} ) ? $this->{$name} : null; + } +} + +endif; +// phpcs:enable Generic.WhiteSpace.ScopeIndent.IncorrectExact, Generic.WhiteSpace.ScopeIndent.Incorrect diff --git a/redis-cache/includes/ui/class-tab.php b/redis-cache/includes/ui/class-tab.php new file mode 100644 index 0000000..af621c4 --- /dev/null +++ b/redis-cache/includes/ui/class-tab.php @@ -0,0 +1,259 @@ + $slug, + 'label' => $label, + 'file' => WP_REDIS_PLUGIN_PATH . "/includes/ui/tabs/{$slug}.php", + ] + ); + + foreach ( $args ?: [] as $property => $value ) { + if ( property_exists( $this, $property ) ) { + $this->{$property} = $value; + } else { + $this->custom[ $property ] = $value; + } + } + } + + /** + * Getter for tab slug + * + * @return string + */ + public function slug() { + return $this->slug; + } + + /** + * Getter for tab label + * + * @return string + */ + public function label() { + return $this->label; + } + + /** + * Getter for tab file + * + * @return string + */ + public function file() { + return $this->file; + } + + /** + * Getter for tab disabled state + * + * @return bool + */ + public function is_disabled() { + return $this->disabled; + } + + /** + * Getter for tab default state + * + * @return bool + */ + public function is_default() { + return $this->default; + } + + /** + * Getter for tab custom data + * + * @param string $key Custom data key. + * @return mixed + */ + public function custom( $key ) { + if ( ! isset( $this->custom[ $key ] ) ) { + return null; + } + + return $this->custom[ $key ]; + } + + /** + * Disabled notice for tab + * + * @return string + */ + public function disabled_notice() { + return sprintf( + // translators: %s = Tab label. + __( '%s are disabled for this site.', 'redis-cache' ), + $this->label + ); + } + + /** + * Displays the tab template + * + * @return void + */ + public function display() { + $roc = Plugin::instance(); + + include $this->file; + } + + /** + * Returns the tab nav id attribute + * + * @return string + */ + public function nav_id() { + $nav_id = "{$this->slug}-tab"; + + /** + * Filters the tab's nav id + * + * @since 2.0.12 + * @param string $nav_id The id attribute of the current tab's nav element. + * @param Tab $instance The current tab. + */ + return apply_filters( 'roc_tab_nav_id', $nav_id, $this ); + } + + /** + * Returns the tab nav css classes + * + * @return string + */ + public function nav_classes() { + $classes = [ + 'nav-tab', + ]; + + if ( $this->default ) { + $classes[] = 'nav-tab-active'; + } + + if ( $this->disabled ) { + $classes[] = 'nav-tab-disabled'; + } + + /** + * Filters the current tab's nav element css classes + * + * @since 2.0.12 + * @param array $classes Array of css classes. + * @param Tab $instance The current tab. + */ + return implode( ' ', apply_filters( 'roc_tab_nav_classes', $classes, $this ) ); + } + + /** + * Returns the tab id attribute + * + * @return string + */ + public function id() { + $tab_id = "{$this->slug}-pane"; + + /** + * Filters the tab's id + * + * @since 2.0.12 + * @param string $tab_id The id attribute of the current tab element. + * @param Tab $instance The current tab. + */ + return apply_filters( 'roc_tab_id', $tab_id, $this ); + } + + /** + * Returns the tab css classes + * + * @return string + */ + public function classes() { + $classes = [ + 'tab-pane', + "tab-pane-{$this->slug}", + ]; + + if ( $this->default ) { + $classes[] = 'active'; + } + + /** + * Filters the current tab's css classes + * + * @since 2.0.12 + * @param array $classes Array of css classes. + * @param Tab $instance The current tab. + */ + return implode( ' ', apply_filters( 'roc_tab_classes', $classes, $this ) ); + } + +} diff --git a/redis-cache/includes/ui/query-monitor.php b/redis-cache/includes/ui/query-monitor.php new file mode 100644 index 0000000..9dd1efa --- /dev/null +++ b/redis-cache/includes/ui/query-monitor.php @@ -0,0 +1,133 @@ + $data + */ + +// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped +echo $this->before_non_tabular_output(); +?> + +
+

+

+
+ +
+

+

%

+
+ +
+

+

+
+ +
+

+

+
+ +
+

+

+
+ + + + +
+ +
+

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

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

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

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

+ + + + $value ) : ?> + + + + + + +
+
+ + +after_non_tabular_output(); diff --git a/redis-cache/includes/ui/settings.php b/redis-cache/includes/ui/settings.php new file mode 100644 index 0000000..c3d7f84 --- /dev/null +++ b/redis-cache/includes/ui/settings.php @@ -0,0 +1,164 @@ + +
+ +

+ +

+ + + +
+ +
+ + + +
+ + is_disabled() ) : ?> +
+ display(); ?> +
+ + +
+ +
+ + + +
+ +
diff --git a/redis-cache/includes/ui/tabs/diagnostics.php b/redis-cache/includes/ui/tabs/diagnostics.php new file mode 100644 index 0000000..68cacbb --- /dev/null +++ b/redis-cache/includes/ui/tabs/diagnostics.php @@ -0,0 +1,23 @@ + + +
+
+
+ +

+ + + + +

diff --git a/redis-cache/includes/ui/tabs/metrics.php b/redis-cache/includes/ui/tabs/metrics.php new file mode 100644 index 0000000..d66b5d6 --- /dev/null +++ b/redis-cache/includes/ui/tabs/metrics.php @@ -0,0 +1,39 @@ + + +
+ + + +
+ +
diff --git a/redis-cache/includes/ui/tabs/overview.php b/redis-cache/includes/ui/tabs/overview.php new file mode 100644 index 0000000..2b0c6f8 --- /dev/null +++ b/redis-cache/includes/ui/tabs/overview.php @@ -0,0 +1,290 @@ +get_redis_status(); +$redis_client = $roc->get_redis_client_name(); +$redis_prefix = $roc->get_redis_prefix(); +$redis_maxttl = $roc->get_redis_maxttl(); +$redis_version = $roc->get_redis_version(); +$redis_connection = $roc->check_redis_connection(); +$filesystem_writable = $roc->test_filesystem_writing(); + +$diagnostics = $roc->get_diagnostics(); + +?> + + +
+

+ + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + get_status() ); ?> + + + + + get_status() ); ?> + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +

+ +

+ +
+ + + +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 ) : ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
    + +
  • + +
+
+
    + +
  • + +
+
+
    + +
  • obscure_url_secrets( $node ) ); ?>
  • + +
+
+ •••••••• +
+ + + +
+ + + +
+ + + +
+ + + +

+ + get_redis_status() ) : ?> + + +   + + + validate_object_cache_dropin() ) : ?> + + + + + + + + + + + + + + + +

diff --git a/redis-cache/includes/ui/widget.php b/redis-cache/includes/ui/widget.php new file mode 100644 index 0000000..2d2a25c --- /dev/null +++ b/redis-cache/includes/ui/widget.php @@ -0,0 +1,45 @@ + +
+ + + +
+ +
diff --git a/redis-cache/languages/redis-cache.pot b/redis-cache/languages/redis-cache.pot new file mode 100644 index 0000000..ee01ff2 --- /dev/null +++ b/redis-cache/languages/redis-cache.pot @@ -0,0 +1,590 @@ +# Copyright (C) 2023 Till Krüss +# This file is distributed under the GPLv3. +msgid "" +msgstr "" +"Project-Id-Version: Redis Object Cache 2.4.4\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/redis-cache\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2023-08-11T20:06:44+00:00\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"X-Generator: WP-CLI 2.8.1\n" +"X-Domain: redis-cache\n" + +#. Plugin Name of the plugin +#: includes/class-plugin.php:151 +#: includes/class-plugin.php:223 +#: includes/ui/settings.php:19 +msgid "Redis Object Cache" +msgstr "" + +#. Plugin URI of the plugin +msgid "https://wordpress.org/plugins/redis-cache/" +msgstr "" + +#. Description of the plugin +msgid "A persistent object cache backend powered by Redis. Supports Predis, PhpRedis, Relay, replication, sentinels, clustering and WP-CLI." +msgstr "" + +#. Author of the plugin +msgid "Till Krüss" +msgstr "" + +#. Author URI of the plugin +msgid "https://objectcache.pro" +msgstr "" + +#: includes/class-plugin.php:152 +msgid "Redis" +msgstr "" + +#: includes/class-plugin.php:188 +msgid "Overview" +msgstr "" + +#: includes/class-plugin.php:194 +msgid "Metrics" +msgstr "" + +#: includes/class-plugin.php:200 +msgid "Diagnostics" +msgstr "" + +#: includes/class-plugin.php:251 +#: includes/class-plugin.php:767 +#: includes/ui/widget.php:38 +msgid "Settings" +msgstr "" + +#: includes/class-plugin.php:291 +msgctxt "verb" +msgid "Upgrade to Pro" +msgstr "" + +#: includes/class-plugin.php:386 +#: includes/ui/tabs/metrics.php:17 +#: includes/ui/widget.php:18 +msgid "Time" +msgstr "" + +#: includes/class-plugin.php:387 +#: includes/ui/tabs/metrics.php:22 +#: includes/ui/widget.php:23 +msgid "Bytes" +msgstr "" + +#: includes/class-plugin.php:388 +#: includes/ui/tabs/metrics.php:27 +#: includes/ui/widget.php:28 +msgid "Ratio" +msgstr "" + +#: includes/class-plugin.php:389 +#: includes/ui/tabs/metrics.php:32 +#: includes/ui/widget.php:33 +msgid "Calls" +msgstr "" + +#: includes/class-plugin.php:390 +msgid "Not enough data collected, yet." +msgstr "" + +#: includes/class-plugin.php:391 +msgid "Enable object cache to collect data." +msgstr "" + +#: includes/class-plugin.php:537 +#: includes/class-qm-collector.php:77 +msgid "Disabled" +msgstr "" + +#: includes/class-plugin.php:541 +msgid "Not enabled" +msgstr "" + +#: includes/class-plugin.php:545 +msgid "Drop-in is outdated" +msgstr "" + +#: includes/class-plugin.php:549 +msgid "Drop-in is invalid" +msgstr "" + +#: includes/class-plugin.php:554 +msgid "Connected" +msgstr "" + +#: includes/class-plugin.php:555 +msgid "Not connected" +msgstr "" + +#: includes/class-plugin.php:558 +#: includes/ui/tabs/overview.php:258 +msgid "Unknown" +msgstr "" + +#. translators: %s = Action link to update the drop-in. +#: includes/class-plugin.php:704 +msgid "The Redis object cache drop-in is outdated. Please update the drop-in." +msgstr "" + +#. translators: %s = Link to settings page. +#: includes/class-plugin.php:711 +msgid "A foreign object cache drop-in was found. To use Redis for object caching, please enable the drop-in." +msgstr "" + +#: includes/class-plugin.php:738 +#: includes/class-qm-collector.php:34 +#: includes/class-qm-output.php:37 +msgid "Object Cache" +msgstr "" + +#: includes/class-plugin.php:759 +#: includes/ui/tabs/overview.php:270 +msgid "Flush Cache" +msgstr "" + +#. translators: %s = The status of the Redis connection. +#: includes/class-plugin.php:783 +msgid "Status: %s" +msgstr "" + +#. translators: 1: Hit ratio, 2: Hits, 3: Misses. 4: Human-readable size of cache. +#: includes/class-plugin.php:803 +msgid "(Current page) Hit Ratio: %1$s%%, Hits %2$s, Misses: %3$s, Size: %4$s" +msgstr "" + +#: includes/class-plugin.php:852 +msgid "Flushing cache..." +msgstr "" + +#: includes/class-plugin.php:928 +msgid "Object cache flushed." +msgstr "" + +#: includes/class-plugin.php:934 +msgid "Object cache could not be flushed." +msgstr "" + +#: includes/class-plugin.php:972 +#: includes/cli/class-commands.php:80 +msgid "Object cache enabled." +msgstr "" + +#: includes/class-plugin.php:978 +#: includes/cli/class-commands.php:82 +msgid "Object cache could not be enabled." +msgstr "" + +#: includes/class-plugin.php:1008 +#: includes/cli/class-commands.php:121 +msgid "Object cache disabled." +msgstr "" + +#: includes/class-plugin.php:1014 +#: includes/cli/class-commands.php:123 +msgid "Object cache could not be disabled." +msgstr "" + +#: includes/class-plugin.php:1039 +#: includes/cli/class-commands.php:162 +msgid "Updated object cache drop-in and enabled Redis object cache." +msgstr "" + +#: includes/class-plugin.php:1045 +#: includes/cli/class-commands.php:164 +msgid "Object cache drop-in could not be updated." +msgstr "" + +#. translators: %s = Action link to update the drop-in. +#: includes/class-plugin.php:1120 +msgid "The Object Cache Pro plugin appears to be installed and should be used. You can safely uninstall Redis Object Cache." +msgstr "" + +#: includes/class-plugin.php:1152 +msgid "Object Cache Pro!" +msgstr "" + +#. translators: %s = Link to the plugin setting screen. +#: includes/class-plugin.php:1155 +msgid "A business class object cache backend. Truly reliable, highly-optimized and fully customizable, with a dedicated engineer when you most need it. Learn more »" +msgstr "" + +#: includes/class-plugin.php:1200 +msgid "Object Cache Pro + WooCommerce = ❤️" +msgstr "" + +#. translators: %s = Link to the plugin's settings screen. +#: includes/class-plugin.php:1203 +msgid "Object Cache Pro is a business class object cache that’s highly-optimized for WooCommerce to provide true reliability, peace of mind and faster load times for your store. Learn more »" +msgstr "" + +#. translators: %1$d = number of objects. %2$s = human-readable size of cache. %3$s = name of the used client. +#: includes/class-plugin.php:1273 +msgid "Retrieved %1$d objects (%2$s) from Redis using %3$s." +msgstr "" + +#: includes/class-plugin.php:1376 +msgid "Could not initialize filesystem." +msgstr "" + +#: includes/class-plugin.php:1383 +msgid "Object cache file doesn’t exist." +msgstr "" + +#: includes/class-plugin.php:1388 +msgid "Test file exists, but couldn’t be deleted." +msgstr "" + +#: includes/class-plugin.php:1393 +msgid "Content directory is not writable." +msgstr "" + +#: includes/class-plugin.php:1397 +msgid "Failed to copy test file." +msgstr "" + +#: includes/class-plugin.php:1401 +msgid "Copied test file doesn’t exist." +msgstr "" + +#: includes/class-plugin.php:1407 +msgid "Couldn’t verify test file contents." +msgstr "" + +#: includes/class-plugin.php:1411 +msgid "Copied test file couldn’t be deleted." +msgstr "" + +#: includes/class-qm-collector.php:77 +msgid "Yes" +msgstr "" + +#: includes/class-qm-output.php:102 +msgid "The Redis Object Cache drop-in is not installed. Use WP CLI or go to \"Settings -> Redis\" to enable drop-in." +msgstr "" + +#: includes/class-qm-output.php:113 +msgid "WordPress is using a foreign object cache drop-in and Redis Object Cache is not being used. Use WP CLI or go to \"Settings -> Redis\" to enable drop-in." +msgstr "" + +#: includes/cli/class-commands.php:58 +msgid "Redis object cache already enabled." +msgstr "" + +#: includes/cli/class-commands.php:60 +#: includes/cli/class-commands.php:112 +msgid "A foreign object cache drop-in was found. To use Redis for object caching, run: `wp redis update-dropin`." +msgstr "" + +#. translators: %s = The Redis connection error message. +#: includes/cli/class-commands.php:67 +msgid "Object cache could not be enabled. Redis server is unreachable: %s" +msgstr "" + +#: includes/cli/class-commands.php:106 +msgid "No object cache drop-in found." +msgstr "" + +#. translators: %s = The Redis connection error message. +#: includes/cli/class-commands.php:159 +msgid "Object cache drop-in could not be updated. Redis server is unreachable: %s" +msgstr "" + +#: includes/object-cache.php:2916 +msgid "Error establishing a Redis connection" +msgstr "" + +#. translators: %s = Formatted wp-config.php file name. +#: includes/object-cache.php:2923 +msgid "WordPress is unable to establish a connection to Redis. This means that the connection information in your %s file are incorrect, or that the Redis server is not reachable." +msgstr "" + +#: includes/object-cache.php:2928 +msgid "Is the correct Redis host and port set?" +msgstr "" + +#: includes/object-cache.php:2929 +msgid "Is the Redis server running?" +msgstr "" + +#. translators: %s = Link to installation instructions. +#: includes/object-cache.php:2934 +msgid "If you need help, please read the installation instructions." +msgstr "" + +#. translators: %1$s = Formatted object-cache.php file name, %2$s = Formatted wp-content directory name. +#: includes/object-cache.php:2941 +msgid "To disable Redis, delete the %1$s file in the %2$s directory." +msgstr "" + +#. translators: %s = Tab label. +#: includes/ui/class-tab.php:154 +msgid "%s are disabled for this site." +msgstr "" + +#: includes/ui/query-monitor.php:20 +msgid "Status" +msgstr "" + +#: includes/ui/query-monitor.php:25 +msgid "Hit Ratio" +msgstr "" + +#: includes/ui/query-monitor.php:30 +msgid "Hits" +msgstr "" + +#: includes/ui/query-monitor.php:35 +msgid "Misses" +msgstr "" + +#: includes/ui/query-monitor.php:40 +msgid "Size" +msgstr "" + +#: includes/ui/query-monitor.php:50 +msgid "Errors" +msgstr "" + +#: includes/ui/query-monitor.php:73 +msgid "Global Groups" +msgstr "" + +#: includes/ui/query-monitor.php:87 +msgid "Non-persistent Groups" +msgstr "" + +#: includes/ui/query-monitor.php:101 +msgid "Unflushable Groups" +msgstr "" + +#: includes/ui/query-monitor.php:115 +msgid "Metadata" +msgstr "" + +#: includes/ui/settings.php:71 +msgid "Resources" +msgstr "" + +#: includes/ui/settings.php:79 +msgid "Need more performance and reliability?" +msgstr "" + +#. translators: %s = Object Cache Pro. +#: includes/ui/settings.php:83 +msgid "Check out %s" +msgstr "" + +#: includes/ui/settings.php:87 +msgid "A business class object cache backend. Truly reliable, highly-optimized and fully customizable, with a dedicated engineer when you most need it." +msgstr "" + +#: includes/ui/settings.php:90 +msgid "Rewritten for raw performance" +msgstr "" + +#: includes/ui/settings.php:91 +msgid "100% WordPress API compliant" +msgstr "" + +#: includes/ui/settings.php:92 +msgid "Faster serialization and compression" +msgstr "" + +#: includes/ui/settings.php:93 +msgid "Easy debugging & logging" +msgstr "" + +#: includes/ui/settings.php:94 +msgid "Cache prefetching and analytics" +msgstr "" + +#: includes/ui/settings.php:95 +msgid "Fully unit tested (100% code coverage)" +msgstr "" + +#: includes/ui/settings.php:96 +msgid "Secure connections with TLS" +msgstr "" + +#: includes/ui/settings.php:97 +msgid "Health checks via WordPress & WP CLI" +msgstr "" + +#: includes/ui/settings.php:98 +msgid "Optimized for WooCommerce, Jetpack & Yoast SEO" +msgstr "" + +#: includes/ui/settings.php:102 +msgid "Learn more" +msgstr "" + +#: includes/ui/settings.php:116 +msgid "Your site meets the system requirements for the Pro version." +msgstr "" + +#: includes/ui/settings.php:123 +msgid "Your site does not meet the requirements for the Pro version:" +msgstr "" + +#. translators: %s = PHP Version. +#: includes/ui/settings.php:132 +msgid "The current version of PHP (%s) is too old. PHP 7.2 or newer is required." +msgstr "" + +#: includes/ui/settings.php:141 +msgid "The PhpRedis extension is not installed." +msgstr "" + +#. translators: %s = Version of the PhpRedis extension. +#: includes/ui/settings.php:148 +msgid "The current version of the PhpRedis extension (%s) is too old. PhpRedis 3.1.1 or newer is required." +msgstr "" + +#: includes/ui/tabs/diagnostics.php:19 +msgid "Copy diagnostics to clipboard" +msgstr "" + +#: includes/ui/tabs/metrics.php:16 +#: includes/ui/widget.php:17 +msgid "The total amount of time (in milliseconds) it took Redis to return cache data." +msgstr "" + +#: includes/ui/tabs/metrics.php:21 +#: includes/ui/widget.php:22 +msgid "The total amount of bytes that was retrieved from Redis." +msgstr "" + +#: includes/ui/tabs/metrics.php:26 +#: includes/ui/widget.php:27 +msgid "The hit/miss ratio of cache data that was already cached." +msgstr "" + +#: includes/ui/tabs/metrics.php:31 +#: includes/ui/widget.php:32 +msgid "The total amount of commands sent to Redis." +msgstr "" + +#: includes/ui/tabs/overview.php:26 +msgid "Redis is unreachable:" +msgstr "" + +#: includes/ui/tabs/overview.php:35 +msgid "Status:" +msgstr "" + +#: includes/ui/tabs/overview.php:52 +msgid "Filesystem:" +msgstr "" + +#: includes/ui/tabs/overview.php:57 +msgid "Not writeable" +msgstr "" + +#: includes/ui/tabs/overview.php:62 +msgid "Writeable" +msgstr "" + +#: includes/ui/tabs/overview.php:69 +msgid "Redis:" +msgstr "" + +#: includes/ui/tabs/overview.php:74 +msgid "Reachable" +msgstr "" + +#: includes/ui/tabs/overview.php:79 +msgid "Unreachable" +msgstr "" + +#: includes/ui/tabs/overview.php:87 +msgid "Key Prefix:" +msgstr "" + +#: includes/ui/tabs/overview.php:96 +msgid "Max. TTL:" +msgstr "" + +#: includes/ui/tabs/overview.php:102 +msgid "This doesn’t appear to be a valid number." +msgstr "" + +#: includes/ui/tabs/overview.php:114 +msgid "Connection" +msgstr "" + +#: includes/ui/tabs/overview.php:121 +msgid "Client:" +msgstr "" + +#: includes/ui/tabs/overview.php:130 +msgid "Host:" +msgstr "" + +#: includes/ui/tabs/overview.php:137 +msgid "Cluster:" +msgstr "" + +#: includes/ui/tabs/overview.php:150 +msgid "Shards:" +msgstr "" + +#: includes/ui/tabs/overview.php:163 +msgid "Servers:" +msgstr "" + +#: includes/ui/tabs/overview.php:176 +msgid "Port:" +msgstr "" + +#: includes/ui/tabs/overview.php:183 +msgid "Username:" +msgstr "" + +#: includes/ui/tabs/overview.php:190 +msgid "Password:" +msgstr "" + +#: includes/ui/tabs/overview.php:199 +msgid "Database:" +msgstr "" + +#: includes/ui/tabs/overview.php:206 +msgid "Connection Timeout:" +msgstr "" + +#. translators: %s = Redis connection/read timeout in seconds. +#: includes/ui/tabs/overview.php:212 +#: includes/ui/tabs/overview.php:229 +msgid "%ss" +msgstr "" + +#: includes/ui/tabs/overview.php:223 +msgid "Read Timeout:" +msgstr "" + +#: includes/ui/tabs/overview.php:240 +msgid "Retry Interval:" +msgstr "" + +#. translators: %s = Redis retry interval in milliseconds. +#: includes/ui/tabs/overview.php:246 +msgid "%sms" +msgstr "" + +#: includes/ui/tabs/overview.php:257 +msgid "Redis Version:" +msgstr "" + +#: includes/ui/tabs/overview.php:276 +msgid "Disable Object Cache" +msgstr "" + +#: includes/ui/tabs/overview.php:281 +#: includes/ui/tabs/overview.php:285 +msgid "Enable Object Cache" +msgstr "" diff --git a/redis-cache/readme.txt b/redis-cache/readme.txt new file mode 100644 index 0000000..61d7209 --- /dev/null +++ b/redis-cache/readme.txt @@ -0,0 +1,666 @@ +=== Redis Object Cache === +Contributors: tillkruess, a5hleyrich +Donate link: https://github.com/sponsors/tillkruss +Tags: redis, object cache, cache, object caching, caching performance, relay, predis, phpredis +Requires at least: 3.3 +Tested up to: 6.3 +Requires PHP: 7.2 +Stable tag: 2.4.4 +License: GPLv3 +License URI: https://www.gnu.org/licenses/gpl-3.0.html + +A persistent object cache backend powered by Redis®¹. Supports Predis, PhpRedis, Relay, replication, sentinels, clustering and WP-CLI. + + +== Description == + +A persistent object cache backend powered by Redis®¹. Supports [Predis](https://github.com/predis/predis/), [PhpRedis (PECL)](https://github.com/phpredis/phpredis), [Relay](https://relaycache.com), replication, sentinels, clustering and [WP-CLI](https://wp-cli.org/). + +To adjust the connection parameters, prefix cache keys or configure replication/clustering, see the [configuration options](https://github.com/rhubarbgroup/redis-cache/#configuration). + += Object Cache Pro = + +A **business class** Redis®¹ object cache backend. Truly reliable, highly optimized, fully customizable and with a dedicated engineer when you most need it. + +* Rewritten for raw performance +* 100% WordPress API compliant +* Faster serialization and compression +* Easy debugging & logging +* Cache prefetching and analytics +* Fully unit tested (100% code coverage) +* Secure connections with TLS +* Health checks via WordPress & WP CLI +* Optimized for WooCommerce, Jetpack & Yoast SEO + +Learn more about [Object Cache Pro](https://objectcache.pro/?ref=oss&utm_source=wp-plugin&utm_medium=readme). + +¹ Redis is a registered trademark of Redis Ltd. Any rights therein are reserved to Redis Ltd. Any use by Redis Object Cache is for referential purposes only and does not indicate any sponsorship, endorsement or affiliation between Redis and Redis Object Cache. + +== Installation == + +For detailed installation instructions, please read the extensive [installation instructions](https://github.com/rhubarbgroup/redis-cache/blob/develop/INSTALL.md). + +== Troubleshooting == + +Answers to common questions and troubleshooting of common errors can be found in the [FAQ](https://github.com/rhubarbgroup/redis-cache/blob/develop/FAQ.md). Reading these is always faster than waiting for a response in the support forums. + +== Configuration == + +The plugin comes with vast set of [configuration options](https://github.com/rhubarbgroup/redis-cache/#configuration) and [connection examples](https://github.com/rhubarbgroup/redis-cache/#connections). Advanced users may consult [Scaling and replication](https://github.com/rhubarbgroup/redis-cache/blob/develop/README.md#scaling) + +== WP CLI commands == + +Redis Object Cache has various WP CLI commands, for more information run `wp help redis` after installing the plugin. + +== Screenshots == + +1. Plugin settings, connected to a single Redis server. +2. Plugin settings, displaying recent response time metrics. +3. Plugin settings, showing diagnostic information. +4. Dashboard widget, displaying recent response time metrics. + + +== Changelog == + += 2.4.4 = + +- Improved handling of unexpected transaction results + += 2.4.3 = + +- Fixed PHP `<=7.2` syntax error +- Fixed loading error-page translations + += 2.4.2 = + +- Made admin-bar script more robust +- Improved recovery instructions on `redis-error.php` +- Improved CloudLinux detection +- Localization improvements +- Prevent W3 Total Cache and LiteSpeed Cache from overwriting drop-in +- Fixed ACL username support when using Predis and array syntax + += 2.4.1 = + +- Fix PHP `<=7.2` syntax error + += 2.4.0 = + +- Flush cache when toggling the object cache +- Show a custom error message when Redis is unreachable +- Don't allow object cache to be enabled when Redis is unreachable +- Deprecated risky `WP_REDIS_SERIALIZER` configuration constant, use `WP_REDIS_IGBINARY` instead +- Support `WP_REDIS_USERNAME` when using Predis +- Show cache hit ratio decimal points in Admin Bar node +- Obscure secrets when displaying `WP_REDIS_SERVERS` +- Improved CloudLinux's Accelerate WP compatibility +- Admin bar cache flush now uses AJAX + += 2.3.0 = + +- Show dashboard widget only to admins +- Added Admin Bar node (disable using `WP_REDIS_DISABLE_ADMINBAR`) +- Added `WP_REDIS_SSL_CONTEXT` configuration constant +- Throw errors when connection error occurs +- Added support for usernames when using Predis +- Added support for loading Predis from `WP_REDIS_PLUGIN_PATH` +- Made Predis unix socket connections stricter +- Fixed rare group flushing bug +- Fixed cluster ping when using Predis +- Updated Predis to v2.1.2 +- Improved documentation + += 2.2.4 = + +- Register `wp redis` CLI command late +- Don't compete with Object Cache Pro for `wp redis` command +- Prevent Perflab from overwriting the object cache drop-in +- Updated Predis to v2.1.1 +- Avoid type error when transaction fails +- Check for incompatible content type headers + += 2.2.3 = + +- Added `wp_cache_flush_group()` support +- Updated Credis to v1.14.0 +- Drop `$delay` parameter from `wp_cache_flush()` +- Prevent rare error in diagnostics when reading connection errors + += 2.2.2 = + +- Use `QM_Data_Cache` instead of `QM_Data` +- Fixed `WP_Error` use statement non-compound name warning + += 2.2.1 = + +- Added WordPress 6.1 `wp_cache_supports()` function +- Updated Predis to v2.0.3 +- Avoid early `microtime()` calls in `WP_Object_Cache::get()` +- Support Query Monitor's new `QM_Data` class +- Throw exception of pipeline returns unexpected results + += 2.2.0 = + +- Added `redis_cache_add_non_persistent_groups` filter +- Fixed `wp_add_dashboard_widget` parameters +- Fixed `WP_REDIS_SERVERS` replication issue with Predis v2.0 +- Fixed `WP_REDIS_CLUSTER` string support +- Fixed issue when `MGET` fails in `get_multiple()` call +- Fixed several warnings in the event of pipeline failures + += 2.1.6 = + +- Fixed SVN discrepancies + += 2.1.5 = + +- Fixed `is_predis()` call + += 2.1.4 = + +- Added `is_predis()` helper + += 2.1.3 = + +- Fixed bug in `wp_cache_add_multiple()` and `wp_cache_set_multiple()` + += 2.1.2 = + +- Fixed and improved `wp_cache_*_multiple()` logic +- Call `redis_object_cache_set` action in `wp_cache_set_multiple()` +- Call `redis_object_cache_delete` action in `wp_cache_delete_multiple()` +- Check if raw group name is ignored, not sanitized name +- Removed tracing + += 2.1.1 = + +- Bumped PHP requirement to 7.2 +- Renamed `WP_REDIS_DIR` to `WP_REDIS_PLUGIN_DIR` +- Fixed rare fatal error in diagnostics +- Allow Predis v1.1 Composer installs +- Support using `WP_REDIS_CLUSTER` string + += 2.1.0 = + +- Bumped PHP requirement to 7.0 +- Deprecated Credis and HHVM clients +- Updated Predis to v2.0.0 +- Updated Credis to v1.13.1 +- Improved cluster readability in diagnostics +- Improved connecting to clusters +- Fixed pinging clusters after connecting +- Fixed several bugs in `connect_using_credis()` + += 2.0.26 = + +- Fixed a bug in `wp_cache_delete_multiple()` when using Predis +- Fixed a bug in `wp_cache_add_multiple()` when cache addition is suspended + += 2.0.25 = + +- Removed broken `wp_cache_add_multiple()` function + += 2.0.24 = + +- Improve metrics label/tooltip formatting +- Fix metrics chart not rendering +- Updated Predis to v1.1.10 +- Updated Credis to v1.13.0 +- Support `composer/installers` v1 and v2 +- Link to settings page when foreign drop-in was found +- Added `wp_cache_flush_runtime()` function +- Added `wp_cache_add_multiple()` function +- Added `wp_cache_delete_multiple()` function + += 2.0.23 = + +- Added support for [Relay](https://relaycache.com) +- Minor UX fixes and improvements +- Fixed PHP 8.1 deprecation notice +- Updated ApexCharts to v3.31.0 + += 2.0.22 = + +- PHP 8.1 compatibility fixes +- Upgraded to Predis v1.1.9 +- Added settings link to widget +- Overhauled diagnostics pane +- Updated ApexCharts to v3.30.0 +- Redirect to plugin settings after activation +- Fixed wrong path to `diagnostics.php` file +- Fixed chart overflow in settings tab +- Fixed Predis cluster ping +- Avoid warning when content folder is not writeable + += 2.0.21 = + +- Added metrics diagnostics +- Added `WP_Object_Cache::decr()` alias +- Moved `diagnostics.php` file + += 2.0.20 = + +- Fix wp.org release + += 2.0.19 = + +- Make metric identifier unique +- Set unique prefix for sites hosted on Cloudways +- Don't print HTML debug comment when `WP_CLI` is `true` + += 2.0.18 = + +- Added `redis_object_cache_trace` action and `WP_REDIS_TRACE` constant +- Updated ApexCharts to v3.26.0 +- Fixed and issue with `WP_REDIS_DISABLE_METRICS` + += 2.0.17 = + +- Code cleanup +- Fixed missing metrics +- Fixed filesystem test + += 2.0.16 = + +- Updated Credis to v1.11.4 +- Fixed drop-in notice styling +- Moved metrics into dedicated class +- Added `redis_cache_validate_dropin` filter +- Use `WP_DEBUG_DISPLAY` (instead of `WP_DEBUG`) constant to display debug information +- Fixed rare error in `wp_cache_get_multiple()` +- Removed `intval()` usage + += 2.0.15 = + +- Reverted `build_key()` changes due to issues in multisite environments + += 2.0.14 = + +- Made Object Cache Pro card translatable +- Added `WP_REDIS_SERIALIZER` to diagnostics +- Improved speed of `build_key()` +- Support settings `WP_REDIS_PREFIX` and `WP_REDIS_SELECTIVE_FLUSH` via environment variable +- Added `WP_REDIS_METRICS_MAX_TIME` to adjust stored metrics timeframe +- Delay loading of text domain and schedule until `init` hook +- Upgraded bundled Predis library to v1.1.6 +- Prevent variable referencing issue in `connect_using_credis()` + += 2.0.13 = + +- Updated bundled Predis library to v1.1.4 +- Made `redis-cache` a global group for improved metrics on multisite +- Switched to short array syntax +- Added `@since` tags to all hooks +- Use `parse_url()` instead of `wp_parse_url()` in drop-in +- Fixed plugin instance variable name in `wp redis status` + += 2.0.12 = + +- Fixed bytes metrics calculation +- Fixed an issue with non-standard Predis configurations +- Improve WordPress Coding Standards + += 2.0.11 = + +- Fixed an issue in `wp_cache_get_multiple()` when using Predis +- Prevent undefined index notice in diagnostics + += 2.0.10 = + +- Fixed unserializing values in `wp_cache_get_multiple()` + += 2.0.9 = + +- Highlight current metric type using color +- Show "Metrics" tab when metrics are disabled +- Refactored connection and Redis status logic +- Updated Predis to v1.1.2 +- Remove Predis deprecation notice +- Fixed fetching derived keys in `wp_cache_get_multiple()` + += 2.0.8 = + +- Fixed tabs not working in 2.0.6 and 2.0.7 due to WP.org SVN issue + += 2.0.7 = + +- Fixed issue with `wp_cache_get_multiple()` + += 2.0.6 = + +- Added experimental filesystem test to diagnostics +- Refactored settings tab logic (fixed jumping, too) +- Fixed issues with `wp_cache_get_multiple()` +- Return boolean from `wp_cache_delete()` +- Use `redis-cache` as JS event namespace +- Hide Pro line in widget when banners are disabled +- Renamed `redis_object_cache_get_multi` action to `redis_object_cache_get_multiple` + += 2.0.5 = + +Version 2.0 is a significant rewrite of the plugin. Please read the v2.0.0 release notes. + +- Fixed multisite action buttons not working +- Removed outdated PHP 5.4 warning +- Added `read_timeout` support to Credis +- Display connection parameters when using Credis +- Added wiki link to Predis upgrade notice + += 2.0.4 = + +- Attempt to reliably update the dropin when it's outdated +- Show ACL username on settings screen +- Show full diagnostics with `wp redis status` +- Always set `FS_CHMOD_FILE` when copying the `object-cache.php` +- Don't encode bullets in password diagnostics +- Call `redis_object_cache_update_dropin` during dropin update + += 2.0.3 = + +- Hide "Metrics" tab when metrics are disabled +- Fixed `admin.js` not loading in multisite environments +- Avoid fatal error when interacting with metrics but Redis went away +- Added `WP_Object_Cache::__get()` for backwards compatibility + += 2.0.2 = + +- Updated POT file and comments for translators + += 2.0.1 = + +- Support older versions of Query Monitor +- Made "Dropin" status more helpful +- Hide Redis version in settings when it isn't available +- Collapsed dependency paths using `composer-custom-directory-installer` package +- Prevent `QM_Collector` conflicts with other plugins +- Prevent metric issues when cache is not available +- Fixed "Settings" link in plugin list +- Fixed `WP_REDIS_DISABLED` logic + += 2.0.0 = + +Version 2.0 is a significant rewrite. The plugin now requires PHP 5.6, just like WordPress 5.2 does. + +The GitHub and Composer repository was moved from `tillkruss/redis-cache` to `rhubarbgroup/redis-cache`. + +On multisite networks, be sure to "Network Activate" the plugin after upgrading to v2.x. + +- Require PHP 5.6 +- Plugin is now "network-only" +- Switch to WPCS for code standards +- Overhauled the settings screen +- Added object cache metrics (on dashboard widget and settings) +- Added support for Query Monitor +- Added `Rhubarb\RedisCache` namespace to all files +- Added support for WP 5.5's new `wp_cache_get_multi()` function +- Added `redis_object_cache()` function to retrieve plugin instance +- Added dropin warnings to network dashboard +- Added support for setting Sentinel database numbers +- Support Redis 6 ACL username and password authentication +- Support overwriting existing dropin on setting screen +- Use singleton pattern to instantiate plugin +- Use Composer to install and load Predis +- Update object cache dropin during plugin update +- Use separate methods to connect with all clients +- Removed `CUSTOM_USER_TABLE` and `CUSTOM_USER_META_TABLE` weirdness +- Added `themes` as ignored group +- Changed default connection and read timeout to 1 second +- Prevent race condition in `add_or_replace()` +- Renamed `WP_CACHE_KEY_SALT` to `WP_REDIS_PREFIX` for clarity +- Replaced "slave" terminology with "replica" +- Only `SELECT` database when it's not `0` + += 1.6.1 = + +- Fixed issue with footer comment showing during AJAX requests + += 1.6.0 = + +- Improved group name sanitization (thanks @naxvog) +- Prevent fatal error when replacing foreign dropin +- Added HTML footer comment with optional debug information +- Removed prefix suggestions + +_The HTML footer comment only prints debug information when `WP_DEBUG` is enabled. To disable the comment entirely, set the `WP_REDIS_DISABLE_COMMENT` constant to `true`._ + += 1.5.9 = + +- Fixed missing `$info` variable assignment in constructor +- Fixed MaxTTL warning condition +- Switched to using default button styles + += 1.5.8 = + +- Added warning message about invalid MaxTTL +- Added warning about unmaintained Predis library +- Added suggestion about shorter, human-readable prefixes +- Added Redis Cache Pro compatibility to settings +- Fixed flushing the cache when the prefix contains special characters +- Fixed calling Redis `INFO` when using clusters +- Cleaned up the settings a little bit + += 1.5.7 = + +- Added support for PhpRedis TLS connections +- Added support for timeout, read timeout and password when using PhpRedis cluster +- Fixed issue with `INFO` command +- Fixed object cloning when setting cache keys + += 1.5.6 = + +- Added object cloning to in-memory cache +- Fixed PHP notice related to `read_timeout` parameter + += 1.5.5 = + +Please flush the object cache after updating the drop to v1.5.5 to avoid dead keys filling up Redis memory. + +- Removed lowercasing keys +- Remove scheduled metrics event +- Fixed Redis version call when using replication + += 1.5.4 = + +- Removed metrics + += 1.5.3 = + +- Fixed: Call to undefined function `get_plugin_data()` +- Fixed: Call to undefined method `WP_Object_Cache::redis_version()` + += 1.5.2 = + +- Added Redis version to diagnostics +- Added `WP_REDIS_DISABLE_BANNERS` constant to disable promotions +- Fixed an issue with `redis.replicate_commands()` + += 1.5.1 = + +This plugin turned 5 years today (Nov 14th) and its only fitting to release the business edition today as well. +[Object Cache Pro](https://objectcache.pro/) is a truly reliable, highly optimized and easy to debug rewrite of this plugin for SMBs. + +- Added execution times to actions +- Added `WP_REDIS_VERSION` constant +- Fixed PhpRedis v3 compatibility +- Fixed an issue with selective flushing +- Fixed an issue with `mb_*` functions not existing +- Replaced Email Address Encoder card with Redis Cache Pro card +- Gather version metrics for better decision making + += 1.5.0 = + +Since Predis isn't maintained any longer, it's highly recommended to switch over to PhpRedis (the Redis PECL extension). + +- Improved Redis key name builder +- Added support for PhpRedis serializers +- Added `redis_object_cache_error` action +- Added timeout, read-timeout and retry configuration +- Added unflushable groups (defaults to `['userlogins']`) +- Fixed passwords not showing in server list + += 1.4.3 = + +- Require PHP 5.4 or newer +- Use pretty print in diagnostics +- Throw exception if Redis library is missing +- Fixed cache not flushing for some users +- Fixed admin issues when `WP_REDIS_DISABLED` is `false` + += 1.4.2 = + +- Added graceful Redis failures and `WP_REDIS_GRACEFUL` constant +- Improved cluster support +- Added `redis_cache_expiration` filter +- Renamed `redis_object_cache_get` filter to `redis_object_cache_get_value` + += 1.4.1 = + +- Fixed potential fatal error related to `wp_suspend_cache_addition()` + += 1.4.0 = + +- Added support for igbinary +- Added support for `wp_suspend_cache_addition()` + += 1.3.9 = + +- Fixed `WP_REDIS_SHARDS` not showing up in server list +- Fixed `WP_REDIS_SHARDS` not working when using PECL extension +- Removed `WP_REDIS_SCHEME` and `WP_REDIS_PATH` leftovers + += 1.3.8 = + +- Switched from single file Predis version to full library + += 1.3.7 = + +- Revert back to single file Predis version + += 1.3.6 = + +- Added support for Redis Sentinel +- Added support for sharing +- Switched to PHAR version of Predis +- Improved diagnostics +- Added `WP_REDIS_SELECTIVE_FLUSH` +- Added `$fail_gracefully` parameter to `WP_Object_Cache::__construct()` +- Always enforce `WP_REDIS_MAXTTL` +- Pass `$selective` and `$salt` to `redis_object_cache_flush` action +- Don’t set `WP_CACHE_KEY_SALT` constant + += 1.3.5 = + +- Added basic diagnostics to admin interface +- Added `WP_REDIS_DISABLED` constant to disable cache at runtime +- Prevent "Invalid plugin header" error +- Return integer from `increment()` and `decrement()` methods +- Prevent object cache from being instantiated more than once +- Always separate cache key `prefix` and `group` by semicolon +- Improved performance of `build_key()` +- Only apply `redis_object_cache_get` filter if callbacks have been registered +- Fixed `add_or_replace()` to only set cache key if it doesn't exist +- Added `redis_object_cache_flush` action +- Added `redis_object_cache_enable` action +- Added `redis_object_cache_disable` action +- Added `redis_object_cache_update_dropin` action + += 1.3.4 = + +- Added WP-CLI support +- Show host and port unless scheme is unix +- Updated default global and ignored groups +- Do a cache flush when activating, deactivating and uninstalling + += 1.3.3 = + +- Updated Predis to `v1.1.1` +- Added `redis_instance()` method +- Added `incr()` method alias for Batcache compatibility +- Added `WP_REDIS_GLOBAL_GROUPS` and `WP_REDIS_IGNORED_GROUPS` constant +- Added `redis_object_cache_delete` action +- Use `WP_PLUGIN_DIR` with `WP_CONTENT_DIR` as fallback +- Set password when using a cluster or replication +- Show Redis client in `stats()` +- Change visibility of `$cache` to public +- Use old array syntax, just in case + += 1.3.2 = + +- Make sure `$result` is not `false` in `WP_Object_Cache::get()` + += 1.3.1 = + +- Fixed connection issue + += 1.3 = + +- New admin interface +- Added support for `wp_cache_get()`'s `$force` and `$found` parameter +- Added support for clustering and replication with Predis + += 1.2.3 = + +- UI improvements + += 1.2.2 = + +- Added `redis_object_cache_set` action +- Added `redis_object_cache_get` action and filter +- Prevented duplicated admin status messages +- Load bundled Predis library only if necessary +- Load bundled Predis library using `WP_CONTENT_DIR` constant +- Updated `stats()` method output to be uniform with WordPress + += 1.2.1 = + +- Added `composer.json` +- Added deactivation and uninstall hooks to delete `object-cache.php` +- Added local serialization functions for better `advanced-cache.php` support +- Updated bundled Predis version to `1.0.3` +- Updated heading structure to be semantic + += 1.2 = + +- Added Multisite support +- Moved admin menu under _Settings_ menu +- Fixed PHP notice in `get_redis_client_name()` + += 1.1.1 = + +- Call `select()` and optionally `auth()` if HHVM extension is used + += 1.1 = + +- Added support for HHVM's Redis extension +- Added support for PECL Redis extension +- Added `WP_REDIS_CLIENT` constant, to set preferred Redis client +- Added `WP_REDIS_MAXTTL` constant, to force expiration of cache keys +- Improved `add_or_replace()`, `get()`, `set()` and `delete()` methods +- Improved admin screen styles +- Removed all internationalization/localization from drop-in + += 1.0.2 = + +- Added "Flush Cache" button +- Added support for UNIX domain sockets +- Improved cache object retrieval performance significantly +- Updated bundled Predis library to version `1.0.1` + += 1.0.1 = + +- Load plugin translations +- Hide global admin notices from non-admin users +- Prevent direct file access to `redis-cache.php` and `admin-page.php` +- Colorize "Disable Object Cache" button +- Call `Predis\Client->connect()` to avoid potential uncaught `Predis\Connection\ConnectionException` + += 1.0 = + +- Initial release + + +== Upgrade Notice == + += 2.4.0 = + +Version 2.4.0 includes several stability and QoL improvements. diff --git a/redis-cache/redis-cache.php b/redis-cache/redis-cache.php new file mode 100644 index 0000000..b8a69ca --- /dev/null +++ b/redis-cache/redis-cache.php @@ -0,0 +1,62 @@ + 'Version' ] ); + +define( 'WP_REDIS_VERSION', $meta['Version'] ); + +require_once WP_REDIS_PLUGIN_PATH . '/includes/class-autoloader.php'; + +$autoloader = new Rhubarb\RedisCache\Autoloader(); +$autoloader->register(); +$autoloader->add_namespace( 'Rhubarb\RedisCache', WP_REDIS_PLUGIN_PATH . '/includes' ); + +if ( defined( 'WP_CLI' ) && WP_CLI && ! defined( 'RedisCachePro\Version' ) && ! defined( 'ObjectCachePro\Version' ) ) { + add_action( + 'plugins_loaded', + function () { + WP_CLI::add_command( 'redis', Rhubarb\RedisCache\CLI\Commands::class ); + } + ); +} + +register_activation_hook( + WP_REDIS_FILE, + [ Rhubarb\RedisCache\Plugin::class, 'on_activation' ] +); + +Rhubarb\RedisCache\Plugin::instance(); + +if ( ! function_exists( 'redis_object_cache' ) ) { + /** + * Returns the plugin instance. + * + * @return Rhubarb\RedisCache\Plugin + */ + function redis_object_cache() { + return Rhubarb\RedisCache\Plugin::instance(); + } +}