File manager - Edit - /home/opticamezl/www/newok/database.zip
Back
PK �9�\��.F� � phpunit.mysqli.xml.distnu &1i� <?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="Tests/bootstrap.php" colors="false"> <php> <env name="JOOMLA_TEST_DB_DRIVER" value="mysqli" /> <env name="JOOMLA_TEST_DB_HOST" value="mysqli" /> <env name="JOOMLA_TEST_DB_PORT" value="3306" /> <env name="JOOMLA_TEST_DB_USER" value="root" /> <env name="JOOMLA_TEST_DB_PASSWORD" value="" /> <env name="JOOMLA_TEST_DB_DATABASE" value="joomla_ut" /> <env name="JOOMLA_TEST_DB_PREFIX" value="" /> <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" /> </php> <testsuites> <testsuite name="Unit"> <directory>Tests/Mysqli</directory> </testsuite> </testsuites> </phpunit> PK �9�\�P�E �E LICENSEnu �[��� GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. <signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. PK �9�\u5D � � phpunit.sqlsrv.xml.distnu &1i� <?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="Tests/bootstrap.php" colors="false"> <php> <env name="JOOMLA_TEST_DB_DRIVER" value="sqlsrv" /> <env name="JOOMLA_TEST_DB_HOST" value="sqlsrv" /> <env name="JOOMLA_TEST_DB_PORT" value="1433" /> <env name="JOOMLA_TEST_DB_USER" value="sa" /> <env name="JOOMLA_TEST_DB_PASSWORD" value="JoomlaFramework123" /> <env name="JOOMLA_TEST_DB_DATABASE" value="joomla_ut" /> <env name="JOOMLA_TEST_DB_PREFIX" value="" /> <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" /> </php> <testsuites> <testsuite name="Unit"> <directory>Tests/Sqlsrv</directory> </testsuite> </testsuites> </phpunit> PK �9�\ ��O� � ! phpunit.appveyor_sql2014.xml.distnu &1i� <?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="Tests/bootstrap.php" colors="false"> <php> <env name="JOOMLA_TEST_DB_DRIVER" value="sqlsrv" /> <env name="JOOMLA_TEST_DB_HOST" value="(local)\SQL2014" /> <env name="JOOMLA_TEST_DB_PORT" value="" /> <env name="JOOMLA_TEST_DB_USER" value="sa" /> <env name="JOOMLA_TEST_DB_PASSWORD" value="Password12!" /> <env name="JOOMLA_TEST_DB_DATABASE" value="joomla_ut" /> <env name="JOOMLA_TEST_DB_PREFIX" value="" /> <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" /> </php> <testsuites> <testsuite name="Unit"> <directory>Tests</directory> </testsuite> </testsuites> </phpunit> PK �9�\��zX� � phpunit.pgsql.xml.distnu &1i� <?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="Tests/bootstrap.php" colors="false"> <php> <env name="JOOMLA_TEST_DB_DRIVER" value="pgsql" /> <env name="JOOMLA_TEST_DB_HOST" value="pgsql" /> <env name="JOOMLA_TEST_DB_PORT" value="5433" /> <env name="JOOMLA_TEST_DB_USER" value="postgres" /> <env name="JOOMLA_TEST_DB_PASSWORD" value="" /> <env name="JOOMLA_TEST_DB_DATABASE" value="joomla_ut" /> <env name="JOOMLA_TEST_DB_PREFIX" value="" /> <env name="JOOMLA_TEST_DB_SELECT" value="yes" /> <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" /> </php> <testsuites> <testsuite name="Unit"> <directory>Tests/Pgsql</directory> </testsuite> </testsuites> </phpunit> PK �9�\��Ք� � phpunit.mariadb.xml.distnu &1i� <?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="Tests/bootstrap.php" colors="false"> <php> <env name="JOOMLA_TEST_DB_DRIVER" value="mysqli" /> <env name="JOOMLA_TEST_DB_HOST" value="mariadb" /> <env name="JOOMLA_TEST_DB_PORT" value="3306" /> <env name="JOOMLA_TEST_DB_USER" value="root" /> <env name="JOOMLA_TEST_DB_PASSWORD" value="" /> <env name="JOOMLA_TEST_DB_DATABASE" value="joomla_ut" /> <env name="JOOMLA_TEST_DB_PREFIX" value="" /> <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" /> </php> <testsuites> <testsuite name="Unit"> <directory>Tests/Mysqli</directory> </testsuite> </testsuites> </phpunit> PK �9�\1P{��0 �0 .drone.jsonnetnu &1i� local volumes = [ { name: 'composer-cache', path: '/tmp/composer-cache', }, ]; local hostvolumes = [ { name: 'composer-cache', host: { path: '/tmp/composer-cache' }, }, ]; local composer(phpversion, params) = { name: 'Composer', image: 'joomlaprojects/docker-images:php' + phpversion, volumes: volumes, commands: [ 'php -v', 'sleep 20', 'composer update ' + params, ], }; local phpunit_common(phpversion) = { name: 'PHPUnit', image: 'joomlaprojects/docker-images:php' + phpversion, [if phpversion == '8.2' then 'failure']: 'ignore', commands: [ 'vendor/bin/phpunit --configuration phpunit.xml.dist --testdox', ], }; local phpunit_mysql(phpversion, driver) = { name: 'PHPUnit', image: 'joomlaprojects/docker-images:php' + phpversion, [if phpversion == '8.2' then 'failure']: 'ignore', commands: [ 'php --ri ' + driver + ' || true', 'sleep 20', 'vendor/bin/phpunit --configuration phpunit.' + driver + '.xml.dist --testdox', ], }; local phpunit(phpversion, driver) = { name: 'PHPUnit', image: 'joomlaprojects/docker-images:php' + phpversion, [if phpversion == '8.2' then 'failure']: 'ignore', commands: [ 'php --ri ' + driver + ' || true', 'vendor/bin/phpunit --configuration phpunit.' + driver + '.xml.dist --testdox', ], }; local phpunit_sqlsrv(phpversion) = { name: 'PHPUnit with MS SQL Server', image: 'joomlaprojects/docker-images:php' + phpversion, commands: [ 'apt-get update', 'apt-get install -y software-properties-common lsb-release gnupg', 'curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -', 'echo "deb [arch=amd64,armhf,arm64] https://packages.microsoft.com/debian/11/prod bullseye main" >> /etc/apt/sources.list', 'apt-get update', 'ACCEPT_EULA=Y apt-get install -y msodbcsql18 unixodbc-dev', 'pecl install sqlsrv && docker-php-ext-enable sqlsrv', 'pecl install pdo_sqlsrv && docker-php-ext-enable pdo_sqlsrv', 'php --ri sqlsrv', 'php --ri pdo_sqlsrv', 'vendor/bin/phpunit --configuration phpunit.sqlsrv.xml.dist --testdox', ], }; local pipeline_sqlite(phpversion, driver, params) = { kind: 'pipeline', name: 'PHP ' + phpversion + ' with SQLite (' + driver + ')', environment: { DB: driver }, volumes: hostvolumes, steps: [ composer(phpversion, params), phpunit(phpversion, driver), ], }; local pipeline_mysql(phpversion, driver, dbversion, params) = { kind: 'pipeline', name: 'PHP ' + phpversion + ' with MySQL ' + dbversion + ' (' + driver + ')', environment: { DB: driver }, volumes: hostvolumes, steps: [ composer(phpversion, params), phpunit_mysql(phpversion, driver), ], services: [ { name: driver, image: 'bitnami/mysql:' + dbversion, environment: { ALLOW_EMPTY_PASSWORD: 'yes', MYSQL_DATABASE: 'joomla_ut', MYSQL_ROOT_PASSWORD: '', MYSQL_AUTHENTICATION_PLUGIN: 'mysql_native_password', }, ports: [ { container: 3306, host: 3306, }, ], }, ], }; local pipeline_mariadb(phpversion, driver, dbversion, params) = { kind: 'pipeline', name: 'PHP ' + phpversion + ' with MariaDB ' + dbversion + ' (' + driver + ')', environment: { DB: driver }, volumes: hostvolumes, steps: [ composer(phpversion, params), phpunit(phpversion, driver), ], services: [ { name: driver, image: 'mariadb:' + dbversion, environment: { MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 'yes', MARIADB_DATABASE: 'joomla_ut', MARIADB_ROOT_PASSWORD: '', # Provide MySQL environments variables for MariaDB < 10.2. MYSQL_ALLOW_EMPTY_PASSWORD: 'yes', MYSQL_DATABASE: 'joomla_ut', MYSQL_ROOT_PASSWORD: '', }, ports: [ { container: 3306, host: 3306, }, ], }, ], }; local pipeline_postgres(phpversion, driver, dbversion, params) = { kind: 'pipeline', name: 'PHP ' + phpversion + ' with PostgreSQL ' + dbversion + ' (' + driver + ')', environment: { DB: driver }, volumes: hostvolumes, steps: [ composer(phpversion, params), phpunit(phpversion, driver), ], services: [ { name: driver, image: 'postgres:' + dbversion, environment: { POSTGRES_HOST_AUTH_METHOD: 'trust', POSTGRES_PASSWORD: '', POSTGRES_USER: 'postgres', }, ports: [ { container: 5432, host: 5432, }, ], commands: [ 'psql -U postgres -c ', 'psql -U postgres -d joomla_ut -a -f Tests/Stubs/Schema/pgsql.sql', ], }, ], }; local pipeline_sqlsrv(phpversion, driver, dbversion, params) = { kind: 'pipeline', name: 'PHP ' + phpversion + ' with MS SQL Server ' + dbversion + ' (' + driver + ')', environment: { DB: driver }, volumes: hostvolumes, steps: [ composer(phpversion, params), phpunit_sqlsrv(phpversion), ], services: [ { name: driver, image: 'mcr.microsoft.com/mssql/server:' + dbversion, environment: { ACCEPT_EULA: 'Y', SA_PASSWORD: 'JoomlaFramework123', }, ports: [ { container: 1433, host: 1433, }, ], }, ], }; [ { kind: 'pipeline', name: 'Codequality', volumes: hostvolumes, steps: [ { name: 'composer', image: 'joomlaprojects/docker-images:php7.4', volumes: volumes, commands: [ 'php -v', 'composer update', 'composer require phpmd/phpmd phpstan/phpstan', ], }, { name: 'phpcs', image: 'joomlaprojects/docker-images:php7.4', depends: [ 'composer' ], commands: [ 'vendor/bin/phpcs --config-set installed_paths vendor/joomla/coding-standards', 'vendor/bin/phpcs --standard=ruleset.xml src/', ], }, { name: 'phpmd', image: 'joomlaprojects/docker-images:php7.4', depends: [ 'composer' ], failure: 'ignore', commands: [ 'vendor/bin/phpmd src text cleancode', 'vendor/bin/phpmd src text codesize', 'vendor/bin/phpmd src text controversial', 'vendor/bin/phpmd src text design', 'vendor/bin/phpmd src text unusedcode', ], }, { name: 'phpstan', image: 'joomlaprojects/docker-images:php7.4', depends: [ 'composer' ], failure: 'ignore', commands: [ 'vendor/bin/phpstan analyse src', ], }, { name: 'phploc', image: 'joomlaprojects/docker-images:php7.4', depends: [ 'composer' ], failure: 'ignore', commands: [ 'phploc src', ], }, { name: 'phpcpd', image: 'joomlaprojects/docker-images:php7.4', depends: [ 'composer' ], failure: 'ignore', commands: [ 'phpcpd src', ], }, ], }, pipeline_sqlite('7.2', 'sqlite', '--prefer-stable --prefer-lowest'), pipeline_sqlite('7.3', 'sqlite', '--prefer-stable'), pipeline_sqlite('7.4', 'sqlite', '--prefer-stable'), pipeline_sqlite('8.0', 'sqlite', '--prefer-stable --ignore-platform-reqs'), pipeline_sqlite('8.1', 'sqlite', '--prefer-stable --ignore-platform-reqs'), pipeline_sqlite('8.2', 'sqlite', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('7.2', 'mysql', '5.7', '--prefer-stable --prefer-lowest'), pipeline_mysql('7.3', 'mysql', '5.7', '--prefer-stable'), pipeline_mysql('7.4', 'mysql', '5.7', '--prefer-stable'), pipeline_mysql('8.0', 'mysql', '5.7', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('8.1', 'mysql', '5.7', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('8.2', 'mysql', '5.7', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('7.3', 'mysql', '8.0', '--prefer-stable'), pipeline_mysql('7.4', 'mysql', '8.0', '--prefer-stable'), pipeline_mysql('8.0', 'mysql', '8.0', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('8.1', 'mysql', '8.0', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('8.2', 'mysql', '8.0', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('7.2', 'mysqli', '5.7', '--prefer-stable --prefer-lowest'), pipeline_mysql('7.3', 'mysqli', '5.7', '--prefer-stable'), pipeline_mysql('7.4', 'mysqli', '5.7', '--prefer-stable'), pipeline_mysql('8.0', 'mysqli', '5.7', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('8.1', 'mysqli', '5.7', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('8.2', 'mysqli', '5.7', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('7.3', 'mysqli', '8.0', '--prefer-stable'), pipeline_mysql('7.4', 'mysqli', '8.0', '--prefer-stable'), pipeline_mysql('8.0', 'mysqli', '8.0', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('8.1', 'mysqli', '8.0', '--prefer-stable --ignore-platform-reqs'), pipeline_mysql('8.2', 'mysqli', '8.0', '--prefer-stable --ignore-platform-reqs'), pipeline_mariadb('7.2', 'mariadb', '10.2', '--prefer-stable --prefer-lowest'), pipeline_mariadb('7.3', 'mariadb', '10.2', '--prefer-stable'), pipeline_mariadb('7.4', 'mariadb', '10.0', '--prefer-stable'), pipeline_mariadb('7.4', 'mariadb', '10.1', '--prefer-stable'), pipeline_mariadb('7.4', 'mariadb', '10.2', '--prefer-stable'), pipeline_mariadb('8.0', 'mariadb', '10.2', '--prefer-stable --ignore-platform-reqs'), pipeline_mariadb('8.1', 'mariadb', '10.2', '--prefer-stable --ignore-platform-reqs'), pipeline_mariadb('8.2', 'mariadb', '10.2', '--prefer-stable --ignore-platform-reqs'), pipeline_postgres('7.2', 'pgsql', '9.4', '--prefer-stable --prefer-lowest'), pipeline_postgres('7.2', 'pgsql', '9.5', '--prefer-stable --prefer-lowest'), pipeline_postgres('7.3', 'pgsql', '9.6', '--prefer-stable'), pipeline_postgres('7.3', 'pgsql', '10', '--prefer-stable'), pipeline_postgres('7.4', 'pgsql', '10', '--prefer-stable'), pipeline_postgres('8.0', 'pgsql', '10', '--prefer-stable --ignore-platform-reqs'), pipeline_postgres('8.1', 'pgsql', '10', '--prefer-stable --ignore-platform-reqs'), pipeline_postgres('8.2', 'pgsql', '10', '--prefer-stable --ignore-platform-reqs'), pipeline_postgres('7.3', 'pgsql', '11', '--prefer-stable'), pipeline_postgres('7.4', 'pgsql', '11', '--prefer-stable'), pipeline_postgres('8.0', 'pgsql', '11', '--prefer-stable --ignore-platform-reqs'), pipeline_postgres('8.1', 'pgsql', '11', '--prefer-stable --ignore-platform-reqs'), pipeline_postgres('8.2', 'pgsql', '11', '--prefer-stable --ignore-platform-reqs'), pipeline_sqlsrv('7.3', 'sqlsrv', '2017-latest', '--prefer-stable'), pipeline_sqlsrv('7.4', 'sqlsrv', '2017-latest', '--prefer-stable'), pipeline_sqlsrv('8.0', 'sqlsrv', '2017-latest', '--prefer-stable --ignore-platform-reqs'), pipeline_sqlsrv('8.1', 'sqlsrv', '2017-latest', '--prefer-stable --ignore-platform-reqs'), pipeline_sqlsrv('8.2', 'sqlsrv', '2017-latest', '--prefer-stable --ignore-platform-reqs'), ] PK �9�\z��3 3 src/Monitor/ChainedMonitor.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Monitor; use Joomla\Database\QueryMonitorInterface; /** * Chained query monitor allowing multiple monitors to be executed. * * @since 2.0.0 */ class ChainedMonitor implements QueryMonitorInterface { /** * The query monitors stored to this chain * * @var QueryMonitorInterface[] * @since 2.0.0 */ private $monitors = []; /** * Register a monitor to the chain. * * @param QueryMonitorInterface $monitor The monitor to add. * * @return void * * @since 2.0.0 */ public function addMonitor(QueryMonitorInterface $monitor): void { $this->monitors[] = $monitor; } /** * Act on a query being started. * * @param string $sql The SQL to be executed. * @param object[]|null $boundParams List of bound params, used with the query. * Each item is an object that holds: value, dataType * * @return void * * @since 2.0.0 */ public function startQuery(string $sql, ?array $boundParams = null): void { foreach ($this->monitors as $monitor) { $monitor->startQuery($sql, $boundParams); } } /** * Act on a query being stopped. * * @return void * * @since 2.0.0 */ public function stopQuery(): void { foreach ($this->monitors as $monitor) { $monitor->stopQuery(); } } } PK �9�\��Կ � src/Monitor/DebugMonitor.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Monitor; use Joomla\Database\QueryMonitorInterface; /** * Query monitor handling logging of queries. * * @since 2.0.0 */ final class DebugMonitor implements QueryMonitorInterface { /** * The log of executed SQL statements call stacks by the database driver. * * @var array * @since 2.0.0 */ private $callStacks = []; /** * The log of executed SQL statements by the database driver. * * @var array * @since 2.0.0 */ private $logs = []; /** * List of bound params, used with the query. * * @var array * @since 2.0.0 */ private $boundParams = []; /** * The log of executed SQL statements memory usage (start and stop memory_get_usage) by the database driver. * * @var array * @since 2.0.0 */ private $memoryLogs = []; /** * The log of executed SQL statements timings (start and stop microtimes) by the database driver. * * @var array * @since 2.0.0 */ private $timings = []; /** * Act on a query being started. * * @param string $sql The SQL to be executed. * @param object[]|null $boundParams List of bound params, used with the query. * Each item is an object that holds: value, dataType * * @return void * * @since 2.0.0 */ public function startQuery(string $sql, ?array $boundParams = null): void { $this->logs[] = $sql; // Dereference bound parameters to prevent reporting wrong value when reusing the same query object. $this->boundParams[] = unserialize(serialize($boundParams)); $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $this->memoryLogs[] = memory_get_usage(); $this->timings[] = microtime(true); } /** * Act on a query being stopped. * * @return void * * @since 2.0.0 */ public function stopQuery(): void { $this->timings[] = microtime(true); $this->memoryLogs[] = memory_get_usage(); } /** * Get the logged call stacks. * * @return array * * @since 2.0.0 */ public function getCallStacks(): array { return $this->callStacks; } /** * Get the logged queries. * * @return array * * @since 2.0.0 */ public function getLogs(): array { return $this->logs; } /** * Get the logged bound params. * * @return array * * @since 2.0.0 */ public function getBoundParams(): array { return $this->boundParams; } /** * Get the logged memory logs. * * @return array * * @since 2.0.0 */ public function getMemoryLogs(): array { return $this->memoryLogs; } /** * Get the logged timings. * * @return array * * @since 2.0.0 */ public function getTimings(): array { return $this->timings; } } PK �9�\1�)� src/Monitor/LoggingMonitor.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Monitor; use Joomla\Database\QueryMonitorInterface; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; /** * Query monitor handling logging of queries. * * @since 2.0.0 */ class LoggingMonitor implements QueryMonitorInterface, LoggerAwareInterface { use LoggerAwareTrait; /** * Act on a query being started. * * @param string $sql The SQL to be executed. * @param object[]|null $boundParams List of bound params, used with the query. * Each item is an object that holds: value, dataType * @return void * * @since 2.0.0 */ public function startQuery(string $sql, ?array $boundParams = null): void { if ($this->logger) { // Add the query to the object queue. $this->logger->info( 'Query Executed: {sql}', ['sql' => $sql, 'trace' => debug_backtrace()] ); } } /** * Act on a query being stopped. * * @return void * * @since 2.0.0 */ public function stopQuery(): void { // Nothing to do } } PK �9�\��3E E src/Event/ConnectionEvent.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Event; use Joomla\Database\DatabaseInterface; use Joomla\Event\Event; /** * Database connection event * * @since 2.0.0 */ class ConnectionEvent extends Event { /** * DatabaseInterface object for this event * * @var DatabaseInterface * @since 2.0.0 */ private $driver; /** * Constructor. * * @param string $name The event name. * @param DatabaseInterface $driver The DatabaseInterface object for this event. * * @since 2.0.0 */ public function __construct(string $name, DatabaseInterface $driver) { parent::__construct($name); $this->driver = $driver; } /** * Retrieve the DatabaseInterface object attached to this event. * * @return DatabaseInterface * * @since 2.0.0 */ public function getDriver(): DatabaseInterface { return $this->driver; } } PK �9�\��kT� � src/UTF8MB4SupportInterface.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Interface defining a driver which has support for the MySQL `utf8mb4` character set * * @since 2.0.0 */ interface UTF8MB4SupportInterface { /** * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. * * Used when the server doesn't support UTF-8 Multibyte. * * @param string $query The query to convert * * @return string The converted query * * @since 2.0.0 */ public function convertUtf8mb4QueryToUtf8($query); /** * Check whether the database engine supports the UTF-8 Multibyte (utf8mb4) character encoding. * * @return boolean True if the database engine supports UTF-8 Multibyte. * * @since 2.0.0 */ public function hasUtf8mb4Support(); } PK �9�\��y�U� U� src/DatabaseDriver.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; use Joomla\Database\Event\ConnectionEvent; use Joomla\Database\Exception\ConnectionFailureException; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\Exception\PrepareStatementFailureException; use Joomla\Event\DispatcherAwareInterface; use Joomla\Event\DispatcherAwareTrait; use Joomla\Event\EventInterface; /** * Joomla Framework Database Driver Class * * @since 1.0 */ abstract class DatabaseDriver implements DatabaseInterface, DispatcherAwareInterface { use DispatcherAwareTrait; /** * The name of the database. * * @var string * @since 1.0 */ private $database; /** * The name of the database driver. * * @var string * @since 1.0 */ protected $name; /** * The type of the database server family supported by this driver. * * @var string * @since 1.4.0 */ public $serverType; /** * The database connection resource. * * @var resource * @since 1.0 */ protected $connection; /** * Holds the list of available db connectors. * * @var array * @since 1.0 */ protected static $connectors = []; /** * The number of SQL statements executed by the database driver. * * @var integer * @since 1.0 */ protected $count = 0; /** * The database connection cursor from the last query. * * @var resource * @since 1.0 */ protected $cursor; /** * Contains the current query execution status * * @var boolean * @since 2.0.0 */ protected $executed = false; /** * The affected row limit for the current SQL statement. * * @var integer * @since 1.0 */ protected $limit = 0; /** * The character(s) used to quote SQL statement names such as table names or field names, etc. * * If a single character string the same character is used for both sides of the quoted name, else the first character will be used for the * opening quote and the second for the closing quote. * * @var string * @since 1.0 */ protected $nameQuote; /** * The null or zero representation of a timestamp for the database driver. * * @var string * @since 1.0 */ protected $nullDate; /** * The affected row offset to apply for the current SQL statement. * * @var integer * @since 1.0 */ protected $offset = 0; /** * Passed in upon instantiation and saved. * * @var array * @since 1.0 */ protected $options; /** * The current SQL statement to execute. * * @var mixed * @since 1.0 */ protected $sql; /** * The prepared statement. * * @var StatementInterface * @since 2.0.0 */ protected $statement; /** * The common database table prefix. * * @var string * @since 1.0 */ protected $tablePrefix; /** * True if the database engine supports UTF-8 character encoding. * * @var boolean * @since 1.0 */ protected $utf = true; /** * The database error number. * * @var integer * @since 1.0 */ protected $errorNum = 0; /** * The database error message. * * @var string * @since 1.0 */ protected $errorMsg; /** * DatabaseDriver instances container. * * @var DatabaseDriver[] * @since 1.0 * @deprecated 3.0 Singleton storage will no longer be supported. */ protected static $instances = []; /** * The minimum supported database version. * * @var string * @since 1.0 */ protected static $dbMinimum; /** * The depth of the current transaction. * * @var integer * @since 1.0 */ protected $transactionDepth = 0; /** * DatabaseFactory object * * @var DatabaseFactory * @since 2.0.0 */ protected $factory; /** * Query monitor object * * @var QueryMonitorInterface * @since 2.0.0 */ protected $monitor; /** * Get a list of available database connectors. * * The list will only be populated with connectors that the class exists for and the environment supports its use. * This gives us the ability to have a multitude of connector classes that are self-aware as to whether or not they * are able to be used on a given system. * * @return array An array of available database connectors. * * @since 1.0 */ public static function getConnectors() { if (empty(self::$connectors)) { // Get an iterator and loop trough the driver classes. $dir = __DIR__; $iterator = new \DirectoryIterator($dir); /** @var $file \DirectoryIterator */ foreach ($iterator as $file) { // Only load for php files. if (!$file->isDir()) { continue; } $baseName = $file->getBasename(); // Derive the class name from the type. /** @var $class DatabaseDriver */ $class = __NAMESPACE__ . '\\' . ucfirst(strtolower($baseName)) . '\\' . ucfirst(strtolower($baseName)) . 'Driver'; // If the class doesn't exist, or if it's not supported on this system, move on to the next type. if (!class_exists($class) || !$class::isSupported()) { continue; } // Everything looks good, add it to the list. self::$connectors[] = $baseName; } } return self::$connectors; } /** * Method to return a DatabaseDriver instance based on the given options. * * There are three global options and then the rest are specific to the database driver. * * - The 'driver' option defines which DatabaseDriver class is used for the connection -- the default is 'mysqli'. * - The 'database' option determines which database is to be used for the connection. * - The 'select' option determines whether the connector should automatically select the chosen database. * * Instances are unique to the given options and new objects are only created when a unique options array is * passed into the method. This ensures that we don't end up with unnecessary database connection resources. * * @param array $options Parameters to be passed to the database driver. * * @return DatabaseDriver * * @since 1.0 * @throws \RuntimeException * @deprecated 3.0 Use DatabaseFactory::getDriver() instead */ public static function getInstance(array $options = []) { trigger_deprecation( 'joomla/database', '2.0.0', '%s() is deprecated and will be removed in 3.0, use %s::getDriver() instead.', __METHOD__, DatabaseFactory::class ); // Sanitize the database connector options. $options['driver'] = isset($options['driver']) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $options['driver']) : 'mysqli'; $options['database'] = $options['database'] ?? null; $options['select'] = $options['select'] ?? true; $options['factory'] = $options['factory'] ?? new DatabaseFactory; $options['monitor'] = $options['monitor'] ?? null; // Get the options signature for the database connector. $signature = md5(serialize($options)); // If we already have a database connector instance for these options then just use that. if (empty(self::$instances[$signature])) { // Set the new connector to the global instances based on signature. self::$instances[$signature] = $options['factory']->getDriver($options['driver'], $options); } return self::$instances[$signature]; } /** * Splits a string of multiple queries into an array of individual queries. * * @param string $sql Input SQL string with which to split into individual queries. * * @return array * * @since 1.0 */ public static function splitSql($sql) { $start = 0; $open = false; $comment = false; $endString = ''; $end = \strlen($sql); $queries = []; $query = ''; for ($i = 0; $i < $end; $i++) { $current = substr($sql, $i, 1); $current2 = substr($sql, $i, 2); $current3 = substr($sql, $i, 3); $lenEndString = \strlen($endString); $testEnd = substr($sql, $i, $lenEndString); if ($current === '"' || $current === "'" || $current2 === '--' || ($current2 === '/*' && $current3 !== '/*!' && $current3 !== '/*+') || ($current === '#' && $current3 !== '#__') || ($comment && $testEnd === $endString)) { // Check if quoted with previous backslash $n = 2; while (substr($sql, $i - $n + 1, 1) === '\\' && $n < $i) { $n++; } // Not quoted if ($n % 2 === 0) { if ($open) { if ($testEnd === $endString) { if ($comment) { $comment = false; if ($lenEndString > 1) { $i += ($lenEndString - 1); $current = substr($sql, $i, 1); } $start = $i + 1; } $open = false; $endString = ''; } } else { $open = true; if ($current2 === '--') { $endString = "\n"; $comment = true; } elseif ($current2 === '/*') { $endString = '*/'; $comment = true; } elseif ($current === '#') { $endString = "\n"; $comment = true; } else { $endString = $current; } if ($comment && $start < $i) { $query .= substr($sql, $start, $i - $start); } } } } if ($comment) { $start = $i + 1; } if (($current === ';' && !$open) || $i === $end - 1) { if ($start <= $i) { $query .= substr($sql, $start, $i - $start + 1); } $query = trim($query); if ($query) { if (($i === $end - 1) && ($current !== ';')) { $query .= ';'; } $queries[] = $query; } $query = ''; $start = $i + 1; } $endComment = false; } return $queries; } /** * Magic method to access properties of the database driver. * * @param string $name The name of the property. * * @return mixed A value if the property name is valid, null otherwise. * * @since 1.4.0 * @deprecated 3.0 This is a B/C proxy since $this->name was previously public */ public function __get($name) { switch ($name) { case 'name': trigger_deprecation( 'joomla/database', '1.4.0', 'Accessing the name property of %s is deprecated, use the getName() method instead.', self::class ); return $this->getName(); default: $trace = debug_backtrace(); trigger_error( sprintf( 'Undefined property via __get(): %1$s in %2$s on line %3$s', $name, $trace[0]['file'], $trace[0]['line'] ), \E_USER_NOTICE ); } } /** * Constructor. * * @param array $options List of options used to configure the connection * * @since 1.0 */ public function __construct(array $options) { // Initialise object variables. $this->database = $options['database'] ?? ''; $this->tablePrefix = $options['prefix'] ?? ''; $this->count = 0; $this->errorNum = 0; // Set class options. $this->options = $options; // Register the DatabaseFactory $this->factory = $options['factory'] ?? new DatabaseFactory; // Register the query monitor if available $this->monitor = $options['monitor'] ?? null; } /** * Destructor. * * @since 2.0.0 */ public function __destruct() { $this->disconnect(); } /** * Alter database's character set. * * @param string $dbName The database name that will be altered * * @return boolean|resource * * @since 2.0.0 * @throws \RuntimeException */ public function alterDbCharacterSet($dbName) { if ($dbName === null) { throw new \RuntimeException('Database name must not be null.'); } $this->setQuery($this->getAlterDbCharacterSet($dbName)); return $this->execute(); } /** * Create a new database using information from $options object. * * @param \stdClass $options Object used to pass user and database name to database driver. This object must have "db_name" and "db_user" set. * @param boolean $utf True if the database supports the UTF-8 character set. * * @return boolean|resource * * @since 2.0.0 * @throws \RuntimeException */ public function createDatabase($options, $utf = true) { if ($options === null) { throw new \RuntimeException('$options object must not be null.'); } if (empty($options->db_name)) { throw new \RuntimeException('$options object must have db_name set.'); } if (empty($options->db_user)) { throw new \RuntimeException('$options object must have db_user set.'); } $this->setQuery($this->getCreateDatabaseQuery($options, $utf)); return $this->execute(); } /** * Create a new DatabaseQuery object. * * @return QueryInterface * * @since 2.2 */ public function createQuery(): QueryInterface { return $this->factory->getQuery($this->name, $this); } /** * Disconnects the database. * * @return void * * @since 2.0.0 */ public function disconnect() { $this->freeResult(); $this->connection = null; $this->dispatchEvent(new ConnectionEvent(DatabaseEvents::POST_DISCONNECT, $this)); } /** * Dispatch an event. * * @param EventInterface $event The event to dispatch * * @return void * * @since 2.0.0 */ protected function dispatchEvent(EventInterface $event) { try { $this->getDispatcher()->dispatch($event->getName(), $event); } catch (\UnexpectedValueException $exception) { // Don't error if a dispatcher hasn't been set } } /** * Drops a table from the database. * * @param string $table The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return $this * * @since 2.0.0 * @throws \RuntimeException */ public function dropTable($table, $ifExists = true) { $this->connect(); $this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $this->quoteName($table)) ->execute(); return $this; } /** * Execute the SQL statement. * * @return boolean * * @since 2.0.0 * @throws \RuntimeException */ public function execute() { $this->connect(); // Increment the query counter. $this->count++; // Get list of bound parameters $bounded =& $this->sql->getBounded(); // If there is a monitor registered, let it know we are starting this query if ($this->monitor) { // Take a local copy so that we don't modify the original query and cause issues later $sql = $this->replacePrefix((string) $this->sql); $this->monitor->startQuery($sql, $bounded); } // Execute the query. $this->executed = false; // Bind the variables foreach ($bounded as $key => $obj) { $this->statement->bindParam($key, $obj->value, $obj->dataType); } try { $this->executed = $this->statement->execute(); // If there is a monitor registered, let it know we have finished this query if ($this->monitor) { $this->monitor->stopQuery(); } return true; } catch (ExecutionFailureException $exception) { // If there is a monitor registered, let it know we have finished this query if ($this->monitor) { $this->monitor->stopQuery(); } // Check if the server was disconnected. if (!$this->connected()) { try { // Attempt to reconnect. $this->connection = null; $this->connect(); } catch (ConnectionFailureException $e) { // If connect fails, ignore that exception and throw the normal exception. throw $exception; } // Since we were able to reconnect, run the query again. return $this->execute(); } // Throw the normal query exception. throw $exception; } } /** * Method to fetch a row from the result set cursor as an array. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 1.0 */ protected function fetchArray() { if ($this->statement) { return $this->statement->fetch(FetchMode::NUMERIC); } } /** * Method to fetch a row from the result set cursor as an associative array. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 1.0 */ protected function fetchAssoc() { if ($this->statement) { return $this->statement->fetch(FetchMode::ASSOCIATIVE); } } /** * Method to fetch a row from the result set cursor as an object. * * Note, the fetch mode should be configured before calling this method using `StatementInterface::setFetchMode()`. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 1.0 */ protected function fetchObject() { if ($this->statement) { return $this->statement->fetch(); } } /** * Method to free up the memory used for the result set. * * @return void * * @since 1.0 */ protected function freeResult() { $this->executed = false; if ($this->statement) { $this->statement->closeCursor(); } } /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer The number of affected rows in the previous operation * * @since 2.0.0 */ public function getAffectedRows() { $this->connect(); if ($this->statement) { return $this->statement->rowCount(); } return 0; } /** * Method that provides access to the underlying database connection. * * @return resource The underlying database connection resource. * * @since 1.0 */ public function getConnection() { return $this->connection; } /** * Get the total number of SQL statements executed by the database driver. * * @return integer * * @since 1.0 */ public function getCount() { return $this->count; } /** * Return the query string to alter the database character set. * * @param string $dbName The database name * * @return string The query that alter the database query string * * @since 1.6.0 */ protected function getAlterDbCharacterSet($dbName) { return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET ' . $this->quote('UTF8'); } /** * Return the query string to create new Database. * * @param stdClass $options Object used to pass user and database name to database driver. This object must have "db_name" and "db_user" set. * @param boolean $utf True if the database supports the UTF-8 character set. * * @return string The query that creates database * * @since 2.0.0 */ protected function getCreateDatabaseQuery($options, $utf) { return 'CREATE DATABASE ' . $this->quoteName($options->db_name); } /** * Gets the name of the database used by this connection. * * @return string * * @since 1.0 */ protected function getDatabase() { return $this->database; } /** * Returns a PHP date() function compliant date format for the database driver. * * @return string * * @since 1.0 */ public function getDateFormat() { return 'Y-m-d H:i:s'; } /** * Get the minimum supported database version. * * @return string * * @since 1.0 */ public function getMinimum() { return static::$dbMinimum; } /** * Get the name of the database driver. * * If $this->name is not set it will try guessing the driver name from the class name. * * @return string * * @since 1.4.0 */ public function getName() { if (empty($this->name)) { $reflect = new \ReflectionClass($this); $this->name = strtolower(str_replace('Driver', '', $reflect->getShortName())); } return $this->name; } /** * Get the number of returned rows for the previous executed SQL statement. * * @return integer The number of returned rows. * * @since 2.0.0 */ public function getNumRows() { $this->connect(); if ($this->statement) { return $this->statement->rowCount(); } return 0; } /** * Get the server family type. * * If $this->serverType is not set it will attempt guessing the server family type from the driver name. If this is not possible the driver * name will be returned instead. * * @return string * * @since 1.4.0 */ public function getServerType() { if (empty($this->serverType)) { $name = $this->getName(); if (stristr($name, 'mysql') !== false) { $this->serverType = 'mysql'; } elseif (stristr($name, 'postgre') !== false) { $this->serverType = 'postgresql'; } elseif (stristr($name, 'pgsql') !== false) { $this->serverType = 'postgresql'; } elseif (stristr($name, 'oracle') !== false) { $this->serverType = 'oracle'; } elseif (stristr($name, 'sqlite') !== false) { $this->serverType = 'sqlite'; } elseif (stristr($name, 'sqlsrv') !== false) { $this->serverType = 'mssql'; } elseif (stristr($name, 'sqlazure') !== false) { $this->serverType = 'mssql'; } elseif (stristr($name, 'mssql') !== false) { $this->serverType = 'mssql'; } else { $this->serverType = $name; } } return $this->serverType; } /** * Get the null or zero representation of a timestamp for the database driver. * * @return string * * @since 1.0 */ public function getNullDate() { return $this->nullDate; } /** * Get the common table prefix for the database driver. * * @return string The common database table prefix. * * @since 1.0 */ public function getPrefix() { return $this->tablePrefix; } /** * Gets an exporter class object. * * @return DatabaseExporter An exporter object. * * @since 1.0 * @throws \RuntimeException */ public function getExporter() { return $this->factory->getExporter($this->name, $this); } /** * Gets an importer class object. * * @return DatabaseImporter * * @since 1.0 */ public function getImporter() { return $this->factory->getImporter($this->name, $this); } /** * Get the current query object or a new DatabaseQuery object. * * @param boolean $new False to return the current query object, True to return a new DatabaseQuery object. * The $new parameter is deprecated in 2.2 and will be removed in 4.0, use createQuery() instead. * * @return DatabaseQuery * * @since 1.0 */ public function getQuery($new = false) { if ($new) { trigger_deprecation( 'joomla/database', '2.2.0', 'The parameter $new is deprecated and will be removed in 4.0, use %s::createQuery() instead.', self::class ); return $this->createQuery(); } return $this->sql; } /** * Get a new iterator on the current query. * * @param string $column An option column to use as the iterator key. * @param string $class The class of object that is returned. * * @return DatabaseIterator * * @since 1.0 */ public function getIterator($column = null, $class = \stdClass::class) { if (!$this->executed) { $this->execute(); } /** * Calling setQuery free's the statement from the iterator which will break the iterator. * So we set statement to null so that freeResult on the statement here has no affect. * If you unset the iterator object then that will close the cursor and free the result. */ $iterator = $this->factory->getIterator($this->name, $this->statement, $column, $class); $this->statement = null; return $iterator; } /** * Shows the table CREATE statement that creates the given tables. * * @param mixed $tables A table name or a list of table names. * * @return array A list of the create SQL for the tables. * * @since 1.0 * @throws \RuntimeException */ abstract public function getTableCreate($tables); /** * Determine whether or not the database engine supports UTF-8 character encoding. * * @return boolean True if the database engine supports UTF-8 character encoding. * * @since 1.0 */ public function hasUtfSupport() { return $this->utf; } /** * Inserts a row into a table based on an object's properties. * * @param string $table The name of the database table to insert into. * @param object $object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. If provided the object property is updated. * * @return boolean * * @since 1.0 * @throws \RuntimeException */ public function insertObject($table, &$object, $key = null) { $fields = []; $values = []; $tableColumns = $this->getTableColumns($table); // Iterate over the object variables to build the query fields and values. foreach (get_object_vars($object) as $k => $v) { // Skip columns that don't exist in the table. if (!\array_key_exists($k, $tableColumns)) { continue; } // Only process non-null scalars. if (\is_array($v) || \is_object($v) || $v === null) { continue; } // Ignore any internal fields. if ($k[0] === '_') { continue; } // Prepare and sanitize the fields and values for the database query. $fields[] = $this->quoteName($k); $values[] = $this->quote($v); } // Create the base insert statement. $query = $this->createQuery() ->insert($this->quoteName($table)) ->columns($fields) ->values(implode(',', $values)); // Set the query and execute the insert. $this->setQuery($query)->execute(); // Update the primary key if it exists. $id = $this->insertid(); if ($key && $id && \is_string($key)) { $object->$key = $id; } return true; } /** * Method to check whether the installed database version is supported by the database driver * * @return boolean True if the database version is supported * * @since 1.0 */ public function isMinimumVersion() { return version_compare($this->getVersion(), $this->getMinimum()) >= 0; } /** * Method to get the first row of the result set from the database query as an associative array of ['field_name' => 'row_value']. * * @return mixed The return value or null if the query failed. * * @since 1.0 * @throws \RuntimeException */ public function loadAssoc() { $this->connect(); $ret = null; // Execute the query and get the result set cursor. $this->execute(); // Get the first row from the result set as an associative array. $array = $this->fetchAssoc(); if ($array) { $ret = $array; } // Free up system resources and return. $this->freeResult(); return $ret; } /** * Method to get an array of the result set rows from the database query where each row is an associative array * of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to * a sequential numeric array. * * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted * behavior and should be avoided. * * @param string $key The name of a field on which to key the result array. * @param string $column An optional column name. Instead of the whole row, only this column value will be in * the result array. * * @return mixed The return value or null if the query failed. * * @since 1.0 * @throws \RuntimeException */ public function loadAssocList($key = null, $column = null) { $this->connect(); $array = []; // Execute the query and get the result set cursor. $this->execute(); // Get all of the rows from the result set. while ($row = $this->fetchAssoc()) { $value = $column ? ($row[$column] ?? $row) : $row; if ($key) { $array[$row[$key]] = $value; } else { $array[] = $value; } } // Free up system resources and return. $this->freeResult(); return $array; } /** * Method to get an array of values from the <var>$offset</var> field in each row of the result set from the database query. * * @param integer $offset The row offset to use to build the result array. * * @return mixed The return value or null if the query failed. * * @since 1.0 * @throws \RuntimeException */ public function loadColumn($offset = 0) { $this->connect(); $array = []; // Execute the query and get the result set cursor. $this->execute(); // Get all of the rows from the result set as arrays. while ($row = $this->fetchArray()) { $array[] = $row[$offset]; } // Free up system resources and return. $this->freeResult(); return $array; } /** * Method to get the first row of the result set from the database query as an object. * * @param string $class The class name to use for the returned row object. * * @return mixed The return value or null if the query failed. * * @since 1.0 * @throws \RuntimeException */ public function loadObject($class = \stdClass::class) { $this->connect(); $ret = null; if ($this->statement) { $fetchMode = $class === \stdClass::class ? FetchMode::STANDARD_OBJECT : FetchMode::CUSTOM_OBJECT; // PDO doesn't allow extra arguments for \PDO::FETCH_CLASS, so only forward the class for the custom object mode if ($fetchMode === FetchMode::STANDARD_OBJECT) { $this->statement->setFetchMode($fetchMode); } else { $this->statement->setFetchMode($fetchMode, $class); } } // Execute the query and get the result set cursor. $this->execute(); // Get the first row from the result set as an object of type $class. $object = $this->fetchObject(); if ($object) { $ret = $object; } // Free up system resources and return. $this->freeResult(); return $ret; } /** * Method to get an array of the result set rows from the database query where each row is an object. The array * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array. * * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted behavior and should be avoided. * * @param string $key The name of a field on which to key the result array. * @param string $class The class name to use for the returned row objects. * * @return mixed The return value or null if the query failed. * * @since 1.0 * @throws \RuntimeException */ public function loadObjectList($key = '', $class = \stdClass::class) { $this->connect(); $array = []; if ($this->statement) { $fetchMode = $class === \stdClass::class ? FetchMode::STANDARD_OBJECT : FetchMode::CUSTOM_OBJECT; // PDO doesn't allow extra arguments for \PDO::FETCH_CLASS, so only forward the class for the custom object mode if ($fetchMode === FetchMode::STANDARD_OBJECT) { $this->statement->setFetchMode($fetchMode); } else { $this->statement->setFetchMode($fetchMode, $class); } } // Execute the query and get the result set cursor. $this->execute(); // Get all of the rows from the result set as objects of type $class. while ($row = $this->fetchObject()) { if ($key) { $array[$row->$key] = $row; } else { $array[] = $row; } } // Free up system resources and return. $this->freeResult(); return $array; } /** * Method to get the first field of the first row of the result set from the database query. * * @return mixed The return value or null if the query failed. * * @since 1.0 * @throws \RuntimeException */ public function loadResult() { $this->connect(); $ret = null; // Execute the query and get the result set cursor. $this->execute(); // Get the first row from the result set as an array. $row = $this->fetchArray(); if ($row) { $ret = $row[0]; } // Free up system resources and return. $this->freeResult(); return $ret; } /** * Method to get the first row of the result set from the database query as an array. * * Columns are indexed numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc. * * @return mixed The return value or null if the query failed. * * @since 1.0 * @throws \RuntimeException */ public function loadRow() { $this->connect(); $ret = null; // Execute the query and get the result set cursor. $this->execute(); // Get the first row from the result set as an array. $row = $this->fetchArray(); if ($row) { $ret = $row; } // Free up system resources and return. $this->freeResult(); return $ret; } /** * Method to get an array of the result set rows from the database query where each row is an array. The array * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array. * * NOTE: Choosing to key the result array by a non-unique field can result in unwanted behavior and should be avoided. * * @param string $key The name of a field on which to key the result array. * * @return array An array of results. * * @since 1.0 * @throws \RuntimeException */ public function loadRowList($key = null) { $this->connect(); $array = []; // Execute the query and get the result set cursor. $this->execute(); // Get all of the rows from the result set as arrays. while ($row = $this->fetchArray()) { if ($key !== null) { $array[$row[$key]] = $row; } else { $array[] = $row; } } // Free up system resources and return. $this->freeResult(); return $array; } /** * Prepares a SQL statement for execution * * @param string $query The SQL query to be prepared. * * @return StatementInterface * * @since 2.0.0 * @throws PrepareStatementFailureException */ abstract protected function prepareStatement(string $query): StatementInterface; /** * Alias for quote method * * @param array|string $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * * @return string The quoted input string. * * @since 1.0 */ public function q($text, $escape = true) { return $this->quote($text, $escape); } /** * Quotes and optionally escapes a string to database requirements for use in database queries. * * @param array|string $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * * @return array|string The quoted input string. * * @since 1.0 */ public function quote($text, $escape = true) { if (\is_array($text)) { foreach ($text as $k => $v) { $text[$k] = $this->quote($v, $escape); } return $text; } return '\'' . ($escape ? $this->escape($text) : $text) . '\''; } /** * Quotes a binary string to database requirements for use in database queries. * * @param string $data A binary string to quote. * * @return string The binary quoted input string. * * @since 1.7.0 */ public function quoteBinary($data) { // SQL standard syntax for hexadecimal literals return "X'" . bin2hex($data) . "'"; } /** * Replace special placeholder representing binary field with the original string. * * @param string|resource $data Encoded string or resource. * * @return string The original string. * * @since 1.7.0 */ public function decodeBinary($data) { return $data; } /** * Alias for quoteName method * * @param array|string $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. * Each type supports dot-notation name. * @param array|string $as The AS query part associated to $name. It can be string or array, in latter case it has to be * same length of $name; if is null there will not be any AS part for string or array element. * * @return array|string The quote wrapped name, same type of $name. * * @since 1.0 */ public function qn($name, $as = null) { return $this->quoteName($name, $as); } /** * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection * risks and reserved word conflicts. * * @param array|string $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. * Each type supports dot-notation name. * @param array|string $as The AS query part associated to $name. It can be string or array, in latter case it has to be * same length of $name; if is null there will not be any AS part for string or array element. * * @return array|string The quote wrapped name, same type of $name. * * @since 1.0 */ public function quoteName($name, $as = null) { if (\is_string($name)) { $name = $this->quoteNameString($name); if ($as !== null) { $name .= ' AS ' . $this->quoteNameString($as, true); } return $name; } $fin = []; if ($as === null) { foreach ($name as $str) { $fin[] = $this->quoteName($str); } } elseif (\is_array($name) && (\count($name) === \count($as))) { $count = \count($name); for ($i = 0; $i < $count; $i++) { $fin[] = $this->quoteName($name[$i], $as[$i]); } } return $fin; } /** * Quote string coming from quoteName call. * * @param string $name Identifier name to be quoted. * @param boolean $asSinglePart Treat the name as a single part of the identifier. * * @return string Quoted identifier string. * * @since 1.7.0 */ protected function quoteNameString($name, $asSinglePart = false) { $q = $this->nameQuote . $this->nameQuote; // Double quote reserved keyword $name = str_replace($q[1], $q[1] . $q[1], $name); if ($asSinglePart) { return $q[0] . $name . $q[1]; } return $q[0] . str_replace('.', "$q[1].$q[0]", $name) . $q[1]; } /** * Quote strings coming from quoteName call. * * @param array $strArr Array of strings coming from quoteName dot-explosion. * * @return string Dot-imploded string of quoted parts. * * @since 1.0 * @deprecated 2.0 Use quoteNameString instead */ protected function quoteNameStr($strArr) { $parts = []; foreach ($strArr as $part) { if ($part === null) { continue; } $parts[] = $this->quoteNameString($part, true); } return implode('.', $parts); } /** * This function replaces a string identifier with the configured table prefix. * * @param string $sql The SQL statement to prepare. * @param string $prefix The table prefix. * * @return string The processed SQL statement. * * @since 1.0 */ public function replacePrefix($sql, $prefix = '#__') { $escaped = false; $startPos = 0; $quoteChar = ''; $literal = ''; $sql = trim($sql); $n = \strlen($sql); while ($startPos < $n) { $ip = strpos($sql, $prefix, $startPos); if ($ip === false) { break; } $j = strpos($sql, "'", $startPos); $k = strpos($sql, '"', $startPos); if (($k !== false) && (($k < $j) || ($j === false))) { $quoteChar = '"'; $j = $k; } else { $quoteChar = "'"; } if ($j === false) { $j = $n; } $literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos)); $startPos = $j; $j = $startPos + 1; if ($j >= $n) { break; } // Quote comes first, find end of quote while (true) { $k = strpos($sql, $quoteChar, $j); $escaped = false; if ($k === false) { break; } $l = $k - 1; while ($l >= 0 && $sql[$l] === '\\') { $l--; $escaped = !$escaped; } if ($escaped) { $j = $k + 1; continue; } break; } if ($k === false) { // Error in the query - no end quote; ignore it break; } $literal .= substr($sql, $startPos, $k - $startPos + 1); $startPos = $k + 1; } if ($startPos < $n) { $literal .= substr($sql, $startPos, $n - $startPos); } return $literal; } /** * Get the query monitor. * * @return QueryMonitorInterface|null The query monitor or null if not set. * * @since 2.0.0 */ public function getMonitor() { return $this->monitor; } /** * Set a query monitor. * * @param QueryMonitorInterface|null $monitor The query monitor. * * @return $this * * @since 2.0.0 */ public function setMonitor(QueryMonitorInterface $monitor = null) { $this->monitor = $monitor; return $this; } /** * Sets the SQL statement string for later execution. * * @param string|QueryInterface $query The SQL statement to set either as a Query object or a string. * @param integer $offset The affected row offset to set. {@deprecated 3.0 Use LimitableInterface::setLimit() instead} * @param integer $limit The maximum affected rows to set. {@deprecated 3.0 Use LimitableInterface::setLimit() instead} * * @return $this * * @since 1.0 * @throws \InvalidArgumentException */ public function setQuery($query, $offset = 0, $limit = 0) { $this->connect(); $this->freeResult(); if (\is_string($query)) { // Allows taking advantage of bound variables in a direct query: $query = $this->createQuery()->setQuery($query); } elseif (!($query instanceof QueryInterface)) { throw new \InvalidArgumentException( sprintf( 'A query must be a string or a %s instance, a %s was given.', QueryInterface::class, \gettype($query) === 'object' ? (\get_class($query) . ' instance') : \gettype($query) ) ); } if ($offset > 0 || $limit > 0) { trigger_deprecation( 'joomla/database', '2.0.0', 'The "$offset" and "$limit" arguments of %s() are deprecated and will be removed in 3.0, use %s::setLimit() instead.', __METHOD__, QueryInterface::class ); } // Check for values set on the query object and use those if there is a zero value passed here if ($limit === 0 && $query->limit > 0) { $limit = $query->limit; } if ($offset === 0 && $query->offset > 0) { $offset = $query->offset; } $query->setLimit($limit, $offset); $sql = $this->replacePrefix((string) $query); $this->statement = $this->prepareStatement($sql); $this->sql = $query; $this->limit = (int) max(0, $limit); $this->offset = (int) max(0, $offset); return $this; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. * * @since 1.0 */ abstract public function setUtf(); /** * Method to truncate a table. * * @param string $table The table to truncate * * @return void * * @since 1.0 * @throws \RuntimeException */ public function truncateTable($table) { $this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table)) ->execute(); } /** * Updates a row in a table based on an object's properties. * * @param string $table The name of the database table to update. * @param object $object A reference to an object whose public properties match the table fields. * @param array|string $key The name of the primary key. * @param boolean $nulls True to update null fields or false to ignore them. * * @return boolean True on success. * * @since 1.0 * @throws \RuntimeException */ public function updateObject($table, &$object, $key, $nulls = false) { $fields = []; $where = []; $tableColumns = $this->getTableColumns($table); if (\is_string($key)) { $key = [$key]; } if (\is_object($key)) { $key = (array) $key; } // Create the base update statement. $statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s'; // Iterate over the object variables to build the query fields/value pairs. foreach (get_object_vars($object) as $k => $v) { // Skip columns that don't exist in the table. if (!\array_key_exists($k, $tableColumns)) { continue; } // Only process scalars that are not internal fields. if (\is_array($v) || \is_object($v) || $k[0] === '_') { continue; } // Set the primary key to the WHERE clause instead of a field to update. if (\in_array($k, $key, true)) { $where[] = $this->quoteName($k) . ($v === null ? ' IS NULL' : ' = ' . $this->quote($v)); continue; } // Prepare and sanitize the fields and values for the database query. if ($v === null) { // If the value is null and we want to update nulls then set it. if ($nulls) { $val = 'NULL'; } else { // If the value is null and we do not want to update nulls then ignore this field. continue; } } else { // The field is not null so we prep it for update. $val = $this->quote($v); } // Add the field to be updated. $fields[] = $this->quoteName($k) . '=' . $val; } // We don't have any fields to update. if (empty($fields)) { return true; } // Set the query and execute the update. $this->setQuery(sprintf($statement, implode(',', $fields), implode(' AND ', $where)))->execute(); return true; } } PK �9�\"�d�� � src/FetchMode.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Class defining the fetch mode for prepared statements * * The values of the constants in this class match the `PDO::FETCH_*` constants. * * @since 2.0.0 */ final class FetchMode { /** * Specifies that the fetch method shall return each row as an array indexed by column name as returned in the corresponding result set. * * If the result set contains multiple columns with the same name, the statement returns only a single value per column name. * * @var integer * @since 2.0.0 * @see \PDO::FETCH_ASSOC */ public const ASSOCIATIVE = 2; /** * Specifies that the fetch method shall return each row as an array indexed by column number as returned in the corresponding result set, * starting at column 0. * * @var integer * @since 2.0.0 * @see \PDO::FETCH_NUM */ public const NUMERIC = 3; /** * Specifies that the fetch method shall return each row as an array indexed by both column name and number as returned in the corresponding * result set, starting at column 0. * * @var integer * @since 2.0.0 * @see \PDO::FETCH_BOTH */ public const MIXED = 4; /** * Specifies that the fetch method shall return each row as an object with property names that correspond to the column names returned in the * result set. * * @var integer * @since 2.0.0 * @see \PDO::FETCH_OBJ */ public const STANDARD_OBJECT = 5; /** * Specifies that the fetch method shall return only a single requested column from the next row in the result set. * * @var integer * @since 2.0.0 * @see \PDO::FETCH_COLUMN */ public const COLUMN = 7; /** * Specifies that the fetch method shall return a new instance of the requested class, mapping the columns to named properties in the class. * * @var integer * @since 2.0.0 * @see \PDO::FETCH_CLASS */ public const CUSTOM_OBJECT = 8; /** * Private constructor to prevent instantiation of this class * * @since 2.0.0 */ private function __construct() { } } PK �9�\�h�� � src/Mysql/MysqlQuery.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Mysql; use Joomla\Database\Pdo\PdoQuery; use Joomla\Database\Query\MysqlQueryBuilder; /** * MySQL Query Building Class. * * @since 1.0 */ class MysqlQuery extends PdoQuery { use MysqlQueryBuilder; /** * The list of zero or null representation of a datetime. * * @var array * @since 2.0.0 */ protected $nullDatetimeList = ['0000-00-00 00:00:00', '1000-01-01 00:00:00']; } PK �9�\�m~ Q Q src/Mysql/MysqlDriver.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Mysql; use Joomla\Database\Exception\ConnectionFailureException; use Joomla\Database\Pdo\PdoDriver; use Joomla\Database\UTF8MB4SupportInterface; /** * MySQL database driver supporting PDO based connections * * @link https://www.php.net/manual/en/ref.pdo-mysql.php * @since 1.0 */ class MysqlDriver extends PdoDriver implements UTF8MB4SupportInterface { /** * The name of the database driver. * * @var string * @since 1.0 */ public $name = 'mysql'; /** * The character(s) used to quote SQL statement names such as table names or field names, etc. * * If a single character string the same character is used for both sides of the quoted name, else the first character will be used for the * opening quote and the second for the closing quote. * * @var string * @since 1.0 */ protected $nameQuote = '`'; /** * The null or zero representation of a timestamp for the database driver. * * @var string * @since 1.0 */ protected $nullDate = '0000-00-00 00:00:00'; /** * True if the database engine supports UTF-8 Multibyte (utf8mb4) character encoding. * * @var boolean * @since 1.4.0 */ protected $utf8mb4 = false; /** * True if the database engine is MariaDB. * * @var boolean * @since 2.0.0 */ protected $mariadb = false; /** * The minimum supported database version. * * @var string * @since 1.0 */ protected static $dbMinimum = '5.6'; /** * The minimum supported MariaDB database version. * * @var string * @since 2.0.0 */ protected static $dbMinMariadb = '10.0'; /** * The default cipher suite for TLS connections. * * @var array * @since 2.0.0 */ protected static $defaultCipherSuite = [ 'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'AES128-CBC-SHA256', 'AES256-CBC-SHA384', 'DES-CBC3-SHA', ]; /** * The default charset. * * @var string * @since 2.0.0 */ public $charset = 'utf8'; /** * Constructor. * * @param array $options Array of database options with keys: host, user, password, database, select. * * @since 1.0 */ public function __construct(array $options) { /** * sql_mode to MySql 5.7.8+ default strict mode minus ONLY_FULL_GROUP_BY * * @link https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-8.html#mysqld-5-7-8-sql-mode */ $sqlModes = [ 'STRICT_TRANS_TABLES', 'ERROR_FOR_DIVISION_BY_ZERO', 'NO_ENGINE_SUBSTITUTION', ]; // Get some basic values from the options. $options['driver'] = 'mysql'; $options['charset'] = $options['charset'] ?? 'utf8'; $options['sqlModes'] = isset($options['sqlModes']) ? (array) $options['sqlModes'] : $sqlModes; $this->charset = $options['charset']; /* * Pre-populate the UTF-8 Multibyte compatibility flag. Unfortunately PDO won't report the server version unless we're connected to it, * and we cannot connect to it unless we know if it supports utf8mb4, which requires us knowing the server version. Because of this * chicken and egg issue, we _assume_ it's supported and we'll just catch any problems at connection time. */ $this->utf8mb4 = $options['charset'] === 'utf8mb4'; // Finalize initialisation. parent::__construct($options); } /** * Connects to the database if needed. * * @return void Returns void if the database connected successfully. * * @since 1.0 * @throws \RuntimeException */ public function connect() { if ($this->getConnection()) { return; } // For SSL/TLS connection encryption. if ($this->options['ssl'] !== [] && $this->options['ssl']['enable'] === true) { $sslContextIsNull = true; // If customised, add cipher suite, ca file path, ca path, private key file path and certificate file path to PDO driver options. foreach (['cipher', 'ca', 'capath', 'key', 'cert'] as $key => $value) { if ($this->options['ssl'][$value] !== null) { $this->options['driverOptions'][constant('\PDO::MYSQL_ATTR_SSL_' . strtoupper($value))] = $this->options['ssl'][$value]; $sslContextIsNull = false; } } // PDO, if no cipher, ca, capath, cert and key are set, can't start TLS one-way connection, set a common ciphers suite to force it. if ($sslContextIsNull === true) { $this->options['driverOptions'][\PDO::MYSQL_ATTR_SSL_CIPHER] = implode(':', static::$defaultCipherSuite); } // If customised, for capable systems (PHP 7.0.14+ and 7.1.4+) verify certificate chain and Common Name to driver options. if ($this->options['ssl']['verify_server_cert'] !== null && defined('\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) { $this->options['driverOptions'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $this->options['ssl']['verify_server_cert']; } } try { // Try to connect to MySQL parent::connect(); } catch (ConnectionFailureException $e) { // If the connection failed, but not because of the wrong character set, then bubble up the exception. if (!$this->utf8mb4) { throw $e; } /* * Otherwise, try connecting again without using utf8mb4 and see if maybe that was the problem. If the connection succeeds, then we * will have learned that the client end of the connection does not support utf8mb4. */ $this->utf8mb4 = false; $this->options['charset'] = 'utf8'; parent::connect(); } $serverVersion = $this->getVersion(); $this->mariadb = stripos($serverVersion, 'mariadb') !== false; if ($this->utf8mb4) { // At this point we know the client supports utf8mb4. Now we must check if the server supports utf8mb4 as well. $this->utf8mb4 = version_compare($serverVersion, '5.5.3', '>='); if ($this->mariadb && version_compare($serverVersion, '10.0.0', '<')) { $this->utf8mb4 = false; } if (!$this->utf8mb4) { // Reconnect with the utf8 character set. parent::disconnect(); $this->options['charset'] = 'utf8'; parent::connect(); } } // If needed, set the sql modes. if ($this->options['sqlModes'] !== []) { $this->connection->query('SET @@SESSION.sql_mode = \'' . implode(',', $this->options['sqlModes']) . '\';'); } $this->setOption(\PDO::ATTR_EMULATE_PREPARES, true); } /** * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. * * Used when the server doesn't support UTF-8 Multibyte. * * @param string $query The query to convert * * @return string The converted query * * @since 1.4.0 */ public function convertUtf8mb4QueryToUtf8($query) { if ($this->hasUTF8mb4Support()) { return $query; } // If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert $beginningOfQuery = substr($query, 0, 12); $beginningOfQuery = strtoupper($beginningOfQuery); if (!\in_array($beginningOfQuery, ['ALTER TABLE ', 'CREATE TABLE'], true)) { return $query; } // Replace utf8mb4 with utf8 return str_replace('utf8mb4', 'utf8', $query); } /** * Test to see if the MySQL connector is available. * * @return boolean True on success, false otherwise. * * @since 1.0 */ public static function isSupported() { return class_exists('\\PDO') && \in_array('mysql', \PDO::getAvailableDrivers(), true); } /** * Select a database for use. * * @param string $database The name of the database to select for use. * * @return boolean * * @since 1.0 * @throws \RuntimeException */ public function select($database) { $this->connect(); $this->setQuery('USE ' . $this->quoteName($database)) ->execute(); return true; } /** * Return the query string to alter the database character set. * * @param string $dbName The database name * * @return string The query that alter the database query string * * @since 2.0.0 */ public function getAlterDbCharacterSet($dbName) { $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `' . $charset . '`'; } /** * Method to get the database collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database (string) or boolean false if not supported. * * @since 1.0 * @throws \RuntimeException */ public function getCollation() { $this->connect(); return $this->setQuery('SELECT @@collation_database;')->loadResult(); } /** * Method to get the database connection collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database connection (string) or boolean false if not supported. * * @since 1.6.0 * @throws \RuntimeException */ public function getConnectionCollation() { $this->connect(); return $this->setQuery('SELECT @@collation_connection;')->loadResult(); } /** * Method to get the database encryption details (cipher and protocol) in use. * * @return string The database encryption details. * * @since 2.0.0 * @throws \RuntimeException */ public function getConnectionEncryption(): string { $this->connect(); $variables = $this->setQuery('SHOW SESSION STATUS WHERE `Variable_name` IN (\'Ssl_version\', \'Ssl_cipher\')') ->loadObjectList('Variable_name'); if (!empty($variables['Ssl_cipher']->Value)) { return $variables['Ssl_version']->Value . ' (' . $variables['Ssl_cipher']->Value . ')'; } return ''; } /** * Method to test if the database TLS connections encryption are supported. * * @return boolean Whether the database supports TLS connections encryption. * * @since 2.0.0 */ public function isConnectionEncryptionSupported(): bool { $this->connect(); $variables = $this->setQuery('SHOW SESSION VARIABLES WHERE `Variable_name` IN (\'have_ssl\')')->loadObjectList('Variable_name'); return !empty($variables['have_ssl']->Value) && $variables['have_ssl']->Value === 'YES'; } /** * Return the query string to create new Database. * * @param stdClass $options Object used to pass user and database name to database driver. This object must have "db_name" and "db_user" set. * @param boolean $utf True if the database supports the UTF-8 character set. * * @return string The query that creates database * * @since 2.0.0 */ protected function getCreateDatabaseQuery($options, $utf) { if ($utf) { $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; $collation = $charset . '_unicode_ci'; return 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `' . $charset . '` COLLATE `' . $collation . '`'; } return 'CREATE DATABASE ' . $this->quoteName($options->db_name); } /** * Shows the table CREATE statement that creates the given tables. * * @param array|string $tables A table name or a list of table names. * * @return array A list of the create SQL for the tables. * * @since 1.0 * @throws \RuntimeException */ public function getTableCreate($tables) { $this->connect(); // Initialise variables. $result = []; // Sanitize input to an array and iterate over the list. $tables = (array) $tables; foreach ($tables as $table) { $row = $this->setQuery('SHOW CREATE TABLE ' . $this->quoteName($table))->loadRow(); // Populate the result array based on the create statements. $result[$table] = $row[1]; } return $result; } /** * Retrieves field information about a given table. * * @param string $table The name of the database table. * @param boolean $typeOnly True to only return field types. * * @return array An array of fields for the database table. * * @since 1.0 * @throws \RuntimeException */ public function getTableColumns($table, $typeOnly = true) { $this->connect(); $result = []; // Set the query to get the table fields statement. $fields = $this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($table))->loadObjectList(); // If we only want the type as the value add just that to the list. if ($typeOnly) { foreach ($fields as $field) { $result[$field->Field] = preg_replace('/[(0-9)]/', '', $field->Type); } } // If we want the whole field data object add that to the list. else { foreach ($fields as $field) { $result[$field->Field] = $field; } } return $result; } /** * Get the details list of keys for a table. * * @param string $table The name of the table. * * @return array An array of the column specification for the table. * * @since 1.0 * @throws \RuntimeException */ public function getTableKeys($table) { $this->connect(); // Get the details columns information. return $this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table))->loadObjectList(); } /** * Method to get an array of all tables in the database. * * @return array An array of all the tables in the database. * * @since 1.0 * @throws \RuntimeException */ public function getTableList() { $this->connect(); // Set the query to get the tables statement. return $this->setQuery('SHOW TABLES')->loadColumn(); } /** * Get the version of the database connector. * * @return string The database connector version. * * @since 2.0.0 */ public function getVersion() { $this->connect(); $version = $this->getOption(\PDO::ATTR_SERVER_VERSION); if (stripos($version, 'mariadb') !== false) { // MariaDB: Strip off any leading '5.5.5-', if present return preg_replace('/^5\.5\.5-/', '', $version); } return $version; } /** * Get the minimum supported database version. * * @return string * * @since 2.0.0 */ public function getMinimum() { return $this->mariadb ? static::$dbMinMariadb : static::$dbMinimum; } /** * Get the null or zero representation of a timestamp for the database driver. * * @return string * * @since 2.0.0 */ public function getNullDate() { // Check the session sql mode; if (\in_array('NO_ZERO_DATE', $this->options['sqlModes']) !== false) { $this->nullDate = '1000-01-01 00:00:00'; } return $this->nullDate; } /** * Determine whether the database engine support the UTF-8 Multibyte (utf8mb4) character encoding. * * @return boolean True if the database engine supports UTF-8 Multibyte. * * @since 2.0.0 */ public function hasUTF8mb4Support() { return $this->utf8mb4; } /** * Determine if the database engine is MariaDB. * * @return boolean * * @since 2.0.0 */ public function isMariaDb(): bool { $this->connect(); return $this->mariadb; } /** * Locks a table in the database. * * @param string $table The name of the table to unlock. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function lockTable($table) { $this->setQuery('LOCK TABLES ' . $this->quoteName($table) . ' WRITE') ->execute(); return $this; } /** * Renames a table in the database. * * @param string $oldTable The name of the table to be renamed * @param string $newTable The new name for the table. * @param string $backup Not used by MySQL. * @param string $prefix Not used by MySQL. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $this->setQuery('RENAME TABLE ' . $this->quoteName($oldTable) . ' TO ' . $this->quoteName($newTable)) ->execute(); return $this; } /** * Inserts a row into a table based on an object's properties. * * @param string $table The name of the database table to insert into. * @param object $object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. If provided the object property is updated. * * @return boolean * * @since 2.0.0 * @throws \RuntimeException */ public function insertObject($table, &$object, $key = null) { $fields = []; $values = []; $tableColumns = $this->getTableColumns($table); // Iterate over the object variables to build the query fields and values. foreach (get_object_vars($object) as $k => $v) { // Skip columns that don't exist in the table. if (!array_key_exists($k, $tableColumns)) { continue; } // Only process non-null scalars. if (\is_array($v) || \is_object($v) || $v === null) { continue; } // Ignore any internal fields. if ($k[0] === '_') { continue; } // Ignore null datetime fields. if ($tableColumns[$k] === 'datetime' && empty($v)) { continue; } // Ignore null integer fields. if (stristr($tableColumns[$k], 'int') !== false && $v === '') { continue; } // Prepare and sanitize the fields and values for the database query. $fields[] = $this->quoteName($k); $values[] = $this->quote($v); } // Create the base insert statement. $query = $this->createQuery() ->insert($this->quoteName($table)) ->columns($fields) ->values(implode(',', $values)); // Set the query and execute the insert. $this->setQuery($query)->execute(); // Update the primary key if it exists. $id = $this->insertid(); if ($key && $id && \is_string($key)) { $object->$key = $id; } return true; } /** * Method to escape a string for usage in an SQL statement. * * Oracle escaping reference: * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F * * SQLite escaping notes: * http://www.sqlite.org/faq.html#q14 * * Method body is as implemented by the Zend Framework * * Note: Using query objects with bound variables is preferable to the below. * * @param string $text The string to be escaped. * @param boolean $extra Unused optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 1.0 */ public function escape($text, $extra = false) { if (\is_int($text)) { return $text; } if (\is_float($text)) { // Force the dot as a decimal point. return str_replace(',', '.', (string) $text); } $this->connect(); $result = substr($this->connection->quote($text), 1, -1); if ($extra) { $result = addcslashes($result, '%_'); } return $result; } /** * Unlocks tables in the database. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function unlockTables() { $this->setQuery('UNLOCK TABLES') ->execute(); return $this; } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionCommit($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { parent::transactionCommit($toSavepoint); } else { $this->transactionDepth--; } } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionRollback($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { parent::transactionRollback($toSavepoint); } else { $savepoint = 'SP_' . ($this->transactionDepth - 1); $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth--; } } } /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionStart($asSavepoint = false) { $this->connect(); if (!$asSavepoint || !$this->transactionDepth) { parent::transactionStart($asSavepoint); } else { $savepoint = 'SP_' . $this->transactionDepth; $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth++; } } } } PK �9�\�H&]. ]. src/Mysql/MysqlImporter.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Mysql; use Joomla\Database\DatabaseImporter; /** * MySQL Database Importer. * * @since 1.0 */ class MysqlImporter extends DatabaseImporter { /** * Checks if all data and options are in order prior to exporting. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function check() { // Check if the db connector has been set. if (!($this->db instanceof MysqlDriver)) { throw new \RuntimeException('Database connection wrong type.'); } // Check if the tables have been specified. if (empty($this->from)) { throw new \RuntimeException('ERROR: No Tables Specified'); } return $this; } /** * Get the SQL syntax to add a key. * * @param string $table The table name. * @param array $keys An array of the fields pertaining to this key. * * @return string * * @since 1.0 */ protected function getAddKeySql($table, $keys) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD ' . $this->getKeySql($keys); } /** * Get alters for table if there is a difference. * * @param \SimpleXMLElement $structure The XML structure of the table. * * @return array * * @since 1.0 */ protected function getAlterTableSql(\SimpleXMLElement $structure) { // Initialise variables. $table = $this->getRealTableName($structure['name']); $oldFields = $this->db->getTableColumns($table); $oldKeys = $this->db->getTableKeys($table); $alters = []; // Get the fields and keys from the XML that we are aiming for. $newFields = $structure->xpath('field'); $newKeys = $structure->xpath('key'); // Loop through each field in the new structure. foreach ($newFields as $field) { $fName = (string) $field['Field']; if (isset($oldFields[$fName])) { // The field exists, check it's the same. $column = $oldFields[$fName]; // Test whether there is a change. $change = ((string) $field['Type'] !== $column->Type) || ((string) $field['Null'] !== $column->Null) || ((string) $field['Default'] !== $column->Default) || ((string) $field['Extra'] !== $column->Extra); if ($change) { $alters[] = $this->getChangeColumnSql($table, $field); } // Unset this field so that what we have left are fields that need to be removed. unset($oldFields[$fName]); } else { // The field is new. $alters[] = $this->getAddColumnSql($table, $field); } } // Any columns left are orphans foreach ($oldFields as $name => $column) { // Delete the column. $alters[] = $this->getDropColumnSql($table, $name); } // Get the lookups for the old and new keys. $oldLookup = $this->getKeyLookup($oldKeys); $newLookup = $this->getKeyLookup($newKeys); // Loop through each key in the new structure. foreach ($newLookup as $name => $keys) { // Check if there are keys on this field in the existing table. if (isset($oldLookup[$name])) { $same = true; $newCount = \count($newLookup[$name]); $oldCount = \count($oldLookup[$name]); // There is a key on this field in the old and new tables. Are they the same? if ($newCount === $oldCount) { // Need to loop through each key and do a fine grained check. for ($i = 0; $i < $newCount; $i++) { $same = (((string) $newLookup[$name][$i]['Non_unique'] === $oldLookup[$name][$i]->Non_unique) && ((string) $newLookup[$name][$i]['Column_name'] === $oldLookup[$name][$i]->Column_name) && ((string) $newLookup[$name][$i]['Seq_in_index'] === $oldLookup[$name][$i]->Seq_in_index) && ((string) $newLookup[$name][$i]['Collation'] === $oldLookup[$name][$i]->Collation) && ((string) $newLookup[$name][$i]['Sub_part'] === $oldLookup[$name][$i]->Sub_part) && ((string) $newLookup[$name][$i]['Index_type'] === $oldLookup[$name][$i]->Index_type)); /* Debug. echo '<pre>'; echo '<br>Non_unique: '. ((string) $newLookup[$name][$i]['Non_unique'] == $oldLookup[$name][$i]->Non_unique ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Non_unique'].' vs '.$oldLookup[$name][$i]->Non_unique; echo '<br>Column_name: '. ((string) $newLookup[$name][$i]['Column_name'] == $oldLookup[$name][$i]->Column_name ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Column_name'].' vs '.$oldLookup[$name][$i]->Column_name; echo '<br>Seq_in_index: '. ((string) $newLookup[$name][$i]['Seq_in_index'] == $oldLookup[$name][$i]->Seq_in_index ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Seq_in_index'].' vs '.$oldLookup[$name][$i]->Seq_in_index; echo '<br>Collation: '. ((string) $newLookup[$name][$i]['Collation'] == $oldLookup[$name][$i]->Collation ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Collation'].' vs '.$oldLookup[$name][$i]->Collation; echo '<br>Sub_part: '. ((string) $newLookup[$name][$i]['Sub_part'] == $oldLookup[$name][$i]->Sub_part ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Sub_part'].' vs '.$oldLookup[$name][$i]->Sub_part; echo '<br>Index_type: '. ((string) $newLookup[$name][$i]['Index_type'] == $oldLookup[$name][$i]->Index_type ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Index_type'].' vs '.$oldLookup[$name][$i]->Index_type; echo '<br>Same = '.($same ? 'true' : 'false'); echo '</pre>'; */ if (!$same) { // Break out of the loop. No need to check further. break; } } } else { // Count is different, just drop and add. $same = false; } if (!$same) { $alters[] = $this->getDropKeySql($table, $name); $alters[] = $this->getAddKeySql($table, $keys); } // Unset this field so that what we have left are fields that need to be removed. unset($oldLookup[$name]); } else { // This is a new key. $alters[] = $this->getAddKeySql($table, $keys); } } // Any keys left are orphans. foreach ($oldLookup as $name => $keys) { if (strtoupper($name) === 'PRIMARY') { $alters[] = $this->getDropPrimaryKeySql($table); } else { $alters[] = $this->getDropKeySql($table, $name); } } return $alters; } /** * Get the syntax to alter a column. * * @param string $table The name of the database table to alter. * @param \SimpleXMLElement $field The XML definition for the field. * * @return string * * @since 1.0 */ protected function getChangeColumnSql($table, \SimpleXMLElement $field) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' CHANGE COLUMN ' . $this->db->quoteName((string) $field['Field']) . ' ' . $this->getColumnSql($field); } /** * Get the SQL syntax for a single column that would be included in a table create or alter statement. * * @param \SimpleXMLElement $field The XML field definition. * * @return string * * @since 1.0 */ protected function getColumnSql(\SimpleXMLElement $field) { // Initialise variables. // TODO Incorporate into parent class and use $this. $blobs = ['text', 'smalltext', 'mediumtext', 'largetext']; $fName = (string) $field['Field']; $fType = (string) $field['Type']; $fNull = (string) $field['Null']; $fDefault = isset($field['Default']) ? (string) $field['Default'] : null; $fExtra = (string) $field['Extra']; $sql = $this->db->quoteName($fName) . ' ' . $fType; if ($fNull === 'NO') { if ($fDefault === null || \in_array($fType, $blobs, true)) { $sql .= ' NOT NULL'; } else { // TODO Don't quote numeric values. if (stristr($fDefault, 'CURRENT') !== false) { $sql .= ' NOT NULL DEFAULT CURRENT_TIMESTAMP()'; } else { $sql .= ' NOT NULL DEFAULT ' . $this->db->quote($fDefault); } } } else { if ($fDefault === null) { $sql .= ' DEFAULT NULL'; } else { // TODO Don't quote numeric values. $sql .= ' DEFAULT ' . $this->db->quote($fDefault); } } if ($fExtra) { // MySql 8.0 introduces DEFAULT_GENERATED in the extra column and should be replaced with the default value if (stristr($fExtra, 'DEFAULT_GENERATED') !== false) { $sql .= ' ' . strtoupper(str_ireplace('DEFAULT_GENERATED', 'DEFAULT ' . $fDefault, $fExtra)); } else { $sql .= ' ' . strtoupper($fExtra); } } return $sql; } /** * Get the SQL syntax to drop a key. * * @param string $table The table name. * @param string $name The name of the key to drop. * * @return string * * @since 1.0 */ protected function getDropKeySql($table, $name) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP KEY ' . $this->db->quoteName($name); } /** * Get the SQL syntax to drop a key. * * @param string $table The table name. * * @return string * * @since 1.0 */ protected function getDropPrimaryKeySql($table) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP PRIMARY KEY'; } /** * Get the details list of keys for a table. * * @param array $keys An array of objects that comprise the keys for the table. * * @return array The lookup array. array({key name} => array(object, ...)) * * @since 1.0 * @throws \Exception */ protected function getKeyLookup($keys) { // First pass, create a lookup of the keys. $lookup = []; foreach ($keys as $key) { if ($key instanceof \SimpleXMLElement) { $kName = (string) $key['Key_name']; } else { $kName = $key->Key_name; } if (empty($lookup[$kName])) { $lookup[$kName] = []; } $lookup[$kName][] = $key; } return $lookup; } /** * Get the SQL syntax for a key. * * @param array $columns An array of SimpleXMLElement objects comprising the key. * * @return string * * @since 1.0 */ protected function getKeySql($columns) { $kNonUnique = (string) $columns[0]['Non_unique']; $kName = (string) $columns[0]['Key_name']; $prefix = ''; if ($kName === 'PRIMARY') { $prefix = 'PRIMARY '; } elseif ($kNonUnique == 0) { $prefix = 'UNIQUE '; } $kColumns = []; foreach ($columns as $column) { $kLength = ''; if (!empty($column['Sub_part'])) { $kLength = '(' . $column['Sub_part'] . ')'; } $kColumns[] = $this->db->quoteName((string) $column['Column_name']) . $kLength; } return $prefix . 'KEY ' . ($kName !== 'PRIMARY' ? $this->db->quoteName($kName) : '') . ' (' . implode(',', $kColumns) . ')'; } /** * Get the SQL syntax to add a table. * * @param \SimpleXMLElement $table The table information. * * @return string * * @since 2.0.0 * @throws \RuntimeException */ protected function xmlToCreate(\SimpleXMLElement $table) { $existingTables = $this->db->getTableList(); $tableName = (string) $table['name']; if (\in_array($tableName, $existingTables)) { throw new \RuntimeException('The table you are trying to create already exists'); } $createTableStatement = 'CREATE TABLE ' . $this->db->quoteName($tableName) . ' ('; foreach ($table->xpath('field') as $field) { $createTableStatement .= $this->getColumnSql($field) . ', '; } $newLookup = $this->getKeyLookup($table->xpath('key')); foreach ($newLookup as $key) { $createTableStatement .= $this->getKeySql($key) . ', '; } $createTableStatement = rtrim($createTableStatement, ', '); $createTableStatement .= ')'; return $createTableStatement; } } PK �9�\���� � src/Mysql/MysqlExporter.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Mysql; use Joomla\Database\DatabaseExporter; /** * MySQL Database Exporter. * * @since 1.0 */ class MysqlExporter extends DatabaseExporter { /** * Builds the XML data for the tables to export. * * @return string An XML string * * @since 1.0 * @throws \Exception if an error occurs. */ protected function buildXml() { $buffer = []; $buffer[] = '<?xml version="1.0"?>'; $buffer[] = '<mysqldump xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'; $buffer[] = ' <database name="">'; if ($this->options->withStructure) { $buffer = array_merge($buffer, $this->buildXmlStructure()); } if ($this->options->withData) { $buffer = array_merge($buffer, $this->buildXmlData()); } $buffer[] = ' </database>'; $buffer[] = '</mysqldump>'; return implode("\n", $buffer); } /** * Builds the XML structure to export. * * @return array An array of XML lines (strings). * * @since 1.0 * @throws \Exception if an error occurs. */ protected function buildXmlStructure() { $buffer = []; foreach ($this->from as $table) { // Replace the magic prefix if found. $table = $this->getGenericTableName($table); // Get the details columns information. $fields = $this->db->getTableColumns($table, false); $keys = $this->db->getTableKeys($table); $buffer[] = ' <table_structure name="' . $table . '">'; foreach ($fields as $field) { $buffer[] = ' <field Field="' . $field->Field . '" Type="' . $field->Type . '" Null="' . $field->Null . '" Key="' . $field->Key . '"' . (isset($field->Default) ? ' Default="' . $field->Default . '"' : '') . ' Extra="' . $field->Extra . '"' . ' />'; } foreach ($keys as $key) { $buffer[] = ' <key Table="' . $table . '" Non_unique="' . $key->Non_unique . '" Key_name="' . $key->Key_name . '"' . ' Seq_in_index="' . $key->Seq_in_index . '" Column_name="' . $key->Column_name . '" Collation="' . $key->Collation . '"' . ' Null="' . $key->Null . '" Index_type="' . $key->Index_type . '"' . ' Sub_part="' . $key->Sub_part . '"' . ' Comment="' . htmlspecialchars($key->Comment, \ENT_COMPAT, 'UTF-8') . '"' . ' />'; } $buffer[] = ' </table_structure>'; } return $buffer; } /** * Checks if all data and options are in order prior to exporting. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function check() { // Check if the db connector has been set. if (!($this->db instanceof MysqlDriver)) { throw new \RuntimeException('Database connection wrong type.'); } // Check if the tables have been specified. if (empty($this->from)) { throw new \RuntimeException('ERROR: No Tables Specified'); } return $this; } } PK �9�\��TO� � src/QueryMonitorInterface.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Interface defining a query monitor. * * @since 2.0.0 */ interface QueryMonitorInterface { /** * Act on a query being started. * * @param string $sql The SQL to be executed. * @param object[]|null $boundParams List of bound params, used with the query. * Each item is an object that holds: value, dataType * * @return void * * @since 2.0.0 */ public function startQuery(string $sql, ?array $boundParams = null): void; /** * Act on a query being stopped. * * @return void * * @since 2.0.0 */ public function stopQuery(): void; } PK �9�\�m src/DatabaseIterator.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Joomla Framework Database Driver Class * * @since 1.0 */ class DatabaseIterator implements \Countable, \Iterator { /** * The class of object to create. * * @var string * @since 1.0 */ protected $class; /** * The name of the column to use for the key of the database record. * * @var mixed * @since 1.0 */ private $column; /** * The current database record. * * @var mixed * @since 1.0 */ private $current; /** * A numeric or string key for the current database record. * * @var scalar * @since 1.0 */ private $key; /** * The number of fetched records. * * @var integer * @since 1.0 */ private $fetched = 0; /** * The statement holding the result set to iterate. * * @var StatementInterface * @since 1.0 */ protected $statement; /** * Database iterator constructor. * * @param StatementInterface $statement The statement holding the result set to iterate. * @param string $column An option column to use as the iterator key. * @param string $class The class of object that is returned. * * @since 1.0 * @throws \InvalidArgumentException */ public function __construct(StatementInterface $statement, $column = null, $class = \stdClass::class) { if (!class_exists($class)) { throw new \InvalidArgumentException(sprintf('new %s(*%s*, cursor)', \get_class($this), \gettype($class))); } if ($statement) { $fetchMode = $class === \stdClass::class ? FetchMode::STANDARD_OBJECT : FetchMode::CUSTOM_OBJECT; // PDO doesn't allow extra arguments for \PDO::FETCH_CLASS, so only forward the class for the custom object mode if ($fetchMode === FetchMode::STANDARD_OBJECT) { $statement->setFetchMode($fetchMode); } else { $statement->setFetchMode($fetchMode, $class); } } $this->statement = $statement; $this->class = $class; $this->column = $column; $this->fetched = 0; $this->next(); } /** * Database iterator destructor. * * @since 1.0 */ public function __destruct() { if ($this->statement) { $this->freeResult(); } } /** * Get the number of rows in the result set for the executed SQL given by the cursor. * * @return integer The number of rows in the result set. * * @see Countable::count() * @since 1.0 */ #[\ReturnTypeWillChange] public function count() { if ($this->statement) { return $this->statement->rowCount(); } return 0; } /** * The current element in the iterator. * * @return object * * @see Iterator::current() * @since 1.0 */ #[\ReturnTypeWillChange] public function current() { return $this->current; } /** * The key of the current element in the iterator. * * @return scalar * * @see Iterator::key() * @since 1.0 */ #[\ReturnTypeWillChange] public function key() { return $this->key; } /** * Moves forward to the next result from the SQL query. * * @return void * * @see Iterator::next() * @since 1.0 */ #[\ReturnTypeWillChange] public function next() { // Set the default key as being the number of fetched object $this->key = $this->fetched; // Try to get an object $this->current = $this->fetchObject(); // If an object has been found if ($this->current) { // Set the key as being the indexed column (if it exists) if ($this->column && isset($this->current->{$this->column})) { $this->key = $this->current->{$this->column}; } // Update the number of fetched object $this->fetched++; } } /** * Rewinds the iterator. * * This iterator cannot be rewound. * * @return void * * @see Iterator::rewind() * @since 1.0 */ #[\ReturnTypeWillChange] public function rewind() { } /** * Checks if the current position of the iterator is valid. * * @return boolean * * @see Iterator::valid() * @since 1.0 */ #[\ReturnTypeWillChange] public function valid() { return (boolean) $this->current; } /** * Method to fetch a row from the result set cursor as an object. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 1.0 */ protected function fetchObject() { if ($this->statement) { return $this->statement->fetch(); } return false; } /** * Method to free up the memory used for the result set. * * @return void * * @since 1.0 */ protected function freeResult() { if ($this->statement) { $this->statement->closeCursor(); } } } PK �9�\x eG& & src/Service/DatabaseProvider.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Service; use Joomla\Database\DatabaseDriver; use Joomla\Database\DatabaseFactory; use Joomla\Database\DatabaseInterface; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; /** * Database service provider * * @since 2.0.0 */ class DatabaseProvider implements ServiceProviderInterface { /** * Registers the service provider with a DI container. * * @param Container $container The DI container. * * @return void * * @since 2.0.0 */ public function register(Container $container) { $container->alias(DatabaseInterface::class, DatabaseDriver::class) ->share( DatabaseDriver::class, function (Container $container) { /** @var \Joomla\Registry\Registry $config */ $config = $container->get('config'); $options = (array) $config->get('database'); return $container->get(DatabaseFactory::class)->getDriver($options['driver'], $options); } ); $container->share( DatabaseFactory::class, function (Container $container) { return new DatabaseFactory; } ); } } PK �9�\X��R8 8 src/DatabaseAwareInterface.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2022 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Defines the interface for a DatabaseInterface aware class. * * @since 2.1.0 */ interface DatabaseAwareInterface { /** * Set the database. * * @param DatabaseInterface $db The database. * * @return void * * @since 2.1.0 */ public function setDatabase(DatabaseInterface $db): void; } PK �9�\����s s src/Mysqli/MysqliDriver.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Mysqli; use Joomla\Database\DatabaseDriver; use Joomla\Database\DatabaseEvents; use Joomla\Database\Event\ConnectionEvent; use Joomla\Database\Exception\ConnectionFailureException; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\Exception\PrepareStatementFailureException; use Joomla\Database\Exception\UnsupportedAdapterException; use Joomla\Database\StatementInterface; use Joomla\Database\UTF8MB4SupportInterface; /** * MySQLi Database Driver * * @link https://www.php.net/manual/en/book.mysqli.php * @since 1.0 */ class MysqliDriver extends DatabaseDriver implements UTF8MB4SupportInterface { /** * The database connection resource. * * @var \mysqli * @since 1.0 */ protected $connection; /** * The name of the database driver. * * @var string * @since 1.0 */ public $name = 'mysqli'; /** * The character(s) used to quote SQL statement names such as table names or field names, etc. * * If a single character string the same character is used for both sides of the quoted name, else the first character will be used for the * opening quote and the second for the closing quote. * * @var string * @since 1.0 */ protected $nameQuote = '`'; /** * The null or zero representation of a timestamp for the database driver. * * @var string * @since 1.0 */ protected $nullDate = '0000-00-00 00:00:00'; /** * True if the database engine supports UTF-8 Multibyte (utf8mb4) character encoding. * * @var boolean * @since 1.4.0 */ protected $utf8mb4 = false; /** * True if the database engine is MariaDB. * * @var boolean * @since 2.0.0 */ protected $mariadb = false; /** * The minimum supported MySQL database version. * * @var string * @since 1.0 */ protected static $dbMinimum = '5.6'; /** * The minimum supported MariaDB database version. * * @var string * @since 2.0.0 */ protected static $dbMinMariadb = '10.0'; /** * Constructor. * * @param array $options List of options used to configure the connection * * @since 1.0 */ public function __construct(array $options) { /** * sql_mode to MySql 5.7.8+ default strict mode minus ONLY_FULL_GROUP_BY * * @link https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-8.html#mysqld-5-7-8-sql-mode */ $sqlModes = [ 'STRICT_TRANS_TABLES', 'ERROR_FOR_DIVISION_BY_ZERO', 'NO_ENGINE_SUBSTITUTION', ]; // Get some basic values from the options. $options['host'] = $options['host'] ?? 'localhost'; $options['user'] = $options['user'] ?? 'root'; $options['password'] = $options['password'] ?? ''; $options['database'] = $options['database'] ?? ''; $options['select'] = isset($options['select']) ? (bool) $options['select'] : true; $options['port'] = isset($options['port']) ? (int) $options['port'] : null; $options['socket'] = $options['socket'] ?? null; $options['utf8mb4'] = isset($options['utf8mb4']) ? (bool) $options['utf8mb4'] : false; $options['sqlModes'] = isset($options['sqlModes']) ? (array) $options['sqlModes'] : $sqlModes; $options['ssl'] = isset($options['ssl']) ? $options['ssl'] : []; if ($options['ssl'] !== []) { $options['ssl']['enable'] = isset($options['ssl']['enable']) ? $options['ssl']['enable'] : false; $options['ssl']['cipher'] = isset($options['ssl']['cipher']) ? $options['ssl']['cipher'] : null; $options['ssl']['ca'] = isset($options['ssl']['ca']) ? $options['ssl']['ca'] : null; $options['ssl']['capath'] = isset($options['ssl']['capath']) ? $options['ssl']['capath'] : null; $options['ssl']['key'] = isset($options['ssl']['key']) ? $options['ssl']['key'] : null; $options['ssl']['cert'] = isset($options['ssl']['cert']) ? $options['ssl']['cert'] : null; $options['ssl']['verify_server_cert'] = isset($options['ssl']['verify_server_cert']) ? $options['ssl']['verify_server_cert'] : null; } // Finalize initialisation. parent::__construct($options); } /** * Check if the database server is responsive. * * @param string $host The host name or IP address. * @param int $port The port number. Optional; default is the MySQL default. * @param int $initialWaitInSeconds The number of seconds to wait before pinging the server. Optional; default is 0 seconds. * @param int $intervalWaitInSeconds The number of seconds to wait between pinging the server. Optional; default is 3 seconds. * @param int $timeoutInSeconds The timeout in seconds for the server to respond. Optional; default is 1 second. * @param int $retries The number of retries. Optional; default is 3. * * @return boolean * @todo This should maybe be moved to the parent class. * */ public function healthCheck(string $host, int $port = 3306, int $initialWaitInSeconds = 0, int $intervalWaitInSeconds = 3, int $timeoutInSeconds = 1, int $retries = 3 ): bool { sleep($initialWaitInSeconds); for ($i = 0; $i < $retries; $i++) { $file = @fsockopen($host, $port, $errno, $errstr, $timeoutInSeconds); if ($file) { fclose($file); return true; } sleep($intervalWaitInSeconds); } return false; } /** * Connects to the database if needed. * * @return void Returns void if the database connected successfully. * * @since 1.0 * @throws \RuntimeException */ public function connect() { if ($this->connection) { return; } // Make sure the MySQLi extension for PHP is installed and enabled. if (!static::isSupported()) { throw new UnsupportedAdapterException('The MySQLi extension is not available'); } /* * Unlike mysql_connect(), mysqli_connect() takes the port and socket as separate arguments. Therefore, we * have to extract them from the host string. */ $port = isset($this->options['port']) ? $this->options['port'] : 3306; if (preg_match('/^unix:(?P<socket>[^:]+)$/', $this->options['host'], $matches)) { // UNIX socket URI, e.g. 'unix:/path/to/unix/socket.sock' $this->options['host'] = null; $this->options['socket'] = $matches['socket']; $this->options['port'] = null; } elseif (preg_match( '/^(?P<host>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(:(?P<port>.+))?$/', $this->options['host'], $matches )) { // It's an IPv4 address with or without port $this->options['host'] = $matches['host']; if (!empty($matches['port'])) { $port = $matches['port']; } } elseif (preg_match('/^(?P<host>\[.*\])(:(?P<port>.+))?$/', $this->options['host'], $matches)) { // We assume square-bracketed IPv6 address with or without port, e.g. [fe80:102::2%eth1]:3306 $this->options['host'] = $matches['host']; if (!empty($matches['port'])) { $port = $matches['port']; } } elseif (preg_match('/^(?P<host>(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P<port>[^:]+))?$/i', $this->options['host'], $matches)) { // Named host (e.g example.com or localhost) with or without port $this->options['host'] = $matches['host']; if (!empty($matches['port'])) { $port = $matches['port']; } } elseif (preg_match('/^:(?P<port>[^:]+)$/', $this->options['host'], $matches)) { // Empty host, just port, e.g. ':3306' $this->options['host'] = 'localhost'; $port = $matches['port']; } // ... else we assume normal (naked) IPv6 address, so host and port stay as they are or default // Get the port number or socket name if (is_numeric($port)) { $this->options['port'] = (int) $port; } else { $this->options['socket'] = $port; } $this->connection = mysqli_init(); $connectionFlags = 0; // For SSL/TLS connection encryption. if ($this->options['ssl'] !== [] && $this->options['ssl']['enable'] === true) { $connectionFlags += MYSQLI_CLIENT_SSL; // Verify server certificate is only available in PHP 5.6.16+. See https://www.php.net/ChangeLog-5.php#5.6.16 if (isset($this->options['ssl']['verify_server_cert'])) { // New constants in PHP 5.6.16+. See https://www.php.net/ChangeLog-5.php#5.6.16 if ($this->options['ssl']['verify_server_cert'] === true && defined('MYSQLI_CLIENT_SSL_VERIFY_SERVER_CERT')) { $connectionFlags += MYSQLI_CLIENT_SSL_VERIFY_SERVER_CERT; } elseif ($this->options['ssl']['verify_server_cert'] === false && defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT')) { $connectionFlags += MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; } elseif (defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT')) { $this->connection->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, $this->options['ssl']['verify_server_cert']); } } // Add SSL/TLS options only if changed. $this->connection->ssl_set( $this->options['ssl']['key'], $this->options['ssl']['cert'], $this->options['ssl']['ca'], $this->options['ssl']['capath'], $this->options['ssl']['cipher'] ); } // Attempt to connect to the server, use error suppression to silence warnings and allow us to throw an Exception separately. $connected = @$this->connection->real_connect( $this->options['host'], $this->options['user'], $this->options['password'], null, $this->options['port'], $this->options['socket'], $connectionFlags ); if (!$connected) { throw new ConnectionFailureException( 'Could not connect to database: ' . $this->connection->connect_error, $this->connection->connect_errno ); } // If needed, set the sql modes. if ($this->options['sqlModes'] !== []) { $this->connection->query('SET @@SESSION.sql_mode = \'' . implode(',', $this->options['sqlModes']) . '\';'); } // And read the real sql mode to mitigate changes in mysql > 5.7.+ $this->options['sqlModes'] = explode(',', $this->setQuery('SELECT @@SESSION.sql_mode;')->loadResult()); // If auto-select is enabled select the given database. if ($this->options['select'] && !empty($this->options['database'])) { $this->select($this->options['database']); } $this->mariadb = stripos($this->connection->server_info, 'mariadb') !== false; $this->utf8mb4 = $this->serverClaimsUtf8mb4Support(); // Set character sets (needed for MySQL 4.1.2+ and MariaDB). $this->utf = $this->setUtf(); $this->dispatchEvent(new ConnectionEvent(DatabaseEvents::POST_CONNECT, $this)); } /** * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. * * Used when the server doesn't support UTF-8 Multibyte. * * @param string $query The query to convert * * @return string The converted query * * @since 1.4.0 */ public function convertUtf8mb4QueryToUtf8($query) { if ($this->utf8mb4) { return $query; } // If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert if (!preg_match('/^(ALTER|CREATE)\s+TABLE\s+/i', $query)) { return $query; } // Don't do preg replacement if string does not exist if (stripos($query, 'utf8mb4') === false) { return $query; } // Replace utf8mb4 with utf8 if not within single or double quotes or name quotes return preg_replace('/[`"\'][^`"\']*[`"\'](*SKIP)(*FAIL)|utf8mb4/i', 'utf8', $query); } /** * Disconnects the database. * * @return void * * @since 1.0 */ public function disconnect() { // Close the connection. if (\is_callable($this->connection, 'close')) { $this->connection->close(); } parent::disconnect(); } /** * Method to escape a string for usage in an SQL statement. * * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 1.0 */ public function escape($text, $extra = false) { if (\is_int($text)) { return $text; } if (\is_float($text)) { // Force the dot as a decimal point. return str_replace(',', '.', (string) $text); } $this->connect(); $result = $this->connection->real_escape_string((string) $text); if ($extra) { $result = addcslashes($result, '%_'); } return $result; } /** * Test to see if the MySQLi connector is available. * * @return boolean True on success, false otherwise. * * @since 1.0 */ public static function isSupported() { return \extension_loaded('mysqli'); } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. * * @since 1.0 */ public function connected() { if (\is_object($this->connection)) { return $this->connection->ping(); } return false; } /** * Return the query string to alter the database character set. * * @param string $dbName The database name * * @return string The query that alter the database query string * * @since 2.0.0 */ public function getAlterDbCharacterSet($dbName) { $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `' . $charset . '`'; } /** * Method to get the database collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database (string) or boolean false if not supported. * * @since 1.0 * @throws \RuntimeException */ public function getCollation() { $this->connect(); return $this->setQuery('SELECT @@collation_database;')->loadResult(); } /** * Method to get the database connection collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database connection (string) or boolean false if not supported. * * @since 1.6.0 * @throws \RuntimeException */ public function getConnectionCollation() { $this->connect(); return $this->setQuery('SELECT @@collation_connection;')->loadResult(); } /** * Method to get the database encryption details (cipher and protocol) in use. * * @return string The database encryption details. * * @since 2.0.0 * @throws \RuntimeException */ public function getConnectionEncryption(): string { $this->connect(); $variables = $this->setQuery('SHOW SESSION STATUS WHERE `Variable_name` IN (\'Ssl_version\', \'Ssl_cipher\')') ->loadObjectList('Variable_name'); if (!empty($variables['Ssl_cipher']->Value)) { return $variables['Ssl_version']->Value . ' (' . $variables['Ssl_cipher']->Value . ')'; } return ''; } /** * Method to test if the database TLS connections encryption are supported. * * @return boolean Whether the database supports TLS connections encryption. * * @since 2.0.0 */ public function isConnectionEncryptionSupported(): bool { $this->connect(); $variables = $this->setQuery('SHOW SESSION VARIABLES WHERE `Variable_name` IN (\'have_ssl\')')->loadObjectList('Variable_name'); return !empty($variables['have_ssl']->Value) && $variables['have_ssl']->Value === 'YES'; } /** * Return the query string to create new Database. * * @param \stdClass $options Object used to pass user and database name to database driver. This object must have "db_name" and "db_user" set. * @param boolean $utf True if the database supports the UTF-8 character set. * * @return string The query that creates database * * @since 2.0.0 */ protected function getCreateDatabaseQuery($options, $utf) { if ($utf) { $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; $collation = $charset . '_unicode_ci'; return 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `' . $charset . '` COLLATE `' . $collation . '`'; } return 'CREATE DATABASE ' . $this->quoteName($options->db_name); } /** * Shows the table CREATE statement that creates the given tables. * * @param mixed $tables A table name or a list of table names. * * @return array A list of the create SQL for the tables. * * @since 1.0 * @throws \RuntimeException */ public function getTableCreate($tables) { $this->connect(); $result = []; // Sanitize input to an array and iterate over the list. $tables = (array) $tables; foreach ($tables as $table) { // Set the query to get the table CREATE statement. $row = $this->setQuery('SHOW CREATE TABLE ' . $this->quoteName($this->escape($table)))->loadRow(); // Populate the result array based on the create statements. $result[$table] = $row[1]; } return $result; } /** * Retrieves field information about a given table. * * @param string $table The name of the database table. * @param boolean $typeOnly True to only return field types. * * @return array An array of fields for the database table. * * @since 1.0 * @throws \RuntimeException */ public function getTableColumns($table, $typeOnly = true) { $this->connect(); $result = []; // Set the query to get the table fields statement. $fields = $this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($this->escape($table)))->loadObjectList(); // If we only want the type as the value add just that to the list. if ($typeOnly) { foreach ($fields as $field) { $result[$field->Field] = preg_replace('/[(0-9)]/', '', $field->Type); } } else { // If we want the whole field data object add that to the list. foreach ($fields as $field) { $result[$field->Field] = $field; } } return $result; } /** * Get the details list of keys for a table. * * @param string $table The name of the table. * * @return array An array of the column specification for the table. * * @since 1.0 * @throws \RuntimeException */ public function getTableKeys($table) { $this->connect(); // Get the details columns information. return $this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table))->loadObjectList(); } /** * Method to get an array of all tables in the database. * * @return array An array of all the tables in the database. * * @since 1.0 * @throws \RuntimeException */ public function getTableList() { $this->connect(); // Set the query to get the tables statement. return $this->setQuery('SHOW TABLES')->loadColumn(); } /** * Get the version of the database connector. * * @return string The database connector version. * * @since 1.0 */ public function getVersion() { $this->connect(); if ($this->mariadb) { // MariaDB: Strip off any leading '5.5.5-', if present return preg_replace('/^5\.5\.5-/', '', $this->connection->server_info); } return $this->connection->server_info; } /** * Get the minimum supported database version. * * @return string * * @since 2.0.0 */ public function getMinimum() { return $this->mariadb ? static::$dbMinMariadb : static::$dbMinimum; } /** * Determine whether the database engine support the UTF-8 Multibyte (utf8mb4) character encoding. * * @return boolean True if the database engine supports UTF-8 Multibyte. * * @since 2.0.0 */ public function hasUTF8mb4Support() { return $this->utf8mb4; } /** * Determine if the database engine is MariaDB. * * @return boolean * * @since 2.0.0 */ public function isMariaDb(): bool { $this->connect(); return $this->mariadb; } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return mixed The value of the auto-increment field from the last inserted row. * If the value is greater than maximal int value, it will return a string. * * @since 1.0 */ public function insertid() { $this->connect(); return $this->connection->insert_id; } /** * Inserts a row into a table based on an object's properties. * * @param string $table The name of the database table to insert into. * @param object $object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. If provided the object property is updated. * * @return boolean * * @since 2.0.0 * @throws \RuntimeException */ public function insertObject($table, &$object, $key = null) { $fields = []; $values = []; $tableColumns = $this->getTableColumns($table); // Iterate over the object variables to build the query fields and values. foreach (get_object_vars($object) as $k => $v) { // Skip columns that don't exist in the table. if (!array_key_exists($k, $tableColumns)) { continue; } // Only process non-null scalars. if (\is_array($v) || \is_object($v) || $v === null) { continue; } // Ignore any internal fields. if ($k[0] === '_') { continue; } // Ignore null datetime fields. if ($tableColumns[$k] === 'datetime' && empty($v)) { continue; } // Ignore null integer fields. if (stristr($tableColumns[$k], 'int') !== false && $v === '') { continue; } // Prepare and sanitize the fields and values for the database query. $fields[] = $this->quoteName($k); $values[] = $this->quote($v); } // Create the base insert statement. $query = $this->createQuery() ->insert($this->quoteName($table)) ->columns($fields) ->values(implode(',', $values)); // Set the query and execute the insert. $this->setQuery($query)->execute(); // Update the primary key if it exists. $id = $this->insertid(); if ($key && $id && \is_string($key)) { $object->$key = $id; } return true; } /** * Locks a table in the database. * * @param string $table The name of the table to unlock. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function lockTable($table) { $this->executeUnpreparedQuery($this->replacePrefix('LOCK TABLES ' . $this->quoteName($table) . ' WRITE')); return $this; } /** * Renames a table in the database. * * @param string $oldTable The name of the table to be renamed * @param string $newTable The new name for the table. * @param string $backup Not used by MySQL. * @param string $prefix Not used by MySQL. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $this->setQuery('RENAME TABLE ' . $oldTable . ' TO ' . $newTable)->execute(); return $this; } /** * Select a database for use. * * @param string $database The name of the database to select for use. * * @return boolean True if the database was successfully selected. * * @since 1.0 * @throws \RuntimeException */ public function select($database) { $this->connect(); if (!$database) { return false; } if (!$this->connection->select_db($database)) { throw new ConnectionFailureException('Could not connect to database.'); } return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. * * @since 1.0 */ public function setUtf() { // If UTF is not supported return false immediately if (!$this->utf) { return false; } // Make sure we're connected to the server $this->connect(); // Which charset should I use, plain utf8 or multibyte utf8mb4? $charset = $this->utf8mb4 && $this->options['utf8mb4'] ? 'utf8mb4' : 'utf8'; $result = @$this->connection->set_charset($charset); /* * If I could not set the utf8mb4 charset then the server doesn't support utf8mb4 despite claiming otherwise. This happens on old MySQL * server versions (less than 5.5.3) using the mysqlnd PHP driver. Since mysqlnd masks the server version and reports only its own we * can not be sure if the server actually does support UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the utf8mb4 charset is * undefined in this case we catch the error and determine that utf8mb4 is not supported! */ if (!$result && $this->utf8mb4 && $this->options['utf8mb4']) { $this->utf8mb4 = false; $result = @$this->connection->set_charset('utf8'); } return $result; } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionCommit($toSavepoint = false) { if (!$toSavepoint || $this->transactionDepth <= 1) { $this->connect(); if ($this->connection->commit()) { $this->transactionDepth = 0; } return; } $this->transactionDepth--; } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionRollback($toSavepoint = false) { if (!$toSavepoint || $this->transactionDepth <= 1) { $this->connect(); if ($this->connection->rollback()) { $this->transactionDepth = 0; } return; } $savepoint = 'SP_' . ($this->transactionDepth - 1); if ($this->executeUnpreparedQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint))) { $this->transactionDepth--; } } /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionStart($asSavepoint = false) { $this->connect(); if (!$asSavepoint || !$this->transactionDepth) { if ($this->connection->begin_transaction()) { $this->transactionDepth = 1; } return; } $savepoint = 'SP_' . $this->transactionDepth; if ($this->connection->savepoint($savepoint)) { $this->transactionDepth++; } } /** * Internal method to execute queries which cannot be run as prepared statements. * * @param string $sql SQL statement to execute. * * @return boolean * * @since 1.5.0 */ protected function executeUnpreparedQuery($sql) { $this->connect(); $cursor = $this->connection->query($sql); // If an error occurred handle it. if (!$cursor) { $errorNum = (int) $this->connection->errno; $errorMsg = (string) $this->connection->error; // Check if the server was disconnected. if (!$this->connected()) { try { // Attempt to reconnect. $this->connection = null; $this->connect(); } catch (ConnectionFailureException $e) { // If connect fails, ignore that exception and throw the normal exception. throw new ExecutionFailureException($sql, $errorMsg, $errorNum); } // Since we were able to reconnect, run the query again. return $this->executeUnpreparedQuery($sql); } // The server was not disconnected. throw new ExecutionFailureException($sql, $errorMsg, $errorNum); } $this->freeResult(); if ($cursor instanceof \mysqli_result) { $cursor->free_result(); } return true; } /** * Prepares a SQL statement for execution * * @param string $query The SQL query to be prepared. * * @return StatementInterface * * @since 2.0.0 * @throws PrepareStatementFailureException */ protected function prepareStatement(string $query): StatementInterface { return new MysqliStatement($this->connection, $query); } /** * Unlocks tables in the database. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function unlockTables() { $this->executeUnpreparedQuery('UNLOCK TABLES'); return $this; } /** * Does the database server claim to have support for UTF-8 Multibyte (utf8mb4) collation? * * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9. * * @return boolean * * @since 1.4.0 */ private function serverClaimsUtf8mb4Support() { $client_version = mysqli_get_client_info(); $server_version = $this->getVersion(); if (version_compare($server_version, '5.5.3', '<')) { return false; } if ($this->mariadb && version_compare($server_version, '10.0.0', '<')) { return false; } if (strpos($client_version, 'mysqlnd') !== false) { $client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version); return version_compare($client_version, '5.0.9', '>='); } return version_compare($client_version, '5.5.3', '>='); } /** * Get the null or zero representation of a timestamp for the database driver. * * @return string * * @since 2.0.0 */ public function getNullDate() { // Check the session sql mode; if (\in_array('NO_ZERO_DATE', $this->options['sqlModes']) !== false) { $this->nullDate = '1000-01-01 00:00:00'; } return $this->nullDate; } } PK �9�\e�Bl� � src/Mysqli/MysqliExporter.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Mysqli; use Joomla\Database\DatabaseExporter; /** * MySQLi Database Exporter. * * @since 1.0 */ class MysqliExporter extends DatabaseExporter { /** * Builds the XML data for the tables to export. * * @return string An XML string * * @since 1.0 * @throws \Exception if an error occurs. */ protected function buildXml() { $buffer = []; $buffer[] = '<?xml version="1.0"?>'; $buffer[] = '<mysqldump xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'; $buffer[] = ' <database name="">'; if ($this->options->withStructure) { $buffer = array_merge($buffer, $this->buildXmlStructure()); } if ($this->options->withData) { $buffer = array_merge($buffer, $this->buildXmlData()); } $buffer[] = ' </database>'; $buffer[] = '</mysqldump>'; return implode("\n", $buffer); } /** * Builds the XML structure to export. * * @return array An array of XML lines (strings). * * @since 1.0 * @throws \Exception if an error occurs. */ protected function buildXmlStructure() { $buffer = []; foreach ($this->from as $table) { // Replace the magic prefix if found. $table = $this->getGenericTableName($table); // Get the details columns information. $fields = $this->db->getTableColumns($table, false); $keys = $this->db->getTableKeys($table); $buffer[] = ' <table_structure name="' . $table . '">'; foreach ($fields as $field) { $buffer[] = ' <field Field="' . $field->Field . '" Type="' . $field->Type . '" Null="' . $field->Null . '" Key="' . $field->Key . '"' . (isset($field->Default) ? ' Default="' . $field->Default . '"' : '') . ' Extra="' . $field->Extra . '"' . ' />'; } foreach ($keys as $key) { $buffer[] = ' <key Table="' . $table . '" Non_unique="' . $key->Non_unique . '" Key_name="' . $key->Key_name . '"' . ' Seq_in_index="' . $key->Seq_in_index . '" Column_name="' . $key->Column_name . '" Collation="' . $key->Collation . '"' . ' Null="' . $key->Null . '" Index_type="' . $key->Index_type . '"' . ' Sub_part="' . $key->Sub_part . '"' . ' Comment="' . htmlspecialchars($key->Comment, \ENT_COMPAT, 'UTF-8') . '"' . ' />'; } $buffer[] = ' </table_structure>'; } return $buffer; } /** * Checks if all data and options are in order prior to exporting. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function check() { // Check if the db connector has been set. if (!($this->db instanceof MysqliDriver)) { throw new \RuntimeException('Database connection wrong type.'); } // Check if the tables have been specified. if (empty($this->from)) { throw new \RuntimeException('ERROR: No Tables Specified'); } return $this; } } PK �9�\RH �9 �9 src/Mysqli/MysqliStatement.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Mysqli; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\Exception\PrepareStatementFailureException; use Joomla\Database\FetchMode; use Joomla\Database\FetchOrientation; use Joomla\Database\ParameterType; use Joomla\Database\StatementInterface; /** * MySQLi Database Statement. * * This class is modeled on \Doctrine\DBAL\Driver\Mysqli\MysqliStatement * * @since 2.0.0 */ class MysqliStatement implements StatementInterface { /** * Values which have been bound to the statement. * * @var array * @since 2.0.0 */ protected $bindedValues; /** * Mapping between named parameters and position in query. * * @var array * @since 2.0.0 */ protected $parameterKeyMapping; /** * Mapping array for parameter types. * * @var array * @since 2.0.0 */ protected $parameterTypeMapping = [ ParameterType::BOOLEAN => 'i', ParameterType::INTEGER => 'i', ParameterType::LARGE_OBJECT => 's', ParameterType::NULL => 's', ParameterType::STRING => 's', ]; /** * Column names from the executed statement. * * @var array|boolean|null * @since 2.0.0 */ protected $columnNames; /** * The database connection resource. * * @var \mysqli * @since 2.0.0 */ protected $connection; /** * The default fetch mode for the statement. * * @var integer * @since 2.0.0 */ protected $defaultFetchStyle = FetchMode::MIXED; /** * The query string being prepared. * * @var string * @since 2.0.0 */ protected $query; /** * Internal tracking flag to set whether there is a result set available for processing * * @var boolean * @since 2.0.0 */ private $result = false; /** * Values which have been bound to the rows of each result set. * * @var array * @since 2.0.0 */ protected $rowBindedValues; /** * The prepared statement. * * @var \mysqli_stmt * @since 2.0.0 */ protected $statement; /** * Bound parameter types. * * @var array * @since 2.0.0 */ protected $typesKeyMapping; /** * Constructor. * * @param \mysqli $connection The database connection resource * @param string $query The query this statement will process * * @since 2.0.0 * @throws PrepareStatementFailureException */ public function __construct(\mysqli $connection, string $query) { $this->connection = $connection; $this->query = $query; $query = $this->prepareParameterKeyMapping($query); $this->statement = $connection->prepare($query); if (!$this->statement) { throw new PrepareStatementFailureException($this->connection->error, $this->connection->errno); } } /** * Replace named parameters with numbered parameters * * @param string $sql The SQL statement to prepare. * * @return string The processed SQL statement. * * @since 2.0.0 */ public function prepareParameterKeyMapping($sql) { $escaped = false; $startPos = 0; $quoteChar = ''; $literal = ''; $mapping = []; $replace = []; $matches = []; $pattern = '/([:][a-zA-Z0-9_]+)/'; if (!preg_match($pattern, $sql, $matches)) { return $sql; } $sql = trim($sql); $n = \strlen($sql); while ($startPos < $n) { if (!preg_match($pattern, $sql, $matches, 0, $startPos)) { break; } $j = strpos($sql, "'", $startPos); $k = strpos($sql, '"', $startPos); if (($k !== false) && (($k < $j) || ($j === false))) { $quoteChar = '"'; $j = $k; } else { $quoteChar = "'"; } if ($j === false) { $j = $n; } // Search for named prepared parameters and replace it with ? and save its position $substring = substr($sql, $startPos, $j - $startPos); if (preg_match_all($pattern, $substring, $matches, PREG_PATTERN_ORDER + PREG_OFFSET_CAPTURE)) { foreach ($matches[0] as $i => $match) { if ($i === 0) { $literal .= substr($substring, 0, $match[1]); } $mapping[$match[0]] = \count($mapping); $endOfPlaceholder = $match[1] + strlen($match[0]); $beginOfNextPlaceholder = $matches[0][$i + 1][1] ?? strlen($substring); $beginOfNextPlaceholder -= $endOfPlaceholder; $literal .= '?' . substr($substring, $endOfPlaceholder, $beginOfNextPlaceholder); } } else { $literal .= $substring; } $startPos = $j; $j++; if ($j >= $n) { break; } // Quote comes first, find end of quote while (true) { $k = strpos($sql, $quoteChar, $j); $escaped = false; if ($k === false) { break; } $l = $k - 1; while ($l >= 0 && $sql[$l] === '\\') { $l--; $escaped = !$escaped; } if ($escaped) { $j = $k + 1; continue; } break; } if ($k === false) { // Error in the query - no end quote; ignore it break; } $literal .= substr($sql, $startPos, $k - $startPos + 1); $startPos = $k + 1; } if ($startPos < $n) { $literal .= substr($sql, $startPos, $n - $startPos); } $this->parameterKeyMapping = $mapping; return $literal; } /** * Binds a parameter to the specified variable name. * * @param string|integer $parameter Parameter identifier. For a prepared statement using named placeholders, this will be a parameter * name of the form `:name`. For a prepared statement using question mark placeholders, this will be * the 1-indexed position of the parameter. * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. * @param integer $dataType Constant corresponding to a SQL datatype, this should be the processed type from the QueryInterface. * @param integer $length The length of the variable. Usually required for OUTPUT parameters. * @param array $driverOptions Optional driver options to be used. * * @return boolean * * @since 2.0.0 */ public function bindParam($parameter, &$variable, string $dataType = ParameterType::STRING, ?int $length = null, ?array $driverOptions = null) { $this->bindedValues[$parameter] =& $variable; // Validate parameter type if (!isset($this->parameterTypeMapping[$dataType])) { throw new \InvalidArgumentException(sprintf('Unsupported parameter type `%s`', $dataType)); } $this->typesKeyMapping[$parameter] = $this->parameterTypeMapping[$dataType]; return true; } /** * Binds a array of values to bound parameters. * * @param array $values The values to bind to the statement * * @return boolean * * @since 2.0.0 */ private function bindValues(array $values) { $params = []; $types = str_repeat('s', \count($values)); if (!empty($this->parameterKeyMapping)) { foreach ($values as $key => &$value) { $params[$this->parameterKeyMapping[$key]] =& $value; } ksort($params); } else { foreach ($values as $key => &$value) { $params[] =& $value; } } array_unshift($params, $types); return \call_user_func_array([$this->statement, 'bind_param'], $params); } /** * Closes the cursor, enabling the statement to be executed again. * * @return void * * @since 2.0.0 */ public function closeCursor(): void { $this->statement->free_result(); $this->result = false; } /** * Fetches the SQLSTATE associated with the last operation on the statement handle. * * @return string * * @since 2.0.0 */ public function errorCode() { return $this->statement->errno; } /** * Fetches extended error information associated with the last operation on the statement handle. * * @return array * * @since 2.0.0 */ public function errorInfo() { return $this->statement->error; } /** * Executes a prepared statement * * @param array|null $parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. * * @return boolean * * @since 2.0.0 */ public function execute(?array $parameters = null) { if ($this->bindedValues !== null) { $params = []; $types = []; if (!empty($this->parameterKeyMapping)) { foreach ($this->bindedValues as $key => &$value) { $params[$this->parameterKeyMapping[$key]] =& $value; $types[$this->parameterKeyMapping[$key]] = $this->typesKeyMapping[$key]; } } else { foreach ($this->bindedValues as $key => &$value) { $params[] =& $value; $types[$key] = $this->typesKeyMapping[$key]; } } ksort($params); ksort($types); array_unshift($params, implode('', $types)); if (!\call_user_func_array([$this->statement, 'bind_param'], $params)) { throw new PrepareStatementFailureException($this->statement->error, $this->statement->errno); } } elseif ($parameters !== null) { if (!$this->bindValues($parameters)) { throw new PrepareStatementFailureException($this->statement->error, $this->statement->errno); } } try { if (!$this->statement->execute()) { throw new ExecutionFailureException($this->query, $this->statement->error, $this->statement->errno); } } catch (\Throwable $e) { throw new ExecutionFailureException($this->query, $e->getMessage(), $e->getCode(), $e); } if ($this->columnNames === null) { $meta = $this->statement->result_metadata(); if ($meta !== false) { $columnNames = []; foreach ($meta->fetch_fields() as $col) { $columnNames[] = $col->name; } $meta->free(); $this->columnNames = $columnNames; } else { $this->columnNames = false; } } if ($this->columnNames !== false) { $this->statement->store_result(); $this->rowBindedValues = array_fill(0, \count($this->columnNames), null); $refs = []; foreach ($this->rowBindedValues as $key => &$value) { $refs[$key] =& $value; } if (!\call_user_func_array([$this->statement, 'bind_result'], $refs)) { throw new \RuntimeException($this->statement->error, $this->statement->errno); } } $this->result = true; return true; } /** * Fetches the next row from a result set * * @param integer|null $fetchStyle Controls how the next row will be returned to the caller. This value must be one of the * FetchMode constants, defaulting to value of FetchMode::MIXED. * @param integer $cursorOrientation For a StatementInterface object representing a scrollable cursor, this value determines which row * will be returned to the caller. This value must be one of the FetchOrientation constants, * defaulting to FetchOrientation::NEXT. * @param integer $cursorOffset For a StatementInterface object representing a scrollable cursor for which the cursorOrientation * parameter is set to FetchOrientation::ABS, this value specifies the absolute number of the row in * the result set that shall be fetched. For a StatementInterface object representing a scrollable * cursor for which the cursorOrientation parameter is set to FetchOrientation::REL, this value * specifies the row to fetch relative to the cursor position before `fetch()` was called. * * @return mixed The return value of this function on success depends on the fetch type. In all cases, boolean false is returned on failure. * * @since 2.0.0 */ public function fetch(?int $fetchStyle = null, int $cursorOrientation = FetchOrientation::NEXT, int $cursorOffset = 0) { if (!$this->result) { return false; } $fetchStyle = $fetchStyle ?: $this->defaultFetchStyle; if ($fetchStyle === FetchMode::COLUMN) { return $this->fetchColumn(); } $values = $this->fetchData(); if ($values === null) { return false; } if ($values === false) { throw new \RuntimeException($this->statement->error, $this->statement->errno); } switch ($fetchStyle) { case FetchMode::NUMERIC: return $values; case FetchMode::ASSOCIATIVE: return array_combine($this->columnNames, $values); case FetchMode::MIXED: $ret = array_combine($this->columnNames, $values); $ret += $values; return $ret; case FetchMode::STANDARD_OBJECT: return (object) array_combine($this->columnNames, $values); default: throw new \InvalidArgumentException("Unknown fetch type '{$fetchStyle}'"); } } /** * Returns a single column from the next row of a result set * * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. * If no value is supplied, the first column is retrieved. * * @return mixed Returns a single column from the next row of a result set or boolean false if there are no more rows. * * @since 2.0.0 */ public function fetchColumn($columnIndex = 0) { $row = $this->fetch(FetchMode::NUMERIC); if ($row === false) { return false; } return $row[$columnIndex] ?? null; } /** * Fetch the data from the statement. * * @return array|boolean * * @since 2.0.0 */ private function fetchData() { $return = $this->statement->fetch(); if ($return === true) { $values = []; foreach ($this->rowBindedValues as $v) { $values[] = $v; } return $values; } return $return; } /** * Returns the number of rows affected by the last SQL statement. * * @return integer * * @since 2.0.0 */ public function rowCount(): int { if ($this->columnNames === false) { return $this->statement->affected_rows; } return $this->statement->num_rows; } /** * Sets the fetch mode to use while iterating this statement. * * @param integer $fetchMode The fetch mode, must be one of the FetchMode constants. * @param mixed ...$args Optional mode-specific arguments. * * @return void * * @since 2.0.0 */ public function setFetchMode(int $fetchMode, ...$args): void { $this->defaultFetchStyle = $fetchMode; } } PK �9�\�h#. . src/Mysqli/MysqliImporter.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Mysqli; use Joomla\Database\DatabaseImporter; /** * MySQLi Database Importer. * * @since 1.0 */ class MysqliImporter extends DatabaseImporter { /** * Checks if all data and options are in order prior to exporting. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function check() { // Check if the db connector has been set. if (!($this->db instanceof MysqliDriver)) { throw new \RuntimeException('Database connection wrong type.'); } // Check if the tables have been specified. if (empty($this->from)) { throw new \RuntimeException('ERROR: No Tables Specified'); } return $this; } /** * Get the SQL syntax to add a table. * * @param \SimpleXMLElement $table The table information. * * @return string * * @since 1.4.0 * @throws \RuntimeException */ protected function xmlToCreate(\SimpleXMLElement $table) { $existingTables = $this->db->getTableList(); $tableName = (string) $table['name']; if (\in_array($tableName, $existingTables, true)) { throw new \RuntimeException('The table you are trying to create already exists'); } $createTableStatement = 'CREATE TABLE ' . $this->db->quoteName($tableName) . ' ('; foreach ($table->xpath('field') as $field) { $createTableStatement .= $this->getColumnSql($field) . ', '; } $newLookup = $this->getKeyLookup($table->xpath('key')); foreach ($newLookup as $key) { $createTableStatement .= $this->getKeySql($key) . ', '; } $createTableStatement = rtrim($createTableStatement, ', '); $createTableStatement .= ')'; return $createTableStatement; } /** * Get the SQL syntax to add a key. * * @param string $table The table name. * @param array $keys An array of the fields pertaining to this key. * * @return string * * @since 1.0 */ protected function getAddKeySql($table, $keys) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD ' . $this->getKeySql($keys); } /** * Get alters for table if there is a difference. * * @param \SimpleXMLElement $structure The XML structure of the table. * * @return array * * @since 1.0 */ protected function getAlterTableSql(\SimpleXMLElement $structure) { $table = $this->getRealTableName($structure['name']); $oldFields = $this->db->getTableColumns($table, false); $oldKeys = $this->db->getTableKeys($table); $alters = []; // Get the fields and keys from the XML that we are aiming for. $newFields = $structure->xpath('field'); $newKeys = $structure->xpath('key'); // Loop through each field in the new structure. foreach ($newFields as $field) { $fName = (string) $field['Field']; if (isset($oldFields[$fName])) { // The field exists, check it's the same. $column = $oldFields[$fName]; // Test whether there is a change. $change = ((string) $field['Type'] !== $column->Type) || ((string) $field['Null'] !== $column->Null) || ((string) $field['Default'] !== $column->Default) || ((string) $field['Extra'] !== $column->Extra); if ($change) { $alters[] = $this->getChangeColumnSql($table, $field); } // Unset this field so that what we have left are fields that need to be removed. unset($oldFields[$fName]); } else { // The field is new. $alters[] = $this->getAddColumnSql($table, $field); } } // Any columns left are orphans foreach ($oldFields as $name => $column) { // Delete the column. $alters[] = $this->getDropColumnSql($table, $name); } // Get the lookups for the old and new keys. $oldLookup = $this->getKeyLookup($oldKeys); $newLookup = $this->getKeyLookup($newKeys); // Loop through each key in the new structure. foreach ($newLookup as $name => $keys) { // Check if there are keys on this field in the existing table. if (isset($oldLookup[$name])) { $same = true; $newCount = \count($newLookup[$name]); $oldCount = \count($oldLookup[$name]); // There is a key on this field in the old and new tables. Are they the same? if ($newCount === $oldCount) { // Need to loop through each key and do a fine grained check. for ($i = 0; $i < $newCount; $i++) { $same = (((string) $newLookup[$name][$i]['Non_unique'] === $oldLookup[$name][$i]->Non_unique) && ((string) $newLookup[$name][$i]['Column_name'] === $oldLookup[$name][$i]->Column_name) && ((string) $newLookup[$name][$i]['Seq_in_index'] === $oldLookup[$name][$i]->Seq_in_index) && ((string) $newLookup[$name][$i]['Collation'] === $oldLookup[$name][$i]->Collation) && ((string) $newLookup[$name][$i]['Sub_part'] == $oldLookup[$name][$i]->Sub_part) && ((string) $newLookup[$name][$i]['Index_type'] === $oldLookup[$name][$i]->Index_type)); /* Debug. echo '<pre>'; echo '<br>Non_unique: '. ((string) $newLookup[$name][$i]['Non_unique'] == $oldLookup[$name][$i]->Non_unique ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Non_unique'].' vs '.$oldLookup[$name][$i]->Non_unique; echo '<br>Column_name: '. ((string) $newLookup[$name][$i]['Column_name'] == $oldLookup[$name][$i]->Column_name ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Column_name'].' vs '.$oldLookup[$name][$i]->Column_name; echo '<br>Seq_in_index: '. ((string) $newLookup[$name][$i]['Seq_in_index'] == $oldLookup[$name][$i]->Seq_in_index ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Seq_in_index'].' vs '.$oldLookup[$name][$i]->Seq_in_index; echo '<br>Collation: '. ((string) $newLookup[$name][$i]['Collation'] == $oldLookup[$name][$i]->Collation ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Collation'].' vs '.$oldLookup[$name][$i]->Collation; echo '<br>Sub_part: '. ((string) $newLookup[$name][$i]['Sub_part'] == $oldLookup[$name][$i]->Sub_part ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Sub_part'].' vs '.$oldLookup[$name][$i]->Sub_part; echo '<br>Index_type: '. ((string) $newLookup[$name][$i]['Index_type'] == $oldLookup[$name][$i]->Index_type ? 'Pass' : 'Fail').' '. (string) $newLookup[$name][$i]['Index_type'].' vs '.$oldLookup[$name][$i]->Index_type; echo '<br>Same = '.($same ? 'true' : 'false'); echo '</pre>'; */ if (!$same) { // Break out of the loop. No need to check further. break; } } } else { // Count is different, just drop and add. $same = false; } if (!$same) { $alters[] = $this->getDropKeySql($table, $name); $alters[] = $this->getAddKeySql($table, $keys); } // Unset this field so that what we have left are fields that need to be removed. unset($oldLookup[$name]); } else { // This is a new key. $alters[] = $this->getAddKeySql($table, $keys); } } // Any keys left are orphans. foreach ($oldLookup as $name => $keys) { if (strtoupper($name) === 'PRIMARY') { $alters[] = $this->getDropPrimaryKeySql($table); } else { $alters[] = $this->getDropKeySql($table, $name); } } return $alters; } /** * Get the syntax to alter a column. * * @param string $table The name of the database table to alter. * @param \SimpleXMLElement $field The XML definition for the field. * * @return string * * @since 1.0 */ protected function getChangeColumnSql($table, \SimpleXMLElement $field) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' CHANGE COLUMN ' . $this->db->quoteName((string) $field['Field']) . ' ' . $this->getColumnSql($field); } /** * Get the SQL syntax for a single column that would be included in a table create or alter statement. * * @param \SimpleXMLElement $field The XML field definition. * * @return string * * @since 1.0 */ protected function getColumnSql(\SimpleXMLElement $field) { // TODO Incorporate into parent class and use $this. $blobs = ['text', 'smalltext', 'mediumtext', 'largetext']; $fName = (string) $field['Field']; $fType = (string) $field['Type']; $fNull = (string) $field['Null']; $fDefault = isset($field['Default']) ? (string) $field['Default'] : null; $fExtra = (string) $field['Extra']; $sql = $this->db->quoteName($fName) . ' ' . $fType; if ($fNull === 'NO') { if ($fDefault === null || \in_array($fType, $blobs, true)) { $sql .= ' NOT NULL'; } else { // TODO Don't quote numeric values. if (stristr($fDefault, 'CURRENT') !== false) { $sql .= ' NOT NULL DEFAULT CURRENT_TIMESTAMP()'; } else { $sql .= ' NOT NULL DEFAULT ' . $this->db->quote($fDefault); } } } else { if ($fDefault === null) { $sql .= ' DEFAULT NULL'; } else { // TODO Don't quote numeric values. $sql .= ' DEFAULT ' . $this->db->quote($fDefault); } } if ($fExtra) { // MySql 8.0 introduces DEFAULT_GENERATED in the extra column and should be replaced with the default value if (stristr($fExtra, 'DEFAULT_GENERATED') !== false) { $sql .= ' ' . strtoupper(str_ireplace('DEFAULT_GENERATED', 'DEFAULT ' . $fDefault, $fExtra)); } else { $sql .= ' ' . strtoupper($fExtra); } } return $sql; } /** * Get the SQL syntax to drop a key. * * @param string $table The table name. * @param string $name The name of the key to drop. * * @return string * * @since 1.0 */ protected function getDropKeySql($table, $name) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP KEY ' . $this->db->quoteName($name); } /** * Get the SQL syntax to drop a key. * * @param string $table The table name. * * @return string * * @since 1.0 */ protected function getDropPrimaryKeySql($table) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP PRIMARY KEY'; } /** * Get the details list of keys for a table. * * @param array $keys An array of objects that comprise the keys for the table. * * @return array The lookup array. array({key name} => array(object, ...)) * * @since 1.0 */ protected function getKeyLookup($keys) { // First pass, create a lookup of the keys. $lookup = []; foreach ($keys as $key) { if ($key instanceof \SimpleXMLElement) { $kName = (string) $key['Key_name']; } else { $kName = $key->Key_name; } if (empty($lookup[$kName])) { $lookup[$kName] = []; } $lookup[$kName][] = $key; } return $lookup; } /** * Get the SQL syntax for a key. * * @param array $columns An array of SimpleXMLElement objects comprising the key. * * @return string * * @since 1.0 */ protected function getKeySql($columns) { $kNonUnique = (string) $columns[0]['Non_unique']; $kName = (string) $columns[0]['Key_name']; $prefix = ''; if ($kName === 'PRIMARY') { $prefix = 'PRIMARY '; } elseif ($kNonUnique == 0) { $prefix = 'UNIQUE '; } $kColumns = []; foreach ($columns as $column) { $kLength = ''; if (!empty($column['Sub_part'])) { $kLength = '(' . $column['Sub_part'] . ')'; } $kColumns[] = $this->db->quoteName((string) $column['Column_name']) . $kLength; } return $prefix . 'KEY ' . ($kName !== 'PRIMARY' ? $this->db->quoteName($kName) : '') . ' (' . implode(',', $kColumns) . ')'; } } PK �9�\�?�� � src/Mysqli/MysqliQuery.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Mysqli; use Joomla\Database\DatabaseQuery; use Joomla\Database\Query\MysqlQueryBuilder; /** * MySQLi Query Building Class. * * @since 1.0 */ class MysqliQuery extends DatabaseQuery { use MysqlQueryBuilder; /** * The list of zero or null representation of a datetime. * * @var array * @since 2.0.0 */ protected $nullDatetimeList = ['0000-00-00 00:00:00', '1000-01-01 00:00:00']; } PK �9�\�3�T T src/FetchOrientation.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Class defining the fetch orientation for prepared statements * * The values of the constants in this class match the `PDO::FETCH_ORI_*` constants. * * @since 2.0.0 */ final class FetchOrientation { /** * Fetch the next row in the result set. Valid only for scrollable cursors. * * @var integer * @since 2.0.0 */ public const NEXT = 0; /** * Fetch the previous row in the result set. Valid only for scrollable cursors. * * @var integer * @since 2.0.0 */ public const PRIOR = 1; /** * Fetch the first row in the result set. Valid only for scrollable cursors. * * @var integer * @since 2.0.0 */ public const FIRST = 2; /** * Fetch the last row in the result set. Valid only for scrollable cursors. * * @var integer * @since 2.0.0 */ public const LAST = 3; /** * Fetch the requested row by row number from the result set. Valid only for scrollable cursors. * * @var integer * @since 2.0.0 */ public const ABS = 4; /** * Fetch the requested row by relative position from the current position of the cursor in the result set. Valid only for scrollable cursors. * * @var integer * @since 2.0.0 */ public const REL = 5; /** * Private constructor to prevent instantiation of this class * * @since 2.0.0 */ private function __construct() { } } PK �9�\�`�w- w- src/Sqlite/SqliteDriver.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Sqlite; use Joomla\Database\Pdo\PdoDriver; /** * SQLite database driver supporting PDO based connections * * @link https://www.php.net/manual/en/ref.pdo-sqlite.php * @since 1.0 */ class SqliteDriver extends PdoDriver { /** * The name of the database driver. * * @var string * @since 1.0 */ public $name = 'sqlite'; /** * The character(s) used to quote SQL statement names such as table names or field names, etc. * * If a single character string the same character is used for both sides of the quoted name, else the first character will be used for the * opening quote and the second for the closing quote. * * @var string * @since 1.0 */ protected $nameQuote = '`'; /** * Destructor. * * @since 1.0 */ public function __destruct() { $this->disconnect(); } /** * Alter database's character set. * * @param string $dbName The database name that will be altered * * @return boolean|resource * * @since 2.0.0 * @throws \RuntimeException */ public function alterDbCharacterSet($dbName) { return false; } /** * Connects to the database if needed. * * @return void Returns void if the database connected successfully. * * @since 2.0.0 * @throws RuntimeException */ public function connect() { if ($this->connection) { return; } parent::connect(); $this->connection->sqliteCreateFunction( 'ROW_NUMBER', function ($init = null) { static $rownum, $partition; if ($init !== null) { $rownum = $init; $partition = null; return $rownum; } $args = \func_get_args(); array_shift($args); $partitionBy = $args ? implode(',', $args) : null; if ($partitionBy === null || $partitionBy === $partition) { $rownum++; } else { $rownum = 1; $partition = $partitionBy; } return $rownum; } ); } /** * Create a new database using information from $options object. * * @param \stdClass $options Object used to pass user and database name to database driver. This object must have "db_name" and "db_user" set. * @param boolean $utf True if the database supports the UTF-8 character set. * * @return boolean|resource * * @since 2.0.0 * @throws \RuntimeException */ public function createDatabase($options, $utf = true) { // SQLite doesn't have a query for this return true; } /** * Method to escape a string for usage in an SQLite statement. * * Note: Using query objects with bound variables is preferable to the below. * * @param string $text The string to be escaped. * @param boolean $extra Unused optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 1.0 */ public function escape($text, $extra = false) { if (\is_int($text)) { return $text; } if (\is_float($text)) { // Force the dot as a decimal point. return str_replace(',', '.', $text); } return \SQLite3::escapeString($text); } /** * Method to get the database collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database or boolean false if not supported. * * @since 1.0 */ public function getCollation() { return false; } /** * Method to get the database connection collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database connection (string) or boolean false if not supported. * * @since 1.6.0 * @throws \RuntimeException */ public function getConnectionCollation() { return false; } /** * Method to get the database encryption details (cipher and protocol) in use. * * @return string The database encryption details. * * @since 2.0.0 * @throws \RuntimeException */ public function getConnectionEncryption(): string { // TODO: Not fake this return ''; } /** * Method to test if the database TLS connections encryption are supported. * * @return boolean Whether the database supports TLS connections encryption. * * @since 2.0.0 */ public function isConnectionEncryptionSupported(): bool { // TODO: Not fake this return false; } /** * Shows the table CREATE statement that creates the given tables. * * Note: Doesn't appear to have support in SQLite * * @param mixed $tables A table name or a list of table names. * * @return array A list of the create SQL for the tables. * * @since 1.0 * @throws \RuntimeException */ public function getTableCreate($tables) { $this->connect(); // Sanitize input to an array and iterate over the list. $tables = (array) $tables; return $tables; } /** * Retrieves field information about a given table. * * @param string $table The name of the database table. * @param boolean $typeOnly True to only return field types. * * @return array An array of fields for the database table. * * @since 1.0 * @throws \RuntimeException */ public function getTableColumns($table, $typeOnly = true) { $this->connect(); $columns = []; $fieldCasing = $this->getOption(\PDO::ATTR_CASE); $this->setOption(\PDO::ATTR_CASE, \PDO::CASE_UPPER); $table = strtoupper($table); $fields = $this->setQuery('pragma table_info(' . $table . ')')->loadObjectList(); if ($typeOnly) { foreach ($fields as $field) { $columns[$field->NAME] = $field->TYPE; } } else { foreach ($fields as $field) { // Do some dirty translation to MySQL output. // TODO: Come up with and implement a standard across databases. $columns[$field->NAME] = (object) [ 'Field' => $field->NAME, 'Type' => $field->TYPE, 'Null' => $field->NOTNULL == '1' ? 'NO' : 'YES', 'Default' => $field->DFLT_VALUE, 'Key' => $field->PK != '0' ? 'PRI' : '', ]; } } $this->setOption(\PDO::ATTR_CASE, $fieldCasing); return $columns; } /** * Get the details list of keys for a table. * * @param string $table The name of the table. * * @return array An array of the column specification for the table. * * @since 1.0 * @throws \RuntimeException */ public function getTableKeys($table) { $this->connect(); $keys = []; $fieldCasing = $this->getOption(\PDO::ATTR_CASE); $this->setOption(\PDO::ATTR_CASE, \PDO::CASE_UPPER); $table = strtoupper($table); $rows = $this->setQuery('pragma table_info( ' . $table . ')')->loadObjectList(); foreach ($rows as $column) { if ($column->PK == 1) { $keys[$column->NAME] = $column; } } $this->setOption(\PDO::ATTR_CASE, $fieldCasing); return $keys; } /** * Method to get an array of all tables in the database (schema). * * @return array An array of all the tables in the database. * * @since 1.0 * @throws \RuntimeException */ public function getTableList() { $this->connect(); $type = 'table'; $query = $this->createQuery() ->select('name') ->from('sqlite_master') ->where('type = :type') ->bind(':type', $type) ->order('name'); return $this->setQuery($query)->loadColumn(); } /** * Get the version of the database connector. * * @return string The database connector version. * * @since 1.0 */ public function getVersion() { $this->connect(); return $this->setQuery('SELECT sqlite_version()')->loadResult(); } /** * Select a database for use. * * @param string $database The name of the database to select for use. * * @return boolean True if the database was successfully selected. * * @since 1.0 * @throws \RuntimeException */ public function select($database) { $this->connect(); return true; } /** * Set the connection to use UTF-8 character encoding. * * Returns false automatically for the Oracle driver since * you can only set the character set when the connection * is created. * * @return boolean True on success. * * @since 1.0 */ public function setUtf() { $this->connect(); return false; } /** * Locks a table in the database. * * @param string $table The name of the table to unlock. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function lockTable($table) { return $this; } /** * Renames a table in the database. * * @param string $oldTable The name of the table to be renamed * @param string $newTable The new name for the table. * @param string $backup Not used by Sqlite. * @param string $prefix Not used by Sqlite. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $this->setQuery('ALTER TABLE ' . $oldTable . ' RENAME TO ' . $newTable)->execute(); return $this; } /** * Method to truncate a table. * * @param string $table The table to truncate * * @return void * * @since 1.2.1 * @throws \RuntimeException */ public function truncateTable($table) { $this->setQuery('DELETE FROM ' . $this->quoteName($table)) ->execute(); } /** * Unlocks tables in the database. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function unlockTables() { return $this; } /** * Test to see if the PDO ODBC connector is available. * * @return boolean True on success, false otherwise. * * @since 1.0 */ public static function isSupported() { return class_exists('\\PDO') && class_exists('\\SQLite3') && \in_array('sqlite', \PDO::getAvailableDrivers(), true); } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionCommit($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { parent::transactionCommit($toSavepoint); } else { $this->transactionDepth--; } } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionRollback($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { parent::transactionRollback($toSavepoint); } else { $savepoint = 'SP_' . ($this->transactionDepth - 1); $this->setQuery('ROLLBACK TO ' . $this->quoteName($savepoint))->execute(); $this->transactionDepth--; } } /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionStart($asSavepoint = false) { $this->connect(); if (!$asSavepoint || !$this->transactionDepth) { parent::transactionStart($asSavepoint); } else { $savepoint = 'SP_' . $this->transactionDepth; $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint))->execute(); $this->transactionDepth++; } } } PK �9�\�B oo o src/Sqlite/SqliteQuery.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Sqlite; use Joomla\Database\DatabaseQuery; use Joomla\Database\Pdo\PdoQuery; use Joomla\Database\Query\QueryElement; /** * SQLite Query Building Class. * * @since 1.0 */ class SqliteQuery extends PdoQuery { /** * Magic function to convert the query to a string. * * @return string The completed query. * * @since 2.0.0 */ public function __toString() { switch ($this->type) { case 'select': if ($this->selectRowNumber) { $orderBy = $this->selectRowNumber['orderBy']; $orderColumnAlias = $this->selectRowNumber['orderColumnAlias']; $column = "ROW_NUMBER() AS $orderColumnAlias"; if ($this->select === null) { $query = PHP_EOL . 'SELECT 1' . (string) $this->from . (string) $this->where; } else { $tmpOffset = $this->offset; $tmpLimit = $this->limit; $this->offset = 0; $this->limit = 0; $tmpOrder = $this->order; $this->order = null; $query = parent::__toString(); $column = "w.*, $column"; $this->order = $tmpOrder; $this->offset = $tmpOffset; $this->limit = $tmpLimit; } // Special sqlite query to count ROW_NUMBER $query = PHP_EOL . "SELECT $column" . PHP_EOL . "FROM ($query" . PHP_EOL . "ORDER BY $orderBy" . PHP_EOL . ') AS w,(SELECT ROW_NUMBER(0)) AS r' // Forbid to flatten subqueries. . ((string) $this->order ?: PHP_EOL . 'ORDER BY NULL'); return $this->processLimit($query, $this->limit, $this->offset); } break; case 'querySet': $query = $this->querySet; if ($query->order || $query->limit || $query->offset) { // If ORDER BY or LIMIT statement exist then parentheses is required for the first query $query = PHP_EOL . "SELECT * FROM ($query)"; } if ($this->merge) { // Special case for merge foreach ($this->merge as $element) { $query .= (string) $element; } } if ($this->order) { $query .= (string) $this->order; } return $query; case 'update': if ($this->join) { $table = $this->update->getElements(); $table = $table[0]; $tableName = explode(' ', $table); $tableName = $tableName[0]; if ($this->columns === null) { $fields = $this->db->getTableColumns($tableName); foreach ($fields as $key => $value) { $fields[$key] = $key; } $this->columns = new QueryElement('()', $fields); } $fields = $this->columns->getElements(); $elements = $this->set->getElements(); foreach ($elements as $nameValue) { $setArray = explode(' = ', $nameValue, 2); if ($setArray[0][0] === '`') { // Unquote column name $setArray[0] = substr($setArray[0], 1, -1); } $fields[$setArray[0]] = $setArray[1]; } $select = new static($this->db); $select->select(array_values($fields)) ->from($table); $select->join = $this->join; $select->where = $this->where; return 'INSERT OR REPLACE INTO ' . $tableName . ' (' . implode(',', array_keys($fields)) . ')' . (string) $select; } } return parent::__toString(); } /** * Gets the number of characters in a string. * * Note, use 'length' to find the number of bytes in a string. * * Usage: * $query->select($query->charLength('a')); * * @param string $field A value. * @param string|null $operator Comparison operator between charLength integer value and $condition * @param string|null $condition Integer value to compare charLength with. * * @return string The required char length call. * * @since 1.1.0 */ public function charLength($field, $operator = null, $condition = null) { $statement = 'length(' . $field . ')'; if ($operator !== null && $condition !== null) { $statement .= ' ' . $operator . ' ' . $condition; } return $statement; } /** * Concatenates an array of column names or values. * * Usage: * $query->select($query->concatenate(array('a', 'b'))); * * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * * @return string The concatenated values. * * @since 1.1.0 */ public function concatenate($values, $separator = null) { if ($separator !== null) { return implode(' || ' . $this->quote($separator) . ' || ', $values); } return implode(' || ', $values); } /** * Method to modify a query already in string format with the needed additions to make the query limited to a particular number of * results, or start at a particular offset. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string * * @since 1.0 */ public function processLimit($query, $limit, $offset = 0) { if ($limit > 0 || $offset > 0) { $query .= ' LIMIT ' . $offset . ', ' . $limit; } return $query; } /** * Return the number of the current row. * * Usage: * $query->select('id'); * $query->selectRowNumber('ordering,publish_up DESC', 'new_ordering'); * $query->from('#__content'); * * @param string $orderBy An expression of ordering for window function. * @param string $orderColumnAlias An alias for new ordering column. * * @return $this * * @since 2.0.0 * @throws \RuntimeException */ public function selectRowNumber($orderBy, $orderColumnAlias) { $this->validateRowNumber($orderBy, $orderColumnAlias); return $this; } /** * Add a query to UNION with the current query. * * Usage: * $query->union('SELECT name FROM #__foo') * $query->union('SELECT name FROM #__foo', true) * * @param DatabaseQuery|string $query The DatabaseQuery object or string to union. * @param boolean $distinct True to only return distinct rows from the union. * * @return $this * * @since 1.0 */ public function union($query, $distinct = true) { // Set up the name with parentheses, the DISTINCT flag is redundant return $this->merge($distinct ? 'UNION SELECT * FROM ()' : 'UNION ALL SELECT * FROM ()', $query); } /** * Aggregate function to get input values concatenated into a string, separated by delimiter * * Usage: * $query->groupConcat('id', ','); * * @param string $expression The expression to apply concatenation to, this may be a column name or complex SQL statement. * @param string $separator The delimiter of each concatenated value * * @return string Input values concatenated into a string, separated by delimiter * * @since 2.0.0 */ public function groupConcat($expression, $separator = ',') { return 'group_concat(' . $expression . ', ' . $this->quote($separator) . ')'; } } PK �9�\CN� � src/Command/ImportCommand.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ // phpcs:disable Generic.PHP.DeprecatedFunctions.Deprecated namespace Joomla\Database\Command; use Joomla\Archive\Archive; use Joomla\Console\Command\AbstractCommand; use Joomla\Database\DatabaseDriver; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\Exception\UnsupportedAdapterException; use Joomla\Filesystem\Exception\FilesystemException; use Joomla\Filesystem\File; use Joomla\Filesystem\Folder; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; /** * Console command for importing the database * * @since 2.0.0 */ class ImportCommand extends AbstractCommand { /** * The default command name * * @var string * @since 2.0.0 */ protected static $defaultName = 'database:import'; /** * Database connector * * @var DatabaseDriver * @since 2.0.0 */ private $db; /** * Instantiate the command. * * @param DatabaseDriver $db Database connector * * @since 2.0.0 */ public function __construct(DatabaseDriver $db) { $this->db = $db; parent::__construct(); } /** * Checks if the zip file contains database export files * * @param string $archive A zip archive to analyze * * @return void * * @since 2.0.0 * @throws \RuntimeException */ private function checkZipFile(string $archive): void { if (!extension_loaded('zip')) { throw new \RuntimeException('The PHP zip extension is not installed or is disabled'); } $zip = zip_open($archive); if (!\is_resource($zip)) { throw new \RuntimeException('Unable to open archive'); } while ($file = @zip_read($zip)) { if (strpos(zip_entry_name($file), $this->db->getPrefix()) === false) { zip_entry_close($file); @zip_close($zip); throw new \RuntimeException('Unable to find table matching database prefix'); } zip_entry_close($file); } @zip_close($zip); } /** * Internal function to execute the command. * * @param InputInterface $input The input to inject into the command. * @param OutputInterface $output The output to inject into the command. * * @return integer The command exit code * * @since 2.0.0 */ protected function doExecute(InputInterface $input, OutputInterface $output): int { $symfonyStyle = new SymfonyStyle($input, $output); $symfonyStyle->title('Importing Database'); $totalTime = microtime(true); // Make sure the database supports imports before we get going try { $importer = $this->db->getImporter() ->withStructure() ->asXml(); } catch (UnsupportedAdapterException $e) { $symfonyStyle->error(sprintf('The "%s" database driver does not support importing data.', $this->db->getName())); return 1; } $folderPath = $input->getOption('folder'); $tableName = $input->getOption('table'); $zipFile = $input->getOption('zip'); if ($zipFile) { if (!class_exists(File::class)) { $symfonyStyle->error('The "joomla/filesystem" Composer package is not installed, cannot process ZIP files.'); return 1; } if (!class_exists(Archive::class)) { $symfonyStyle->error('The "joomla/archive" Composer package is not installed, cannot process ZIP files.'); return 1; } $zipPath = $folderPath . '/' . $zipFile; try { $this->checkZipFile($zipPath); } catch (\RuntimeException $e) { $symfonyStyle->error($e->getMessage()); return 1; } $folderPath .= File::stripExt($zipFile); try { Folder::create($folderPath); } catch (FilesystemException $e) { $symfonyStyle->error($e->getMessage()); return 1; } try { (new Archive)->extract($zipPath, $folderPath); } catch (\RuntimeException $e) { $symfonyStyle->error($e->getMessage()); Folder::delete($folderPath); return 1; } } if ($tableName) { $tables = [$tableName . '.xml']; } else { $tables = Folder::files($folderPath, '\.xml$'); } foreach ($tables as $table) { $taskTime = microtime(true); $percorso = $folderPath . '/' . $table; // Check file if (!file_exists($percorso)) { $symfonyStyle->error(sprintf('The %s file does not exist.', $table)); return 1; } $tableName = str_replace('.xml', '', $table); $symfonyStyle->text(sprintf('Importing %1$s from %2$s', $tableName, $table)); $importer->from(file_get_contents($percorso)); $symfonyStyle->text(sprintf('Processing the %s table', $tableName)); try { $this->db->dropTable($tableName, true); } catch (ExecutionFailureException $e) { $symfonyStyle->error(sprintf('Error executing the DROP TABLE statement for %1$s: %2$s', $tableName, $e->getMessage())); return 1; } try { $importer->mergeStructure(); } catch (\Exception $e) { $symfonyStyle->error(sprintf('Error merging the structure for %1$s: %2$s', $tableName, $e->getMessage())); return 1; } try { $importer->importData(); } catch (\Exception $e) { $symfonyStyle->error(sprintf('Error importing the data for %1$s: %2$s', $tableName, $e->getMessage())); return 1; } $symfonyStyle->text(sprintf('Imported data for %s in %d seconds', $table, round(microtime(true) - $taskTime, 3))); } if ($zipFile) { Folder::delete($folderPath); } $symfonyStyle->success(sprintf('Import completed in %d seconds', round(microtime(true) - $totalTime, 3))); return 0; } /** * Configure the command. * * @return void * * @since 2.0.0 */ protected function configure(): void { $this->setDescription('Import the database'); $this->addOption('folder', null, InputOption::VALUE_OPTIONAL, 'Path to the folder containing files to import', '.'); $this->addOption('zip', null, InputOption::VALUE_REQUIRED, 'The name of a ZIP file to import'); $this->addOption('table', null, InputOption::VALUE_REQUIRED, 'The name of the database table to import'); } } PK �9�\m��� � src/Command/ExportCommand.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Command; use Joomla\Archive\Archive; use Joomla\Archive\Zip; use Joomla\Console\Command\AbstractCommand; use Joomla\Database\DatabaseDriver; use Joomla\Database\Exception\UnsupportedAdapterException; use Joomla\Filesystem\File; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Input\InputOption; /** * Console command for exporting the database * * @since 2.0.0 */ class ExportCommand extends AbstractCommand { /** * The default command name * * @var string * @since 2.0.0 */ protected static $defaultName = 'database:export'; /** * Database connector * * @var DatabaseDriver * @since 2.0.0 */ private $db; /** * Instantiate the command. * * @param DatabaseDriver $db Database connector * * @since 2.0.0 */ public function __construct(DatabaseDriver $db) { $this->db = $db; parent::__construct(); } /** * Internal function to execute the command. * * @param InputInterface $input The input to inject into the command. * @param OutputInterface $output The output to inject into the command. * * @return integer The command exit code * * @since 2.0.0 */ protected function doExecute(InputInterface $input, OutputInterface $output): int { $symfonyStyle = new SymfonyStyle($input, $output); $symfonyStyle->title('Exporting Database'); $totalTime = microtime(true); if (!class_exists(File::class)) { $symfonyStyle->error('The "joomla/filesystem" Composer package is not installed, cannot create an export.'); return 1; } // Make sure the database supports exports before we get going try { $exporter = $this->db->getExporter() ->withStructure(); } catch (UnsupportedAdapterException $e) { $symfonyStyle->error(sprintf('The "%s" database driver does not support exporting data.', $this->db->getName())); return 1; } $folderPath = $input->getOption('folder'); $tableName = $input->getOption('table'); $zip = $input->getOption('zip'); $zipFile = $folderPath . '/data_exported_' . date("Y-m-d\TH-i-s") . '.zip'; $tables = $this->db->getTableList(); $prefix = $this->db->getPrefix(); if ($tableName) { if (!\in_array($tableName, $tables)) { $symfonyStyle->error(sprintf('The %s table does not exist in the database.', $tableName)); return 1; } $tables = [$tableName]; } if ($zip) { if (!class_exists(Archive::class)) { $symfonyStyle->error('The "joomla/archive" Composer package is not installed, cannot create ZIP files.'); return 1; } /** @var Zip $zipArchive */ $zipArchive = (new Archive)->getAdapter('zip'); } foreach ($tables as $table) { // If an empty prefix is in use then we will dump all tables, otherwise the prefix must match if (strlen($prefix) === 0 || strpos(substr($table, 0, strlen($prefix)), $prefix) !== false) { $taskTime = microtime(true); $filename = $folderPath . '/' . $table . '.xml'; $symfonyStyle->text(sprintf('Processing the %s table', $table)); $data = (string) $exporter->from($table)->withData(true); if (file_exists($filename)) { File::delete($filename); } File::write($filename, $data); if ($zip) { $zipFilesArray = [['name' => $table . '.xml', 'data' => $data]]; $zipArchive->create($zipFile, $zipFilesArray); File::delete($filename); } $symfonyStyle->text(sprintf('Exported data for %s in %d seconds', $table, round(microtime(true) - $taskTime, 3))); } } $symfonyStyle->success(sprintf('Export completed in %d seconds', round(microtime(true) - $totalTime, 3))); return 0; } /** * Configure the command. * * @return void * * @since 2.0.0 */ protected function configure(): void { $this->setDescription('Export the database'); $this->addOption('folder', null, InputOption::VALUE_OPTIONAL, 'Path to write the export files to', '.'); $this->addOption('table', null, InputOption::VALUE_REQUIRED, 'The name of the database table to export'); $this->addOption('zip', null, InputOption::VALUE_NONE, 'Flag indicating the export will be saved to a ZIP archive'); } } PK �9�\+�*=? =? src/DatabaseInterface.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Joomla Framework Database Interface * * @since 1.0 */ interface DatabaseInterface { /** * Connects to the database if needed. * * @return void * * @since 2.0.0 * @throws \RuntimeException */ public function connect(); /** * Determines if the connection to the server is active. * * @return boolean * * @since 2.0.0 */ public function connected(); /** * Create a new database using information from $options object. * * @param \stdClass $options Object used to pass user and database name to database driver. This object must have "db_name" and "db_user" set. * @param boolean $utf True if the database supports the UTF-8 character set. * * @return boolean|resource * * @since 2.0.0 * @throws \RuntimeException */ public function createDatabase($options, $utf = true); /** * Replace special placeholder representing binary field with the original string. * * @param string|resource $data Encoded string or resource. * * @return string The original string. * * @since 1.7.0 */ public function decodeBinary($data); /** * Disconnects the database. * * @return void * * @since 2.0.0 */ public function disconnect(); /** * Drops a table from the database. * * @param string $table The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return $this * * @since 2.0.0 * @throws \RuntimeException */ public function dropTable($table, $ifExists = true); /** * Escapes a string for usage in an SQL statement. * * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 2.0.0 */ public function escape($text, $extra = false); /** * Execute the SQL statement. * * @return boolean * * @since 2.0.0 * @throws \RuntimeException */ public function execute(); /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer * * @since 2.0.0 */ public function getAffectedRows(); /** * Method to get the database collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database or boolean false if not supported. * * @since 2.0.0 */ public function getCollation(); /** * Method that provides access to the underlying database connection. * * @return resource The underlying database connection resource. * * @since 2.0.0 */ public function getConnection(); /** * Method to get the database connection collation, as reported by the driver. * * If the connector doesn't support reporting this value please return an empty string. * * @return string * * @since 2.0.0 */ public function getConnectionCollation(); /** * Method to get the database encryption details (cipher and protocol) in use. * * @return string The database encryption details. * * @since 2.0.0 */ public function getConnectionEncryption(): string; /** * Method to test if the database TLS connections encryption are supported. * * @return boolean Whether the database supports TLS connections encryption. * * @since 2.0.0 */ public function isConnectionEncryptionSupported(): bool; /** * Method to check whether the installed database version is supported by the database driver * * @return boolean True if the database version is supported * * @since 2.0.0 */ public function isMinimumVersion(); /** * Get the total number of SQL statements executed by the database driver. * * @return integer * * @since 2.0.0 */ public function getCount(); /** * Returns a PHP date() function compliant date format for the database driver. * * @return string * * @since 2.0.0 */ public function getDateFormat(); /** * Get the minimum supported database version. * * @return string * * @since 2.0.0 */ public function getMinimum(); /** * Get the name of the database driver. * * @return string * * @since 2.0.0 */ public function getName(); /** * Get the null or zero representation of a timestamp for the database driver. * * @return string * * @since 2.0.0 */ public function getNullDate(); /** * Get the number of returned rows for the previous executed SQL statement. * * @return integer * * @since 2.0.0 */ public function getNumRows(); /** * Get the current query object or a new QueryInterface object. * * @param boolean $new False to return the current query object, True to return a new QueryInterface object. * * @return QueryInterface * * @since 2.0.0 * @throws \RuntimeException */ public function getQuery($new = false); /** * Get the server family type. * * @return string * * @since 2.0.0 */ public function getServerType(); /** * Retrieves field information about the given tables. * * @param string $table The name of the database table. * @param boolean $typeOnly True (default) to only return field types. * * @return array * * @since 2.0.0 * @throws \RuntimeException */ public function getTableColumns($table, $typeOnly = true); /** * Retrieves field information about the given tables. * * @param mixed $tables A table name or a list of table names. * * @return array * * @since 2.0.0 * @throws \RuntimeException */ public function getTableKeys($tables); /** * Method to get an array of all tables in the database. * * @return array * * @since 2.0.0 * @throws \RuntimeException */ public function getTableList(); /** * Get the version of the database connector. * * @return string * * @since 2.0.0 */ public function getVersion(); /** * Determine whether or not the database engine supports UTF-8 character encoding. * * @return boolean True if the database engine supports UTF-8 character encoding. * * @since 2.0.0 */ public function hasUtfSupport(); /** * Method to get the auto-incremented value from the last INSERT statement. * * @return mixed The value of the auto-increment field from the last inserted row. * * @since 2.0.0 */ public function insertid(); /** * Inserts a row into a table based on an object's properties. * * @param string $table The name of the database table to insert into. * @param object $object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. If provided the object property is updated. * * @return boolean * * @since 2.0.0 * @throws \RuntimeException */ public function insertObject($table, &$object, $key = null); /** * Test to see if the connector is available. * * @return boolean * * @since 1.0 */ public static function isSupported(); /** * Method to get the first row of the result set from the database query as an associative array of ['field_name' => 'row_value']. * * @return mixed The return value or null if the query failed. * * @since 2.0.0 * @throws \RuntimeException */ public function loadAssoc(); /** * Method to get an array of the result set rows from the database query where each row is an associative array * of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to * a sequential numeric array. * * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted * behavior and should be avoided. * * @param string $key The name of a field on which to key the result array. * @param string $column An optional column name. Instead of the whole row, only this column value will be in the result array. * * @return mixed The return value or null if the query failed. * * @since 2.0.0 * @throws \RuntimeException */ public function loadAssocList($key = null, $column = null); /** * Method to get an array of values from the <var>$offset</var> field in each row of the result set from the database query. * * @param integer $offset The row offset to use to build the result array. * * @return mixed The return value or null if the query failed. * * @since 2.0.0 * @throws \RuntimeException */ public function loadColumn($offset = 0); /** * Method to get the first row of the result set from the database query as an object. * * @param string $class The class name to use for the returned row object. * * @return mixed The return value or null if the query failed. * * @since 2.0.0 * @throws \RuntimeException */ public function loadObject($class = \stdClass::class); /** * Method to get an array of the result set rows from the database query where each row is an object. The array * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array. * * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted behavior and should be avoided. * * @param string $key The name of a field on which to key the result array. * @param string $class The class name to use for the returned row objects. * * @return mixed The return value or null if the query failed. * * @since 2.0.0 * @throws \RuntimeException */ public function loadObjectList($key = '', $class = \stdClass::class); /** * Method to get the first field of the first row of the result set from the database query. * * @return mixed The return value or null if the query failed. * * @since 2.0.0 * @throws \RuntimeException */ public function loadResult(); /** * Method to get the first row of the result set from the database query as an array. * * Columns are indexed numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc. * * @return mixed The return value or null if the query failed. * * @since 2.0.0 * @throws \RuntimeException */ public function loadRow(); /** * Method to get an array of the result set rows from the database query where each row is an array. The array * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array. * * NOTE: Choosing to key the result array by a non-unique field can result in unwanted behavior and should be avoided. * * @param string $key The name of a field on which to key the result array. * * @return mixed The return value or null if the query failed. * * @since 2.0.0 * @throws \RuntimeException */ public function loadRowList($key = null); /** * Locks a table in the database. * * @param string $tableName The name of the table to unlock. * * @return $this * * @since 2.0.0 * @throws \RuntimeException */ public function lockTable($tableName); /** * Quotes and optionally escapes a string to database requirements for use in database queries. * * @param array|string $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * * @return string * * @since 2.0.0 */ public function quote($text, $escape = true); /** * Quotes a binary string to database requirements for use in database queries. * * @param string $data A binary string to quote. * * @return string The binary quoted input string. * * @since 1.7.0 */ public function quoteBinary($data); /** * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection * risks and reserved word conflicts. * * @param array|string $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. * Each type supports dot-notation name. * @param array|string $as The AS query part associated to $name. It can be string or array, in latter case it has to be * same length of $name; if is null there will not be any AS part for string or array element. * * @return array|string The quote wrapped name, same type of $name. * * @since 2.0.0 */ public function quoteName($name, $as = null); /** * Renames a table in the database. * * @param string $oldTable The name of the table to be renamed * @param string $newTable The new name for the table. * @param string $backup Table prefix * @param string $prefix For the table - used to rename constraints in non-mysql databases * * @return $this * * @since 2.0.0 * @throws \RuntimeException */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null); /** * This function replaces a string identifier with the configured table prefix. * * @param string $sql The SQL statement to prepare. * @param string $prefix The table prefix. * * @return string The processed SQL statement. * * @since 2.0.0 */ public function replacePrefix($sql, $prefix = '#__'); /** * Select a database for use. * * @param string $database The name of the database to select for use. * * @return boolean * * @since 2.0.0 * @throws \RuntimeException */ public function select($database); /** * Sets the SQL statement string for later execution. * * @param mixed $query The SQL statement to set either as a Query object or a string. * @param integer $offset The affected row offset to set. {@deprecated 3.0 Use LimitableInterface::setLimit() instead} * @param integer $limit The maximum affected rows to set. {@deprecated 3.0 Use LimitableInterface::setLimit() instead} * * @return $this * * @since 2.0.0 */ public function setQuery($query, $offset = 0, $limit = 0); /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 2.0.0 * @throws \RuntimeException */ public function transactionCommit($toSavepoint = false); /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 2.0.0 * @throws \RuntimeException */ public function transactionRollback($toSavepoint = false); /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 2.0.0 * @throws \RuntimeException */ public function transactionStart($asSavepoint = false); /** * Method to truncate a table. * * @param string $table The table to truncate * * @return void * * @since 2.0.0 * @throws \RuntimeException */ public function truncateTable($table); /** * Unlocks tables in the database. * * @return $this * * @since 2.0.0 * @throws \RuntimeException */ public function unlockTables(); /** * Updates a row in a table based on an object's properties. * * @param string $table The name of the database table to update. * @param object $object A reference to an object whose public properties match the table fields. * @param array|string $key The name of the primary key. * @param boolean $nulls True to update null fields or false to ignore them. * * @return boolean * * @since 2.0.0 * @throws \RuntimeException */ public function updateObject($table, &$object, $key, $nulls = false); } PK �9�\\�[ src/DatabaseExporter.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Joomla Framework Database Exporter Class * * @since 1.0 */ abstract class DatabaseExporter { /** * The type of output format. * * @var string * @since 1.0 */ protected $asFormat = 'xml'; /** * An array of cached data. * * @var array * @since 1.0 */ protected $cache = ['columns' => [], 'keys' => []]; /** * The database connector to use for exporting structure and/or data. * * @var DatabaseInterface * @since 1.0 */ protected $db; /** * An array input sources (table names). * * @var string[] * @since 1.0 */ protected $from = []; /** * An array of options for the exporter. * * @var \stdClass * @since 1.0 */ protected $options; /** * Constructor. * * Sets up the default options for the exporter. * * @since 1.0 */ public function __construct() { $this->options = new \stdClass; // Set up the class defaults: // Export not only structure $this->withStructure(); $this->withData(); // Export as xml. $this->asXml(); // Default destination is a string using $output = (string) $exporter; } /** * Magic function to exports the data to a string. * * @return string * * @since 1.0 */ public function __toString() { $buffer = ''; try { // Check everything is ok to run first. $this->check(); // Get the format. switch ($this->asFormat) { case 'xml': default: $buffer = $this->buildXml(); break; } } catch (\Exception $e) { // Do nothing } return $buffer; } /** * Set the output option for the exporter to XML format. * * @return $this * * @since 1.0 */ public function asXml() { $this->asFormat = 'xml'; return $this; } /** * Builds the XML data for the tables to export. * * @return string An XML string * * @since 1.0 * @throws \Exception if an error occurs. */ abstract protected function buildXml(); /** * Builds the XML structure to export. * * @return array An array of XML lines (strings). * * @since 1.0 * @throws \Exception if an error occurs. */ abstract protected function buildXmlStructure(); /** * Checks if all data and options are in order prior to exporting. * * @return $this * * @since 1.0 * @throws \Exception if an error is encountered. */ abstract public function check(); /** * Specifies a list of table names to export. * * @param string[]|string $from The name of a single table, or an array of the table names to export. * * @return $this * * @since 1.0 * @throws \InvalidArgumentException */ public function from($from) { if (\is_string($from)) { $this->from = [$from]; } elseif (\is_array($from)) { $this->from = $from; } else { throw new \InvalidArgumentException('The exporter requires either a single table name or array of table names'); } return $this; } /** * Get the generic name of the table, converting the database prefix to the wildcard string. * * @param string $table The name of the table. * * @return string The name of the table with the database prefix replaced with #__. * * @since 1.0 */ protected function getGenericTableName($table) { $prefix = $this->db->getPrefix(); // Replace the magic prefix if found. return preg_replace("|^$prefix|", '#__', $table); } /** * Sets the database connector to use for importing structure and/or data. * * @param DatabaseInterface $db The database connector. * * @return $this * * @since 1.0 */ public function setDbo(DatabaseInterface $db) { $this->db = $db; return $this; } /** * Sets an internal option to export the structure of the input table(s). * * @param boolean $setting True to export the structure, false to not. * * @return $this * * @since 1.0 */ public function withStructure($setting = true) { $this->options->withStructure = (boolean) $setting; return $this; } /** * Sets an internal option to export the data of the input table(s). * * @param boolean $setting True to export the data, false to not. * * @return $this * * @since 2.0.0 */ public function withData($setting = false) { $this->options->withData = (boolean) $setting; return $this; } /** * Builds the XML data to export. * * @return array An array of XML lines (strings). * * @since 2.0.0 * @throws \Exception if an error occurs. */ protected function buildXmlData() { $buffer = []; foreach ($this->from as $table) { // Replace the magic prefix if found. $table = $this->getGenericTableName($table); // Get the details columns information. $fields = $this->db->getTableColumns($table, false); $colblob = []; foreach ($fields as $field) { // Catch blob for conversion xml if ($field->Type == 'mediumblob') { $colblob[] = $field->Field; } } $this->db->setQuery( $this->db->getQuery(true) ->select($this->db->quoteName(array_keys($fields))) ->from($this->db->quoteName($table)) ); $rows = $this->db->loadObjectList(); if (!count($rows)) { continue; } $buffer[] = ' <table_data name="' . $table . '">'; foreach ($rows as $row) { $buffer[] = ' <row>'; foreach ($row as $key => $value) { if (!in_array($key, $colblob)) { if (is_null($value)) { $buffer[] = ' <field name="' . $key . '" value_is_null></field>'; } else { $buffer[] = ' <field name="' . $key . '">' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '</field>'; } } else { $buffer[] = ' <field name="' . $key . '">' . base64_encode($value) . '</field>'; } } $buffer[] = ' </row>'; } $buffer[] = ' </table_data>'; } return $buffer; } } PK �9�\���I I src/ParameterType.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Class defining the parameter types for prepared statements * * @since 2.0.0 */ final class ParameterType { /** * Defines a boolean parameter * * @var string * @since 2.0.0 */ public const BOOLEAN = 'boolean'; /** * Defines an integer parameter * * @var string * @since 2.0.0 */ public const INTEGER = 'int'; /** * Defines a large object parameter * * @var string * @since 2.0.0 */ public const LARGE_OBJECT = 'lob'; /** * Defines a null parameter * * @var string * @since 2.0.0 */ public const NULL = 'null'; /** * Defines a string parameter * * @var string * @since 2.0.0 */ public const STRING = 'string'; /** * Private constructor to prevent instantiation of this class * * @since 2.0.0 */ private function __construct() { } } PK �9�\z���� �� src/DatabaseQuery.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; use Joomla\Database\Exception\QueryTypeAlreadyDefinedException; use Joomla\Database\Exception\UnknownTypeException; /** * Joomla Framework Query Building Class. * * @since 1.0 * * @property-read array $bounded Holds key / value pair of bound objects. * @property-read array $parameterMapping Mapping array for parameter types. * @property-read DatabaseInterface $db The database driver. * @property-read string $sql The SQL query (if a direct query string was provided). * @property-read string $type The query type. * @property-read string|null $alias The query alias. * @property-read Query\QueryElement $element The query element for a generic query (type = null). * @property-read Query\QueryElement $select The select element. * @property-read Query\QueryElement $delete The delete element. * @property-read Query\QueryElement $update The update element. * @property-read Query\QueryElement $insert The insert element. * @property-read Query\QueryElement $from The from element. * @property-read Query\QueryElement[]|null $join The join elements. * @property-read Query\QueryElement $set The set element. * @property-read Query\QueryElement $where The where element. * @property-read Query\QueryElement $group The group element. * @property-read Query\QueryElement $having The having element. * @property-read Query\QueryElement $columns The column list for an INSERT statement. * @property-read Query\QueryElement $values The values list for an INSERT statement. * @property-read Query\QueryElement $order The order element. * @property-read boolean $autoIncrementField The auto increment insert field element. * @property-read Query\QueryElement $call The call element. * @property-read Query\QueryElement $exec The exec element. * @property-read Query\QueryElement[]|null $merge The list of query elements. * @property-read DatabaseQuery|null $querySet The query object. * @property-read array|null $selectRowNumber Details of window function. * @property-read string[] $nullDatetimeList The list of zero or null representation of a datetime. * @property-read integer|null $offset The offset for the result set. * @property-read integer|null $limit The limit for the result set. * @property-read integer $preparedIndex An internal index for the bindArray function for unique prepared parameters. */ abstract class DatabaseQuery implements QueryInterface { /** * Holds key / value pair of bound objects. * * @var array * @since 2.0.0 */ protected $bounded = []; /** * Mapping array for parameter types. * * @var array * @since 2.0.0 */ protected $parameterMapping = [ ParameterType::BOOLEAN => ParameterType::BOOLEAN, ParameterType::INTEGER => ParameterType::INTEGER, ParameterType::LARGE_OBJECT => ParameterType::LARGE_OBJECT, ParameterType::NULL => ParameterType::NULL, ParameterType::STRING => ParameterType::STRING, ]; /** * The database driver. * * @var DatabaseInterface * @since 1.0 */ protected $db; /** * The SQL query (if a direct query string was provided). * * @var string * @since 1.0 */ protected $sql; /** * The query type. * * @var string|null * @since 1.0 */ protected $type = ''; /** * The query alias. * * @var string|null * @since 2.0.0 */ protected $alias = null; /** * The query element for a generic query (type = null). * * @var Query\QueryElement * @since 1.0 */ protected $element; /** * The select element. * * @var Query\QueryElement * @since 1.0 */ protected $select; /** * The delete element. * * @var Query\QueryElement * @since 1.0 */ protected $delete; /** * The update element. * * @var Query\QueryElement * @since 1.0 */ protected $update; /** * The insert element. * * @var Query\QueryElement * @since 1.0 */ protected $insert; /** * The from element. * * @var Query\QueryElement * @since 1.0 */ protected $from; /** * The join elements. * * @var Query\QueryElement[] * @since 1.0 */ protected $join; /** * The set element. * * @var Query\QueryElement * @since 1.0 */ protected $set; /** * The where element. * * @var Query\QueryElement * @since 1.0 */ protected $where; /** * The group by element. * * @var Query\QueryElement * @since 1.0 */ protected $group; /** * The having element. * * @var Query\QueryElement * @since 1.0 */ protected $having; /** * The column list for an INSERT statement. * * @var Query\QueryElement * @since 1.0 */ protected $columns; /** * The values list for an INSERT statement. * * @var Query\QueryElement * @since 1.0 */ protected $values; /** * The order element. * * @var Query\QueryElement * @since 1.0 */ protected $order; /** * The auto increment insert field element. * * @var boolean * @since 1.0 */ protected $autoIncrementField = false; /** * The call element. * * @var Query\QueryElement * @since 1.0 */ protected $call; /** * The exec element. * * @var Query\QueryElement * @since 1.0 */ protected $exec; /** * The list of query elements, which may include UNION, UNION ALL, EXCEPT and INTERSECT. * * @var Query\QueryElement[] * @since 2.0.0 */ protected $merge; /** * The query object. * * @var Query\DatabaseQuery * @since 2.0.0 */ protected $querySet; /** * Details of window function. * * @var array|null * @since 2.0.0 */ protected $selectRowNumber; /** * The list of zero or null representation of a datetime. * * @var string[] * @since 2.0.0 */ protected $nullDatetimeList = []; /** * The offset for the result set. * * @var integer|null * @since 2.0.0 */ protected $offset; /** * The limit for the result set. * * @var integer|null * @since 2.0.0 */ protected $limit; /** * An internal index for the bindArray function for unique prepared parameters. * * @var integer * @since 2.0.0 */ protected $preparedIndex = 0; /** * Class constructor. * * @param DatabaseInterface $db The database driver. * * @since 1.0 */ public function __construct(DatabaseInterface $db = null) { $this->db = $db; } /** * Magic function to convert the query to a string. * * @return string The completed query. * * @since 1.0 */ public function __toString() { if ($this->sql) { return $this->processLimit($this->sql, $this->limit, $this->offset); } $query = ''; switch ($this->type) { case 'element': $query .= (string) $this->element; break; case 'select': $query .= (string) $this->select; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->selectRowNumber === null) { if ($this->group) { $query .= (string) $this->group; } if ($this->having) { $query .= (string) $this->having; } if ($this->merge) { // Special case for merge foreach ($this->merge as $element) { $query .= (string) $element; } } } if ($this->order) { $query .= (string) $this->order; } break; case 'querySet': $query = $this->querySet; if ($query->order || ($query->limit || $query->offset)) { // If ORDER BY or LIMIT statement exist then parentheses is required for the first query $query = "($query)"; } if ($this->merge) { // Special case for merge foreach ($this->merge as $element) { $query .= (string) $element; } } if ($this->order) { $query .= (string) $this->order; } break; case 'delete': $query .= (string) $this->delete; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } break; case 'update': $query .= (string) $this->update; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } $query .= (string) $this->set; if ($this->where) { $query .= (string) $this->where; } break; case 'insert': $query .= (string) $this->insert; // Set method if ($this->set) { $query .= (string) $this->set; } elseif ($this->values) { // Columns-Values method if ($this->columns) { $query .= (string) $this->columns; } $elements = $this->values->getElements(); if (!($elements[0] instanceof $this)) { $query .= ' VALUES '; } $query .= (string) $this->values; } break; case 'call': $query .= (string) $this->call; break; case 'exec': $query .= (string) $this->exec; break; } $query = $this->processLimit($query, $this->limit, $this->offset); if ($this->type === 'select' && $this->alias !== null) { $query = '(' . $query . ') AS ' . $this->alias; } return $query; } /** * Magic function to get protected variable value * * @param string $name The name of the variable. * * @return mixed * * @since 1.0 */ public function __get($name) { if (property_exists($this, $name)) { return $this->$name; } $trace = debug_backtrace(); trigger_error( 'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE ); } /** * Add a single column, or array of columns to the CALL clause of the query. * * Usage: * $query->call('a.*')->call('b.id'); * $query->call(array('a.*', 'b.id')); * * @param mixed $columns A string or an array of field names. * * @return $this * * @since 1.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function call($columns) { if ($this->type !== null && $this->type !== '' && $this->type !== 'call') { throw new QueryTypeAlreadyDefinedException( \sprintf( 'Cannot set the query type to "call" as the query type is already set to "%s".' . ' You should either call the `clear()` method to reset the type or create a new query object.', $this->type ) ); } $this->type = 'call'; if ($this->call === null) { $this->call = new Query\QueryElement('CALL', $columns); } else { $this->call->append($columns); } return $this; } /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * Usage: * $query->select($query->castAs('CHAR', 'a')); * * @param string $type The type of string to cast as. * @param string $value The value to cast as a char. * @param string $length Optionally specify the length of the field (if the type supports it otherwise * ignored). * * @return string SQL statement to cast the value as a char type. * * @since 1.0 */ public function castAs(string $type, string $value, ?string $length = null) { switch (strtoupper($type)) { case 'CHAR': return $value; default: throw new UnknownTypeException( sprintf( 'Type %s was not recognised by the database driver as valid for casting', $type ) ); } } /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * Usage: * $query->select($query->castAsChar('a')); * * @param string $value The value to cast as a char. * * @return string SQL statement to cast the value as a char type. * * @since 1.0 * @deprecated 3.0 Use $query->castAs('CHAR', $value) */ public function castAsChar($value) { return $this->castAs('CHAR', $value); } /** * Gets the number of characters in a string. * * Note, use 'length' to find the number of bytes in a string. * * Usage: * $query->select($query->charLength('a')); * * @param string $field A value. * @param string|null $operator Comparison operator between charLength integer value and $condition * @param string|null $condition Integer value to compare charLength with. * * @return string The required char length call. * * @since 1.0 */ public function charLength($field, $operator = null, $condition = null) { $statement = 'CHAR_LENGTH(' . $field . ')'; if ($operator !== null && $condition !== null) { $statement .= ' ' . $operator . ' ' . $condition; } return $statement; } /** * Clear data from the query or a specific clause of the query. * * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query. * * @return $this * * @since 1.0 */ public function clear($clause = null) { $this->sql = null; switch ($clause) { case 'alias': $this->alias = null; break; case 'select': $this->select = null; $this->type = null; $this->selectRowNumber = null; break; case 'delete': $this->delete = null; $this->type = null; break; case 'update': $this->update = null; $this->type = null; break; case 'insert': $this->insert = null; $this->type = null; $this->autoIncrementField = null; break; case 'querySet': $this->querySet = null; $this->type = null; break; case 'from': $this->from = null; break; case 'join': $this->join = null; break; case 'set': $this->set = null; break; case 'where': $this->where = null; break; case 'group': $this->group = null; break; case 'having': $this->having = null; break; case 'merge': $this->merge = null; break; case 'order': $this->order = null; break; case 'columns': $this->columns = null; break; case 'values': $this->values = null; break; case 'exec': $this->exec = null; $this->type = null; break; case 'call': $this->call = null; $this->type = null; break; case 'limit': $this->offset = 0; $this->limit = 0; break; case 'offset': $this->offset = 0; break; case 'bounded': $this->bounded = []; break; default: $this->type = null; $this->alias = null; $this->bounded = []; $this->select = null; $this->selectRowNumber = null; $this->delete = null; $this->update = null; $this->insert = null; $this->querySet = null; $this->from = null; $this->join = null; $this->set = null; $this->where = null; $this->group = null; $this->having = null; $this->merge = null; $this->order = null; $this->columns = null; $this->values = null; $this->autoIncrementField = null; $this->exec = null; $this->call = null; $this->offset = 0; $this->limit = 0; break; } return $this; } /** * Adds a column, or array of column names that would be used for an INSERT INTO statement. * * @param array|string $columns A column name, or array of column names. * * @return $this * * @since 1.0 */ public function columns($columns) { if ($this->columns === null) { $this->columns = new Query\QueryElement('()', $columns); } else { $this->columns->append($columns); } return $this; } /** * Concatenates an array of column names or values. * * Usage: * $query->select($query->concatenate(array('a', 'b'))); * * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * * @return string The concatenated values. * * @since 1.0 */ public function concatenate($values, $separator = null) { if ($separator !== null) { return 'CONCATENATE(' . implode(' || ' . $this->quote($separator) . ' || ', $values) . ')'; } return 'CONCATENATE(' . implode(' || ', $values) . ')'; } /** * Gets the current date and time. * * Usage: * $query->where('published_up < '.$query->currentTimestamp()); * * @return string * * @since 1.0 */ public function currentTimestamp() { return 'CURRENT_TIMESTAMP()'; } /** * Add to the current date and time. * * Usage: * $query->select($query->dateAdd()); * * Prefixing the interval with a - (negative sign) will cause subtraction to be used. * Note: Not all drivers support all units. * * @param string $date The db quoted string representation of the date to add to. May be date or datetime * @param string $interval The string representation of the appropriate number of units * @param string $datePart The part of the date to perform the addition on * * @return string The string with the appropriate sql for addition of dates * * @link https://dev.mysql.com/doc/en/date-and-time-functions.html * @since 1.5.0 */ public function dateAdd($date, $interval, $datePart) { return 'DATE_ADD(' . $date . ', INTERVAL ' . $interval . ' ' . $datePart . ')'; } /** * Returns a PHP date() function compliant date format for the database driver. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the getDateFormat method directly. * * @return string The format string. * * @since 1.0 * @throws \RuntimeException */ public function dateFormat() { if (!($this->db instanceof DatabaseInterface)) { throw new \RuntimeException(sprintf('A %s instance is not set to the query object.', DatabaseInterface::class)); } return $this->db->getDateFormat(); } /** * Creates a HTML formatted dump of the query for debugging purposes. * * Usage: * echo $query->dump(); * * @return string * * @since 1.0 * @deprecated 3.0 Deprecated without replacement */ public function dump() { trigger_deprecation( 'joomla/database', '2.0.0', '%s() is deprecated and will be removed in 3.0.', __METHOD__ ); return '<pre class="jdatabasequery">' . str_replace('#__', $this->db->getPrefix(), $this) . '</pre>'; } /** * Add a table name to the DELETE clause of the query. * * Usage: * $query->delete('#__a')->where('id = 1'); * * @param string $table The name of the table to delete from. * * @return $this * * @since 1.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function delete($table = null) { if ($this->type !== null && $this->type !== '' && $this->type !== 'delete') { throw new QueryTypeAlreadyDefinedException( \sprintf( 'Cannot set the query type to "delete" as the query type is already set to "%s".' . ' You should either call the `clear()` method to reset the type or create a new query object.', $this->type ) ); } $this->type = 'delete'; $this->delete = new Query\QueryElement('DELETE', null); if (!empty($table)) { $this->from($table); } return $this; } /** * Alias for escape method * * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 1.0 * @throws \RuntimeException if the internal db property is not a valid object. */ public function e($text, $extra = false) { return $this->escape($text, $extra); } /** * Method to escape a string for usage in an SQL statement. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the escape method directly. * * Note that 'e' is an alias for this method as it is in DatabaseDriver. * * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 1.0 * @throws \RuntimeException if the internal db property is not a valid object. */ public function escape($text, $extra = false) { if (!($this->db instanceof DatabaseInterface)) { throw new \RuntimeException(sprintf('A %s instance is not set to the query object.', DatabaseInterface::class)); } return $this->db->escape($text, $extra); } /** * Add a single column, or array of columns to the EXEC clause of the query. * * Usage: * $query->exec('a.*')->exec('b.id'); * $query->exec(array('a.*', 'b.id')); * * @param array|string $columns A string or an array of field names. * * @return $this * * @since 1.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function exec($columns) { if ($this->type !== null && $this->type !== '' && $this->type !== 'exec') { throw new QueryTypeAlreadyDefinedException( \sprintf( 'Cannot set the query type to "exec" as the query type is already set to "%s".' . ' You should either call the `clear()` method to reset the type or create a new query object.', $this->type ) ); } $this->type = 'exec'; if ($this->exec === null) { $this->exec = new Query\QueryElement('EXEC', $columns); } else { $this->exec->append($columns); } return $this; } /** * Find a value in a varchar used like a set. * * Ensure that the value is an integer before passing to the method. * * Usage: * $query->findInSet((int) $parent->id, 'a.assigned_cat_ids') * * @param string $value The value to search for. * @param string $set The set of values. * * @return string A representation of the MySQL find_in_set() function for the driver. * * @since 1.5.0 */ public function findInSet($value, $set) { return ''; } /** * Add a table to the FROM clause of the query. * * Usage: * $query->select('*')->from('#__a'); * $query->select('*')->from($subquery->alias('a')); * * @param string|DatabaseQuery $table The name of the table or a DatabaseQuery object (or a child of it) with alias set. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function from($table) { if ($table instanceof $this && $table->alias === null) { throw new \RuntimeException('JLIB_DATABASE_ERROR_NULL_SUBQUERY_ALIAS'); } if ($this->from === null) { $this->from = new Query\QueryElement('FROM', $table); } else { $this->from->append($table); } return $this; } /** * Add alias for current query. * * Usage: * $query->select('*')->from('#__a')->alias('subquery'); * * @param string $alias Alias used for a JDatabaseQuery. * * @return $this * * @since 2.0.0 */ public function alias($alias) { $this->alias = $alias; return $this; } /** * Used to get a string to extract year from date column. * * Usage: * $query->select($query->year($query->quoteName('dateColumn'))); * * @param string $date Date column containing year to be extracted. * * @return string Returns string to extract year from a date. * * @since 1.0 */ public function year($date) { return 'YEAR(' . $date . ')'; } /** * Used to get a string to extract month from date column. * * Usage: * $query->select($query->month($query->quoteName('dateColumn'))); * * @param string $date Date column containing month to be extracted. * * @return string Returns string to extract month from a date. * * @since 1.0 */ public function month($date) { return 'MONTH(' . $date . ')'; } /** * Used to get a string to extract day from date column. * * Usage: * $query->select($query->day($query->quoteName('dateColumn'))); * * @param string $date Date column containing day to be extracted. * * @return string Returns string to extract day from a date. * * @since 1.0 */ public function day($date) { return 'DAY(' . $date . ')'; } /** * Used to get a string to extract hour from date column. * * Usage: * $query->select($query->hour($query->quoteName('dateColumn'))); * * @param string $date Date column containing hour to be extracted. * * @return string Returns string to extract hour from a date. * * @since 1.0 */ public function hour($date) { return 'HOUR(' . $date . ')'; } /** * Used to get a string to extract minute from date column. * * Usage: * $query->select($query->minute($query->quoteName('dateColumn'))); * * @param string $date Date column containing minute to be extracted. * * @return string Returns string to extract minute from a date. * * @since 1.0 */ public function minute($date) { return 'MINUTE(' . $date . ')'; } /** * Used to get a string to extract seconds from date column. * * Usage: * $query->select($query->second($query->quoteName('dateColumn'))); * * @param string $date Date column containing second to be extracted. * * @return string Returns string to extract second from a date. * * @since 1.0 */ public function second($date) { return 'SECOND(' . $date . ')'; } /** * Add a grouping column to the GROUP clause of the query. * * Usage: * $query->group('id'); * * @param array|string $columns A string or array of ordering columns. * * @return $this * * @since 1.0 */ public function group($columns) { if ($this->group === null) { $this->group = new Query\QueryElement('GROUP BY', $columns); } else { $this->group->append($columns); } return $this; } /** * A conditions to the HAVING clause of the query. * * Usage: * $query->group('id')->having('COUNT(id) > 5'); * * @param array|string $conditions A string or array of columns. * @param string $glue The glue by which to join the conditions. Defaults to AND. * * @return $this * * @since 1.0 */ public function having($conditions, $glue = 'AND') { if ($this->having === null) { $glue = strtoupper($glue); $this->having = new Query\QueryElement('HAVING', $conditions, " $glue "); } else { $this->having->append($conditions); } return $this; } /** * Add a table name to the INSERT clause of the query. * * Usage: * $query->insert('#__a')->set('id = 1'); * $query->insert('#__a')->columns('id, title')->values('1,2')->values('3,4'); * $query->insert('#__a')->columns('id, title')->values(array('1,2', '3,4')); * * @param string $table The name of the table to insert data into. * @param boolean $incrementField The name of the field to auto increment. * * @return $this * * @since 1.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function insert($table, $incrementField = false) { if ($this->type !== null && $this->type !== '' && $this->type !== 'insert') { throw new QueryTypeAlreadyDefinedException( \sprintf( 'Cannot set the query type to "insert" as the query type is already set to "%s".' . ' You should either call the `clear()` method to reset the type or create a new query object.', $this->type ) ); } $this->type = 'insert'; $this->insert = new Query\QueryElement('INSERT INTO', $table); $this->autoIncrementField = $incrementField; return $this; } /** * Add a JOIN clause to the query. * * Usage: * $query->join('INNER', 'b', 'b.id = a.id); * * @param string $type The type of join. This string is prepended to the JOIN keyword. * @param string $table The name of table. * @param string $condition The join condition. * * @return $this * * @since 1.0 */ public function join($type, $table, $condition = null) { $type = strtoupper($type) . ' JOIN'; if ($condition !== null) { $this->join[] = new Query\QueryElement($type, [$table, $condition], ' ON '); } else { $this->join[] = new Query\QueryElement($type, $table); } return $this; } /** * Add an INNER JOIN clause to the query. * * Usage: * $query->innerJoin('b', 'b.id = a.id')->innerJoin('c', 'c.id = b.id'); * * @param string $table The name of table. * @param string $condition The join condition. * * @return $this * * @since 1.0 */ public function innerJoin($table, $condition = null) { return $this->join('INNER', $table, $condition); } /** * Add an OUTER JOIN clause to the query. * * Usage: * $query->outerJoin('b', 'b.id = a.id')->leftJoin('c', 'c.id = b.id'); * * @param string $table The name of table. * @param string $condition The join condition. * * @return $this * * @since 1.0 */ public function outerJoin($table, $condition = null) { return $this->join('OUTER', $table, $condition); } /** * Add a LEFT JOIN clause to the query. * * Usage: * $query->leftJoin('b', 'b.id = a.id')->leftJoin('c', 'c.id = b.id'); * * @param string $table The name of table. * @param string $condition The join condition. * * @return $this * * @since 1.0 */ public function leftJoin($table, $condition = null) { return $this->join('LEFT', $table, $condition); } /** * Add a RIGHT JOIN clause to the query. * * Usage: * $query->rightJoin('b', 'b.id = a.id')->rightJoin('c', 'c.id = b.id'); * * @param string $table The name of table. * @param string $condition The join condition. * * @return $this * * @since 1.0 */ public function rightJoin($table, $condition = null) { return $this->join('RIGHT', $table, $condition); } /** * Get the length of a string in bytes. * * Note, use 'charLength' to find the number of characters in a string. * * Usage: * query->where($query->length('a').' > 3'); * * @param string $value The string to measure. * * @return integer * * @since 1.0 */ public function length($value) { return 'LENGTH(' . $value . ')'; } /** * Get the null or zero representation of a timestamp for the database driver. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the nullDate method directly. * * Usage: * $query->where('modified_date <> '.$query->nullDate()); * * @param boolean $quoted Optionally wraps the null date in database quotes (true by default). * * @return string Null or zero representation of a timestamp. * * @since 1.0 * @throws \RuntimeException */ public function nullDate($quoted = true) { if (!($this->db instanceof DatabaseInterface)) { throw new \RuntimeException(sprintf('A %s instance is not set to the query object.', DatabaseInterface::class)); } $result = $this->db->getNullDate(); if ($quoted) { return $this->db->quote($result); } return $result; } /** * Generate a SQL statement to check if column represents a zero or null datetime. * * Usage: * $query->where($query->isNullDatetime('modified_date')); * * @param string $column A column name. * * @return string * * @since 2.0.0 */ public function isNullDatetime($column) { if (!$this->db instanceof DatabaseInterface) { throw new \RuntimeException(sprintf('A %s instance is not set to the query object.', DatabaseInterface::class)); } if ($this->nullDatetimeList) { return "($column IN (" . implode(', ', $this->db->quote($this->nullDatetimeList)) . ") OR $column IS NULL)"; } return "$column IS NULL"; } /** * Add a ordering column to the ORDER clause of the query. * * Usage: * $query->order('foo')->order('bar'); * $query->order(array('foo','bar')); * * @param array|string $columns A string or array of ordering columns. * * @return $this * * @since 1.0 */ public function order($columns) { if ($this->order === null) { $this->order = new Query\QueryElement('ORDER BY', $columns); } else { $this->order->append($columns); } return $this; } /** * Alias for quote method * * @param array|string $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * * @return string The quoted input string. * * @since 1.0 * @throws \RuntimeException if the internal db property is not a valid object. */ public function q($text, $escape = true) { return $this->quote($text, $escape); } /** * Method to quote and optionally escape a string to database requirements for insertion into the database. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the quote method directly. * * Note that 'q' is an alias for this method as it is in DatabaseDriver. * * Usage: * $query->quote('fulltext'); * $query->q('fulltext'); * $query->q(array('option', 'fulltext')); * * @param array|string $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * * @return string The quoted input string. * * @since 1.0 * @throws \RuntimeException if the internal db property is not a valid object. */ public function quote($text, $escape = true) { if (!($this->db instanceof DatabaseInterface)) { throw new \RuntimeException(sprintf('A %s instance is not set to the query object.', DatabaseInterface::class)); } return $this->db->quote($text, $escape); } /** * Alias for quoteName method * * @param array|string $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. * Each type supports dot-notation name. * @param array|string $as The AS query part associated to $name. It can be string or array, in latter case it has to be * same length of $name; if is null there will not be any AS part for string or array element. * * @return array|string The quote wrapped name, same type of $name. * * @since 1.0 * @throws \RuntimeException if the internal db property is not a valid object. */ public function qn($name, $as = null) { return $this->quoteName($name, $as); } /** * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection * risks and reserved word conflicts. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the quoteName method directly. * * Note that 'qn' is an alias for this method as it is in DatabaseDriver. * * Usage: * $query->quoteName('#__a'); * $query->qn('#__a'); * * @param array|string $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. * Each type supports dot-notation name. * @param array|string $as The AS query part associated to $name. It can be string or array, in latter case it has to be * same length of $name; if is null there will not be any AS part for string or array element. * * @return array|string The quote wrapped name, same type of $name. * * @since 1.0 * @throws \RuntimeException if the internal db property is not a valid object. */ public function quoteName($name, $as = null) { if (!($this->db instanceof DatabaseInterface)) { throw new \RuntimeException(sprintf('A %s instance is not set to the query object.', DatabaseInterface::class)); } return $this->db->quoteName($name, $as); } /** * Get the function to return a random floating-point value * * Usage: * $query->rand(); * * @return string * * @since 1.5.0 */ public function rand() { return ''; } /** * Get the regular expression operator * * Usage: * $query->where('field ' . $query->regexp($search)); * * @param string $value The regex pattern. * * @return string * * @since 1.5.0 */ public function regexp($value) { return ' ' . $value; } /** * Add a single column, or array of columns to the SELECT clause of the query. * * Note that you must not mix insert, update, delete and select method calls when building a query. * The select method can, however, be called multiple times in the same query. * * Usage: * $query->select('a.*')->select('b.id'); * $query->select(array('a.*', 'b.id')); * * @param array|string $columns A string or an array of field names. * * @return $this * * @since 1.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function select($columns) { if ($this->type !== null && $this->type !== '' && $this->type !== 'select') { throw new QueryTypeAlreadyDefinedException( \sprintf( 'Cannot set the query type to "select" as the query type is already set to "%s".' . ' You should either call the `clear()` method to reset the type or create a new query object.', $this->type ) ); } $this->type = 'select'; if ($this->select === null) { $this->select = new Query\QueryElement('SELECT', $columns); } else { $this->select->append($columns); } return $this; } /** * Add a single condition string, or an array of strings to the SET clause of the query. * * Usage: * $query->set('a = 1')->set('b = 2'); * $query->set(array('a = 1', 'b = 2'); * * @param array|string $conditions A string or array of string conditions. * @param string $glue The glue by which to join the condition strings. Defaults to ,. * Note that the glue is set on first use and cannot be changed. * * @return $this * * @since 1.0 */ public function set($conditions, $glue = ',') { if ($this->set === null) { $glue = strtoupper($glue); $this->set = new Query\QueryElement('SET', $conditions, \PHP_EOL . "\t$glue "); } else { $this->set->append($conditions); } return $this; } /** * Sets the offset and limit for the result set, if the database driver supports it. * * Usage: * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record) * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record) * * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return $this * * @since 2.0.0 */ public function setLimit($limit = 0, $offset = 0) { $this->limit = (int) $limit; $this->offset = (int) $offset; return $this; } /** * Allows a direct query to be provided to the database driver's setQuery() method, but still allow queries * to have bounded variables. * * Usage: * $query->setQuery('select * from #__users'); * * @param DatabaseQuery|string $sql A SQL query string or DatabaseQuery object * * @return $this * * @since 1.0 */ public function setQuery($sql) { $this->sql = $sql; return $this; } /** * Add a table name to the UPDATE clause of the query. * * Usage: * $query->update('#__foo')->set(...); * * @param string $table A table to update. * * @return $this * * @since 1.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function update($table) { if ($this->type !== null && $this->type !== '' && $this->type !== 'update') { throw new QueryTypeAlreadyDefinedException( \sprintf( 'Cannot set the query type to "update" as the query type is already set to "%s".' . ' You should either call the `clear()` method to reset the type or create a new query object.', $this->type ) ); } $this->type = 'update'; $this->update = new Query\QueryElement('UPDATE', $table); return $this; } /** * Adds a tuple, or array of tuples that would be used as values for an INSERT INTO statement. * * Usage: * $query->values('1,2,3')->values('4,5,6'); * $query->values(array('1,2,3', '4,5,6')); * * @param array|string $values A single tuple, or array of tuples. * * @return $this * * @since 1.0 */ public function values($values) { if ($this->values === null) { $this->values = new Query\QueryElement('()', $values, '),('); } else { $this->values->append($values); } return $this; } /** * Add a single condition, or an array of conditions to the WHERE clause of the query. * * Usage: * $query->where('a = 1')->where('b = 2'); * $query->where(array('a = 1', 'b = 2')); * * @param array|string $conditions A string or array of where conditions. * @param string $glue The glue by which to join the conditions. Defaults to AND. * Note that the glue is set on first use and cannot be changed. * * @return $this * * @since 1.0 */ public function where($conditions, $glue = 'AND') { if ($this->where === null) { $glue = strtoupper($glue); $this->where = new Query\QueryElement('WHERE', $conditions, " $glue "); } else { $this->where->append($conditions); } return $this; } /** * Add a WHERE IN statement to the query. * * Note that all values must be the same data type. * * Usage * $query->whereIn('id', [1, 2, 3]); * * @param string $keyName Key name for the where clause * @param array $keyValues Array of values to be matched * @param array|string $dataType Constant corresponding to a SQL datatype. It can be an array, in this case it * has to be same length of $keyValues * * @return $this * * @since 2.0.0 */ public function whereIn(string $keyName, array $keyValues, $dataType = ParameterType::INTEGER) { return $this->where( $keyName . ' IN (' . implode(',', $this->bindArray($keyValues, $dataType)) . ')' ); } /** * Add a WHERE NOT IN statement to the query. * * Note that all values must be the same data type. * * Usage * $query->whereNotIn('id', [1, 2, 3]); * * @param string $keyName Key name for the where clause * @param array $keyValues Array of values to be matched * @param array|string $dataType Constant corresponding to a SQL datatype. It can be an array, in this case it * has to be same length of $keyValues * * @return $this * * @since 2.0.0 */ public function whereNotIn(string $keyName, array $keyValues, $dataType = ParameterType::INTEGER) { return $this->where( $keyName . ' NOT IN (' . implode(',', $this->bindArray($keyValues, $dataType)) . ')' ); } /** * Extend the WHERE clause with a single condition or an array of conditions, with a potentially * different logical operator from the one in the current WHERE clause. * * Usage: * $query->where(array('a = 1', 'b = 2'))->extendWhere('XOR', array('c = 3', 'd = 4')); * will produce: WHERE ((a = 1 AND b = 2) XOR (c = 3 AND d = 4) * * @param string $outerGlue The glue by which to join the conditions to the current WHERE conditions. * @param mixed $conditions A string or array of WHERE conditions. * @param string $innerGlue The glue by which to join the conditions. Defaults to AND. * * @return $this * * @since 1.3.0 */ public function extendWhere($outerGlue, $conditions, $innerGlue = 'AND') { // Replace the current WHERE with a new one which has the old one as an unnamed child. $this->where = new Query\QueryElement('WHERE', $this->where->setName('()'), " $outerGlue "); // Append the new conditions as a new unnamed child. $this->where->append(new Query\QueryElement('()', $conditions, " $innerGlue ")); return $this; } /** * Extend the WHERE clause with an OR and a single condition or an array of conditions. * * Usage: * $query->where(array('a = 1', 'b = 2'))->orWhere(array('c = 3', 'd = 4')); * will produce: WHERE ((a = 1 AND b = 2) OR (c = 3 AND d = 4) * * @param mixed $conditions A string or array of WHERE conditions. * @param string $glue The glue by which to join the conditions. Defaults to AND. * * @return $this * * @since 1.3.0 */ public function orWhere($conditions, $glue = 'AND') { return $this->extendWhere('OR', $conditions, $glue); } /** * Extend the WHERE clause with an AND and a single condition or an array of conditions. * * Usage: * $query->where(array('a = 1', 'b = 2'))->andWhere(array('c = 3', 'd = 4')); * will produce: WHERE ((a = 1 AND b = 2) AND (c = 3 OR d = 4) * * @param mixed $conditions A string or array of WHERE conditions. * @param string $glue The glue by which to join the conditions. Defaults to OR. * * @return $this * * @since 1.3.0 */ public function andWhere($conditions, $glue = 'OR') { return $this->extendWhere('AND', $conditions, $glue); } /** * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. * * @param array|string|integer $key The key that will be used in your SQL query to reference the value. Usually of * the form ':key', but can also be an integer. * @param mixed $value The value that will be bound. It can be an array, in this case it has to be * same length of $key; The value is passed by reference to support output * parameters such as those possible with stored procedures. * @param array|string $dataType Constant corresponding to a SQL datatype. It can be an array, in this case it * has to be same length of $key * @param integer $length The length of the variable. Usually required for OUTPUT parameters. * @param array $driverOptions Optional driver options to be used. * * @return $this * * @since 1.5.0 * @throws \InvalidArgumentException */ public function bind($key, &$value, $dataType = ParameterType::STRING, $length = 0, $driverOptions = []) { if (!$key) { throw new \InvalidArgumentException('A key is required'); } $key = (array) $key; $count = \count($key); if (\is_array($value)) { if ($count != \count($value)) { throw new \InvalidArgumentException('Array length of $key and $value are not equal'); } reset($value); } if (\is_array($dataType) && $count != \count($dataType)) { throw new \InvalidArgumentException('Array length of $key and $dataType are not equal'); } foreach ($key as $index) { if (\is_array($value)) { $localValue = &$value[key($value)]; next($value); } else { $localValue = &$value; } if (\is_array($dataType)) { $localDataType = array_shift($dataType); } else { $localDataType = $dataType; } // Validate parameter type if (!isset($this->parameterMapping[$localDataType])) { throw new \InvalidArgumentException(sprintf('Unsupported parameter type `%s`', $localDataType)); } $obj = new \stdClass; $obj->value = &$localValue; $obj->dataType = $this->parameterMapping[$localDataType]; $obj->length = $length; $obj->driverOptions = $driverOptions; // Add the Key/Value into the bounded array $this->bounded[$index] = $obj; unset($localValue); } return $this; } /** * Method to unbind a bound variable. * * @param array|string|integer $key The key or array of keys to unbind. * * @return $this * * @since 2.0.0 */ public function unbind($key) { if (\is_array($key)) { foreach ($key as $k) { unset($this->bounded[$k]); } } else { unset($this->bounded[$key]); } return $this; } /** * Binds an array of values and returns an array of prepared parameter names. * * Note that all values must be the same data type. * * Usage: * $query->where('column in (' . implode(',', $query->bindArray($keyValues, $dataType)) . ')'); * * @param array $values Values to bind * @param array|string $dataType Constant corresponding to a SQL datatype. It can be an array, in this case it * has to be same length of $key * * @return array An array with parameter names * * @since 2.0.0 */ public function bindArray(array $values, $dataType = ParameterType::INTEGER) { $parameterNames = []; for ($i = 0; $i < count($values); $i++) { $parameterNames[] = ':preparedArray' . (++$this->preparedIndex); } $this->bind($parameterNames, $values, $dataType); return $parameterNames; } /** * Method to provide basic copy support. * * Any object pushed into the data of this class should have its own __clone() implementation. * This method does not support copying objects in a multidimensional array. * * @return void * * @since 1.0 */ public function __clone() { foreach ($this as $k => $v) { if ($k === 'db') { continue; } if (\is_object($v)) { $this->{$k} = clone $v; } elseif (\is_array($v)) { foreach ($v as $i => $element) { if (\is_object($element)) { $this->{$k}[$i] = clone $element; } } } } } /** * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is * returned. * * @param mixed $key The bounded variable key to retrieve. * * @return mixed * * @since 1.5.0 */ public function &getBounded($key = null) { if (empty($key)) { return $this->bounded; } if (isset($this->bounded[$key])) { return $this->bounded[$key]; } } /** * Combine a select statement to the current query by one of the set operators. * Operators: UNION, UNION ALL, EXCEPT or INTERSECT. * * @param string $name The name of the set operator with parentheses. * @param DatabaseQuery|string $query The DatabaseQuery object or string. * * @return $this * * @since 2.0.0 */ protected function merge($name, $query) { $this->type = $this->type ?: 'select'; $this->merge[] = new Query\QueryElement($name, $query); return $this; } /** * Add a query to UNION with the current query. * * Usage: * $query->union('SELECT name FROM #__foo') * $query->union('SELECT name FROM #__foo', true) * * @param DatabaseQuery|string $query The DatabaseQuery object or string to union. * @param boolean $distinct True to only return distinct rows from the union. * * @return $this * * @since 1.0 */ public function union($query, $distinct = true) { // Set up the name with parentheses, the DISTINCT flag is redundant return $this->merge($distinct ? 'UNION ()' : 'UNION ALL ()', $query); } /** * Add a query to UNION ALL with the current query. * * Usage: * $query->unionAll('SELECT name FROM #__foo') * * @param DatabaseQuery|string $query The DatabaseQuery object or string to union. * * @return $this * * @see union * @since 1.5.0 */ public function unionAll($query) { return $this->union($query, false); } /** * Set a single query to the query set. * On this type of DatabaseQuery you can use union(), unionAll(), order() and setLimit() * * Usage: * $query->querySet($query2->select('name')->from('#__foo')->order('id DESC')->setLimit(1)) * ->unionAll($query3->select('name')->from('#__foo')->order('id')->setLimit(1)) * ->order('name') * ->setLimit(1) * * @param DatabaseQuery $query The DatabaseQuery object or string. * * @return $this * * @since 2.0.0 */ public function querySet($query) { $this->type = 'querySet'; $this->querySet = $query; return $this; } /** * Create a DatabaseQuery object of type querySet from current query. * * Usage: * $query->select('name')->from('#__foo')->order('id DESC')->setLimit(1) * ->toQuerySet() * ->unionAll($query2->select('name')->from('#__foo')->order('id')->setLimit(1)) * ->order('name') * ->setLimit(1) * * @return DatabaseQuery A new object of the DatabaseQuery. * * @since 2.0.0 */ public function toQuerySet() { return (new static($this->db))->querySet($this); } /** * Find and replace sprintf-like tokens in a format string. * Each token takes one of the following forms: * %% - A literal percent character. * %[t] - Where [t] is a type specifier. * %[n]$[x] - Where [n] is an argument specifier and [t] is a type specifier. * * Types: * a - Numeric: Replacement text is coerced to a numeric type but not quoted or escaped. * e - Escape: Replacement text is passed to $this->escape(). * E - Escape (extra): Replacement text is passed to $this->escape() with true as the second argument. * n - Name Quote: Replacement text is passed to $this->quoteName(). * q - Quote: Replacement text is passed to $this->quote(). * Q - Quote (no escape): Replacement text is passed to $this->quote() with false as the second argument. * r - Raw: Replacement text is used as-is. (Be careful) * * Date Types: * - Replacement text automatically quoted (use uppercase for Name Quote). * - Replacement text should be a string in date format or name of a date column. * y/Y - Year * m/M - Month * d/D - Day * h/H - Hour * i/I - Minute * s/S - Second * * Invariable Types: * - Takes no argument. * - Argument index not incremented. * t - Replacement text is the result of $this->currentTimestamp(). * z - Replacement text is the result of $this->nullDate(false). * Z - Replacement text is the result of $this->nullDate(true). * * Usage: * $query->format('SELECT %1$n FROM %2$n WHERE %3$n = %4$a', 'foo', '#__foo', 'bar', 1); * Returns: SELECT `foo` FROM `#__foo` WHERE `bar` = 1 * * Notes: * The argument specifier is optional but recommended for clarity. * The argument index used for unspecified tokens is incremented only when used. * * @param string $format The formatting string. * * @return string Returns a string produced according to the formatting string. * * @since 1.0 */ public function format($format) { $query = $this; $args = \array_slice(\func_get_args(), 1); array_unshift($args, null); $i = 1; $func = function ($match) use ($query, $args, &$i) { if (isset($match[6]) && $match[6] === '%') { return '%'; } // No argument required, do not increment the argument index. switch ($match[5]) { case 't': return $query->currentTimestamp(); case 'z': return $query->nullDate(false); case 'Z': return $query->nullDate(true); } // Increment the argument index only if argument specifier not provided. $index = is_numeric($match[4]) ? (int) $match[4] : $i++; if (!$index || !isset($args[$index])) { // TODO - What to do? sprintf() throws a Warning in these cases. $replacement = ''; } else { $replacement = $args[$index]; } switch ($match[5]) { case 'a': return 0 + $replacement; case 'e': return $query->escape($replacement); case 'E': return $query->escape($replacement, true); case 'n': return $query->quoteName($replacement); case 'q': return $query->quote($replacement); case 'Q': return $query->quote($replacement, false); case 'r': return $replacement; // Dates case 'y': return $query->year($query->quote($replacement)); case 'Y': return $query->year($query->quoteName($replacement)); case 'm': return $query->month($query->quote($replacement)); case 'M': return $query->month($query->quoteName($replacement)); case 'd': return $query->day($query->quote($replacement)); case 'D': return $query->day($query->quoteName($replacement)); case 'h': return $query->hour($query->quote($replacement)); case 'H': return $query->hour($query->quoteName($replacement)); case 'i': return $query->minute($query->quote($replacement)); case 'I': return $query->minute($query->quoteName($replacement)); case 's': return $query->second($query->quote($replacement)); case 'S': return $query->second($query->quoteName($replacement)); } return ''; }; /** * Regexp to find an replace all tokens. * Matched fields: * 0: Full token * 1: Everything following '%' * 2: Everything following '%' unless '%' * 3: Argument specifier and '$' * 4: Argument specifier * 5: Type specifier * 6: '%' if full token is '%%' */ return preg_replace_callback('#%(((([\d]+)\$)?([aeEnqQryYmMdDhHiIsStzZ]))|(%))#', $func, $format); } /** * Validate arguments which are passed to selectRowNumber method and set up common variables. * * @param string $orderBy An expression of ordering for window function. * @param string $orderColumnAlias An alias for new ordering column. * * @return void * * @since 2.0.0 * @throws \RuntimeException */ protected function validateRowNumber($orderBy, $orderColumnAlias) { if ($this->selectRowNumber) { throw new \RuntimeException("Method 'selectRowNumber' can be called only once per instance."); } $this->type = 'select'; $this->selectRowNumber = [ 'orderBy' => $orderBy, 'orderColumnAlias' => $orderColumnAlias, ]; } /** * Return the number of the current row. * * Usage: * $query->select('id'); * $query->selectRowNumber('ordering,publish_up DESC', 'new_ordering'); * $query->from('#__content'); * * @param string $orderBy An expression of ordering for window function. * @param string $orderColumnAlias An alias for new ordering column. * * @return $this * * @since 2.0.0 * @throws \RuntimeException */ public function selectRowNumber($orderBy, $orderColumnAlias) { $this->validateRowNumber($orderBy, $orderColumnAlias); return $this->select("ROW_NUMBER() OVER (ORDER BY $orderBy) AS $orderColumnAlias"); } } PK �9�\�.� � src/StatementInterface.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Interface defining a query statement. * * This interface is a partial standalone implementation of PDOStatement. * * @since 2.0.0 */ interface StatementInterface { /** * Binds a parameter to the specified variable name. * * @param string|integer $parameter Parameter identifier. For a prepared statement using named placeholders, this will be a parameter * name of the form `:name`. For a prepared statement using question mark placeholders, this will be * the 1-indexed position of the parameter. * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. * @param string $dataType Constant corresponding to a SQL datatype, this should be the processed type from the QueryInterface. * @param integer $length The length of the variable. Usually required for OUTPUT parameters. * @param array $driverOptions Optional driver options to be used. * * @return boolean * * @since 2.0.0 */ public function bindParam($parameter, &$variable, string $dataType = ParameterType::STRING, ?int $length = null, ?array $driverOptions = null); /** * Closes the cursor, enabling the statement to be executed again. * * @return void * * @since 2.0.0 */ public function closeCursor(): void; /** * Fetches the SQLSTATE associated with the last operation on the statement handle. * * @return string * * @since 2.0.0 */ public function errorCode(); /** * Fetches extended error information associated with the last operation on the statement handle. * * @return array * * @since 2.0.0 */ public function errorInfo(); /** * Executes a prepared statement * * @param array|null $parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. * * @return boolean * * @since 2.0.0 */ public function execute(?array $parameters = null); /** * Fetches the next row from a result set * * @param integer|null $fetchStyle Controls how the next row will be returned to the caller. This value must be one of the * FetchMode constants, defaulting to value of FetchMode::MIXED. * @param integer $cursorOrientation For a StatementInterface object representing a scrollable cursor, this value determines which row * will be returned to the caller. This value must be one of the FetchOrientation constants, * defaulting to FetchOrientation::NEXT. * @param integer $cursorOffset For a StatementInterface object representing a scrollable cursor for which the cursorOrientation * parameter is set to FetchOrientation::ABS, this value specifies the absolute number of the row in * the result set that shall be fetched. For a StatementInterface object representing a scrollable * cursor for which the cursorOrientation parameter is set to FetchOrientation::REL, this value * specifies the row to fetch relative to the cursor position before `fetch()` was called. * * @return mixed The return value of this function on success depends on the fetch type. In all cases, boolean false is returned on failure. * * @since 2.0.0 */ public function fetch(?int $fetchStyle = null, int $cursorOrientation = FetchOrientation::NEXT, int $cursorOffset = 0); /** * Returns the number of rows affected by the last SQL statement. * * @return integer * * @since 2.0.0 */ public function rowCount(): int; /** * Sets the fetch mode to use while iterating this statement. * * @param integer $fetchMode The fetch mode, must be one of the FetchMode constants. * @param mixed ...$args Optional mode-specific arguments. * * @return void * * @since 2.0.0 */ public function setFetchMode(int $fetchMode, ...$args): void; } PK �9�\ � 7| | src/Pgsql/PgsqlQuery.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Pgsql; use Joomla\Database\Pdo\PdoQuery; use Joomla\Database\Query\PostgresqlQueryBuilder; use Joomla\Database\Query\QueryElement; /** * PDO PostgreSQL Query Building Class. * * @since 1.0 * * @property-read QueryElement $forUpdate The FOR UPDATE element used in "FOR UPDATE" lock * @property-read QueryElement $forShare The FOR SHARE element used in "FOR SHARE" lock * @property-read QueryElement $noWait The NOWAIT element used in "FOR SHARE" and "FOR UPDATE" lock * @property-read QueryElement $returning The RETURNING element of INSERT INTO */ class PgsqlQuery extends PdoQuery { use PostgresqlQueryBuilder; /** * The list of zero or null representation of a datetime. * * @var array * @since 2.0.0 */ protected $nullDatetimeList = ['1970-01-01 00:00:00']; /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * Usage: * $query->select($query->castAsChar('a')); * $query->select($query->castAsChar('a', 40)); * * @param string $value The value to cast as a char. * @param string $length The length of the char. * * @return string Returns the cast value. * * @since 1.8.0 */ public function castAsChar($value, $length = null) { if ((int) $length < 1) { return $value . '::text'; } return 'CAST(' . $value . ' AS CHAR(' . $length . '))'; } } PK �9�\ ��� � src/Pgsql/PgsqlExporter.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Pgsql; use Joomla\Database\DatabaseExporter; /** * PDO PostgreSQL Database Exporter. * * @since 1.5.0 */ class PgsqlExporter extends DatabaseExporter { /** * Builds the XML data for the tables to export. * * @return string An XML string * * @since 1.0 * @throws \Exception if an error occurs. */ protected function buildXml() { $buffer = []; $buffer[] = '<?xml version="1.0"?>'; $buffer[] = '<postgresqldump xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'; $buffer[] = ' <database name="">'; if ($this->options->withStructure) { $buffer = array_merge($buffer, $this->buildXmlStructure()); } if ($this->options->withData) { $buffer = array_merge($buffer, $this->buildXmlData()); } $buffer[] = ' </database>'; $buffer[] = '</postgresqldump>'; return implode("\n", $buffer); } /** * Builds the XML structure to export. * * @return array An array of XML lines (strings). * * @since 1.0 * @throws \Exception if an error occurs. */ protected function buildXmlStructure() { $buffer = []; foreach ($this->from as $table) { // Replace the magic prefix if found. $table = $this->getGenericTableName($table); // Get the details columns information. $fields = $this->db->getTableColumns($table, false); $keys = $this->db->getTableKeys($table); $sequences = $this->db->getTableSequences($table); $buffer[] = ' <table_structure name="' . $table . '">'; foreach ($sequences as $sequence) { $buffer[] = ' <sequence Name="' . $this->getGenericTableName($sequence->sequence) . '" Schema="' . $sequence->schema . '"' . ' Table="' . $table . '" Column="' . $sequence->column . '" Type="' . $sequence->data_type . '"' . ' Start_Value="' . $sequence->start_value . '" Min_Value="' . $sequence->minimum_value . '"' . ' Max_Value="' . $sequence->maximum_value . '" Last_Value="' . $this->db->getSequenceLastValue($sequence->sequence) . '"' . ' Increment="' . $sequence->increment . '" Cycle_option="' . $sequence->cycle_option . '"' . ' Is_called="' . $this->db->getSequenceIsCalled($sequence->sequence) . '"' . ' />'; } foreach ($fields as $field) { $buffer[] = ' <field Field="' . $field->column_name . '" Type="' . $field->type . '" Null="' . $field->null . '"' . ' Default="' . $field->Default . '" Comments="' . $field->comments . '" />'; } foreach ($keys as $key) { $buffer[] = ' <key Index="' . $this->getGenericTableName($key->idxName) . '" is_primary="' . $key->isPrimary . '"' . ' is_unique="' . $key->isUnique . '" Key_name="' . $this->db->getNamesKey($table, $key->indKey) . '"' . ' Query=\'' . $key->Query . '\' />'; } $buffer[] = ' </table_structure>'; } return $buffer; } /** * Builds the XML data to export. * * @return array An array of XML lines (strings). * * @since 2.0.0 * @throws \Exception if an error occurs. */ protected function buildXmlData() { $buffer = []; foreach ($this->from as $table) { // Replace the magic prefix if found. $table = $this->getGenericTableName($table); // Get the details columns information. $fields = $this->db->getTableColumns($table, false); $colblob = []; foreach ($fields as $field) { // Catch blob for xml conversion // PostgreSQL binary large object type if ($field->Type == 'bytea') { $colblob[] = $field->Field; } } $query = $this->db->getQuery(true); $query->select($query->quoteName(array_keys($fields))) ->from($query->quoteName($table)); $this->db->setQuery($query); $rows = $this->db->loadObjectList(); if (!count($rows)) { continue; } $buffer[] = ' <table_data name="' . $table . '">'; foreach ($rows as $row) { $buffer[] = ' <row>'; foreach ($row as $key => $value) { if (!in_array($key, $colblob)) { $buffer[] = ' <field name="' . $key . '">' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '</field>'; } else { $buffer[] = ' <field name="' . $key . '">' . stream_get_contents($value) . '</field>'; } } $buffer[] = ' </row>'; } $buffer[] = ' </table_data>'; } return $buffer; } /** * Checks if all data and options are in order prior to exporting. * * @return $this * * @since 1.5.0 * @throws \RuntimeException */ public function check() { // Check if the db connector has been set. if (!($this->db instanceof PgsqlDriver)) { throw new \RuntimeException('Database connection wrong type.'); } // Check if the tables have been specified. if (empty($this->from)) { throw new \RuntimeException('ERROR: No Tables Specified'); } return $this; } } PK �9�\ ���p �p src/Pgsql/PgsqlDriver.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Pgsql; use Joomla\Database\Pdo\PdoDriver; /** * PostgreSQL PDO Database Driver * * @since 1.5.0 */ class PgsqlDriver extends PdoDriver { /** * The database driver name * * @var string * @since 1.5.0 */ public $name = 'pgsql'; /** * The character(s) used to quote SQL statement names such as table names or field names, * etc. The child classes should define this as necessary. If a single character string the * same character is used for both sides of the quoted name, else the first character will be * used for the opening quote and the second for the closing quote. * * @var string * @since 1.5.0 */ protected $nameQuote = '"'; /** * The null or zero representation of a timestamp for the database driver. This should be * defined in child classes to hold the appropriate value for the engine. * * @var string * @since 1.5.0 */ protected $nullDate = '1970-01-01 00:00:00'; /** * The minimum supported database version. * * @var string * @since 1.5.0 */ protected static $dbMinimum = '9.4.0'; /** * Operator used for concatenation * * @var string * @since 1.5.0 */ protected $concat_operator = '||'; /** * Database object constructor * * @param array $options List of options used to configure the connection * * @since 1.5.0 */ public function __construct($options) { $options['driver'] = 'pgsql'; $options['host'] = $options['host'] ?? 'localhost'; $options['user'] = $options['user'] ?? ''; $options['password'] = $options['password'] ?? ''; $options['database'] = $options['database'] ?? ''; $options['port'] = $options['port'] ?? null; // Finalize initialization parent::__construct($options); } /** * Connects to the database if needed. * * @return void * * @since 1.5.0 * @throws \RuntimeException */ public function connect() { if ($this->getConnection()) { return; } parent::connect(); $this->setQuery('SET standard_conforming_strings = off')->execute(); } /** * Method to get the database collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database or boolean false if not supported. * * @since 1.5.0 * @throws \RuntimeException */ public function getCollation() { $this->setQuery('SHOW LC_COLLATE'); $array = $this->loadAssocList(); return $array[0]['lc_collate']; } /** * Method to get the database connection collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database connection (string) or boolean false if not supported. * * @since 1.6.0 * @throws \RuntimeException */ public function getConnectionCollation() { $this->setQuery('SHOW LC_COLLATE'); $array = $this->loadAssocList(); return $array[0]['lc_collate']; } /** * Method to get the database encryption details (cipher and protocol) in use. * * @return string The database encryption details. * * @since 2.0.0 * @throws \RuntimeException */ public function getConnectionEncryption(): string { // Requires PostgreSQL 9.5 or newer if (version_compare($this->getVersion(), '9.5', '<')) { return ''; } $query = $this->createQuery() ->select($this->quoteName(['version', 'cipher'])) ->from($this->quoteName('pg_stat_ssl')) ->where($this->quoteName('pid') . ' = pg_backend_pid()'); $variables = $this->setQuery($query)->loadAssoc(); if (!empty($variables['cipher'])) { return $variables['version'] . ' (' . $variables['cipher'] . ')'; } return ''; } /** * Method to test if the database TLS connections encryption are supported. * * @return boolean Whether the database supports TLS connections encryption. * * @since 2.0.0 */ public function isConnectionEncryptionSupported(): bool { $variables = $this->setQuery('SHOW "ssl"')->loadAssoc(); return !empty($variables['ssl']) && $variables['ssl'] === 'on'; } /** * Internal function to get the name of the default schema for the current PostgreSQL connection. * That is the schema where tables are created by Joomla. * * @return string * * @since 1.8.0 */ private function getDefaultSchema() { // Supported since PostgreSQL 7.3 $this->setQuery('SELECT (current_schemas(false))[1]'); return $this->loadResult(); } /** * Shows the table CREATE statement that creates the given tables. * * This is unsupported by PostgreSQL. * * @param mixed $tables A table name or a list of table names. * * @return string An empty string because this function is not supported by PostgreSQL. * * @since 1.5.0 * @throws \RuntimeException */ public function getTableCreate($tables) { return ''; } /** * Retrieves field information about a given table. * * @param string $table The name of the database table. * @param boolean $typeOnly True to only return field types. * * @return array An array of fields for the database table. * * @since 1.5.0 * @throws \RuntimeException */ public function getTableColumns($table, $typeOnly = true) { $this->connect(); $result = []; $tableSub = $this->replacePrefix($table); $defaultSchema = $this->getDefaultSchema(); $this->setQuery(' SELECT a.attname AS "column_name", pg_catalog.format_type(a.atttypid, a.atttypmod) as "type", CASE WHEN a.attnotnull IS TRUE THEN \'NO\' ELSE \'YES\' END AS "null", CASE WHEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) IS NOT NULL THEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) END as "Default", CASE WHEN pg_catalog.col_description(a.attrelid, a.attnum) IS NULL THEN \'\' ELSE pg_catalog.col_description(a.attrelid, a.attnum) END AS "comments" FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef ON a.attrelid=adef.adrelid AND a.attnum=adef.adnum LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid WHERE a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname=' . $this->quote($tableSub) . ' AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = ' . $this->quote($defaultSchema) . ') ) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum' ); $fields = $this->loadObjectList(); if ($typeOnly) { foreach ($fields as $field) { $result[$field->column_name] = preg_replace('/[(0-9)]/', '', $field->type); } } else { foreach ($fields as $field) { if ($field->Default !== null) { // Normalise default values like datetime if (preg_match('/^\'(.*)\'::.*/', $field->Default, $matches)) { $field->Default = $matches[1]; } // Change Postgresql's NULL::* type with PHP's null one. Do this last to avoid PHP type issues in PHP 8.1 and up if (preg_match('/^NULL::*/', $field->Default)) { $field->Default = null; } } // Do some dirty translation to MySQL output. // @todo: Come up with and implement a standard across databases. $result[$field->column_name] = (object) [ 'column_name' => $field->column_name, 'type' => $field->type, 'null' => $field->null, 'Default' => $field->Default, 'comments' => '', 'Field' => $field->column_name, 'Type' => $field->type, 'Null' => $field->null, // @todo: Improve query above to return primary key info as well // 'Key' => ($field->PK == '1' ? 'PRI' : '') ]; } } return $result; } /** * Get the details list of keys for a table. * * @param string $table The name of the table. * * @return array An array of the column specification for the table. * * @since 1.5.0 * @throws \RuntimeException */ public function getTableKeys($table) { $this->connect(); // To check if table exists and prevent SQL injection $tableList = $this->getTableList(); $tableSub = $this->replacePrefix($table); if (\in_array($tableSub, $tableList, true)) { // Get the details columns information. $this->setQuery(' SELECT indexname AS "idxName", indisprimary AS "isPrimary", indisunique AS "isUnique", indkey AS "indKey", CASE WHEN indisprimary = true THEN ( SELECT \'ALTER TABLE \' || tablename || \' ADD \' || pg_catalog.pg_get_constraintdef(const.oid, true) FROM pg_constraint AS const WHERE const.conname= pgClassFirst.relname ) ELSE pg_catalog.pg_get_indexdef(indexrelid, 0, true) END AS "Query" FROM pg_indexes LEFT JOIN pg_class AS pgClassFirst ON indexname=pgClassFirst.relname LEFT JOIN pg_index AS pgIndex ON pgClassFirst.oid=pgIndex.indexrelid WHERE tablename=' . $this->quote($tableSub) . ' ORDER BY indkey' ); return $this->loadObjectList(); } return []; } /** * Get the list of column names this index indexes. * * @param string $table The name of the table. * @param string $indKey The list of column numbers for the table * * @return string A list of the column names for the table. * * @since 2.0.0 * @throws \RuntimeException */ public function getNamesKey($table, $indKey) { $this->connect(); $tableSub = $this->replacePrefix($table); $tabInd = explode(' ', $indKey); $colNames = []; foreach ($tabInd as $numCol) { $query = $this->createQuery() ->select('attname') ->from('pg_attribute') ->join('LEFT', 'pg_class ON pg_class.relname=' . $this->quote($tableSub)) ->where('attnum=' . $numCol . ' AND attrelid=pg_class.oid'); $this->setQuery($query); $colNames[] = $this->loadResult(); } return implode(', ', $colNames); } /** * Method to get an array of all tables in the database. * * @return array An array of all the tables in the database. * * @since 1.5.0 * @throws \RuntimeException */ public function getTableList() { $query = $this->createQuery() ->select('table_name') ->from('information_schema.tables') ->where('table_type = ' . $this->quote('BASE TABLE')) ->where('table_schema NOT IN (' . $this->quote('pg_catalog') . ', ' . $this->quote('information_schema') . ')') ->order('table_name ASC'); $this->setQuery($query); return $this->loadColumn(); } /** * Get the details list of sequences for a table. * * @param string $table The name of the table. * * @return array An array of sequences specification for the table. * * @since 1.5.0 * @throws \RuntimeException */ public function getTableSequences($table) { // To check if table exists and prevent SQL injection $tableList = $this->getTableList(); $tableSub = $this->replacePrefix($table); if (\in_array($tableSub, $tableList, true)) { $name = [ 's.relname', 'n.nspname', 't.relname', 'a.attname', 'info.data_type', 'info.minimum_value', 'info.maximum_value', 'info.increment', 'info.cycle_option', 'info.start_value', ]; $as = [ 'sequence', 'schema', 'table', 'column', 'data_type', 'minimum_value', 'maximum_value', 'increment', 'cycle_option', 'start_value', ]; // Get the details columns information. $query = $this->createQuery() ->select($this->quoteName($name, $as)) ->from('pg_class AS s') ->leftJoin("pg_depend d ON d.objid = s.oid AND d.classid = 'pg_class'::regclass AND d.refclassid = 'pg_class'::regclass") ->leftJoin('pg_class t ON t.oid = d.refobjid') ->leftJoin('pg_namespace n ON n.oid = t.relnamespace') ->leftJoin('pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.refobjsubid') ->leftJoin('information_schema.sequences AS info ON info.sequence_name = s.relname') ->where('s.relkind = ' . $this->quote('S') . ' AND d.deptype = ' . $this->quote('a') . ' AND t.relname = ' . $this->quote($tableSub)); $this->setQuery($query); return $this->loadObjectList(); } return []; } /** * Method to get the last value of a sequence in the database. * * @param string $sequence The name of the sequence. * * @return integer The last value of the sequence. * * @since 2.0.0 * @throws \RuntimeException */ public function getSequenceLastValue($sequence) { $this->connect(); $query = $this->createQuery() ->select($this->quoteName('last_value')) ->from($sequence); $this->setQuery($query); return $this->loadResult(); } /** * Method to get the is_called attribute of a sequence. * * @param string $sequence The name of the sequence. * * @return boolean The is_called attribute of the sequence. * * @since 2.0.0 * @throws \RuntimeException */ public function getSequenceIsCalled($sequence) { $this->connect(); $query = $this->createQuery() ->select($this->quoteName('is_called')) ->from($sequence); $this->setQuery($query); return $this->loadResult(); } /** * Locks a table in the database. * * @param string $tableName The name of the table to unlock. * * @return $this * * @since 1.5.0 * @throws \RuntimeException */ public function lockTable($tableName) { $this->transactionStart(); $this->setQuery('LOCK TABLE ' . $this->quoteName($tableName) . ' IN ACCESS EXCLUSIVE MODE')->execute(); return $this; } /** * Renames a table in the database. * * @param string $oldTable The name of the table to be renamed * @param string $newTable The new name for the table. * @param string $backup Not used by PostgreSQL. * @param string $prefix Not used by PostgreSQL. * * @return $this * * @since 1.5.0 * @throws \RuntimeException */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $this->connect(); $oldTable = $this->replacePrefix($oldTable); $newTable = $this->replacePrefix($newTable); // To check if table exists and prevent SQL injection $tableList = $this->getTableList(); // Origin Table does not exist if (!\in_array($oldTable, $tableList, true)) { // Origin Table not found throw new \RuntimeException('Table not found in Postgresql database.'); } // Rename indexes $subQuery = $this->createQuery() ->select('indexrelid') ->from('pg_index, pg_class') ->where('pg_class.relname = ' . $this->quote($oldTable)) ->where('pg_class.oid = pg_index.indrelid'); $this->setQuery( $this->createQuery() ->select('relname') ->from('pg_class') ->where('oid IN (' . (string) $subQuery . ')') ); $oldIndexes = $this->loadColumn(); foreach ($oldIndexes as $oldIndex) { $changedIdxName = str_replace($oldTable, $newTable, $oldIndex); $this->setQuery('ALTER INDEX ' . $this->escape($oldIndex) . ' RENAME TO ' . $this->escape($changedIdxName))->execute(); } // Rename sequences $subQuery = $this->createQuery() ->select('oid') ->from('pg_namespace') ->where('nspname NOT LIKE ' . $this->quote('pg_%')) ->where('nspname != ' . $this->quote('information_schema')); $this->setQuery( $this->createQuery() ->select('relname') ->from('pg_class') ->where('relkind = ' . $this->quote('S')) ->where('relnamespace IN (' . (string) $subQuery . ')') ->where('relname LIKE ' . $this->quote("%$oldTable%")) ); $oldSequences = $this->loadColumn(); foreach ($oldSequences as $oldSequence) { $changedSequenceName = str_replace($oldTable, $newTable, $oldSequence); $this->setQuery('ALTER SEQUENCE ' . $this->escape($oldSequence) . ' RENAME TO ' . $this->escape($changedSequenceName))->execute(); } // Rename table $this->setQuery('ALTER TABLE ' . $this->escape($oldTable) . ' RENAME TO ' . $this->escape($newTable))->execute(); return $this; } /** * This function return a field value as a prepared string to be used in a SQL statement. * * @param array $columns Array of table's column returned by ::getTableColumns. * @param string $fieldName The table field's name. * @param string $fieldValue The variable value to quote and return. * * @return string The quoted string. * * @since 1.5.0 */ public function sqlValue($columns, $fieldName, $fieldValue) { switch ($columns[$fieldName]) { case 'boolean': $val = 'NULL'; if ($fieldValue === 't' || $fieldValue === true || $fieldValue === 1 || $fieldValue === '1') { $val = 'TRUE'; } elseif ($fieldValue === 'f' || $fieldValue === false || $fieldValue === 0 || $fieldValue === '0') { $val = 'FALSE'; } break; case 'bigint': case 'bigserial': case 'integer': case 'money': case 'numeric': case 'real': case 'smallint': case 'serial': case 'numeric,': $val = $fieldValue === '' ? 'NULL' : $fieldValue; break; case 'timestamp without time zone': case 'date': if (empty($fieldValue)) { $fieldValue = $this->getNullDate(); } $val = $this->quote($fieldValue); break; default: $val = $this->quote($fieldValue); break; } return $val; } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionCommit($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { parent::transactionCommit($toSavepoint); } else { $this->transactionDepth--; } } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionRollback($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { parent::transactionRollback($toSavepoint); } else { $savepoint = 'SP_' . ($this->transactionDepth - 1); $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth--; $this->setQuery('RELEASE SAVEPOINT ' . $this->quoteName($savepoint))->execute(); } } } /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionStart($asSavepoint = false) { $this->connect(); if (!$asSavepoint || !$this->transactionDepth) { parent::transactionStart($asSavepoint); } else { $savepoint = 'SP_' . $this->transactionDepth; $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth++; } } } /** * Inserts a row into a table based on an object's properties. * * @param string $table The name of the database table to insert into. * @param object $object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. If provided the object property is updated. * * @return boolean True on success. * * @since 1.5.0 * @throws \RuntimeException */ public function insertObject($table, &$object, $key = null) { $columns = $this->getTableColumns($table); $fields = []; $values = []; // Iterate over the object variables to build the query fields and values. foreach (get_object_vars($object) as $k => $v) { // Skip columns that don't exist in the table. if (!\array_key_exists($k, $columns)) { continue; } // Only process non-null scalars. if (\is_array($v) || \is_object($v) || $v === null) { continue; } // Ignore any internal fields or primary keys with value 0. if (($k[0] === '_') || ($k == $key && (($v === 0) || ($v === '0')))) { continue; } // Ignore null timestamp fields. if ($columns[$k] === 'timestamp without time zone' && empty($v)) { continue; } // Prepare and sanitize the fields and values for the database query. $fields[] = $this->quoteName($k); $values[] = $this->sqlValue($columns, $k, $v); } // Create the base insert statement. $query = $this->createQuery(); $query->insert($this->quoteName($table)) ->columns($fields) ->values(implode(',', $values)); if ($key) { $query->returning($key); // Set the query and execute the insert. $object->$key = $this->setQuery($query)->loadResult(); } else { // Set the query and execute the insert. $this->setQuery($query)->execute(); } return true; } /** * Test to see if the PostgreSQL connector is available. * * @return boolean True on success, false otherwise. * * @since 1.5.0 */ public static function isSupported() { return class_exists('\\PDO') && \in_array('pgsql', \PDO::getAvailableDrivers(), true); } /** * Returns an array containing database's table list. * * @return array The database's table list. * * @since 1.5.0 */ public function showTables() { $query = $this->createQuery() ->select('table_name') ->from('information_schema.tables') ->where('table_type=' . $this->quote('BASE TABLE')) ->where('table_schema NOT IN (' . $this->quote('pg_catalog') . ', ' . $this->quote('information_schema') . ' )'); $this->setQuery($query); return $this->loadColumn(); } /** * Get the substring position inside a string * * @param string $substring The string being sought * @param string $string The string/column being searched * * @return integer The position of $substring in $string * * @since 1.5.0 */ public function getStringPositionSql($substring, $string) { $this->setQuery("SELECT POSITION($substring IN $string)"); $position = $this->loadRow(); return $position['position']; } /** * Generate a random value * * @return float The random generated number * * @since 1.5.0 */ public function getRandom() { $this->setQuery('SELECT RANDOM()'); $random = $this->loadAssoc(); return $random['random']; } /** * Get the query string to alter the database character set. * * @param string $dbName The database name * * @return string The query that alter the database query string * * @since 1.5.0 */ public function getAlterDbCharacterSet($dbName) { return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' SET CLIENT_ENCODING TO ' . $this->quote('UTF8'); } /** * Get the query string to create new Database in correct PostgreSQL syntax. * * @param object $options object coming from "initialise" function to pass user and database name to database driver. * @param boolean $utf True if the database supports the UTF-8 character set, not used in PostgreSQL "CREATE DATABASE" query. * * @return string The query that creates database, owned by $options['user'] * * @since 1.5.0 */ public function getCreateDbQuery($options, $utf) { $query = 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' OWNER ' . $this->quoteName($options->db_user); if ($utf) { $query .= ' ENCODING ' . $this->quote('UTF-8'); } return $query; } /** * This function replaces a string identifier with the configured table prefix. * * @param string $sql The SQL statement to prepare. * @param string $prefix The table prefix. * * @return string The processed SQL statement. * * @since 1.5.0 */ public function replacePrefix($sql, $prefix = '#__') { $sql = trim($sql); if (strpos($sql, '\'')) { // Sequence name quoted with ' ' but need to be replaced if (strpos($sql, 'currval')) { $sql = explode('currval', $sql); for ($nIndex = 1, $nIndexMax = \count($sql); $nIndex < $nIndexMax; $nIndex += 2) { $sql[$nIndex] = str_replace($prefix, $this->tablePrefix, $sql[$nIndex]); } $sql = implode('currval', $sql); } // Sequence name quoted with ' ' but need to be replaced if (strpos($sql, 'nextval')) { $sql = explode('nextval', $sql); for ($nIndex = 1, $nIndexMax = \count($sql); $nIndex < $nIndexMax; $nIndex += 2) { $sql[$nIndex] = str_replace($prefix, $this->tablePrefix, $sql[$nIndex]); } $sql = implode('nextval', $sql); } // Sequence name quoted with ' ' but need to be replaced if (strpos($sql, 'setval')) { $sql = explode('setval', $sql); for ($nIndex = 1, $nIndexMax = \count($sql); $nIndex < $nIndexMax; $nIndex += 2) { $sql[$nIndex] = str_replace($prefix, $this->tablePrefix, $sql[$nIndex]); } $sql = implode('setval', $sql); } $explodedQuery = explode('\'', $sql); for ($nIndex = 0, $nIndexMax = \count($explodedQuery); $nIndex < $nIndexMax; $nIndex += 2) { if (strpos($explodedQuery[$nIndex], $prefix)) { $explodedQuery[$nIndex] = str_replace($prefix, $this->tablePrefix, $explodedQuery[$nIndex]); } } $replacedQuery = implode('\'', $explodedQuery); } else { $replacedQuery = str_replace($prefix, $this->tablePrefix, $sql); } return $replacedQuery; } /** * Unlocks tables in the database, this command does not exist in PostgreSQL, it is automatically done on commit or rollback. * * @return $this * * @since 1.5.0 * @throws \RuntimeException */ public function unlockTables() { $this->transactionCommit(); return $this; } /** * Updates a row in a table based on an object's properties. * * @param string $table The name of the database table to update. * @param object $object A reference to an object whose public properties match the table fields. * @param array|string $key The name of the primary key. * @param boolean $nulls True to update null fields or false to ignore them. * * @return boolean * * @since 1.5.0 * @throws \RuntimeException */ public function updateObject($table, &$object, $key, $nulls = false) { $columns = $this->getTableColumns($table); $fields = []; $where = []; if (\is_string($key)) { $key = [$key]; } if (\is_object($key)) { $key = (array) $key; } // Create the base update statement. $statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s'; // Iterate over the object variables to build the query fields/value pairs. foreach (get_object_vars($object) as $k => $v) { // Skip columns that don't exist in the table. if (!\array_key_exists($k, $columns)) { continue; } // Only process scalars that are not internal fields. if (\is_array($v) || \is_object($v) || $k[0] === '_') { continue; } // Set the primary key to the WHERE clause instead of a field to update. if (\in_array($k, $key, true)) { $key_val = $this->sqlValue($columns, $k, $v); $where[] = $this->quoteName($k) . '=' . $key_val; continue; } // Prepare and sanitize the fields and values for the database query. if ($v === null) { // If the value is null and we do not want to update nulls then ignore this field. if (!$nulls) { continue; } // If the value is null and we want to update nulls then set it. $val = 'NULL'; } else { // The field is not null so we prep it for update. $val = $this->sqlValue($columns, $k, $v); } // Add the field to be updated. $fields[] = $this->quoteName($k) . '=' . $val; } // We don't have any fields to update. if (empty($fields)) { return true; } // Set the query and execute the update. $this->setQuery(sprintf($statement, implode(',', $fields), implode(' AND ', $where))); return $this->execute(); } /** * Quotes a binary string to database requirements for use in database queries. * * @param string $data A binary string to quote. * * @return string The binary quoted input string. * * @since 1.7.0 */ public function quoteBinary($data) { return "decode('" . bin2hex($data) . "', 'hex')"; } /** * Replace special placeholder representing binary field with the original string. * * @param string|resource $data Encoded string or resource. * * @return string The original string. * * @since 1.7.0 */ public function decodeBinary($data) { if (\is_resource($data)) { return stream_get_contents($data); } return $data; } } PK �9�\�綿�= �= src/Pgsql/PgsqlImporter.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Pgsql; use Joomla\Database\DatabaseImporter; /** * PDO PostgreSQL Database Importer. * * @since 1.5.0 */ class PgsqlImporter extends DatabaseImporter { /** * Checks if all data and options are in order prior to exporting. * * @return $this * * @since 1.5.0 * @throws \RuntimeException if an error is encountered. */ public function check() { // Check if the db connector has been set. if (!($this->db instanceof PgsqlDriver)) { throw new \RuntimeException('Database connection wrong type.'); } // Check if the tables have been specified. if (empty($this->from)) { throw new \RuntimeException('ERROR: No Tables Specified'); } return $this; } /** * Get the SQL syntax to add an index. * * @param \SimpleXMLElement $field The XML index definition. * * @return string * * @since 1.0 */ protected function getAddIndexSql(\SimpleXMLElement $field) { return (string) $field['Query']; } /** * Get alters for table if there is a difference. * * @param \SimpleXMLElement $structure The XML structure of the table. * * @return array * * @since 1.0 */ protected function getAlterTableSql(\SimpleXMLElement $structure) { $table = $this->getRealTableName($structure['name']); $oldFields = $this->db->getTableColumns($table); $oldKeys = $this->db->getTableKeys($table); $oldSequence = $this->db->getTableSequences($table); $alters = []; // Get the fields and keys from the XML that we are aiming for. $newFields = $structure->xpath('field'); $newKeys = $structure->xpath('key'); $newSequence = $structure->xpath('sequence'); /* * Sequence section */ $oldSeq = $this->getSeqLookup($oldSequence); $newSequenceLook = $this->getSeqLookup($newSequence); foreach ($newSequenceLook as $kSeqName => $vSeq) { if (isset($oldSeq[$kSeqName])) { // The field exists, check it's the same. $column = $oldSeq[$kSeqName][0]; // Test whether there is a change. $change = ((string) $vSeq[0]['Type'] !== $column->Type) || ((string) $vSeq[0]['Start_Value'] !== $column->Start_Value) || ((string) $vSeq[0]['Min_Value'] !== $column->Min_Value) || ((string) $vSeq[0]['Max_Value'] !== $column->Max_Value) || ((string) $vSeq[0]['Increment'] !== $column->Increment) || ((string) $vSeq[0]['Cycle_option'] !== $column->Cycle_option) || ((string) $vSeq[0]['Table'] !== $column->Table) || ((string) $vSeq[0]['Column'] !== $column->Column) || ((string) $vSeq[0]['Schema'] !== $column->Schema) || ((string) $vSeq[0]['Name'] !== $column->Name); if ($change) { $alters[] = $this->getChangeSequenceSql($kSeqName, $vSeq); $alters[] = $this->getSetvalSequenceSql($kSeqName, $vSeq); } // Unset this field so that what we have left are fields that need to be removed. unset($oldSeq[$kSeqName]); } else { // The sequence is new $alters[] = $this->getAddSequenceSql($newSequenceLook[$kSeqName][0]); $alters[] = $this->getSetvalSequenceSql($newSequenceLook[$kSeqName][0]); } } // Any sequences left are orphans foreach ($oldSeq as $name => $column) { // Delete the sequence. $alters[] = $this->getDropSequenceSql($name); } /* * Field section */ // Loop through each field in the new structure. foreach ($newFields as $field) { $fName = (string) $field['Field']; if (isset($oldFields[$fName])) { // The field exists, check it's the same. $column = $oldFields[$fName]; // Test whether there is a change. $change = ((string) $field['Type'] !== $column->Type) || ((string) $field['Null'] !== $column->Null) || ((string) $field['Default'] !== $column->Default); if ($change) { $alters[] = $this->getChangeColumnSql($table, $field); } // Unset this field so that what we have left are fields that need to be removed. unset($oldFields[$fName]); } else { // The field is new. $alters[] = $this->getAddColumnSql($table, $field); } } // Any columns left are orphans foreach ($oldFields as $name => $column) { // Delete the column. $alters[] = $this->getDropColumnSql($table, $name); } /* * Index section */ // Get the lookups for the old and new keys $oldLookup = $this->getKeyLookup($oldKeys); $newLookup = $this->getKeyLookup($newKeys); // Loop through each key in the new structure. foreach ($newLookup as $name => $keys) { // Check if there are keys on this field in the existing table. if (isset($oldLookup[$name])) { $same = true; $newCount = \count($newLookup[$name]); $oldCount = \count($oldLookup[$name]); // There is a key on this field in the old and new tables. Are they the same? if ($newCount === $oldCount) { for ($i = 0; $i < $newCount; $i++) { // Check only query field -> different query means different index $same = ((string) $newLookup[$name][$i]['Query'] === $oldLookup[$name][$i]->Query); if (!$same) { // Break out of the loop. No need to check further. break; } } } else { // Count is different, just drop and add. $same = false; } if (!$same) { $alters[] = $this->getDropIndexSql($name); $alters[] = (string) $newLookup[$name][0]['Query']; } // Unset this field so that what we have left are fields that need to be removed. unset($oldLookup[$name]); } else { // This is a new key. $alters[] = (string) $newLookup[$name][0]['Query']; } } // Any keys left are orphans. foreach ($oldLookup as $name => $keys) { if ($oldLookup[$name][0]->is_primary === 'TRUE') { $alters[] = $this->getDropPrimaryKeySql($table, $oldLookup[$name][0]->Index); } else { $alters[] = $this->getDropIndexSql($name); } } return $alters; } /** * Get the SQL syntax to drop a sequence. * * @param string $name The name of the sequence to drop. * * @return string * * @since 1.0 */ protected function getDropSequenceSql($name) { return 'DROP SEQUENCE ' . $this->db->quoteName($name); } /** * Get the syntax to add a sequence. * * @param \SimpleXMLElement $field The XML definition for the sequence. * * @return string * * @since 1.0 */ protected function getAddSequenceSql(\SimpleXMLElement $field) { $sql = 'CREATE SEQUENCE IF NOT EXISTS ' . (string) $field['Name'] . ' INCREMENT BY ' . (string) $field['Increment'] . ' MINVALUE ' . $field['Min_Value'] . ' MAXVALUE ' . (string) $field['Max_Value'] . ' START ' . (string) $field['Start_Value'] . (((string) $field['Cycle_option'] === 'NO') ? ' NO' : '') . ' CYCLE' . ' OWNED BY ' . $this->db->quoteName((string) $field['Schema'] . '.' . (string) $field['Table'] . '.' . (string) $field['Column']); return $sql; } /** * Get the syntax to alter a sequence. * * @param \SimpleXMLElement $field The XML definition for the sequence. * * @return string * * @since 1.0 */ protected function getChangeSequenceSql(\SimpleXMLElement $field) { $sql = 'ALTER SEQUENCE ' . (string) $field['Name'] . ' INCREMENT BY ' . (string) $field['Increment'] . ' MINVALUE ' . (string) $field['Min_Value'] . ' MAXVALUE ' . (string) $field['Max_Value'] . ' START ' . (string) $field['Start_Value'] . ' OWNED BY ' . $this->db->quoteName((string) $field['Schema'] . '.' . (string) $field['Table'] . '.' . (string) $field['Column']); return $sql; } /** * Get the syntax to setval a sequence. * * @param \SimpleXMLElement $field The XML definition for the sequence. * * @return string * * @since 2.0.0 */ protected function getSetvalSequenceSql($field) { $is_called = $field['Is_called'] == 't' || $field['Is_called'] == '1' ? 'TRUE' : 'FALSE'; return 'SELECT setval(\'' . (string) $field['Name'] . '\', ' . (string) $field['Last_Value'] . ', ' . $is_called . ')'; } /** * Get the syntax to alter a column. * * @param string $table The name of the database table to alter. * @param \SimpleXMLElement $field The XML definition for the field. * * @return string * * @since 1.0 */ protected function getChangeColumnSql($table, \SimpleXMLElement $field) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' ALTER COLUMN ' . $this->db->quoteName((string) $field['Field']) . ' ' . $this->getAlterColumnSql($table, $field); } /** * Get the SQL syntax for a single column that would be included in a table create statement. * * @param string $table The name of the database table to alter. * @param \SimpleXMLElement $field The XML field definition. * * @return string * * @since 1.0 */ protected function getAlterColumnSql($table, \SimpleXMLElement $field) { // TODO Incorporate into parent class and use $this. $blobs = ['text', 'smalltext', 'mediumtext', 'largetext']; $fName = (string) $field['Field']; $fType = (string) $field['Type']; $fNull = (string) $field['Null']; $fDefault = (isset($field['Default']) && $field['Default'] != 'NULL') ? preg_match('/^[0-9]$/', $field['Default']) ? $field['Default'] : $this->db->quote((string) $field['Default']) : null; $sql = ' TYPE ' . $fType; if ($fNull === 'NO') { if ($fDefault === null || \in_array($fType, $blobs, true)) { $sql .= ",\nALTER COLUMN " . $this->db->quoteName($fName) . ' SET NOT NULL' . ",\nALTER COLUMN " . $this->db->quoteName($fName) . ' DROP DEFAULT'; } else { $sql .= ",\nALTER COLUMN " . $this->db->quoteName($fName) . ' SET NOT NULL' . ",\nALTER COLUMN " . $this->db->quoteName($fName) . ' SET DEFAULT ' . $fDefault; } } else { if ($fDefault !== null) { $sql .= ",\nALTER COLUMN " . $this->db->quoteName($fName) . ' DROP NOT NULL' . ",\nALTER COLUMN " . $this->db->quoteName($fName) . ' SET DEFAULT ' . $fDefault; } } // Sequence was created in other function, here is associated a default value but not yet owner if (strpos($fDefault, 'nextval') !== false) { $sequence = $table . '_' . $fName . '_seq'; $owner = $table . '.' . $fName; $sql .= ";\nALTER SEQUENCE " . $this->db->quoteName($sequence) . ' OWNED BY ' . $this->db->quoteName($owner); } return $sql; } /** * Get the SQL syntax for a single column that would be included in a table create statement. * * @param \SimpleXMLElement $field The XML field definition. * * @return string * * @since 1.0 */ protected function getColumnSql(\SimpleXMLElement $field) { $fName = (string) $field['Field']; $fType = (string) $field['Type']; $fNull = (string) $field['Null']; if (strpos($field['Default'], '::') != false) { $fDefault = strstr($field['Default'], '::', true); } else { $fDefault = isset($field['Default']) && strlen($field['Default']) > 0 ? preg_match('/^[0-9]$/', $field['Default']) ? $field['Default'] : $this->db->quote((string) $field['Default']) : null; } // Note, nextval() as default value means that type field is serial. if (strpos($fDefault, 'nextval') !== false) { $sql = $this->db->quoteName($fName) . ' SERIAL'; } else { $sql = $this->db->quoteName($fName) . ' ' . $fType; if ($fNull == 'NO') { if ($fDefault === null) { $sql .= ' NOT NULL'; } else { $sql .= ' NOT NULL DEFAULT ' . $fDefault; } } else { if ($fDefault !== null) { $sql .= ' DEFAULT ' . $fDefault; } } } return $sql; } /** * Get the SQL syntax to drop an index. * * @param string $name The name of the key to drop. * * @return string * * @since 1.0 */ protected function getDropIndexSql($name) { return 'DROP INDEX ' . $this->db->quoteName($name); } /** * Get the SQL syntax to drop a key. * * @param string $table The table name. * @param string $name The constraint name. * * @return string * * @since 1.0 */ protected function getDropPrimaryKeySql($table, $name) { return 'ALTER TABLE ONLY ' . $this->db->quoteName($table) . ' DROP CONSTRAINT ' . $this->db->quoteName($name); } /** * Get the details list of keys for a table. * * @param array $keys An array of objects that comprise the keys for the table. * * @return array The lookup array. array({key name} => array(object, ...)) * * @since 1.2.0 */ protected function getKeyLookup($keys) { // First pass, create a lookup of the keys. $lookup = []; foreach ($keys as $key) { if ($key instanceof \SimpleXMLElement) { $kName = (string) $key['Index']; } else { $kName = $key->Index; } if (empty($lookup[$kName])) { $lookup[$kName] = []; } $lookup[$kName][] = $key; } return $lookup; } /** * Get the SQL syntax to add a unique constraint for a table key. * * @param string $table The table name. * @param array $key The key. * * @return string * * @since 2.0.0 */ protected function getAddUniqueSql($table, $key) { if ($key instanceof \SimpleXMLElement) { $kName = (string) $key['Key_name']; $kIndex = (string) $key['Index']; } else { $kName = $key->Key_name; $kIndex = $key->Index; } $unique = $kIndex . ' UNIQUE (' . $kName . ')'; return 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD CONSTRAINT ' . $unique; } /** * Get the details list of sequences for a table. * * @param array $sequences An array of objects that comprise the sequences for the table. * * @return array The lookup array. array({key name} => array(object, ...)) * * @since 1.0 */ protected function getSeqLookup($sequences) { // First pass, create a lookup of the keys. $lookup = []; foreach ($sequences as $seq) { if ($seq instanceof \SimpleXMLElement) { $sName = (string) $seq['Name']; } else { $sName = $seq->Name; } if (empty($lookup[$sName])) { $lookup[$sName] = []; } $lookup[$sName][] = $seq; } return $lookup; } /** * Get the SQL syntax to add a table. * * @param \SimpleXMLElement $table The table information. * * @return string * * @since 2.0.0 * @throws \RuntimeException */ protected function xmlToCreate(\SimpleXMLElement $table) { $existingTables = $this->db->getTableList(); $tableName = (string) $table['name']; if (in_array($tableName, $existingTables)) { throw new \RuntimeException('The table you are trying to create already exists'); } $createTableStatement = 'CREATE TABLE ' . $this->db->quoteName($tableName) . ' ('; foreach ($table->xpath('field') as $field) { $createTableStatement .= $this->getColumnSql($field) . ', '; } $createTableStatement = rtrim($createTableStatement, ', '); $createTableStatement .= ');'; foreach ($table->xpath('sequence') as $seq) { $createTableStatement .= $this->getAddSequenceSql($seq) . ';'; $createTableStatement .= $this->getSetvalSequenceSql($seq) . ';'; } foreach ($table->xpath('key') as $key) { if ((($key['is_primary'] == 'f') || ($key['is_primary'] == '')) && (($key['is_unique'] == 't') || ($key['is_unique'] == '1'))) { $createTableStatement .= $this->getAddUniqueSql($tableName, $key) . ';'; } else { $createTableStatement .= $this->getAddIndexSql($key) . ';'; } } return $createTableStatement; } } PK �9�\�36w2: 2: $ src/Query/PostgresqlQueryBuilder.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Query; /** * Trait for PostgreSQL Query Building. * * @since 2.0.0 */ trait PostgresqlQueryBuilder { /** * The FOR UPDATE element used in "FOR UPDATE" lock * * @var QueryElement * @since 2.0.0 */ protected $forUpdate; /** * The FOR SHARE element used in "FOR SHARE" lock * * @var QueryElement * @since 2.0.0 */ protected $forShare; /** * The NOWAIT element used in "FOR SHARE" and "FOR UPDATE" lock * * @var QueryElement * @since 2.0.0 */ protected $noWait; /** * The LIMIT element * * @var QueryElement * @since 2.0.0 */ protected $limit; /** * The OFFSET element * * @var QueryElement * @since 2.0.0 */ protected $offset; /** * The RETURNING element of INSERT INTO * * @var QueryElement * @since 2.0.0 */ protected $returning; /** * Magic function to convert the query to a string, only for PostgreSQL specific queries * * @return string The completed query. * * @since 2.0.0 */ public function __toString() { $query = ''; switch ($this->type) { case 'select': $query .= (string) $this->select; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->selectRowNumber) { if ($this->order) { $query .= (string) $this->order; } break; } if ($this->group) { $query .= (string) $this->group; } if ($this->having) { $query .= (string) $this->having; } if ($this->merge) { // Special case for merge foreach ($this->merge as $element) { $query .= (string) $element; } } if ($this->order) { $query .= (string) $this->order; } if ($this->forUpdate) { $query .= (string) $this->forUpdate; } else { if ($this->forShare) { $query .= (string) $this->forShare; } } if ($this->noWait) { $query .= (string) $this->noWait; } $query = $this->processLimit($query, $this->limit, $this->offset); break; case 'update': $query .= (string) $this->update; $query .= (string) $this->set; if ($this->join) { $tmpFrom = $this->from; $tmpWhere = $this->where ? clone $this->where : null; $this->from = null; // Workaround for special case of JOIN with UPDATE foreach ($this->join as $join) { $joinElem = $join->getElements(); $this->from($joinElem[0]); if (isset($joinElem[1])) { $this->where($joinElem[1]); } } $query .= (string) $this->from; if ($this->where) { $query .= (string) $this->where; } $this->from = $tmpFrom; $this->where = $tmpWhere; } elseif ($this->where) { $query .= (string) $this->where; } $query = $this->processLimit($query, $this->limit, $this->offset); break; case 'insert': $query .= (string) $this->insert; if ($this->values) { if ($this->columns) { $query .= (string) $this->columns; } $elements = $this->values->getElements(); if (!($elements[0] instanceof $this)) { $query .= ' VALUES '; } $query .= (string) $this->values; if ($this->returning) { $query .= (string) $this->returning; } } $query = $this->processLimit($query, $this->limit, $this->offset); break; default: $query = parent::__toString(); break; } if ($this->type === 'select' && $this->alias !== null) { $query = '(' . $query . ') AS ' . $this->alias; } return $query; } /** * Clear data from the query or a specific clause of the query. * * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query. * * @return $this * * @since 2.0.0 */ public function clear($clause = null) { switch ($clause) { case 'limit': $this->limit = null; break; case 'offset': $this->offset = null; break; case 'forUpdate': $this->forUpdate = null; break; case 'forShare': $this->forShare = null; break; case 'noWait': $this->noWait = null; break; case 'returning': $this->returning = null; break; case 'select': case 'update': case 'delete': case 'insert': case 'querySet': case 'from': case 'join': case 'set': case 'where': case 'group': case 'having': case 'merge': case 'order': case 'columns': case 'values': parent::clear($clause); break; default: $this->forUpdate = null; $this->forShare = null; $this->noWait = null; $this->returning = null; parent::clear($clause); break; } return $this; } /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * Usage: * $query->select($query->castAs('CHAR', 'a')); * * @param string $type The type of string to cast as. * @param string $value The value to cast as a char. * @param string $length The value to cast as a char. * * @return string SQL statement to cast the value as a char type. * * @since 1.0 */ public function castAs(string $type, string $value, ?string $length = null) { switch (strtoupper($type)) { case 'CHAR': if (!$length) { return $value . '::text'; } else { return 'CAST(' . $value . ' AS CHAR(' . $length . '))'; } case 'INT': return 'CAST(' . $value . ' AS INTEGER)'; } return parent::castAs($type, $value, $length); } /** * Concatenates an array of column names or values. * * Usage: * $query->select($query->concatenate(array('a', 'b'))); * * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * * @return string The concatenated values. * * @since 2.0.0 */ public function concatenate($values, $separator = null) { if ($separator !== null) { return implode(' || ' . $this->quote($separator) . ' || ', $values); } return implode(' || ', $values); } /** * Gets the current date and time. * * @return string Return string used in query to obtain * * @since 2.0.0 */ public function currentTimestamp() { return 'NOW()'; } /** * Sets the FOR UPDATE lock on select's output row * * @param string $tableName The table to lock * @param string $glue The glue by which to join the conditions. Defaults to ',' . * * @return $this * * @since 2.0.0 */ public function forUpdate($tableName, $glue = ',') { $this->type = 'forUpdate'; if ($this->forUpdate === null) { $glue = strtoupper($glue); $this->forUpdate = new QueryElement('FOR UPDATE', 'OF ' . $tableName, "$glue "); } else { $this->forUpdate->append($tableName); } return $this; } /** * Sets the FOR SHARE lock on select's output row * * @param string $tableName The table to lock * @param string $glue The glue by which to join the conditions. Defaults to ',' . * * @return $this * * @since 2.0.0 */ public function forShare($tableName, $glue = ',') { $this->type = 'forShare'; if ($this->forShare === null) { $glue = strtoupper($glue); $this->forShare = new QueryElement('FOR SHARE', 'OF ' . $tableName, "$glue "); } else { $this->forShare->append($tableName); } return $this; } /** * Aggregate function to get input values concatenated into a string, separated by delimiter * * Usage: * $query->groupConcat('id', ','); * * @param string $expression The expression to apply concatenation to, this may be a column name or complex SQL statement. * @param string $separator The delimiter of each concatenated value * * @return string Input values concatenated into a string, separated by delimiter * * @since 2.0.0 */ public function groupConcat($expression, $separator = ',') { return 'string_agg(' . $expression . ', ' . $this->quote($separator) . ')'; } /** * Used to get a string to extract year from date column. * * Usage: * $query->select($query->year($query->quoteName('dateColumn'))); * * @param string $date Date column containing year to be extracted. * * @return string Returns string to extract year from a date. * * @since 2.0.0 */ public function year($date) { return 'EXTRACT (YEAR FROM ' . $date . ')'; } /** * Used to get a string to extract month from date column. * * Usage: * $query->select($query->month($query->quoteName('dateColumn'))); * * @param string $date Date column containing month to be extracted. * * @return string Returns string to extract month from a date. * * @since 2.0.0 */ public function month($date) { return 'EXTRACT (MONTH FROM ' . $date . ')'; } /** * Used to get a string to extract day from date column. * * Usage: * $query->select($query->day($query->quoteName('dateColumn'))); * * @param string $date Date column containing day to be extracted. * * @return string Returns string to extract day from a date. * * @since 2.0.0 */ public function day($date) { return 'EXTRACT (DAY FROM ' . $date . ')'; } /** * Used to get a string to extract hour from date column. * * Usage: * $query->select($query->hour($query->quoteName('dateColumn'))); * * @param string $date Date column containing hour to be extracted. * * @return string Returns string to extract hour from a date. * * @since 2.0.0 */ public function hour($date) { return 'EXTRACT (HOUR FROM ' . $date . ')'; } /** * Used to get a string to extract minute from date column. * * Usage: * $query->select($query->minute($query->quoteName('dateColumn'))); * * @param string $date Date column containing minute to be extracted. * * @return string Returns string to extract minute from a date. * * @since 2.0.0 */ public function minute($date) { return 'EXTRACT (MINUTE FROM ' . $date . ')'; } /** * Used to get a string to extract seconds from date column. * * Usage: * $query->select($query->second($query->quoteName('dateColumn'))); * * @param string $date Date column containing second to be extracted. * * @return string Returns string to extract second from a date. * * @since 2.0.0 */ public function second($date) { return 'EXTRACT (SECOND FROM ' . $date . ')'; } /** * Sets the NOWAIT lock on select's output row * * @return $this * * @since 2.0.0 */ public function noWait() { $this->type = 'noWait'; if ($this->noWait === null) { $this->noWait = new QueryElement('NOWAIT', null); } return $this; } /** * Set the LIMIT clause to the query * * @param integer $limit Number of rows to return * * @return $this * * @since 2.0.0 */ public function limit($limit = 0) { if ($this->limit === null) { $this->limit = new QueryElement('LIMIT', (int) $limit); } return $this; } /** * Set the OFFSET clause to the query * * @param integer $offset An integer for skipping rows * * @return $this * * @since 2.0.0 */ public function offset($offset = 0) { if ($this->offset === null) { $this->offset = new QueryElement('OFFSET', (int) $offset); } return $this; } /** * Add the RETURNING element to INSERT INTO statement. * * @param mixed $pkCol The name of the primary key column. * * @return $this * * @since 2.0.0 */ public function returning($pkCol) { if ($this->returning === null) { $this->returning = new QueryElement('RETURNING', $pkCol); } return $this; } /** * Method to modify a query already in string format with the needed additions to make the query limited to a particular number of * results, or start at a particular offset. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string * * @since 2.0.0 */ public function processLimit($query, $limit, $offset = 0) { if ($limit > 0) { $query .= ' LIMIT ' . $limit; } if ($offset > 0) { $query .= ' OFFSET ' . $offset; } return $query; } /** * Add to the current date and time. * * Usage: * $query->select($query->dateAdd()); * * Prefixing the interval with a - (negative sign) will cause subtraction to be used. * * @param string $date The db quoted string representation of the date to add to * @param string $interval The string representation of the appropriate number of units * @param string $datePart The part of the date to perform the addition on * * @return string The string with the appropriate sql for addition of dates * * @since 2.0.0 * @link http://www.postgresql.org/docs/9.0/static/functions-datetime.html. */ public function dateAdd($date, $interval, $datePart) { if (substr($interval, 0, 1) !== '-') { return 'timestamp ' . $date . " + interval '" . $interval . ' ' . $datePart . "'"; } return 'timestamp ' . $date . " - interval '" . ltrim($interval, '-') . ' ' . $datePart . "'"; } /** * Get the regular expression operator * * Usage: * $query->where('field ' . $query->regexp($search)); * * @param string $value The regex pattern. * * @return string * * @since 2.0.0 */ public function regexp($value) { return ' ~* ' . $value; } /** * Get the function to return a random floating-point value * * Usage: * $query->rand(); * * @return string * * @since 2.0.0 */ public function rand() { return ' RANDOM() '; } /** * Find a value in a varchar used like a set. * * Ensure that the value is an integer before passing to the method. * * Usage: * $query->findInSet((int) $parent->id, 'a.assigned_cat_ids') * * @param string $value The value to search for. * @param string $set The set of values. * * @return string A representation of the MySQL find_in_set() function for the driver. * * @since 2.0.0 */ public function findInSet($value, $set) { return " $value = ANY (string_to_array($set, ',')::integer[]) "; } } PK �9�\�� src/Query/MysqlQueryBuilder.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Query; /** * Trait for MySQL Query Building. * * @since 2.0.0 */ trait MysqlQueryBuilder { /** * Magic function to convert the query to a string. * * @return string The completed query. * * @since 2.0.0 */ public function __toString() { switch ($this->type) { case 'select': if ($this->selectRowNumber) { $orderBy = $this->selectRowNumber['orderBy']; $tmpOffset = $this->offset; $tmpLimit = $this->limit; $this->offset = 0; $this->limit = 0; $tmpOrder = $this->order; $this->order = null; $query = parent::__toString(); $this->order = $tmpOrder; $this->offset = $tmpOffset; $this->limit = $tmpLimit; // Add support for second order by, offset and limit $query = PHP_EOL . 'SELECT * FROM (' . $query . PHP_EOL . "ORDER BY $orderBy" . PHP_EOL . ') w'; if ($this->order) { $query .= (string) $this->order; } return $this->processLimit($query, $this->limit, $this->offset); } } return parent::__toString(); } /** * Method to modify a query already in string format with the needed additions to make the query limited to a particular number of * results, or start at a particular offset. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string * * @since 2.0.0 */ public function processLimit($query, $limit, $offset = 0) { if ($limit > 0 && $offset > 0) { $query .= ' LIMIT ' . $offset . ', ' . $limit; } elseif ($limit > 0) { $query .= ' LIMIT ' . $limit; } return $query; } /** * Concatenates an array of column names or values. * * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * * @return string The concatenated values. * * @since 2.0.0 */ public function concatenate($values, $separator = null) { if ($separator !== null) { $statement = 'CONCAT_WS(' . $this->quote($separator); foreach ($values as $value) { $statement .= ', ' . $value; } return $statement . ')'; } return 'CONCAT(' . implode(',', $values) . ')'; } /** * Aggregate function to get input values concatenated into a string, separated by delimiter * * Usage: * $query->groupConcat('id', ','); * * @param string $expression The expression to apply concatenation to, this may be a column name or complex SQL statement. * @param string $separator The delimiter of each concatenated value * * @return string Input values concatenated into a string, separated by delimiter * * @since 2.0.0 */ public function groupConcat($expression, $separator = ',') { return 'GROUP_CONCAT(' . $expression . ' SEPARATOR ' . $this->quote($separator) . ')'; } /** * Method to quote and optionally escape a string to database requirements for insertion into the database. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the quote method directly. * * Note that 'q' is an alias for this method as it is in DatabaseDriver. * * Usage: * $query->quote('fulltext'); * $query->q('fulltext'); * $query->q(array('option', 'fulltext')); * * @param array|string $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * * @return string The quoted input string. * * @since 2.0.0 * @throws \RuntimeException if the internal db property is not a valid object. */ abstract public function quote($text, $escape = true); /** * Get the regular expression operator * * Usage: * $query->where('field ' . $query->regexp($search)); * * @param string $value The regex pattern. * * @return string * * @since 2.0.0 */ public function regexp($value) { return ' REGEXP ' . $value; } /** * Get the function to return a random floating-point value * * Usage: * $query->rand(); * * @return string * * @since 2.0.0 */ public function rand() { return ' RAND() '; } /** * Find a value in a varchar used like a set. * * Ensure that the value is an integer before passing to the method. * * Usage: * $query->findInSet((int) $parent->id, 'a.assigned_cat_ids') * * @param string $value The value to search for. * @param string $set The set of values. * * @return string A representation of the MySQL find_in_set() function for the driver. * * @since 2.0.0 */ public function findInSet($value, $set) { return ' find_in_set(' . $value . ', ' . $set . ')'; } /** * Return the number of the current row. * * Usage: * $query->select('id'); * $query->selectRowNumber('ordering,publish_up DESC', 'new_ordering'); * $query->from('#__content'); * * @param string $orderBy An expression of ordering for window function. * @param string $orderColumnAlias An alias for new ordering column. * * @return $this * * @since 2.0.0 * @throws \RuntimeException */ public function selectRowNumber($orderBy, $orderColumnAlias) { $this->validateRowNumber($orderBy, $orderColumnAlias); return $this->select("(SELECT @rownum := @rownum + 1 FROM (SELECT @rownum := 0) AS r) AS $orderColumnAlias"); } /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * Usage: * $query->select($query->castAs('CHAR', 'a')); * * @param string $type The type of string to cast as. * @param string $value The value to cast as a char. * @param string $length The value to cast as a char. * * @return string SQL statement to cast the value as a char type. * * @since 1.0 */ public function castAs(string $type, string $value, ?string $length = null) { switch (strtoupper($type)) { case 'CHAR': if (!$length) { return $value; } else { return 'CAST(' . $value . ' AS CHAR(' . $length . '))'; } case 'INT': return '(' . $value . ' + 0)'; } return parent::castAs($type, $value, $length); } } PK �9�\\���s s ! src/Query/PreparableInterface.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Query; use Joomla\Database\ParameterType; use Joomla\Database\QueryInterface; trigger_deprecation( 'joomla/database', '2.0.0', '%s() is deprecated and will be removed in 3.0, all query objects should implement %s instead.', PreparableInterface::class, QueryInterface::class ); /** * Joomla Database Query Preparable Interface. * * Adds bind/unbind methods as well as a getBounded() method to retrieve the stored bounded variables on demand prior to query execution. * * @since 1.0 * @deprecated 3.0 Capabilities will be required in Joomla\Database\QueryInterface */ interface PreparableInterface { /** * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. * * @param array|string|integer $key The key that will be used in your SQL query to reference the value. Usually of * the form ':key', but can also be an integer. * @param mixed $value The value that will be bound. It can be an array, in this case it has to be * same length of $key; The value is passed by reference to support output * parameters such as those possible with stored procedures. * @param array|string $dataType Constant corresponding to a SQL datatype. It can be an array, in this case it * has to be same length of $key * @param integer $length The length of the variable. Usually required for OUTPUT parameters. * @param array $driverOptions Optional driver options to be used. * * @return $this * * @since 1.0 */ public function bind($key, &$value, $dataType = ParameterType::STRING, $length = 0, $driverOptions = []); /** * Method to unbind a bound variable. * * @param array|string|integer $key The key or array of keys to unbind. * * @return $this * * @since 2.0.0 */ public function unbind($key); /** * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is returned. * * @param mixed $key The bounded variable key to retrieve. * * @return mixed * * @since 1.0 */ public function &getBounded($key = null); } PK �9�\Z��� � src/Query/QueryElement.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Query; /** * Query Element Class. * * @since 1.0 */ class QueryElement { /** * The name of the element. * * @var string * @since 1.0 */ protected $name; /** * An array of elements. * * @var string[] * @since 1.0 */ protected $elements = []; /** * Glue piece. * * @var string * @since 1.0 */ protected $glue; /** * Constructor. * * @param string $name The name of the element. * @param string[]|string $elements String or array. * @param string $glue The glue for elements. * * @since 1.0 */ public function __construct($name, $elements, $glue = ',') { $this->name = $name; $this->glue = $glue; $this->append($elements); } /** * Magic function to convert the query element to a string. * * @return string * * @since 1.0 */ public function __toString() { if (substr($this->name, -2) === '()') { return \PHP_EOL . substr($this->name, 0, -2) . '(' . implode($this->glue, $this->elements) . ')'; } return \PHP_EOL . $this->name . ' ' . implode($this->glue, $this->elements); } /** * Appends element parts to the internal list. * * @param string[]|string $elements String or array. * * @return void * * @since 1.0 */ public function append($elements) { if (\is_array($elements)) { $this->elements = array_merge($this->elements, $elements); } else { $this->elements = array_merge($this->elements, [$elements]); } } /** * Gets the elements of this element. * * @return string[] * * @since 1.0 */ public function getElements() { return $this->elements; } /** * Gets the glue of this element. * * @return string Glue of the element. * * @since 2.0.0 */ public function getGlue() { return $this->glue; } /** * Gets the name of this element. * * @return string Name of the element. * * @since 1.7.0 */ public function getName() { return $this->name; } /** * Sets the name of this element. * * @param string $name Name of the element. * * @return $this * * @since 1.3.0 */ public function setName($name) { $this->name = $name; return $this; } /** * Method to provide basic copy support. * * Any object pushed into the data of this class should have its own __clone() implementation. * This method does not support copying objects in a multidimensional array. * * @return void * * @since 1.0 */ public function __clone() { foreach ($this as $k => $v) { if (\is_object($v)) { $this->{$k} = clone $v; } elseif (\is_array($v)) { foreach ($v as $i => $element) { if (\is_object($element)) { $this->{$k}[$i] = clone $element; } } } } } } PK �9�\��>"y y src/Query/LimitableInterface.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Query; use Joomla\Database\QueryInterface; trigger_deprecation( 'joomla/database', '2.0.0', '%s() is deprecated and will be removed in 3.0, all query objects should implement %s instead.', LimitableInterface::class, QueryInterface::class ); /** * Joomla Database Query LimitableInterface. * * @since 1.0 * @deprecated 3.0 Capabilities will be required in Joomla\Database\QueryInterface */ interface LimitableInterface { /** * Method to modify a query already in string format with the needed additions to make the query limited to a particular number of * results, or start at a particular offset. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string * * @since 1.0 */ public function processLimit($query, $limit, $offset = 0); /** * Sets the offset and limit for the result set, if the database driver supports it. * * Usage: * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record) * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record) * * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return $this * * @since 1.0 */ public function setLimit($limit = 0, $offset = 0); } PK �9�\P��� � src/DatabaseAwareTrait.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2022 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; use Joomla\Database\Exception\DatabaseNotFoundException; /** * Defines the trait for a Database Aware Class. * * @since 2.1.0 */ trait DatabaseAwareTrait { /** * Database * * @var DatabaseInterface * @since 2.1.0 */ private $databaseAwareTraitDatabase; /** * Get the database. * * @return DatabaseInterface * * @since 2.1.0 * @throws DatabaseNotFoundException May be thrown if the database has not been set. */ protected function getDatabase(): DatabaseInterface { if ($this->databaseAwareTraitDatabase) { return $this->databaseAwareTraitDatabase; } throw new DatabaseNotFoundException('Database not set in ' . \get_class($this)); } /** * Set the database. * * @param DatabaseInterface $db The database. * * @return void * * @since 2.1.0 */ public function setDatabase(DatabaseInterface $db): void { $this->databaseAwareTraitDatabase = $db; } } PK �9�\�����V �V src/Sqlsrv/SqlsrvDriver.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Sqlsrv; use Joomla\Database\DatabaseDriver; use Joomla\Database\DatabaseEvents; use Joomla\Database\Event\ConnectionEvent; use Joomla\Database\Exception\ConnectionFailureException; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\Exception\PrepareStatementFailureException; use Joomla\Database\Exception\UnsupportedAdapterException; use Joomla\Database\StatementInterface; /** * SQL Server Database Driver * * @link https://www.php.net/manual/en/book.sqlsrv.php * @since 1.0 */ class SqlsrvDriver extends DatabaseDriver { /** * The name of the database driver. * * @var string * @since 1.0 */ public $name = 'sqlsrv'; /** * The character(s) used to quote SQL statement names such as table names or field names, etc. * * If a single character string the same character is used for both sides of the quoted name, else the first character will be used for the * opening quote and the second for the closing quote. * * @var string * @since 1.0 */ protected $nameQuote = '[]'; /** * The null or zero representation of a timestamp for the database driver. * * @var string * @since 1.0 */ protected $nullDate = '1900-01-01 00:00:00'; /** * The minimum supported database version. * * @var string * @since 1.0 */ protected static $dbMinimum = '11.0.2100.60'; /** * Test to see if the SQLSRV connector is available. * * @return boolean True on success, false otherwise. * * @since 1.0 */ public static function isSupported() { return \function_exists('sqlsrv_connect'); } /** * Constructor. * * @param array $options List of options used to configure the connection * * @since 1.0 */ public function __construct(array $options) { // Get some basic values from the options. $options['host'] = $options['host'] ?? 'localhost'; $options['user'] = $options['user'] ?? ''; $options['password'] = $options['password'] ?? ''; $options['database'] = $options['database'] ?? ''; $options['select'] = isset($options['select']) ? (bool) $options['select'] : true; $options['trust_certificate'] = isset($options['trust_certificate']) ? (bool) $options['trust_certificate'] : false; // Finalize initialisation parent::__construct($options); } /** * Connects to the database if needed. * * @return void Returns void if the database connected successfully. * * @since 1.0 * @throws \RuntimeException */ public function connect() { if ($this->connection) { return; } // Make sure the SQLSRV extension for PHP is installed and enabled. if (!static::isSupported()) { throw new UnsupportedAdapterException('PHP extension sqlsrv_connect is not available.'); } // Build the connection configuration array. $config = [ 'Database' => $this->options['database'], 'uid' => $this->options['user'], 'pwd' => $this->options['password'], 'CharacterSet' => 'UTF-8', 'ReturnDatesAsStrings' => true, 'TrustServerCertificate' => $this->options['trust_certificate'], ]; // Attempt to connect to the server. if (!($this->connection = @ sqlsrv_connect($this->options['host'], $config))) { throw new ConnectionFailureException('Could not connect to SQL Server'); } // Make sure that DB warnings are not returned as errors. sqlsrv_configure('WarningsReturnAsErrors', 0); // If auto-select is enabled select the given database. if ($this->options['select'] && !empty($this->options['database'])) { $this->select($this->options['database']); } $this->dispatchEvent(new ConnectionEvent(DatabaseEvents::POST_CONNECT, $this)); } /** * Disconnects the database. * * @return void * * @since 1.0 */ public function disconnect() { // Close the connection. if (\is_resource($this->connection)) { sqlsrv_close($this->connection); } parent::disconnect(); } /** * Get table constraints * * @param string $tableName The name of the database table. * * @return array Any constraints available for the table. * * @since 1.0 */ protected function getTableConstraints($tableName) { $this->connect(); return $this->setQuery('SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = ' . $this->quote($tableName)) ->loadColumn(); } /** * Rename constraints. * * @param array $constraints Array(strings) of table constraints * @param string $prefix A string * @param string $backup A string * * @return void * * @since 1.0 */ protected function renameConstraints($constraints = [], $prefix = null, $backup = null) { $this->connect(); foreach ($constraints as $constraint) { $this->setQuery('sp_rename ' . $constraint . ',' . str_replace($prefix, $backup, $constraint)) ->execute(); } } /** * Method to escape a string for usage in an SQL statement. * * The escaping for MSSQL isn't handled in the driver though that would be nice. Because of this we need to handle the escaping ourselves. * * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 1.0 */ public function escape($text, $extra = false) { if (\is_int($text)) { return $text; } if (\is_float($text)) { // Force the dot as a decimal point. return str_replace(',', '.', $text); } $result = str_replace("'", "''", $text); // SQL Server does not accept NULL byte in query string $result = str_replace("\0", "' + CHAR(0) + N'", $result); // Fix for SQL Sever escape sequence, see https://support.microsoft.com/en-us/kb/164291 $result = str_replace( ["\\\n", "\\\r", "\\\\\r\r\n"], ["\\\\\n\n", "\\\\\r\r", "\\\\\r\n\r\n"], $result ); if ($extra) { // Escape special chars $result = str_replace( ['[', '_', '%'], ['[[]', '[_]', '[%]'], $result ); } return $result; } /** * Quotes and optionally escapes a string to database requirements for use in database queries. * * @param mixed $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * * @return string The quoted input string. * * @since 1.6.0 */ public function quote($text, $escape = true) { if (\is_array($text)) { return parent::quote($text, $escape); } // To support unicode on MSSQL we have to add prefix N return 'N\'' . ($escape ? $this->escape($text) : $text) . '\''; } /** * Quotes a binary string to database requirements for use in database queries. * * @param string $data A binary string to quote. * * @return string The binary quoted input string. * * @since 1.7.0 */ public function quoteBinary($data) { // ODBC syntax for hexadecimal literals return '0x' . bin2hex($data); } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. * * @since 1.0 */ public function connected() { // TODO: Run a blank query here return true; } /** * Drops a table from the database. * * @param string $table The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return $this * * @since 1.0 */ public function dropTable($table, $ifExists = true) { $this->connect(); if ($ifExists) { $this->setQuery( 'IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ' . $this->quote($table) . ') DROP TABLE ' . $table ); } else { $this->setQuery('DROP TABLE ' . $table); } $this->execute(); return $this; } /** * Method to get the database collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database or boolean false if not supported. * * @since 1.0 */ public function getCollation() { // TODO: Not fake this return 'MSSQL UTF-8 (UCS2)'; } /** * Method to get the database connection collation in use by sampling a text field of a table in the database. * * @return string|boolean The collation in use by the database connection (string) or boolean false if not supported. * * @since 1.6.0 * @throws \RuntimeException */ public function getConnectionCollation() { // TODO: Not fake this return 'MSSQL UTF-8 (UCS2)'; } /** * Method to get the database encryption details (cipher and protocol) in use. * * @return string The database encryption details. * * @since 2.0.0 * @throws \RuntimeException */ public function getConnectionEncryption(): string { // TODO: Not fake this return ''; } /** * Method to test if the database TLS connections encryption are supported. * * @return boolean Whether the database supports TLS connections encryption. * * @since 2.0.0 */ public function isConnectionEncryptionSupported(): bool { // TODO: Not fake this return false; } /** * Retrieves field information about the given tables. * * @param mixed $table A table name * @param boolean $typeOnly True to only return field types. * * @return array An array of fields. * * @since 1.0 * @throws \RuntimeException */ public function getTableColumns($table, $typeOnly = true) { $result = []; $table_temp = $this->replacePrefix((string) $table); // Set the query to get the table fields statement. $this->setQuery( 'SELECT column_name as Field, data_type as Type, is_nullable as \'Null\', column_default as \'Default\'' . ' FROM information_schema.columns WHERE table_name = ' . $this->quote($table_temp) ); $fields = $this->loadObjectList(); // If we only want the type as the value add just that to the list. if ($typeOnly) { foreach ($fields as $field) { $result[$field->Field] = preg_replace('/[(0-9)]/', '', $field->Type); } } else { // If we want the whole field data object add that to the list. foreach ($fields as $field) { $field->Default = preg_replace("/(^(\(\(|\('|\(N'|\()|(('\)|(?<!\()\)\)|\))$))/i", '', $field->Default); $result[$field->Field] = $field; } } return $result; } /** * Shows the table CREATE statement that creates the given tables. * * This is unsupported by MSSQL. * * @param mixed $tables A table name or a list of table names. * * @return array A list of the create SQL for the tables. * * @since 1.0 * @throws \RuntimeException */ public function getTableCreate($tables) { $this->connect(); return ''; } /** * Get the details list of keys for a table. * * @param string $table The name of the table. * * @return array An array of the column specification for the table. * * @since 1.0 * @throws \RuntimeException */ public function getTableKeys($table) { $this->connect(); // TODO To implement. return []; } /** * Method to get an array of all tables in the database. * * @return array An array of all the tables in the database. * * @since 1.0 * @throws \RuntimeException */ public function getTableList() { $this->connect(); // Set the query to get the tables statement. return $this->setQuery('SELECT name FROM ' . $this->getDatabase() . '.sys.Tables WHERE type = \'U\';')->loadColumn(); } /** * Get the version of the database connector. * * @return string The database connector version. * * @since 1.0 */ public function getVersion() { $this->connect(); $version = sqlsrv_server_info($this->connection); return $version['SQLServerVersion']; } /** * Inserts a row into a table based on an object's properties. * * @param string $table The name of the database table to insert into. * @param object $object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. If provided the object property is updated. * * @return boolean True on success. * * @since 1.0 * @throws \RuntimeException */ public function insertObject($table, &$object, $key = null) { $fields = []; $values = []; $tableColumns = $this->getTableColumns($table); $statement = 'INSERT INTO ' . $this->quoteName($table) . ' (%s) VALUES (%s)'; foreach (get_object_vars($object) as $k => $v) { // Skip columns that don't exist in the table. if (!\array_key_exists($k, $tableColumns)) { continue; } // Only process non-null scalars. if (\is_array($v) || \is_object($v) || $v === null) { continue; } if (!$this->checkFieldExists($table, $k)) { continue; } if ($k[0] === '_') { // Internal field continue; } if ($k === $key && $key == 0) { continue; } $fields[] = $this->quoteName($k); $values[] = $this->quote($v); } // Set the query and execute the insert. $this->setQuery(sprintf($statement, implode(',', $fields), implode(',', $values)))->execute(); $id = $this->insertid(); if ($key && $id) { $object->$key = $id; } return true; } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return integer The value of the auto-increment field from the last inserted row. * * @since 1.0 */ public function insertid() { $this->connect(); // TODO: SELECT IDENTITY $this->setQuery('SELECT @@IDENTITY'); return (int) $this->loadResult(); } /** * Execute the SQL statement. * * @return boolean * * @since 1.0 * @throws \RuntimeException */ public function execute() { $this->connect(); // Take a local copy so that we don't modify the original query and cause issues later $sql = $this->replacePrefix((string) $this->sql); // Increment the query counter. $this->count++; // Get list of bounded parameters $bounded =& $this->sql->getBounded(); // If there is a monitor registered, let it know we are starting this query if ($this->monitor) { $this->monitor->startQuery($sql, $bounded); } // Execute the query. $this->executed = false; // Bind the variables foreach ($bounded as $key => $obj) { $this->statement->bindParam($key, $obj->value, $obj->dataType); } try { $this->executed = $this->statement->execute(); // If there is a monitor registered, let it know we have finished this query if ($this->monitor) { $this->monitor->stopQuery(); } return true; } catch (ExecutionFailureException $exception) { // If there is a monitor registered, let it know we have finished this query if ($this->monitor) { $this->monitor->stopQuery(); } // Check if the server was disconnected. if (!$this->connected()) { try { // Attempt to reconnect. $this->connection = null; $this->connect(); } catch (ConnectionFailureException $e) { // If connect fails, ignore that exception and throw the normal exception. throw $exception; } // Since we were able to reconnect, run the query again. return $this->execute(); } // Throw the normal query exception. throw $exception; } } /** * This function replaces a string identifier with the configured table prefix. * * @param string $sql The SQL statement to prepare. * @param string $prefix The table prefix. * * @return string The processed SQL statement. * * @since 1.0 */ public function replacePrefix($sql, $prefix = '#__') { $escaped = false; $startPos = 0; $quoteChar = ''; $literal = ''; $sql = trim($sql); $n = \strlen($sql); while ($startPos < $n) { $ip = strpos($sql, $prefix, $startPos); if ($ip === false) { break; } $j = strpos($sql, "N'", $startPos); $k = strpos($sql, '"', $startPos); if (($k !== false) && (($k < $j) || ($j === false))) { $quoteChar = '"'; $j = $k; } else { $quoteChar = "'"; } if ($j === false) { $j = $n; } $literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos)); $startPos = $j; $j = $startPos + 1; if ($j >= $n) { break; } // Quote comes first, find end of quote while (true) { $k = strpos($sql, $quoteChar, $j); $escaped = false; if ($k === false) { break; } $l = $k - 1; while ($l >= 0 && $sql[$l] === '\\') { $l--; $escaped = !$escaped; } if ($escaped) { $j = $k + 1; continue; } break; } if ($k === false) { // Error in the query - no end quote; ignore it break; } $literal .= substr($sql, $startPos, $k - $startPos + 1); $startPos = $k + 1; } if ($startPos < $n) { $literal .= substr($sql, $startPos, $n - $startPos); } return $literal; } /** * Select a database for use. * * @param string $database The name of the database to select for use. * * @return boolean True if the database was successfully selected. * * @since 1.0 * @throws ConnectionFailureException */ public function select($database) { $this->connect(); if (!$database) { return false; } if (!sqlsrv_query($this->connection, 'USE [' . $database . ']', null, ['scrollable' => \SQLSRV_CURSOR_STATIC])) { throw new ConnectionFailureException('Could not connect to database'); } return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. * * @since 1.0 */ public function setUtf() { return true; } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionCommit($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { $this->setQuery('COMMIT TRANSACTION')->execute(); $this->transactionDepth = 0; return; } $this->transactionDepth--; } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionRollback($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { $this->setQuery('ROLLBACK TRANSACTION')->execute(); $this->transactionDepth = 0; return; } $savepoint = 'SP_' . ($this->transactionDepth - 1); $this->setQuery('ROLLBACK TRANSACTION ' . $this->quoteName($savepoint))->execute(); $this->transactionDepth--; } /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionStart($asSavepoint = false) { $this->connect(); if (!$asSavepoint || !$this->transactionDepth) { $this->setQuery('BEGIN TRANSACTION')->execute(); $this->transactionDepth = 1; return; } $savepoint = 'SP_' . $this->transactionDepth; $this->setQuery('BEGIN TRANSACTION ' . $this->quoteName($savepoint))->execute(); $this->transactionDepth++; } /** * Method to check and see if a field exists in a table. * * @param string $table The table in which to verify the field. * @param string $field The field to verify. * * @return boolean True if the field exists in the table. * * @since 1.0 */ protected function checkFieldExists($table, $field) { $this->connect(); $table = $this->replacePrefix((string) $table); $this->setQuery( "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field' ORDER BY ORDINAL_POSITION" ); return (bool) $this->loadResult(); } /** * Prepares a SQL statement for execution * * @param string $query The SQL query to be prepared. * * @return StatementInterface * * @since 2.0.0 * @throws PrepareStatementFailureException */ protected function prepareStatement(string $query): StatementInterface { return new SqlsrvStatement($this->connection, $query); } /** * Renames a table in the database. * * @param string $oldTable The name of the table to be renamed * @param string $newTable The new name for the table. * @param string $backup Table prefix * @param string $prefix For the table - used to rename constraints in non-mysql databases * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $constraints = []; if ($prefix !== null && $backup !== null) { $constraints = $this->getTableConstraints($oldTable); } if (!empty($constraints)) { $this->renameConstraints($constraints, $prefix, $backup); } $this->setQuery("sp_rename '" . $oldTable . "', '" . $newTable . "'"); $this->execute(); return $this; } /** * Locks a table in the database. * * @param string $tableName The name of the table to lock. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function lockTable($tableName) { return $this; } /** * Unlocks tables in the database. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function unlockTables() { return $this; } } PK �9�\Sȡ�: �: src/Sqlsrv/SqlsrvStatement.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Sqlsrv; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\Exception\PrepareStatementFailureException; use Joomla\Database\FetchMode; use Joomla\Database\FetchOrientation; use Joomla\Database\ParameterType; use Joomla\Database\StatementInterface; /** * SQL Server Database Statement. * * This class is modeled on \Doctrine\DBAL\Driver\SQLSrv\SQLSrvStatement * * @since 2.0.0 */ class SqlsrvStatement implements StatementInterface { /** * The database connection resource. * * @var resource * @since 2.0.0 */ protected $connection; /** * The default fetch mode for the statement. * * @var integer * @since 2.0.0 */ protected $defaultFetchStyle = FetchMode::MIXED; /** * The default class to use for building object result sets. * * @var integer * @since 2.0.0 */ protected $defaultObjectClass = \stdClass::class; /** * Mapping array converting fetch modes to the native engine type. * * @var array * @since 2.0.0 */ private $fetchMap = [ FetchMode::MIXED => SQLSRV_FETCH_BOTH, FetchMode::ASSOCIATIVE => SQLSRV_FETCH_ASSOC, FetchMode::NUMERIC => SQLSRV_FETCH_NUMERIC, ]; /** * The query string being prepared. * * @var string * @since 2.0.0 */ protected $query; /** * Internal tracking flag to set whether there is a result set available for processing * * @var boolean * @since 2.0.0 */ private $result = false; /** * The prepared statement. * * @var resource * @since 2.0.0 */ protected $statement; /** * Bound parameter types. * * @var array * @since 2.0.0 */ protected $typesKeyMapping; /** * References to the variables bound as statement parameters. * * @var array * @since 2.0.0 */ private $bindedValues = []; /** * Mapping between named parameters and position in query. * * @var array * @since 2.0.0 */ protected $parameterKeyMapping; /** * Mapping array for parameter types. * * @var array * @since 2.0.0 */ protected $parameterTypeMapping = [ ParameterType::BOOLEAN => ParameterType::BOOLEAN, ParameterType::INTEGER => ParameterType::INTEGER, ParameterType::LARGE_OBJECT => ParameterType::LARGE_OBJECT, ParameterType::NULL => ParameterType::NULL, ParameterType::STRING => ParameterType::STRING, ]; /** * Constructor. * * @param resource $connection The database connection resource * @param string $query The query this statement will process * * @since 2.0.0 * @throws PrepareStatementFailureException */ public function __construct($connection, string $query) { // Initial parameter types for prepared statements $this->parameterTypeMapping = [ ParameterType::BOOLEAN => SQLSRV_PHPTYPE_INT, ParameterType::INTEGER => SQLSRV_PHPTYPE_INT, ParameterType::LARGE_OBJECT => SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), ParameterType::NULL => SQLSRV_PHPTYPE_NULL, ParameterType::STRING => SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), ]; $this->connection = $connection; $this->query = $this->prepareParameterKeyMapping($query); } /** * Replace named parameters with numbered parameters * * @param string $sql The SQL statement to prepare. * * @return string The processed SQL statement. * * @since 2.0.0 */ public function prepareParameterKeyMapping($sql) { $escaped = false; $startPos = 0; $quoteChar = ''; $literal = ''; $mapping = []; $replace = []; $matches = []; $pattern = '/([:][a-zA-Z0-9_]+)/'; if (!preg_match($pattern, $sql, $matches)) { return $sql; } $sql = trim($sql); $n = \strlen($sql); while ($startPos < $n) { if (!preg_match($pattern, $sql, $matches, 0, $startPos)) { break; } $j = strpos($sql, "'", $startPos); $k = strpos($sql, '"', $startPos); if (($k !== false) && (($k < $j) || ($j === false))) { $quoteChar = '"'; $j = $k; } else { $quoteChar = "'"; } if ($j === false) { $j = $n; } // Search for named prepared parameters and replace it with ? and save its position $substring = substr($sql, $startPos, $j - $startPos); if (preg_match_all($pattern, $substring, $matches, PREG_PATTERN_ORDER + PREG_OFFSET_CAPTURE)) { foreach ($matches[0] as $i => $match) { if ($i === 0) { $literal .= substr($substring, 0, $match[1]); } $mapping[$match[0]] = \count($mapping); $endOfPlaceholder = $match[1] + strlen($match[0]); $beginOfNextPlaceholder = $matches[0][$i + 1][1] ?? strlen($substring); $beginOfNextPlaceholder -= $endOfPlaceholder; $literal .= '?' . substr($substring, $endOfPlaceholder, $beginOfNextPlaceholder); } } else { $literal .= $substring; } $startPos = $j; $j++; if ($j >= $n) { break; } // Quote comes first, find end of quote while (true) { $k = strpos($sql, $quoteChar, $j); $escaped = false; if ($k === false) { break; } $l = $k - 1; while ($l >= 0 && $sql[$l] === '\\') { $l--; $escaped = !$escaped; } if ($escaped) { $j = $k + 1; continue; } break; } if ($k === false) { // Error in the query - no end quote; ignore it break; } $literal .= substr($sql, $startPos, $k - $startPos + 1); $startPos = $k + 1; } if ($startPos < $n) { $literal .= substr($sql, $startPos, $n - $startPos); } $this->parameterKeyMapping = $mapping; return $literal; } /** * Binds a parameter to the specified variable name. * * @param string|integer $parameter Parameter identifier. For a prepared statement using named placeholders, this will be a parameter * name of the form `:name`. For a prepared statement using question mark placeholders, this will be * the 1-indexed position of the parameter. * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. * @param string $dataType Constant corresponding to a SQL datatype, this should be the processed type from the QueryInterface. * @param integer $length The length of the variable. Usually required for OUTPUT parameters. * @param array $driverOptions Optional driver options to be used. * * @return boolean * * @since 2.0.0 */ public function bindParam($parameter, &$variable, string $dataType = ParameterType::STRING, ?int $length = null, ?array $driverOptions = null) { $this->bindedValues[$parameter] =& $variable; // Validate parameter type if (!isset($this->parameterTypeMapping[$dataType])) { throw new \InvalidArgumentException(sprintf('Unsupported parameter type `%s`', $dataType)); } $this->typesKeyMapping[$parameter] = $this->parameterTypeMapping[$dataType]; $this->statement = null; return true; } /** * Binds a value to the specified variable. * * @param string|integer $parameter Parameter identifier. For a prepared statement using named placeholders, this will be a parameter * name of the form `:name`. For a prepared statement using question mark placeholders, this will be * the 1-indexed position of the parameter. * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. * @param string $dataType Constant corresponding to a SQL datatype, this should be the processed type from the QueryInterface. * * @return void * * @since 2.0.0 */ private function bindValue($parameter, $variable, $dataType = ParameterType::STRING) { $this->bindedValues[$parameter] = $variable; $this->typesKeyMapping[$parameter] = $dataType; } /** * Closes the cursor, enabling the statement to be executed again. * * @return void * * @since 2.0.0 */ public function closeCursor(): void { if (!$this->result || !\is_resource($this->statement)) { return; } // Emulate freeing the result fetching and discarding rows, similarly to what PDO does in this case while (sqlsrv_fetch($this->statement)) { // Do nothing (see above) } $this->result = false; } /** * Fetches the SQLSTATE associated with the last operation on the statement handle. * * @return string * * @since 2.0.0 */ public function errorCode() { $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); if ($errors) { return $errors[0]['code']; } return false; } /** * Fetches extended error information associated with the last operation on the statement handle. * * @return array * * @since 2.0.0 */ public function errorInfo() { return sqlsrv_errors(SQLSRV_ERR_ERRORS); } /** * Executes a prepared statement * * @param array|null $parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. * * @return boolean * * @since 2.0.0 */ public function execute(?array $parameters = null) { if (empty($this->bindedValues) && $parameters !== null) { $hasZeroIndex = array_key_exists(0, $parameters); foreach ($parameters as $key => $val) { $key = ($hasZeroIndex && is_numeric($key)) ? $key + 1 : $key; $this->bindValue($key, $val); } } if (!$this->statement) { $this->statement = $this->prepare(); } if (!sqlsrv_execute($this->statement)) { $errors = $this->errorInfo(); throw new ExecutionFailureException($this->query, $errors[0]['message'], $errors[0]['code']); } $this->result = true; return true; } /** * Fetches the next row from a result set * * @param integer|null $fetchStyle Controls how the next row will be returned to the caller. This value must be one of the * FetchMode constants, defaulting to value of FetchMode::MIXED. * @param integer $cursorOrientation For a StatementInterface object representing a scrollable cursor, this value determines which row * will be returned to the caller. This value must be one of the FetchOrientation constants, * defaulting to FetchOrientation::NEXT. * @param integer $cursorOffset For a StatementInterface object representing a scrollable cursor for which the cursorOrientation * parameter is set to FetchOrientation::ABS, this value specifies the absolute number of the row in * the result set that shall be fetched. For a StatementInterface object representing a scrollable * cursor for which the cursorOrientation parameter is set to FetchOrientation::REL, this value * specifies the row to fetch relative to the cursor position before `fetch()` was called. * * @return mixed The return value of this function on success depends on the fetch type. In all cases, boolean false is returned on failure. * * @since 2.0.0 */ public function fetch(?int $fetchStyle = null, int $cursorOrientation = FetchOrientation::NEXT, int $cursorOffset = 0) { if (!$this->result) { return false; } $fetchStyle = $fetchStyle ?: $this->defaultFetchStyle; if ($fetchStyle === FetchMode::COLUMN) { return $this->fetchColumn(); } if (isset($this->fetchMap[$fetchStyle])) { return sqlsrv_fetch_array($this->statement, $this->fetchMap[$fetchStyle]) ?: false; } if (\in_array($fetchStyle, [FetchMode::STANDARD_OBJECT, FetchMode::CUSTOM_OBJECT], true)) { return sqlsrv_fetch_object($this->statement, $this->defaultObjectClass) ?: false; } throw new \InvalidArgumentException("Unknown fetch type '{$fetchStyle}'"); } /** * Returns a single column from the next row of a result set * * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. * If no value is supplied, the first column is retrieved. * * @return mixed Returns a single column from the next row of a result set or boolean false if there are no more rows. * * @since 2.0.0 */ public function fetchColumn($columnIndex = 0) { $row = $this->fetch(FetchMode::NUMERIC); if ($row === false) { return false; } return $row[$columnIndex] ?? null; } /** * Prepares the SQL Server statement resource for execution * * @return resource * * @since 2.0.0 */ private function prepare() { $params = []; $options = []; foreach ($this->bindedValues as $key => &$value) { $variable = [ &$value, SQLSRV_PARAM_IN ]; if ($this->typesKeyMapping[$key] === $this->parameterTypeMapping[ParameterType::LARGE_OBJECT]) { $variable[] = $this->typesKeyMapping[$key]; $variable[] = SQLSRV_SQLTYPE_VARBINARY('max'); } if (isset($this->parameterKeyMapping[$key])) { $params[$this->parameterKeyMapping[$key]] = $variable; } else { $params[] = $variable; } } // Cleanup referenced variable unset($value); // SQLSRV Function sqlsrv_num_rows requires a static or keyset cursor. if (strncmp(strtoupper(ltrim($this->query)), 'SELECT', \strlen('SELECT')) === 0) { $options = ['Scrollable' => SQLSRV_CURSOR_KEYSET]; } $statement = sqlsrv_prepare($this->connection, $this->query, $params, $options); if (!$statement) { $errors = $this->errorInfo(); throw new PrepareStatementFailureException($errors[0]['message'], $errors[0]['code']); } return $statement; } /** * Returns the number of rows affected by the last SQL statement. * * @return integer * * @since 2.0.0 */ public function rowCount(): int { if (strncmp(strtoupper(ltrim($this->query)), 'SELECT', \strlen('SELECT')) === 0) { return sqlsrv_num_rows($this->statement); } return sqlsrv_rows_affected($this->statement); } /** * Sets the fetch mode to use while iterating this statement. * * @param integer $fetchMode The fetch mode, must be one of the FetchMode constants. * @param mixed ...$args Optional mode-specific arguments. * * @return void * * @since 2.0.0 */ public function setFetchMode(int $fetchMode, ...$args): void { $this->defaultFetchStyle = $fetchMode; if (isset($args[0])) { $this->defaultObjectClass = $args[0]; } } } PK �9�\�`��n �n src/Sqlsrv/SqlsrvQuery.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Sqlsrv; use Joomla\Database\DatabaseInterface; use Joomla\Database\DatabaseQuery; use Joomla\Database\Query\QueryElement; /** * SQL Server Query Building Class. * * @since 1.0 */ class SqlsrvQuery extends DatabaseQuery { /** * The list of zero or null representation of a datetime. * * @var array * @since 2.0.0 */ protected $nullDatetimeList = ['1900-01-01 00:00:00']; /** * Magic function to convert the query to a string. * * @return string The completed query. * * @since 1.0 */ public function __toString() { // For the moment if we are given a query string we can't effectively process limits, fix this later if ($this->sql) { return $this->sql; } $query = ''; switch ($this->type) { case 'select': // Add required aliases for offset or fixGroupColumns method $columns = $this->fixSelectAliases(); $query = (string) $this->select; if ($this->group) { $this->fixGroupColumns($columns); } $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->selectRowNumber === null) { if ($this->group) { $query .= (string) $this->group; } if ($this->having) { $query .= (string) $this->having; } if ($this->merge) { // Special case for merge foreach ($this->merge as $idx => $element) { $query .= (string) $element . ' AS merge_' . (int) ($idx + 1); } } } if ($this->order) { $query .= (string) $this->order; } else { $query .= PHP_EOL . '/*ORDER BY (SELECT 0)*/'; } $query = $this->processLimit($query, $this->limit, $this->offset); if ($this->alias !== null) { $query = '(' . $query . ') AS ' . $this->alias; } break; case 'querySet': $query = $this->querySet; if ($query->order || $query->limit || $query->offset) { // If ORDER BY or LIMIT statement exist then parentheses is required for the first query $query = PHP_EOL . "SELECT * FROM ($query) AS merge_0"; } if ($this->merge) { // Special case for merge foreach ($this->merge as $idx => $element) { $query .= (string) $element . ' AS merge_' . (int) ($idx + 1); } } if ($this->order) { $query .= (string) $this->order; } $query = $this->processLimit($query, $this->limit, $this->offset); break; case 'insert': $query .= (string) $this->insert; // Set method if ($this->set) { $query .= (string) $this->set; } elseif ($this->values) { // Columns-Values method if ($this->columns) { $query .= (string) $this->columns; } $elements = $this->insert->getElements(); $tableName = array_shift($elements); $query .= 'VALUES '; $query .= (string) $this->values; if ($this->autoIncrementField) { $query = 'SET IDENTITY_INSERT ' . $tableName . ' ON;' . $query . 'SET IDENTITY_INSERT ' . $tableName . ' OFF;'; } if ($this->where) { $query .= (string) $this->where; } } break; case 'delete': $query .= (string) $this->delete; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->order) { $query .= (string) $this->order; } break; case 'update': if ($this->join) { $tmpUpdate = $this->update; $tmpFrom = $this->from; $this->update = null; $this->from = null; $updateElem = $tmpUpdate->getElements(); $updateArray = explode(' ', $updateElem[0]); // Use table alias if exists $this->update(end($updateArray)); $this->from($updateElem[0]); $query .= (string) $this->update; $query .= (string) $this->set; $query .= (string) $this->from; $this->update = $tmpUpdate; $this->from = $tmpFrom; // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } else { $query .= (string) $this->update; $query .= (string) $this->set; } if ($this->where) { $query .= (string) $this->where; } if ($this->order) { $query .= (string) $this->order; } break; default: $query = parent::__toString(); break; } return $query; } /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * Usage: * $query->select($query->castAs('CHAR', 'a')); * * @param string $type The type of string to cast as. * @param string $value The value to cast as a char. * @param string $length The value to cast as a char. * * @return string SQL statement to cast the value as a char type. * * @since 1.0 */ public function castAs(string $type, string $value, ?string $length = null) { switch (strtoupper($type)) { case 'CHAR': if (!$length) { $length = '10'; } return 'CAST(' . $value . ' as NVARCHAR(' . $length . '))'; case 'INT': return 'CAST(' . $value . ' AS INT)'; } return parent::castAs($type, $value, $length); } /** * Gets the function to determine the length of a character string. * * @param string $field A value. * @param string|null $operator Comparison operator between charLength integer value and $condition * @param string|null $condition Integer value to compare charLength with. * * @return string The required char length call. * * @since 1.0 */ public function charLength($field, $operator = null, $condition = null) { $statement = 'DATALENGTH(' . $field . ')'; if ($operator !== null && $condition !== null) { $statement .= ' ' . $operator . ' ' . $condition; } return $statement; } /** * Concatenates an array of column names or values. * * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * * @return string The concatenated values. * * @since 1.0 */ public function concatenate($values, $separator = null) { if ($separator !== null) { return '(' . implode('+' . $this->quote($separator) . '+', $values) . ')'; } return '(' . implode('+', $values) . ')'; } /** * Gets the current date and time. * * @return string * * @since 1.0 */ public function currentTimestamp() { return 'GETDATE()'; } /** * Get the length of a string in bytes. * * @param string $value The string to measure. * * @return integer * * @since 1.0 */ public function length($value) { return 'LEN(' . $value . ')'; } /** * Add a grouping column to the GROUP clause of the query. * * Usage: * $query->group('id'); * * @param mixed $columns A string or array of ordering columns. * * @return SqlsrvQuery Returns this object to allow chaining. * * @since 1.5.0 */ public function group($columns) { if (!($this->db instanceof DatabaseInterface)) { throw new \RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT'); } // Transform $columns into an array for filtering purposes \is_string($columns) && $columns = explode(',', str_replace(' ', '', $columns)); // Get the _formatted_ FROM string and remove everything except `table AS alias` $fromStr = str_replace(['[', ']'], '', str_replace('#__', $this->db->getPrefix(), str_replace('FROM ', '', (string) $this->from))); // Start setting up an array of alias => table list($table, $alias) = preg_split("/\sAS\s/i", $fromStr); $tmpCols = $this->db->getTableColumns(trim($table)); $cols = []; foreach ($tmpCols as $name => $type) { $cols[] = $alias . '.' . $name; } // Now we need to get all tables from any joins // Go through all joins and add them to the tables array foreach ($this->join as $join) { $joinTbl = str_replace( '#__', $this->db->getPrefix(), str_replace( ']', '', preg_replace("/.*(#.+\sAS\s[^\s]*).*/i", '$1', (string) $join) ) ); list($table, $alias) = preg_split("/\sAS\s/i", $joinTbl); $tmpCols = $this->db->getTableColumns(trim($table)); foreach ($tmpCols as $name => $tmpColType) { $cols[] = $alias . '.' . $name; } } $selectStr = str_replace('SELECT ', '', (string) $this->select); // Remove any functions (e.g. COUNT(), SUM(), CONCAT()) $selectCols = preg_replace("/([^,]*\([^\)]*\)[^,]*,?)/", '', $selectStr); // Remove any "as alias" statements $selectCols = preg_replace("/(\sas\s[^,]*)/i", '', $selectCols); // Remove any extra commas $selectCols = preg_replace('/,{2,}/', ',', $selectCols); // Remove any trailing commas and all whitespaces $selectCols = trim(str_replace(' ', '', preg_replace('/,?$/', '', $selectCols))); // Get an array to compare against $selectCols = explode(',', $selectCols); // Find all alias.* and fill with proper table column names foreach ($selectCols as $key => $aliasColName) { if (preg_match("/.+\*/", $aliasColName, $match)) { // Grab the table alias minus the .* $aliasStar = preg_replace("/(.+)\.\*/", '$1', $aliasColName); // Unset the array key unset($selectCols[$key]); // Get the table name $tableColumns = preg_grep("/{$aliasStar}\.+/", $cols); $columns = array_merge($columns, $tableColumns); } } // Finally, get a unique string of all column names that need to be included in the group statement $columns = array_unique(array_merge($columns, $selectCols)); $columns = implode(',', $columns); // Recreate it every time, to ensure we have checked _all_ select statements $this->group = new QueryElement('GROUP BY', $columns); return $this; } /** * Aggregate function to get input values concatenated into a string, separated by delimiter * * Usage: * $query->groupConcat('id', ','); * * @param string $expression The expression to apply concatenation to, this may be a column name or complex SQL statement. * @param string $separator The delimiter of each concatenated value * * @return string Input values concatenated into a string, separated by delimiter * * @since 2.0.0 */ public function groupConcat($expression, $separator = ',') { return 'string_agg(' . $expression . ', ' . $this->quote($separator) . ')'; } /** * Get the function to return a random floating-point value * * Usage: * $query->rand(); * * @return string * * @since 1.5.0 */ public function rand() { return ' NEWID() '; } /** * Find a value in a varchar used like a set. * * Ensure that the value is an integer before passing to the method. * * Usage: * $query->findInSet((int) $parent->id, 'a.assigned_cat_ids') * * @param string $value The value to search for. * @param string $set The set of values. * * @return string A representation of the MySQL find_in_set() function for the driver. * * @since 1.5.0 */ public function findInSet($value, $set) { return "CHARINDEX(',$value,', ',' + $set + ',') > 0"; } /** * Add required aliases to columns for select statement in subquery. * * @return array[] Array of columns with added missing aliases. * * @since 2.0.0 */ protected function fixSelectAliases() { $operators = [ '+' => '', '-' => '', '*' => '', '/' => '', '%' => '', '&' => '', '|' => '', '~' => '', '^' => '', ]; // Split into array and remove comments $columns = $this->splitSqlExpression(implode(',', $this->select->getElements())); foreach ($columns as $i => $column) { $size = \count($column); if ($size == 0) { continue; } if ($size > 2 && strcasecmp($column[$size - 2], 'AS') === 0) { // Alias exists, replace it to uppercase $columns[$i][$size - 2] = 'AS'; continue; } if ($i == 0 && stripos(' DISTINCT ALL ', " $column[0] ") !== false) { // This words are reserved, they are not column names array_shift($column); $size--; } $lastWord = strtoupper($column[$size - 1]); $length = \strlen($lastWord); $lastChar = $lastWord[$length - 1]; if ($lastChar == '*') { // Skip on wildcard continue; } if ($lastChar == ')' || ($size == 1 && $lastChar == "'") || $lastWord[0] == '@' || $lastWord == 'NULL' || $lastWord == 'END' || is_numeric($lastWord)) { /* * Ends with: * - SQL function * - single static value like 'only '+'string' * - @@var * - NULL * - CASE ... END * - Numeric */ $columns[$i][] = 'AS'; $columns[$i][] = $this->quoteName('columnAlias' . $i); continue; } if ($size == 1) { continue; } $lastChar2 = substr($column[$size - 2], -1); // Check if column ends with '- a.x' or '- a. x' if (isset($operators[$lastChar2]) || ($size > 2 && $lastChar2 === '.' && isset($operators[substr($column[$size - 3], -1)]))) { // Ignore plus signs if column start with them if ($size != 2 || ltrim($column[0], '+') !== '' || $column[1][0] === "'") { // If operator exists before last word then alias is required for subquery $columns[$i][] = 'AS'; $columns[$i][] = $this->quoteName('columnAlias' . $i); continue; } } elseif ($column[$size - 1][0] !== '.' && $lastChar2 !== '.') { // If columns is like name name2 then second word is alias. // Add missing AS before the alias, exception for 'a. x' and 'a .x' array_splice($columns[$i], -1, 0, 'AS'); } } $selectColumns = []; foreach ($columns as $i => $column) { $selectColumns[$i] = implode(' ', $column); } $this->select = new QueryElement('SELECT', $selectColumns); return $columns; } /** * Add missing columns names to GROUP BY clause. * * @param array[] $selectColumns Array of columns from splitSqlExpression method. * * @return $this * * @since 2.0.0 */ protected function fixGroupColumns($selectColumns) { // Cache tables columns static $cacheCols = []; // Known columns of all included tables $knownColumnsByAlias = []; $iquotes = ['"' => '', '[' => '', "'" => '']; $nquotes = ['"', '[', ']']; // Aggregate functions $aFuncs = [ 'AVG(', 'CHECKSUM_AGG(', 'COUNT(', 'COUNT_BIG(', 'GROUPING(', 'GROUPING_ID(', 'MIN(', 'MAX(', 'SUM(', 'STDEV(', 'STDEVP(', 'VAR(', 'VARP(', ]; // Aggregated columns $filteredColumns = []; // Aliases found in SELECT statement $knownAliases = []; $wildcardTables = []; foreach ($selectColumns as $i => $column) { $size = \count($column); if ($size === 0) { continue; } if ($i == 0 && stripos(' DISTINCT ALL ', " $column[0] ") !== false) { // These words are reserved, they are not column names array_shift($selectColumns[0]); array_shift($column); $size--; } if ($size > 2 && $column[$size - 2] === 'AS') { // Save and remove AS alias $alias = $column[$size - 1]; if (isset($iquotes[$alias[0]])) { $alias = substr($alias, 1, -1); } // Remove alias $selectColumns[$i] = $column = \array_slice($column, 0, -2); if ($size === 3 || ($size === 4 && strpos('+-*/%&|~^', $column[0][0]) !== false)) { $lastWord = $column[$size - 3]; if ($lastWord[0] === "'" || $lastWord === 'NULL' || is_numeric($lastWord)) { unset($selectColumns[$i]); continue; } } // Remember pair alias => column expression $knownAliases[$alias] = implode(' ', $column); } $aggregated = false; foreach ($column as $j => $block) { if (substr($block, -2) === '.*') { // Found column ends with .* if (isset($iquotes[$block[0]])) { // Quoted table $wildcardTables[] = substr($block, 1, -3); } else { $wildcardTables[] = substr($block, 0, -2); } } elseif (str_ireplace($aFuncs, '', $block) != $block) { $aggregated = true; } if ($block[0] === "'") { // Shrink static strings which could contain column name $column[$j] = "''"; } } if (!$aggregated) { // Without aggregated columns and aliases $filteredColumns[] = implode(' ', $selectColumns[$i]); } // Without aliases and static strings $selectColumns[$i] = implode(' ', $column); } // If select statement use table.* expression if ($wildcardTables) { // Split FROM statement into list of tables $tables = $this->splitSqlExpression(implode(',', $this->from->getElements())); foreach ($tables as $i => $table) { $table = implode(' ', $table); // Exclude subquery from the FROM clause if (strpos($table, '(') === false) { // Unquote $table = str_replace($nquotes, '', $table); $table = str_replace('#__', $this->db->getPrefix(), $table); $table = explode(' ', $table); $alias = end($table); $table = $table[0]; // Chek if exists a wildcard with current alias table? if (\in_array($alias, $wildcardTables, true)) { if (!isset($cacheCols[$table])) { $cacheCols[$table] = $this->db->getTableColumns($table); } if ($this->join || $table != $alias) { foreach ($cacheCols[$table] as $name => $type) { $knownColumnsByAlias[$alias][] = $alias . '.' . $name; } } else { foreach ($cacheCols[$table] as $name => $type) { $knownColumnsByAlias[$alias][] = $name; } } } } } // Now we need to get all tables from any joins // Go through all joins and add them to the tables array if ($this->join) { foreach ($this->join as $join) { // Unquote and replace prefix $joinTbl = str_replace($nquotes, '', (string) $join); $joinTbl = str_replace('#__', $this->db->getPrefix(), $joinTbl); // Exclude subquery if (preg_match('/JOIN\s+(\w+)(?:\s+AS)?(?:\s+(\w+))?/i', $joinTbl, $matches)) { $table = $matches[1]; $alias = $matches[2] ?? $table; // Chek if exists a wildcard with current alias table? if (\in_array($alias, $wildcardTables, true)) { if (!isset($cacheCols[$table])) { $cacheCols[$table] = $this->db->getTableColumns($table); } foreach ($cacheCols[$table] as $name => $type) { $knownColumnsByAlias[$alias][] = $alias . '.' . $name; } } } } } } $selectExpression = implode(',', $selectColumns); // Split into the right columns $groupColumns = $this->splitSqlExpression(implode(',', $this->group->getElements())); // Remove column aliases from GROUP statement - SQLSRV does not support it foreach ($groupColumns as $i => $column) { $groupColumns[$i] = implode(' ', $column); $column = str_replace($nquotes, '', $groupColumns[$i]); if (isset($knownAliases[$column])) { // Be sure that this is not a valid column name if (!preg_match('/\b' . preg_quote($column, '/') . '\b/', $selectExpression)) { // Replace column alias by column expression $groupColumns[$i] = $knownAliases[$column]; } } } // Find all alias.* and fill with proper table column names foreach ($filteredColumns as $i => $column) { if (substr($column, -2) === '.*') { unset($filteredColumns[$i]); // Extract alias.* columns into GROUP BY statement $groupColumns = array_merge($groupColumns, $knownColumnsByAlias[substr($column, 0, -2)]); } } $groupColumns = array_merge($groupColumns, $filteredColumns); if ($this->order) { // Remove direction suffixes $dir = [" DESC\v", " ASC\v"]; $orderColumns = $this->splitSqlExpression(implode(',', $this->order->getElements())); foreach ($orderColumns as $i => $column) { $column = implode(' ', $column); $orderColumns[$i] = $column = trim(str_ireplace($dir, '', "$column\v"), "\v"); if (isset($knownAliases[str_replace($nquotes, '', $column)])) { unset($orderColumns[$i]); } if (str_ireplace($aFuncs, '', $column) != $column) { // Do not add aggregate expression unset($orderColumns[$i]); } } $groupColumns = array_merge($groupColumns, $orderColumns); } // Get a unique string of all column names that need to be included in the group statement $this->group = new QueryElement('GROUP BY', array_unique($groupColumns)); return $this; } /** * Split a string of sql expression into an array of individual columns. * Single line or line end comments and multi line comments are stripped off. * Always return at least one column. * * @param string $string Input string of sql expression like select expression. * * @return array[] The columns from the input string separated into an array. * * @since 2.0.0 */ protected function splitSqlExpression($string) { // Append whitespace as equivalent to the last comma $string .= ' '; $colIdx = 0; $start = 0; $open = false; $openC = 0; $comment = false; $endString = ''; $length = \strlen($string); $columns = []; $column = []; $current = ''; $previous = null; $operators = [ '+' => '', '-' => '', '*' => '', '/' => '', '%' => '', '&' => '', '|' => '', '~' => '', '^' => '', ]; $addBlock = function ($block) use (&$column, &$colIdx) { if (isset($column[$colIdx])) { $column[$colIdx] .= $block; } else { $column[$colIdx] = $block; } }; for ($i = 0; $i < $length; $i++) { $current = substr($string, $i, 1); $current2 = substr($string, $i, 2); $current3 = substr($string, $i, 3); $lenEndString = \strlen($endString); $testEnd = substr($string, $i, $lenEndString); if ($current == '[' || $current == '"' || $current == "'" || $current2 == '--' || ($current2 == '/*') || ($current == '#' && $current3 != '#__') || ($lenEndString && $testEnd == $endString)) { if ($open) { if ($testEnd === $endString) { if ($comment) { if ($lenEndString > 1) { $i += ($lenEndString - 1); } // Move cursor after close tag of comment $start = $i + 1; $comment = false; } elseif ($current == "'" || $current == ']' || $current == '"') { // Check for escaped quote like '', ]] or "" $n = 1; while ($i + $n < $length && $string[$i + $n] == $current) { $n++; } // Jump to the last quote $i += $n - 1; if ($n % 2 === 0) { // There is only escaped quote continue; } if ($n > 2) { // The last right close quote is not escaped $current = $string[$i]; } } $open = false; $endString = ''; } } else { $open = true; if ($current == '#' || $current2 == '--') { $endString = "\n"; $comment = true; } elseif ($current2 == '/*') { $endString = '*/'; $comment = true; } elseif ($current == '[') { $endString = ']'; } else { $endString = $current; } if ($comment && $start < $i) { // Add string exists before comment $addBlock(substr($string, $start, $i - $start)); $previous = $string[$i - 1]; $start = $i; } } } elseif (!$open) { if ($current == '(') { $openC++; $previous = $current; } elseif ($current == ')') { $openC--; $previous = $current; } elseif ($current == '.') { if ($i === $start && $colIdx > 0 && !isset($column[$colIdx])) { // Remove whitespace placed before dot $colIdx--; } $previous = $current; } elseif ($openC === 0) { if (ctype_space($current)) { // Normalize whitespace $string[$i] = ' '; if ($start < $i) { // Add text placed before whitespace $addBlock(substr($string, $start, $i - $start)); $colIdx++; $previous = $string[$i - 1]; } elseif (isset($column[$colIdx])) { if ($colIdx > 1 || !isset($operators[$previous])) { // There was whitespace after comment $colIdx++; } } // Move cursor forward $start = $i + 1; } elseif (isset($operators[$current]) && ($current !== '*' || $previous !== '.')) { if ($start < $i) { // Add text before operator $addBlock(substr($string, $start, $i - $start)); $colIdx++; } elseif (!isset($column[$colIdx]) && isset($operators[$previous])) { // Do not create whitespace between operators $colIdx--; } // Add operator $addBlock($current); $previous = $current; $colIdx++; // Move cursor forward $start = $i + 1; } else { $previous = $current; } } } if (($current == ',' && !$open && $openC == 0) || $i == $length - 1) { if ($start < $i && !$comment) { // Save remaining text $addBlock(substr($string, $start, $i - $start)); } $columns[] = $column; // Reset values $column = []; $colIdx = 0; $previous = null; // Column saved, move cursor forward after comma $start = $i + 1; } } return $columns; } /** * Method to modify a query already in string format with the needed additions to make the query limited to a particular number of * results, or start at a particular offset. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string * * @since 2.0.0 */ public function processLimit($query, $limit, $offset = 0) { if ($offset > 0) { // Find a position of the last comment $commentPos = strrpos($query, '/*ORDER BY (SELECT 0)*/'); // If the last comment belongs to this query, not previous subquery if ($commentPos !== false && $commentPos + 2 === strripos($query, 'ORDER BY', $commentPos + 2)) { // We can not use OFFSET without ORDER BY $query = substr_replace($query, 'ORDER BY (SELECT 0)', $commentPos, 23); } $query .= PHP_EOL . 'OFFSET ' . (int) $offset . ' ROWS'; if ($limit > 0) { $query .= PHP_EOL . 'FETCH NEXT ' . (int) $limit . ' ROWS ONLY'; } } elseif ($limit > 0) { $position = stripos($query, 'SELECT'); $distinct = stripos($query, 'SELECT DISTINCT'); if ($position === $distinct) { $query = substr_replace($query, 'SELECT DISTINCT TOP ' . (int) $limit, $position, 15); } else { $query = substr_replace($query, 'SELECT TOP ' . (int) $limit, $position, 6); } } return $query; } /** * Add a query to UNION with the current query. * * Usage: * $query->union('SELECT name FROM #__foo') * $query->union('SELECT name FROM #__foo', true) * * @param DatabaseQuery|string $query The DatabaseQuery object or string to union. * @param boolean $distinct True to only return distinct rows from the union. * * @return $this * * @since 1.0 */ public function union($query, $distinct = true) { // Set up the name with parentheses, the DISTINCT flag is redundant return $this->merge($distinct ? 'UNION SELECT * FROM ()' : 'UNION ALL SELECT * FROM ()', $query); } } PK �9�\6��: : src/DatabaseEvents.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Class defining the events dispatched by the database API * * @since 2.0.0 */ final class DatabaseEvents { /** * Private constructor to prevent instantiation of this class * * @since 2.0.0 */ private function __construct() { } /** * Database event which is dispatched after the connection to the database server is opened. * * Listeners to this event receive a `Joomla\Database\Event\ConnectionEvent` object. * * @var string * @since 2.0.0 */ public const POST_CONNECT = 'onAfterConnect'; /** * Database event which is dispatched after the connection to the database server is closed. * * Listeners to this event receive a `Joomla\Database\Event\ConnectionEvent` object. * * @var string * @since 2.0.0 */ public const POST_DISCONNECT = 'onAfterDisconnect'; } PK �9�\7*�h� � src/Sqlazure/SqlazureQuery.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Sqlazure; use Joomla\Database\Sqlsrv\SqlsrvQuery; /** * SQL Azure Query Building Class. * * @since 1.0 */ class SqlazureQuery extends SqlsrvQuery { } PK �9�\�$�M M src/Sqlazure/SqlazureDriver.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Sqlazure; use Joomla\Database\Sqlsrv\SqlsrvDriver; /** * SQL Azure Database Driver * * @link https://msdn.microsoft.com/en-us/library/ee336279.aspx * @since 1.0 */ class SqlazureDriver extends SqlsrvDriver { /** * The name of the database driver. * * @var string * @since 1.0 */ public $name = 'sqlazure'; } PK �9�\g.� � src/DatabaseImporter.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Joomla Framework Database Importer Class * * @since 1.0 */ abstract class DatabaseImporter { /** * An array of cached data. * * @var array * @since 1.0 */ protected $cache = ['columns' => [], 'keys' => []]; /** * The database connector to use for exporting structure and/or data. * * @var DatabaseInterface * @since 1.0 */ protected $db; /** * The input source. * * @var mixed * @since 1.0 */ protected $from = []; /** * The type of input format. * * @var string * @since 1.0 */ protected $asFormat = 'xml'; /** * An array of options for the exporter. * * @var \stdClass * @since 1.0 */ protected $options; /** * Constructor. * * Sets up the default options for the importer. * * @since 1.0 */ public function __construct() { $this->options = new \stdClass; // Set up the class defaults: // Import with only structure $this->withStructure(); // Export as XML. $this->asXml(); // Default destination is a string using $output = (string) $importer; } /** * Set the output option for the importer to XML format. * * @return $this * * @since 1.0 */ public function asXml() { $this->asFormat = 'xml'; return $this; } /** * Checks if all data and options are in order prior to importer. * * @return $this * * @since 1.0 * @throws \RuntimeException */ abstract public function check(); /** * Specifies the data source to import. * * @param \SimpleXMLElement|string $from The data source to import, either as a SimpleXMLElement object or XML string. * * @return $this * * @since 1.0 */ public function from($from) { $this->from = $from; return $this; } /** * Get the SQL syntax to add a column. * * @param string $table The table name. * @param \SimpleXMLElement $field The XML field definition. * * @return string * * @since 1.0 */ protected function getAddColumnSql($table, \SimpleXMLElement $field) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD COLUMN ' . $this->getColumnSQL($field); } /** * Get alters for table if there is a difference. * * @param \SimpleXMLElement $structure The XML structure of the table. * * @return array * * @since 2.0.0 */ abstract protected function getAlterTableSql(\SimpleXMLElement $structure); /** * Get the syntax to alter a column. * * @param string $table The name of the database table to alter. * @param \SimpleXMLElement $field The XML definition for the field. * * @return string * * @since 1.0 */ protected function getChangeColumnSql($table, \SimpleXMLElement $field) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' CHANGE COLUMN ' . $this->db->quoteName((string) $field['Field']) . ' ' . $this->getColumnSQL($field); } /** * Get the SQL syntax to drop a column. * * @param string $table The table name. * @param string $name The name of the field to drop. * * @return string * * @since 1.0 */ protected function getDropColumnSql($table, $name) { return 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP COLUMN ' . $this->db->quoteName($name); } /** * Get the details list of keys for a table. * * @param array $keys An array of objects that comprise the keys for the table. * * @return array The lookup array. array({key name} => array(object, ...)) * * @since 1.0 */ protected function getKeyLookup($keys) { // First pass, create a lookup of the keys. $lookup = []; foreach ($keys as $key) { if ($key instanceof \SimpleXMLElement) { $kName = (string) $key['Key_name']; } else { $kName = $key->Key_name; } if (empty($lookup[$kName])) { $lookup[$kName] = []; } $lookup[$kName][] = $key; } return $lookup; } /** * Get the real name of the table, converting the prefix wildcard string if present. * * @param string $table The name of the table. * * @return string The real name of the table. * * @since 1.0 */ protected function getRealTableName($table) { $prefix = $this->db->getPrefix(); // Replace the magic prefix if found. $table = preg_replace('|^#__|', $prefix, $table); return $table; } /** * Import the data from the source into the existing tables. * * @return void * * @note Currently only supports XML format. * @since 2.0.0 * @throws \RuntimeException on error. */ public function importData() { if ($this->from instanceof \SimpleXMLElement) { $xml = $this->from; } else { $xml = new \SimpleXMLElement($this->from); } // Get all the table definitions. $xmlTables = $xml->xpath('database/table_data'); foreach ($xmlTables as $table) { // Convert the magic prefix into the real table name. $tableName = $this->getRealTableName((string) $table['name']); $rows = $table->children(); foreach ($rows as $row) { if ($row->getName() == 'row') { $entry = new \stdClass; foreach ($row->children() as $data) { if (isset($data['value_is_null'])) { $entry->{(string) $data['name']} = null; } else { $entry->{(string) $data['name']} = (string) $data; } } $this->db->insertObject($tableName, $entry); } } } } /** * Merges the incoming structure definition with the existing structure. * * @return void * * @note Currently only supports XML format. * @since 1.0 * @throws \RuntimeException on error. */ public function mergeStructure() { $tables = $this->db->getTableList(); if ($this->from instanceof \SimpleXMLElement) { $xml = $this->from; } else { $xml = new \SimpleXMLElement($this->from); } // Get all the table definitions. $xmlTables = $xml->xpath('database/table_structure'); foreach ($xmlTables as $table) { // Convert the magic prefix into the real table name. $tableName = $this->getRealTableName((string) $table['name']); if (\in_array($tableName, $tables, true)) { // The table already exists. Now check if there is any difference. if ($queries = $this->getAlterTableSql($table)) { // Run the queries to upgrade the data structure. foreach ($queries as $query) { $this->db->setQuery((string) $query); $this->db->execute(); } } } else { // This is a new table. $sql = $this->xmlToCreate($table); $queries = explode(';', (string) $sql); foreach ($queries as $query) { if (!empty($query)) { $this->db->setQuery((string) $query); $this->db->execute(); } } } } } /** * Sets the database connector to use for exporting structure and/or data. * * @param DatabaseInterface $db The database connector. * * @return $this * * @since 1.0 */ public function setDbo(DatabaseInterface $db) { $this->db = $db; return $this; } /** * Sets an internal option to merge the structure based on the input data. * * @param boolean $setting True to import the structure, false to not. * * @return $this * * @since 1.0 */ public function withStructure($setting = true) { $this->options->withStructure = (boolean) $setting; return $this; } /** * Get the SQL syntax to add a table. * * @param \SimpleXMLElement $table The table information. * * @return string * * @since 2.0.0 * @throws \RuntimeException */ abstract protected function xmlToCreate(\SimpleXMLElement $table); } PK �9�\)���M �M src/Pdo/PdoDriver.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Pdo; use Joomla\Database\DatabaseDriver; use Joomla\Database\DatabaseEvents; use Joomla\Database\Event\ConnectionEvent; use Joomla\Database\Exception\ConnectionFailureException; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\Exception\PrepareStatementFailureException; use Joomla\Database\Exception\UnsupportedAdapterException; use Joomla\Database\StatementInterface; /** * Joomla Framework PDO Database Driver Class * * @link https://www.php.net/pdo * @since 1.0 */ abstract class PdoDriver extends DatabaseDriver { /** * The database connection resource. * * @var \PDO * @since 1.0 */ protected $connection; /** * The name of the database driver. * * @var string * @since 1.0 */ public $name = 'pdo'; /** * The character(s) used to quote SQL statement names such as table names or field names, etc. * * If a single character string the same character is used for both sides of the quoted name, else the first character will be used for the * opening quote and the second for the closing quote. * * @var string * @since 1.0 */ protected $nameQuote = "'"; /** * The null or zero representation of a timestamp for the database driver. * * @var string * @since 1.0 */ protected $nullDate = '0000-00-00 00:00:00'; /** * Constructor. * * @param array $options List of options used to configure the connection * * @since 1.0 */ public function __construct(array $options) { // Get some basic values from the options. $options['driver'] = $options['driver'] ?? 'odbc'; $options['dsn'] = $options['dsn'] ?? ''; $options['host'] = $options['host'] ?? 'localhost'; $options['database'] = $options['database'] ?? ''; $options['user'] = $options['user'] ?? ''; $options['port'] = isset($options['port']) ? (int) $options['port'] : null; $options['password'] = $options['password'] ?? ''; $options['driverOptions'] = $options['driverOptions'] ?? []; $options['ssl'] = isset($options['ssl']) ? $options['ssl'] : []; $options['socket'] = \strpos($options['host'], 'unix:') !== false ? \str_replace('unix:', '', $options['host']) : null; if ($options['ssl'] !== []) { $options['ssl']['enable'] = isset($options['ssl']['enable']) ? $options['ssl']['enable'] : false; $options['ssl']['cipher'] = isset($options['ssl']['cipher']) ? $options['ssl']['cipher'] : null; $options['ssl']['ca'] = isset($options['ssl']['ca']) ? $options['ssl']['ca'] : null; $options['ssl']['capath'] = isset($options['ssl']['capath']) ? $options['ssl']['capath'] : null; $options['ssl']['key'] = isset($options['ssl']['key']) ? $options['ssl']['key'] : null; $options['ssl']['cert'] = isset($options['ssl']['cert']) ? $options['ssl']['cert'] : null; $options['ssl']['verify_server_cert'] = isset($options['ssl']['verify_server_cert']) ? $options['ssl']['verify_server_cert'] : null; } // Finalize initialisation parent::__construct($options); } /** * Destructor. * * @since 1.0 */ public function __destruct() { $this->disconnect(); } /** * Connects to the database if needed. * * @return void Returns void if the database connected successfully. * * @since 1.0 * @throws \RuntimeException */ public function connect() { if ($this->connection) { return; } // Make sure the PDO extension for PHP is installed and enabled. if (!static::isSupported()) { throw new UnsupportedAdapterException('PDO Extension is not available.', 1); } // Find the correct PDO DSN Format to use: switch ($this->options['driver']) { case 'cubrid': $this->options['port'] = $this->options['port'] ?? 33000; $format = 'cubrid:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; $replace = ['#HOST#', '#PORT#', '#DBNAME#']; $with = [$this->options['host'], $this->options['port'], $this->options['database']]; break; case 'dblib': $this->options['port'] = $this->options['port'] ?? 1433; $format = 'dblib:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; $replace = ['#HOST#', '#PORT#', '#DBNAME#']; $with = [$this->options['host'], $this->options['port'], $this->options['database']]; break; case 'firebird': $this->options['port'] = $this->options['port'] ?? 3050; $format = 'firebird:dbname=#DBNAME#'; $replace = ['#DBNAME#']; $with = [$this->options['database']]; break; case 'ibm': $this->options['port'] = $this->options['port'] ?? 56789; if (!empty($this->options['dsn'])) { $format = 'ibm:DSN=#DSN#'; $replace = ['#DSN#']; $with = [$this->options['dsn']]; } else { $format = 'ibm:hostname=#HOST#;port=#PORT#;database=#DBNAME#'; $replace = ['#HOST#', '#PORT#', '#DBNAME#']; $with = [$this->options['host'], $this->options['port'], $this->options['database']]; } break; case 'informix': $this->options['port'] = $this->options['port'] ?? 1526; $this->options['protocol'] = $this->options['protocol'] ?? 'onsoctcp'; if (!empty($this->options['dsn'])) { $format = 'informix:DSN=#DSN#'; $replace = ['#DSN#']; $with = [$this->options['dsn']]; } else { $format = 'informix:host=#HOST#;service=#PORT#;database=#DBNAME#;server=#SERVER#;protocol=#PROTOCOL#'; $replace = ['#HOST#', '#PORT#', '#DBNAME#', '#SERVER#', '#PROTOCOL#']; $with = [ $this->options['host'], $this->options['port'], $this->options['database'], $this->options['server'], $this->options['protocol'], ]; } break; case 'mssql': $this->options['port'] = $this->options['port'] ?? 1433; $format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; $replace = ['#HOST#', '#PORT#', '#DBNAME#']; $with = [$this->options['host'], $this->options['port'], $this->options['database']]; break; case 'mysql': $this->options['port'] = $this->options['port'] ?? 3306; if ($this->options['socket'] !== null) { $format = 'mysql:unix_socket=#SOCKET#;dbname=#DBNAME#;charset=#CHARSET#'; } else { $format = 'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#'; } $replace = ['#HOST#', '#PORT#', '#SOCKET#', '#DBNAME#', '#CHARSET#']; $with = [ $this->options['host'], $this->options['port'], $this->options['socket'], $this->options['database'], $this->options['charset'] ]; break; case 'oci': $this->options['port'] = $this->options['port'] ?? 1521; $this->options['charset'] = $this->options['charset'] ?? 'AL32UTF8'; if (!empty($this->options['dsn'])) { $format = 'oci:dbname=#DSN#'; $replace = ['#DSN#']; $with = [$this->options['dsn']]; } else { $format = 'oci:dbname=//#HOST#:#PORT#/#DBNAME#'; $replace = ['#HOST#', '#PORT#', '#DBNAME#']; $with = [$this->options['host'], $this->options['port'], $this->options['database']]; } $format .= ';charset=' . $this->options['charset']; break; case 'odbc': $format = 'odbc:DSN=#DSN#;UID:#USER#;PWD=#PASSWORD#'; $replace = ['#DSN#', '#USER#', '#PASSWORD#']; $with = [$this->options['dsn'], $this->options['user'], $this->options['password']]; break; case 'pgsql': $this->options['port'] = $this->options['port'] ?? 5432; if ($this->options['socket'] !== null) { $format = 'pgsql:host=#SOCKET#;dbname=#DBNAME#'; } else { $format = 'pgsql:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; } $replace = ['#HOST#', '#PORT#', '#SOCKET#', '#DBNAME#']; $with = [$this->options['host'], $this->options['port'], $this->options['socket'], $this->options['database']]; // For data in transit TLS encryption. if ($this->options['ssl'] !== [] && $this->options['ssl']['enable'] === true) { if (isset($this->options['ssl']['verify_server_cert']) && $this->options['ssl']['verify_server_cert'] === true) { $format .= ';sslmode=verify-full'; } else { $format .= ';sslmode=require'; } $sslKeysMapping = [ 'cipher' => null, 'ca' => 'sslrootcert', 'capath' => null, 'key' => 'sslkey', 'cert' => 'sslcert', ]; // If customised, add cipher suite, ca file path, ca path, private key file path and certificate file path to PDO driver options. foreach ($sslKeysMapping as $key => $value) { if ($value !== null && $this->options['ssl'][$key] !== null) { $format .= ';' . $value . '=' . $this->options['ssl'][$key]; } } } break; case 'sqlite': if (isset($this->options['version']) && $this->options['version'] == 2) { $format = 'sqlite2:#DBNAME#'; } else { $format = 'sqlite:#DBNAME#'; } $replace = ['#DBNAME#']; $with = [$this->options['database']]; break; case 'sybase': $this->options['port'] = $this->options['port'] ?? 1433; $format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; $replace = ['#HOST#', '#PORT#', '#DBNAME#']; $with = [$this->options['host'], $this->options['port'], $this->options['database']]; break; default: throw new UnsupportedAdapterException('The ' . $this->options['driver'] . ' driver is not supported.'); } // Create the connection string: $connectionString = str_replace($replace, $with, $format); try { $this->connection = new \PDO( $connectionString, $this->options['user'], $this->options['password'], $this->options['driverOptions'] ); } catch (\PDOException $e) { throw new ConnectionFailureException('Could not connect to PDO: ' . $e->getMessage(), $e->getCode(), $e); } $this->setOption(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $this->dispatchEvent(new ConnectionEvent(DatabaseEvents::POST_CONNECT, $this)); } /** * Method to escape a string for usage in an SQL statement. * * Oracle escaping reference: * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F * * SQLite escaping notes: * http://www.sqlite.org/faq.html#q14 * * Method body is as implemented by the Zend Framework * * Note: Using query objects with bound variables is preferable to the below. * * @param string $text The string to be escaped. * @param boolean $extra Unused optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 1.0 */ public function escape($text, $extra = false) { if (\is_int($text)) { return $text; } if (\is_float($text)) { // Force the dot as a decimal point. return str_replace(',', '.', (string) $text); } $text = str_replace("'", "''", (string) $text); return addcslashes($text, "\000\n\r\\\032"); } /** * Execute the SQL statement. * * @return boolean * * @since 1.0 * @throws \Exception * @throws \RuntimeException */ public function execute() { $this->connect(); // Take a local copy so that we don't modify the original query and cause issues later $sql = $this->replacePrefix((string) $this->sql); // Increment the query counter. $this->count++; // Get list of bounded parameters $bounded =& $this->sql->getBounded(); // If there is a monitor registered, let it know we are starting this query if ($this->monitor) { $this->monitor->startQuery($sql, $bounded); } // Execute the query. $this->executed = false; // Bind the variables foreach ($bounded as $key => $obj) { $this->statement->bindParam($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions); } try { $this->executed = $this->statement->execute(); // If there is a monitor registered, let it know we have finished this query if ($this->monitor) { $this->monitor->stopQuery(); } return true; } catch (\PDOException $exception) { // If there is a monitor registered, let it know we have finished this query if ($this->monitor) { $this->monitor->stopQuery(); } // Get the error number and message before we execute any more queries. $errorNum = (int) $this->statement->errorCode(); $errorMsg = (string) implode(', ', $this->statement->errorInfo()); // Check if the server was disconnected. try { if (!$this->connected()) { try { // Attempt to reconnect. $this->connection = null; $this->connect(); } catch (ConnectionFailureException $e) { // If connect fails, ignore that exception and throw the normal exception. throw new ExecutionFailureException($sql, $errorMsg, $errorNum); } // Since we were able to reconnect, run the query again. return $this->execute(); } } catch (\LogicException $e) { throw new ExecutionFailureException($sql, $errorMsg, $errorNum, $e); } // Throw the normal query exception. throw new ExecutionFailureException($sql, $errorMsg, $errorNum); } } /** * Retrieve a PDO database connection attribute * https://www.php.net/manual/en/pdo.getattribute.php * * Usage: $db->getOption(PDO::ATTR_CASE); * * @param mixed $key One of the PDO::ATTR_* Constants * * @return mixed * * @since 1.0 */ public function getOption($key) { $this->connect(); return $this->connection->getAttribute($key); } /** * Get the version of the database connector. * * @return string The database connector version. * * @since 1.5.0 */ public function getVersion() { $this->connect(); return $this->getOption(\PDO::ATTR_SERVER_VERSION); } /** * Get a query to run and verify the database is operational. * * @return string The query to check the health of the DB. * * @since 1.0 */ public function getConnectedQuery() { return 'SELECT 1'; } /** * Sets an attribute on the PDO database handle. * https://www.php.net/manual/en/pdo.setattribute.php * * Usage: $db->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER); * * @param integer $key One of the PDO::ATTR_* Constants * @param mixed $value One of the associated PDO Constants * related to the particular attribute * key. * * @return boolean * * @since 1.0 */ public function setOption($key, $value) { $this->connect(); return $this->connection->setAttribute($key, $value); } /** * Test to see if the PDO extension is available. * Override as needed to check for specific PDO Drivers. * * @return boolean True on success, false otherwise. * * @since 1.0 */ public static function isSupported() { return \defined('\\PDO::ATTR_DRIVER_NAME'); } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. * * @since 1.0 * @throws \LogicException */ public function connected() { // Flag to prevent recursion into this function. static $checkingConnected = false; if ($checkingConnected) { // Reset this flag and throw an exception. $checkingConnected = false; throw new \LogicException('Recursion trying to check if connected.'); } // Backup the query state. $sql = $this->sql; $limit = $this->limit; $offset = $this->offset; $statement = $this->statement; try { // Set the checking connection flag. $checkingConnected = true; // Run a simple query to check the connection. $this->setQuery($this->getConnectedQuery()); $status = (bool) $this->loadResult(); } catch (\Exception $e) { // If we catch an exception here, we must not be connected. $status = false; } // Restore the query state. $this->sql = $sql; $this->limit = $limit; $this->offset = $offset; $this->statement = $statement; $checkingConnected = false; return $status; } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return string The value of the auto-increment field from the last inserted row. * * @since 1.0 */ public function insertid() { $this->connect(); // Error suppress this to prevent PDO warning us that the driver doesn't support this operation. return @$this->connection->lastInsertId(); } /** * Select a database for use. * * @param string $database The name of the database to select for use. * * @return boolean True if the database was successfully selected. * * @since 1.0 * @throws \RuntimeException */ public function select($database) { $this->connect(); return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. * * @since 1.0 */ public function setUtf() { return false; } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionCommit($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth === 1) { $this->connection->commit(); } $this->transactionDepth--; } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionRollback($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth === 1) { $this->connection->rollBack(); } $this->transactionDepth--; } /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 1.0 * @throws \RuntimeException */ public function transactionStart($asSavepoint = false) { $this->connect(); if (!$asSavepoint || !$this->transactionDepth) { $this->connection->beginTransaction(); } $this->transactionDepth++; } /** * Prepares a SQL statement for execution * * @param string $query The SQL query to be prepared. * * @return StatementInterface * * @since 2.0.0 * @throws PrepareStatementFailureException */ protected function prepareStatement(string $query): StatementInterface { try { return new PdoStatement($this->connection->prepare($query, $this->options['driverOptions'])); } catch (\PDOException $exception) { throw new PrepareStatementFailureException($exception->getMessage(), $exception->getCode(), $exception); } } /** * PDO does not support serialize * * @return array * * @since 1.0 */ public function __sleep() { $serializedProperties = []; $reflect = new \ReflectionClass($this); // Get properties of the current class $properties = $reflect->getProperties(); foreach ($properties as $property) { // Do not serialize properties that are PDO if ($property->isStatic() === false && !($this->{$property->name} instanceof \PDO)) { $serializedProperties[] = $property->name; } } return $serializedProperties; } /** * Wake up after serialization * * @return void * * @since 1.0 */ public function __wakeup() { // Get connection back $this->__construct($this->options); } } PK �9�\M��m� � src/Pdo/PdoStatement.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Pdo; use Joomla\Database\FetchMode; use Joomla\Database\FetchOrientation; use Joomla\Database\ParameterType; use Joomla\Database\StatementInterface; /** * PDO Database Statement. * * @since 2.0.0 */ class PdoStatement implements StatementInterface { /** * Mapping array for fetch modes. * * @var array * @since 2.0.0 */ private const FETCH_MODE_MAP = [ FetchMode::ASSOCIATIVE => \PDO::FETCH_ASSOC, FetchMode::NUMERIC => \PDO::FETCH_NUM, FetchMode::MIXED => \PDO::FETCH_BOTH, FetchMode::STANDARD_OBJECT => \PDO::FETCH_OBJ, FetchMode::COLUMN => \PDO::FETCH_COLUMN, FetchMode::CUSTOM_OBJECT => \PDO::FETCH_CLASS, ]; /** * Mapping array for parameter types. * * @var array * @since 2.0.0 */ private const PARAMETER_TYPE_MAP = [ ParameterType::BOOLEAN => \PDO::PARAM_BOOL, ParameterType::INTEGER => \PDO::PARAM_INT, ParameterType::LARGE_OBJECT => \PDO::PARAM_LOB, ParameterType::NULL => \PDO::PARAM_NULL, ParameterType::STRING => \PDO::PARAM_STR, ]; /** * The decorated PDOStatement object. * * @var \PDOStatement * @since 2.0.0 */ protected $pdoStatement; /** * Statement constructor * * @param \PDOStatement $pdoStatement The decorated PDOStatement object. * * @since 2.0.0 */ public function __construct(\PDOStatement $pdoStatement) { $this->pdoStatement = $pdoStatement; } /** * Binds a parameter to the specified variable name. * * @param string|integer $parameter Parameter identifier. For a prepared statement using named placeholders, this will be a parameter * name of the form `:name`. For a prepared statement using question mark placeholders, this will be * the 1-indexed position of the parameter. * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. * @param string $dataType Constant corresponding to a SQL datatype, this should be the processed type from the QueryInterface. * @param integer $length The length of the variable. Usually required for OUTPUT parameters. * @param array $driverOptions Optional driver options to be used. * * @return boolean * * @since 2.0.0 */ public function bindParam($parameter, &$variable, string $dataType = ParameterType::STRING, ?int $length = null, ?array $driverOptions = null) { $type = $this->convertParameterType($dataType); $extraParameters = array_slice(func_get_args(), 3); if (count($extraParameters) !== 0) { $extraParameters[0] = $extraParameters[0] ?? 0; } $this->pdoStatement->bindParam($parameter, $variable, $type, ...$extraParameters); return true; } /** * Closes the cursor, enabling the statement to be executed again. * * @return void * * @since 2.0.0 */ public function closeCursor(): void { $this->pdoStatement->closeCursor(); } /** * Fetches the SQLSTATE associated with the last operation on the statement handle. * * @return string * * @since 2.0.0 */ public function errorCode() { return $this->pdoStatement->errorCode(); } /** * Fetches extended error information associated with the last operation on the statement handle. * * @return array * * @since 2.0.0 */ public function errorInfo() { return $this->pdoStatement->errorInfo(); } /** * Executes a prepared statement * * @param array|null $parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. * * @return boolean * * @since 2.0.0 */ public function execute(?array $parameters = null) { return $this->pdoStatement->execute($parameters); } /** * Fetches the next row from a result set * * @param integer|null $fetchStyle Controls how the next row will be returned to the caller. This value must be one of the * FetchMode constants, defaulting to value of FetchMode::MIXED. * @param integer $cursorOrientation For a StatementInterface object representing a scrollable cursor, this value determines which row * will be returned to the caller. This value must be one of the FetchOrientation constants, * defaulting to FetchOrientation::NEXT. * @param integer $cursorOffset For a StatementInterface object representing a scrollable cursor for which the cursorOrientation * parameter is set to FetchOrientation::ABS, this value specifies the absolute number of the row in * the result set that shall be fetched. For a StatementInterface object representing a scrollable * cursor for which the cursorOrientation parameter is set to FetchOrientation::REL, this value * specifies the row to fetch relative to the cursor position before `fetch()` was called. * * @return mixed The return value of this function on success depends on the fetch type. In all cases, boolean false is returned on failure. * * @since 2.0.0 */ public function fetch(?int $fetchStyle = null, int $cursorOrientation = FetchOrientation::NEXT, int $cursorOffset = 0) { if ($fetchStyle === null) { return $this->pdoStatement->fetch(); } return $this->pdoStatement->fetch($this->convertFetchMode($fetchStyle), $cursorOrientation, $cursorOffset); } /** * Returns the number of rows affected by the last SQL statement. * * @return integer * * @since 2.0.0 */ public function rowCount(): int { return $this->pdoStatement->rowCount(); } /** * Sets the fetch mode to use while iterating this statement. * * @param integer $fetchMode The fetch mode, must be one of the FetchMode constants. * @param mixed ...$args Optional mode-specific arguments. * * @return void * * @since 2.0.0 */ public function setFetchMode(int $fetchMode, ...$args): void { $this->pdoStatement->setFetchMode($this->convertFetchMode($fetchMode), ...$args); } /** * Converts the database API's fetch mode to a PDO fetch mode * * @param integer $mode Fetch mode to convert * * @return integer * * @since 2.0.0 * @throws \InvalidArgumentException if the fetch mode is unsupported */ private function convertFetchMode(int $mode): int { if (!isset(self::FETCH_MODE_MAP[$mode])) { throw new \InvalidArgumentException(sprintf('Unsupported fetch mode `%s`', $mode)); } return self::FETCH_MODE_MAP[$mode]; } /** * Converts the database API's parameter type to a PDO parameter type * * @param string $type Parameter type to convert * * @return integer * * @since 2.0.0 * @throws \InvalidArgumentException if the parameter type is unsupported */ private function convertParameterType(string $type): int { if (!isset(self::PARAMETER_TYPE_MAP[$type])) { throw new \InvalidArgumentException(sprintf('Unsupported parameter type `%s`', $type)); } return self::PARAMETER_TYPE_MAP[$type]; } } PK �9�\I�{ { src/Pdo/PdoQuery.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Pdo; use Joomla\Database\DatabaseQuery; /** * PDO Query Building Class. * * @since 1.0 */ abstract class PdoQuery extends DatabaseQuery { /** * The list of zero or null representation of a datetime. * * @var array * @since 2.0.0 */ protected $nullDatetimeList = ['0000-00-00 00:00:00']; /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * Usage: * $query->select($query->castAsChar('a')); * $query->select($query->castAsChar('a', 40)); * * @param string $value The value to cast as a char. * @param string $len The length of the char. * * @return string Returns the cast value. * * @since 1.8.0 */ public function castAsChar($value, $len = null) { if (!$len) { return $value; } else { return 'CAST(' . $value . ' AS CHAR(' . $len . '))'; } } } PK �9�\t� src/DatabaseFactory.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; /** * Joomla Framework Database Factory class * * @since 1.0 */ class DatabaseFactory { /** * Method to return a database driver based on the given options. * * There are three global options and then the rest are specific to the database driver. The 'database' option determines which database is to * be used for the connection. The 'select' option determines whether the connector should automatically select the chosen database. * * @param string $name Name of the database driver you'd like to instantiate * @param array $options Parameters to be passed to the database driver. * * @return DatabaseInterface * * @since 1.0 * @throws Exception\UnsupportedAdapterException if there is not a compatible database driver */ public function getDriver(string $name = 'mysqli', array $options = []): DatabaseInterface { // Sanitize the database connector options. $options['driver'] = preg_replace('/[^A-Z0-9_\.-]/i', '', $name); $options['database'] = $options['database'] ?? null; $options['select'] = $options['select'] ?? true; $options['factory'] = $options['factory'] ?? $this; // Derive the class name from the driver. $class = __NAMESPACE__ . '\\' . ucfirst(strtolower($options['driver'])) . '\\' . ucfirst(strtolower($options['driver'])) . 'Driver'; // If the class still doesn't exist we have nothing left to do but throw an exception. We did our best. if (!class_exists($class)) { throw new Exception\UnsupportedAdapterException(sprintf('Unable to load Database Driver: %s', $options['driver'])); } return new $class($options); } /** * Gets an exporter class object. * * @param string $name Name of the driver you want an exporter for. * @param DatabaseInterface|null $db Optional database driver to inject into the query object. * * @return DatabaseExporter * * @since 1.0 * @throws Exception\UnsupportedAdapterException if there is not a compatible database exporter */ public function getExporter(string $name, ?DatabaseInterface $db = null): DatabaseExporter { // Derive the class name from the driver. $class = __NAMESPACE__ . '\\' . ucfirst(strtolower($name)) . '\\' . ucfirst(strtolower($name)) . 'Exporter'; // Make sure we have an exporter class for this driver. if (!class_exists($class)) { // If it doesn't exist we are at an impasse so throw an exception. throw new Exception\UnsupportedAdapterException('Database Exporter not found.'); } /** @var $o DatabaseExporter */ $o = new $class; if ($db) { $o->setDbo($db); } return $o; } /** * Gets an importer class object. * * @param string $name Name of the driver you want an importer for. * @param DatabaseInterface|null $db Optional database driver to inject into the query object. * * @return DatabaseImporter * * @since 1.0 * @throws Exception\UnsupportedAdapterException if there is not a compatible database importer */ public function getImporter(string $name, ?DatabaseInterface $db = null): DatabaseImporter { // Derive the class name from the driver. $class = __NAMESPACE__ . '\\' . ucfirst(strtolower($name)) . '\\' . ucfirst(strtolower($name)) . 'Importer'; // Make sure we have an importer class for this driver. if (!class_exists($class)) { // If it doesn't exist we are at an impasse so throw an exception. throw new Exception\UnsupportedAdapterException('Database importer not found.'); } /** @var $o DatabaseImporter */ $o = new $class; if ($db) { $o->setDbo($db); } return $o; } /** * Get a new iterator on the current query. * * @param string $name Name of the driver you want an iterator for. * @param StatementInterface $statement Statement holding the result set to be iterated. * @param string|null $column An optional column to use as the iterator key. * @param string $class The class of object that is returned. * * @return DatabaseIterator * * @since 2.0.0 */ public function getIterator( string $name, StatementInterface $statement, ?string $column = null, string $class = \stdClass::class ): DatabaseIterator { // Derive the class name from the driver. $iteratorClass = __NAMESPACE__ . '\\' . ucfirst($name) . '\\' . ucfirst($name) . 'Iterator'; // Make sure we have an iterator class for this driver. if (!class_exists($iteratorClass)) { // We can work with the base iterator class so use that $iteratorClass = DatabaseIterator::class; } // Return a new iterator return new $iteratorClass($statement, $column, $class); } /** * Get the current query object or a new Query object. * * @param string $name Name of the driver you want an query object for. * @param DatabaseInterface|null $db Optional database driver to inject into the query object. * * @return QueryInterface * * @since 1.0 * @throws Exception\UnsupportedAdapterException if there is not a compatible database query object */ public function getQuery(string $name, ?DatabaseInterface $db = null): QueryInterface { // Derive the class name from the driver. $class = __NAMESPACE__ . '\\' . ucfirst(strtolower($name)) . '\\' . ucfirst(strtolower($name)) . 'Query'; // Make sure we have a query class for this driver. if (!class_exists($class)) { // If it doesn't exist we are at an impasse so throw an exception. throw new Exception\UnsupportedAdapterException('Database Query class not found'); } return new $class($db); } } PK �9�\�{��K �K src/QueryInterface.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database; use Joomla\Database\Exception\QueryTypeAlreadyDefinedException; use Joomla\Database\Query\LimitableInterface; use Joomla\Database\Query\PreparableInterface; use Joomla\Database\Exception\UnknownTypeException; /** * Joomla Framework Query Building Interface. * * @since 2.0.0 */ interface QueryInterface extends PreparableInterface, LimitableInterface { /** * Convert the query object to a string. * * @return string * * @since 2.0.0 */ public function __toString(); /** * Add a single column, or array of columns to the CALL clause of the query. * * Usage: * $query->call('a.*')->call('b.id'); * $query->call(array('a.*', 'b.id')); * * @param array|string $columns A string or an array of field names. * * @return $this * * @since 2.0.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function call($columns); /** * Casts a value to a specified type. * * Ensure that the value is properly quoted before passing to the method. * * Usage: * $query->select($query->castAs('CHAR', 'a')); * * @param string $type The type of string to cast as. * @param string $value The value to cast as a char. * @param string $length Optionally specify the length of the field (if the type supports it otherwise * ignored). * * @return string SQL statement to cast the value as a char type. * * @since 2.0.0 * @throws UnknownTypeException When unsupported cast for a database driver */ public function castAs(string $type, string $value, ?string $length = null); /** * Gets the number of characters in a string. * * Note, use 'length' to find the number of bytes in a string. * * Usage: * $query->select($query->charLength('a')); * * @param string $field A value. * @param string|null $operator Comparison operator between charLength integer value and $condition * @param string|null $condition Integer value to compare charLength with. * * @return string SQL statement to get the length of a character. * * @since 2.0.0 */ public function charLength($field, $operator = null, $condition = null); /** * Clear data from the query or a specific clause of the query. * * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query. * * @return $this * * @since 2.0.0 */ public function clear($clause = null); /** * Adds a column, or array of column names that would be used for an INSERT INTO statement. * * @param array|string $columns A column name, or array of column names. * * @return $this * * @since 2.0.0 */ public function columns($columns); /** * Concatenates an array of column names or values. * * Usage: * $query->select($query->concatenate(array('a', 'b'))); * * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * * @return string SQL statement representing the concatenated values. * * @since 2.0.0 */ public function concatenate($values, $separator = null); /** * Gets the current date and time. * * Usage: * $query->where('published_up < '.$query->currentTimestamp()); * * @return string SQL statement to get the current timestamp. * * @since 2.0.0 */ public function currentTimestamp(); /** * Add a table name to the DELETE clause of the query. * * Usage: * $query->delete('#__a')->where('id = 1'); * * @param string $table The name of the table to delete from. * * @return $this * * @since 2.0.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function delete($table = null); /** * Add a single column, or array of columns to the EXEC clause of the query. * * Usage: * $query->exec('a.*')->exec('b.id'); * $query->exec(array('a.*', 'b.id')); * * @param array|string $columns A string or an array of field names. * * @return $this * * @since 2.0.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function exec($columns); /** * Find a value in a varchar used like a set. * * Ensure that the value is an integer before passing to the method. * * Usage: * $query->findInSet((int) $parent->id, 'a.assigned_cat_ids') * * @param string $value The value to search for. * @param string $set The set of values. * * @return string A representation of the MySQL find_in_set() function for the driver. * * @since 2.0.0 */ public function findInSet($value, $set); /** * Add a table to the FROM clause of the query. * * Usage: * $query->select('*')->from('#__a'); * $query->select('*')->from($subquery->alias('a')); * * @param string|QueryInterface $table The name of the table or a QueryInterface object (or a child of it) with alias set. * * @return $this * * @since 2.0.0 */ public function from($table); /** * Add alias for current query. * * Usage: * $query->select('*')->from('#__a')->alias('subquery'); * * @param string $alias Alias used for a JDatabaseQuery. * * @return $this * * @since 2.0.0 */ public function alias($alias); /** * Used to get a string to extract year from date column. * * Usage: * $query->select($query->year($query->quoteName('dateColumn'))); * * @param string $date Date column containing year to be extracted. * * @return string SQL statement to get the year from a date value. * * @since 2.0.0 */ public function year($date); /** * Used to get a string to extract month from date column. * * Usage: * $query->select($query->month($query->quoteName('dateColumn'))); * * @param string $date Date column containing month to be extracted. * * @return string SQL statement to get the month from a date value. * * @since 2.0.0 */ public function month($date); /** * Used to get a string to extract day from date column. * * Usage: * $query->select($query->day($query->quoteName('dateColumn'))); * * @param string $date Date column containing day to be extracted. * * @return string SQL statement to get the day from a date value. * * @since 2.0.0 */ public function day($date); /** * Used to get a string to extract hour from date column. * * Usage: * $query->select($query->hour($query->quoteName('dateColumn'))); * * @param string $date Date column containing hour to be extracted. * * @return string SQL statement to get the hour from a date/time value. * * @since 2.0.0 */ public function hour($date); /** * Used to get a string to extract minute from date column. * * Usage: * $query->select($query->minute($query->quoteName('dateColumn'))); * * @param string $date Date column containing minute to be extracted. * * @return string SQL statement to get the minute from a date/time value. * * @since 2.0.0 */ public function minute($date); /** * Used to get a string to extract seconds from date column. * * Usage: * $query->select($query->second($query->quoteName('dateColumn'))); * * @param string $date Date column containing second to be extracted. * * @return string SQL statement to get the second from a date/time value. * * @since 2.0.0 */ public function second($date); /** * Add a grouping column to the GROUP clause of the query. * * Usage: * $query->group('id'); * * @param array|string $columns A string or array of ordering columns. * * @return $this * * @since 2.0.0 */ public function group($columns); /** * Aggregate function to get input values concatenated into a string, separated by delimiter * * Usage: * $query->groupConcat('id', ','); * * @param string $expression The expression to apply concatenation to, this may be a column name or complex SQL statement. * @param string $separator The delimiter of each concatenated value * * @return string Input values concatenated into a string, separated by delimiter * * @since 2.0.0 */ public function groupConcat($expression, $separator = ','); /** * A conditions to the HAVING clause of the query. * * Usage: * $query->group('id')->having('COUNT(id) > 5'); * * @param array|string $conditions A string or array of columns. * @param string $glue The glue by which to join the conditions. Defaults to AND. * * @return $this * * @since 2.0.0 */ public function having($conditions, $glue = 'AND'); /** * Add a table name to the INSERT clause of the query. * * Usage: * $query->insert('#__a')->set('id = 1'); * $query->insert('#__a')->columns('id, title')->values('1,2')->values('3,4'); * $query->insert('#__a')->columns('id, title')->values(array('1,2', '3,4')); * * @param string $table The name of the table to insert data into. * @param boolean $incrementField The name of the field to auto increment. * * @return $this * * @since 2.0.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function insert($table, $incrementField = false); /** * Add a JOIN clause to the query. * * Usage: * $query->join('INNER', 'b', 'b.id = a.id); * * @param string $type The type of join. This string is prepended to the JOIN keyword. * @param string $table The name of table. * @param string $condition The join condition. * * @return $this * * @since 2.0.0 */ public function join($type, $table, $condition = null); /** * Get the length of a string in bytes. * * Note, use 'charLength' to find the number of characters in a string. * * Usage: * query->where($query->length('a').' > 3'); * * @param string $value The string to measure. * * @return integer * * @since 2.0.0 */ public function length($value); /** * Get the null or zero representation of a timestamp for the database driver. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the nullDate method directly. * * Usage: * $query->where('modified_date <> '.$query->nullDate()); * * @param boolean $quoted Optionally wraps the null date in database quotes (true by default). * * @return string Null or zero representation of a timestamp. * * @since 2.0.0 * @throws \RuntimeException */ public function nullDate($quoted = true); /** * Generate a SQL statement to check if column represents a zero or null datetime. * * Usage: * $query->where($query->isNullDatetime('modified_date')); * * @param string $column A column name. * * @return string * * @since 2.0.0 */ public function isNullDatetime($column); /** * Add an ordering column to the ORDER clause of the query. * * Usage: * $query->order('foo')->order('bar'); * $query->order(array('foo','bar')); * * @param array|string $columns A string or array of ordering columns. * * @return $this * * @since 2.0.0 */ public function order($columns); /** * Get the function to return a random floating-point value * * Usage: * $query->rand(); * * @return string * * @since 2.0.0 */ public function rand(); /** * Get the regular expression operator * * Usage: * $query->where('field ' . $query->regexp($search)); * * @param string $value The regex pattern. * * @return string * * @since 2.0.0 */ public function regexp($value); /** * Add a single column, or array of columns to the SELECT clause of the query. * * Usage: * $query->select('a.*')->select('b.id'); * $query->select(array('a.*', 'b.id')); * * @param array|string $columns A string or an array of field names. * * @return $this * * @since 2.0.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function select($columns); /** * Return the number of the current row. * * Usage: * $query->select('id'); * $query->selectRowNumber('ordering,publish_up DESC', 'new_ordering'); * $query->from('#__content'); * * @param string $orderBy An expression of ordering for window function. * @param string $orderColumnAlias An alias for new ordering column. * * @return $this * * @since 2.0.0 * @throws \RuntimeException */ public function selectRowNumber($orderBy, $orderColumnAlias); /** * Add a single condition string, or an array of strings to the SET clause of the query. * * Usage: * $query->set('a = 1')->set('b = 2'); * $query->set(array('a = 1', 'b = 2'); * * @param array|string $conditions A string or array of string conditions. * @param string $glue The glue by which to join the condition strings. Defaults to `,`. * Note that the glue is set on first use and cannot be changed. * * @return $this * * @since 2.0.0 */ public function set($conditions, $glue = ','); /** * Add a table name to the UPDATE clause of the query. * * Usage: * $query->update('#__foo')->set(...); * * @param string $table A table to update. * * @return $this * * @since 2.0.0 * @throws QueryTypeAlreadyDefinedException if the query type has already been defined */ public function update($table); /** * Adds a tuple, or array of tuples that would be used as values for an INSERT INTO statement. * * Usage: * $query->values('1,2,3')->values('4,5,6'); * $query->values(array('1,2,3', '4,5,6')); * * @param array|string $values A single tuple, or array of tuples. * * @return $this * * @since 2.0.0 */ public function values($values); /** * Add a single condition, or an array of conditions to the WHERE clause of the query. * * Usage: * $query->where('a = 1')->where('b = 2'); * $query->where(array('a = 1', 'b = 2')); * * @param array|string $conditions A string or array of where conditions. * @param string $glue The glue by which to join the conditions. Defaults to AND. * Note that the glue is set on first use and cannot be changed. * * @return $this * * @since 2.0.0 */ public function where($conditions, $glue = 'AND'); /** * Add a WHERE IN statement to the query. * * Note that all values must be the same data type. * * Usage * $query->whereIn('id', [1, 2, 3]); * * @param string $keyName Key name for the where clause * @param array $keyValues Array of values to be matched * @param array|string $dataType Constant corresponding to a SQL datatype. It can be an array, in this case it * has to be same length of $keyValues * * @return $this * * @since 2.0.0 */ public function whereIn(string $keyName, array $keyValues, $dataType = ParameterType::INTEGER); /** * Add a WHERE NOT IN statement to the query. * * Note that all values must be the same data type. * * Usage * $query->whereNotIn('id', [1, 2, 3]); * * @param string $keyName Key name for the where clause * @param array $keyValues Array of values to be matched * @param array|string $dataType Constant corresponding to a SQL datatype. It can be an array, in this case it * has to be same length of $keyValues * * @return $this * * @since 2.0.0 */ public function whereNotIn(string $keyName, array $keyValues, $dataType = ParameterType::INTEGER); /** * Extend the WHERE clause with a single condition or an array of conditions, with a potentially different logical operator from the one in the * current WHERE clause. * * Usage: * $query->where(array('a = 1', 'b = 2'))->extendWhere('XOR', array('c = 3', 'd = 4')); * will produce: WHERE ((a = 1 AND b = 2) XOR (c = 3 AND d = 4) * * @param string $outerGlue The glue by which to join the conditions to the current WHERE conditions. * @param mixed $conditions A string or array of WHERE conditions. * @param string $innerGlue The glue by which to join the conditions. Defaults to AND. * * @return $this * * @since 2.0.0 */ public function extendWhere($outerGlue, $conditions, $innerGlue = 'AND'); /** * Binds an array of values and returns an array of prepared parameter names. * * Note that all values must be the same data type. * * Usage: * $query->whereIn('column in (' . implode(',', $query->bindArray($keyValues, $dataType)) . ')'); * * @param array $values Values to bind * @param array|string $dataType Constant corresponding to a SQL datatype. It can be an array, in this case it * has to be same length of $key * * @return array An array with parameter names * * @since 2.0.0 */ public function bindArray(array $values, $dataType = ParameterType::INTEGER); /** * Add a query to UNION with the current query. * * Usage: * $query->union('SELECT name FROM #__foo') * $query->union('SELECT name FROM #__foo', true) * * @param DatabaseQuery|string $query The DatabaseQuery object or string to union. * @param boolean $distinct True to only return distinct rows from the union. * * @return $this * * @since 1.0 */ public function union($query, $distinct = true); /** * Add a query to UNION ALL with the current query. * * Usage: * $query->unionAll('SELECT name FROM #__foo') * * @param DatabaseQuery|string $query The DatabaseQuery object or string to union. * * @return $this * * @see union * @since 1.5.0 */ public function unionAll($query); /** * Set a single query to the query set. * On this type of DatabaseQuery you can use union(), unionAll(), order() and setLimit() * * Usage: * $query->querySet($query2->select('name')->from('#__foo')->order('id DESC')->setLimit(1)) * ->unionAll($query3->select('name')->from('#__foo')->order('id')->setLimit(1)) * ->order('name') * ->setLimit(1) * * @param DatabaseQuery|string $query The DatabaseQuery object or string. * * @return $this * * @since 2.0.0 */ public function querySet($query); /** * Create a DatabaseQuery object of type querySet from current query. * * Usage: * $query->select('name')->from('#__foo')->order('id DESC')->setLimit(1) * ->toQuerySet() * ->unionAll($query2->select('name')->from('#__foo')->order('id')->setLimit(1)) * ->order('name') * ->setLimit(1) * * @return DatabaseQuery A new object of the DatabaseQuery. * * @since 2.0.0 */ public function toQuerySet(); } PK �9�\h %� � 2 src/Exception/QueryTypeAlreadyDefinedException.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Exception; /** * Exception class defining an exception when attempting to change a query type * * @since 2.0.0 */ class QueryTypeAlreadyDefinedException extends \RuntimeException { } PK �9�\�s�N� � 2 src/Exception/PrepareStatementFailureException.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Exception; /** * Exception class defining an error preparing the SQL statement for execution * * @since 2.0.0 */ class PrepareStatementFailureException extends \RuntimeException { /** * Construct the exception * * @param string $message The Exception message to throw. [optional] * @param integer $code The Exception code. [optional] * @param Exception $previous The previous exception used for the exception chaining. [optional] * * @since 2.0.0 */ public function __construct($message = '', $code = 0, \Exception $previous = null) { // PDO uses strings for exception codes, PHP forces numeric codes, so "force" the string code to be used parent::__construct($message, 0, $previous); $this->code = $code; } } PK �9�\.m{ { + src/Exception/DatabaseNotFoundException.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2022 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Exception; /** * No database is available. * * @since 2.1.0 */ class DatabaseNotFoundException extends \RuntimeException { } PK �9�\�Q.xZ Z + src/Exception/ExecutionFailureException.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Exception; /** * Exception class defining an error executing a statement * * @since 1.5.0 */ class ExecutionFailureException extends \RuntimeException { /** * The SQL statement that was executed. * * @var string * @since 1.5.0 */ private $query; /** * Construct the exception * * @param string $query The SQL statement that was executed. * @param string $message The Exception message to throw. [optional] * @param integer $code The Exception code. [optional] * @param Exception $previous The previous exception used for the exception chaining. [optional] * * @since 1.5.0 */ public function __construct($query, $message = '', $code = 0, \Exception $previous = null) { // PDO uses strings for exception codes, PHP forces numeric codes, so "force" the string code to be used parent::__construct($message, 0, $previous); $this->code = $code; $this->query = $query; } /** * Get the SQL statement that was executed * * @return string * * @since 1.5.0 */ public function getQuery() { return $this->query; } } PK �9�\Sw�W� � , src/Exception/ConnectionFailureException.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Exception; /** * Exception class defining an error connecting to the database platform * * @since 1.5.0 */ class ConnectionFailureException extends \RuntimeException { /** * Construct the exception * * @param string $message The Exception message to throw. [optional] * @param integer $code The Exception code. [optional] * @param Exception $previous The previous exception used for the exception chaining. [optional] * * @since 2.0.0 */ public function __construct($message = '', $code = 0, \Exception $previous = null) { // PDO uses strings for exception codes, PHP forces numeric codes, so "force" the string code to be used parent::__construct($message, 0, $previous); $this->code = $code; } } PK �9�\w��� � - src/Exception/UnsupportedAdapterException.phpnu �[��� <?php /** * Part of the Joomla Framework Database Package * * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\Database\Exception; /** * Exception class defining an unsupported database object * * @since 1.5.0 */ class UnsupportedAdapterException extends \RuntimeException { } PK �9�\�uzѫ � &