From 550e914c089f5245a8aadf7cbe94bc075ed5e072 Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Mon, 22 Oct 2018 15:21:49 +1000 Subject: [PATCH] Issue #2677532 by alexpott, Mile23, kim.pepper, dawehner, andypost, Eric_A, xjm, Mixologic, larowlan, phenaproxima, markcarver, borisson_: Move drupal_check_incompatibility() functionality to a new Dependency class and Version component --- core/composer.json | 4 +- core/includes/common.inc | 6 +- core/includes/install.inc | 12 +- .../Drupal/Component/Version/Constraint.php | 135 +++++++ core/lib/Drupal/Component/Version/LICENSE.txt | 339 ++++++++++++++++++ core/lib/Drupal/Component/Version/README.txt | 12 + core/lib/Drupal/Component/Version/TESTING.txt | 18 + .../Drupal/Component/Version/composer.json | 15 + core/lib/Drupal/Core/Extension/Dependency.php | 193 ++++++++++ .../Core/Extension/ModuleExtensionList.php | 2 +- .../Drupal/Core/Extension/ModuleHandler.php | 66 +--- .../system/src/Form/ModulesListForm.php | 10 +- core/modules/system/system.install | 9 +- core/modules/system/system.module | 4 +- .../update_script_test.module | 14 + .../Functional/Update/UpdateScriptTest.php | 13 + .../Common/DrupalCheckIncompatibilityTest.php | 47 +++ .../Component/Version/ConstraintTest.php | 107 ++++++ .../Drupal/Tests/ComposerIntegrationTest.php | 1 + .../Tests/Core/Extension/DependencyTest.php | 103 ++++++ .../Extension/DeprecatedModuleHandlerTest.php | 143 ++++++++ .../Core/Extension/ModuleHandlerTest.php | 33 -- 22 files changed, 1185 insertions(+), 101 deletions(-) create mode 100644 core/lib/Drupal/Component/Version/Constraint.php create mode 100644 core/lib/Drupal/Component/Version/LICENSE.txt create mode 100644 core/lib/Drupal/Component/Version/README.txt create mode 100644 core/lib/Drupal/Component/Version/TESTING.txt create mode 100644 core/lib/Drupal/Component/Version/composer.json create mode 100644 core/lib/Drupal/Core/Extension/Dependency.php create mode 100644 core/tests/Drupal/KernelTests/Core/Common/DrupalCheckIncompatibilityTest.php create mode 100644 core/tests/Drupal/Tests/Component/Version/ConstraintTest.php create mode 100644 core/tests/Drupal/Tests/Core/Extension/DependencyTest.php create mode 100644 core/tests/Drupal/Tests/Core/Extension/DeprecatedModuleHandlerTest.php diff --git a/core/composer.json b/core/composer.json index 37120ae8ceec..abc5c3f7135d 100644 --- a/core/composer.json +++ b/core/composer.json @@ -109,6 +109,7 @@ "drupal/core-transliteration": "self.version", "drupal/core-utility": "self.version", "drupal/core-uuid": "self.version", + "drupal/core-version": "self.version", "drupal/datetime": "self.version", "drupal/datetime_range": "self.version", "drupal/dblog": "self.version", @@ -196,7 +197,8 @@ "core/lib/Drupal/Component/Serialization/composer.json", "core/lib/Drupal/Component/Transliteration/composer.json", "core/lib/Drupal/Component/Utility/composer.json", - "core/lib/Drupal/Component/Uuid/composer.json" + "core/lib/Drupal/Component/Uuid/composer.json", + "core/lib/Drupal/Component/Version/composer.json" ], "recurse": false, "replace": false, diff --git a/core/includes/common.inc b/core/includes/common.inc index bd9a90735da8..8adbf3be1b2a 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1214,9 +1214,13 @@ function debug($data, $label = NULL, $print_r = TRUE) { * NULL if compatible, otherwise the original dependency version string that * caused the incompatibility. * - * @see \Drupal\Core\Extension\ModuleHandler::parseDependency() + * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use + * \Drupal\Core\Extension\Dependency::isCompatible() instead. + * + * @see https://www.drupal.org/node/2756875 */ function drupal_check_incompatibility($v, $current_version) { + @trigger_error(__FUNCTION__ . '() is deprecated. Use \Drupal\Core\Extension\Dependency::isCompatible() instead. See https://www.drupal.org/node/2756875', E_USER_DEPRECATED); if (!empty($v['versions'])) { foreach ($v['versions'] as $required_version) { if ((isset($required_version['op']) && !version_compare($current_version, $required_version['version'], $required_version['op']))) { diff --git a/core/includes/install.inc b/core/includes/install.inc index 03dea88c331f..6ce2d81c6d00 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -5,6 +5,7 @@ * API functions for installing modules and themes. */ +use Drupal\Core\Extension\Dependency; use Drupal\Component\Utility\Unicode; use Symfony\Component\HttpFoundation\RedirectResponse; use Drupal\Component\Utility\Crypt; @@ -1120,15 +1121,14 @@ function install_profile_info($profile, $langcode = 'en') { $info = \Drupal::service('info_parser')->parse("$profile_path/$profile.info.yml"); $info += $defaults; + $dependency_name_function = function ($dependency) { + return Dependency::createFromString($dependency)->getName(); + }; // Convert dependencies in [project:module] format. - $info['dependencies'] = array_map(function ($dependency) { - return ModuleHandler::parseDependency($dependency)['name']; - }, $info['dependencies']); + $info['dependencies'] = array_map($dependency_name_function, $info['dependencies']); // Convert install key in [project:module] format. - $info['install'] = array_map(function ($dependency) { - return ModuleHandler::parseDependency($dependency)['name']; - }, $info['install']); + $info['install'] = array_map($dependency_name_function, $info['install']); // drupal_required_modules() includes the current profile as a dependency. // Remove that dependency, since a module cannot depend on itself. diff --git a/core/lib/Drupal/Component/Version/Constraint.php b/core/lib/Drupal/Component/Version/Constraint.php new file mode 100644 index 000000000000..cbf72704e0d6 --- /dev/null +++ b/core/lib/Drupal/Component/Version/Constraint.php @@ -0,0 +1,135 @@ +<?php + +namespace Drupal\Component\Version; + +/** + * A value object representing a Drupal version constraint. + */ +class Constraint { + + /** + * The constraint represented as a string. For example '>=8.x-5.x'. + * + * @var string + */ + protected $constraint; + + /** + * A list of associative arrays representing the constraint. + * + * Each containing the keys: + * - 'op': can be one of: '=', '==', '!=', '<>', '<', '<=', '>', or '>='. + * - 'version': A complete version, e.g. '4.5-beta3'. + * + * @var array[] + */ + protected $constraintArray = []; + + /** + * Constraint constructor. + * + * @param string $constraint + * The constraint string to create the object from. For example, '>8.x-1.1'. + * @param string $core_compatibility + * Core compatibility declared for the current version of Drupal core. + * Normally this is set to \Drupal::CORE_COMPATIBILITY by the caller. + */ + public function __construct($constraint, $core_compatibility) { + $this->constraint = $constraint; + $this->parseConstraint($constraint, $core_compatibility); + } + + /** + * Gets the constraint as a string. + * + * Can be used in the UI for reporting incompatibilities. + * + * @return string + * The constraint as a string. + */ + public function __toString() { + return $this->constraint; + } + + /** + * A list of associative arrays representing the constraint. + * + * Each containing the keys: + * - 'op': can be one of: '=', '==', '!=', '<>', '<', '<=', '>', or '>='. + * - 'version': A complete version, e.g. '4.5-beta3'. + * + * @return array[] + * The constraint represented as an array. + * + * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0. + * Only exists to provide a backwards compatibility layer. + * + * @see https://www.drupal.org/node/2756875 + */ + public function toArray() { + @trigger_error(sprintf('%s() only exists to provide a backwards compatibility layer. See https://www.drupal.org/node/2756875', __METHOD__), E_USER_DEPRECATED); + return $this->constraintArray; + } + + /** + * Determines if the provided version is satisfied by this constraint. + * + * @param string $version + * The version to check, for example '4.2'. + * + * @return bool + * TRUE if the provided version is satisfied by this constraint, FALSE if + * not. + */ + public function isCompatible($version) { + foreach ($this->constraintArray as $constraint) { + if (!version_compare($version, $constraint['version'], $constraint['op'])) { + return FALSE; + } + } + return TRUE; + } + + /** + * Parses a constraint string. + * + * @param string $constraint_string + * The constraint string to parse. + * @param string $core_compatibility + * Core compatibility declared for the current version of Drupal core. + * Normally this is set to \Drupal::CORE_COMPATIBILITY by the caller. + */ + private function parseConstraint($constraint_string, $core_compatibility) { + // We use named subpatterns and support every op that version_compare + // supports. Also, op is optional and defaults to equals. + $p_op = '(?<operation>!=|==|=|<|<=|>|>=|<>)?'; + // Core version is always optional: 8.x-2.x and 2.x is treated the same. + $p_core = '(?:' . preg_quote($core_compatibility) . '-)?'; + $p_major = '(?<major>\d+)'; + // By setting the minor version to x, branches can be matched. + $p_minor = '(?<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)'; + foreach (explode(',', $constraint_string) as $constraint) { + if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $constraint, $matches)) { + $op = !empty($matches['operation']) ? $matches['operation'] : '='; + if ($matches['minor'] == 'x') { + // Drupal considers "2.x" to mean any version that begins with + // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(), + // on the other hand, treats "x" as a string; so to + // version_compare(), "2.x" is considered less than 2.0. This + // means that >=2.x and <2.x are handled by version_compare() + // as we need, but > and <= are not. + if ($op == '>' || $op == '<=') { + $matches['major']++; + } + // Equivalence can be checked by adding two restrictions. + if ($op == '=' || $op == '==') { + $this->constraintArray[] = ['op' => '<', 'version' => ($matches['major'] + 1) . '.x']; + $op = '>='; + } + } + $this->constraintArray[] = ['op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']]; + } + } + } + +} diff --git a/core/lib/Drupal/Component/Version/LICENSE.txt b/core/lib/Drupal/Component/Version/LICENSE.txt new file mode 100644 index 000000000000..94fb84639c4b --- /dev/null +++ b/core/lib/Drupal/Component/Version/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/core/lib/Drupal/Component/Version/README.txt b/core/lib/Drupal/Component/Version/README.txt new file mode 100644 index 000000000000..a62aa79ed4fc --- /dev/null +++ b/core/lib/Drupal/Component/Version/README.txt @@ -0,0 +1,12 @@ +The Drupal Version Component + +Thanks for using this Drupal component. + +You can participate in its development on Drupal.org, through our issue system: +https://www.drupal.org/project/issues/drupal + +You can get the full Drupal repo here: +https://www.drupal.org/project/drupal/git-instructions + +You can browse the full Drupal repo here: +http://cgit.drupalcode.org/drupal diff --git a/core/lib/Drupal/Component/Version/TESTING.txt b/core/lib/Drupal/Component/Version/TESTING.txt new file mode 100644 index 000000000000..adc5fcd252c7 --- /dev/null +++ b/core/lib/Drupal/Component/Version/TESTING.txt @@ -0,0 +1,18 @@ +HOW-TO: Test this Drupal component + +In order to test this component, you'll need to get the entire Drupal repo and +run the tests there. + +You'll find the tests under core/tests/Drupal/Tests/Component. + +You can get the full Drupal repo here: +https://www.drupal.org/project/drupal/git-instructions + +You can find more information about running PHPUnit tests with Drupal here: +https://www.drupal.org/node/2116263 + +Each component in the Drupal\Component namespace has its own annotated test +group. You can use this group to run only the tests for this component. Like +this: + +$ ./vendor/bin/phpunit -c core --group Version diff --git a/core/lib/Drupal/Component/Version/composer.json b/core/lib/Drupal/Component/Version/composer.json new file mode 100644 index 000000000000..146cbe3aed35 --- /dev/null +++ b/core/lib/Drupal/Component/Version/composer.json @@ -0,0 +1,15 @@ +{ + "name": "drupal/core-version", + "description": "Utility classes for process Drupal specific version information.", + "keywords": ["drupal"], + "homepage": "https://www.drupal.org/project/drupal", + "license": "GPL-2.0-or-later", + "require": { + "php": ">=5.5.9" + }, + "autoload": { + "psr-4": { + "Drupal\\Component\\Version\\": "" + } + } +} diff --git a/core/lib/Drupal/Core/Extension/Dependency.php b/core/lib/Drupal/Core/Extension/Dependency.php new file mode 100644 index 000000000000..0c745b593d20 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/Dependency.php @@ -0,0 +1,193 @@ +<?php + +namespace Drupal\Core\Extension; + +use Drupal\Component\Version\Constraint; + +/** + * A value object representing dependency information. + * + * This class implements \ArrayAccess to provide a backwards compatibility layer + * for Drupal 8.x. This will be removed before Drupal 9.x. + * + * @see https://www.drupal.org/node/2756875 + */ +class Dependency implements \ArrayAccess { + + /** + * The name of the dependency. + * + * @var string + */ + protected $name; + + /** + * The project namespace for the dependency. + * + * @var string + */ + protected $project; + + /** + * The constraint string. + * + * @var \Drupal\Component\Version\Constraint + */ + protected $constraintString; + + /** + * The Constraint object from the constraint string. + * + * @var \Drupal\Component\Version\Constraint + */ + protected $constraint; + + /** + * Dependency constructor. + * + * @param string $name + * The name of the dependency. + * @param string $project + * The project namespace for the dependency. + * @param string $constraint + * The constraint string. For example, '>8.x-1.1'. + */ + public function __construct($name, $project, $constraint) { + $this->name = $name; + $this->project = $project; + $this->constraintString = $constraint; + } + + /** + * Gets the dependency's name. + * + * @return string + * The dependency's name. + */ + public function getName() { + return $this->name; + } + + /** + * Gets the dependency's project namespace. + * + * @return string + * The dependency's project namespace. + */ + public function getProject() { + return $this->project; + } + + /** + * Gets constraint string from the dependency. + * + * @return string + * The constraint string. + */ + public function getConstraintString() { + return $this->constraintString; + } + + /** + * Gets the Constraint object. + * + * @return \Drupal\Component\Version\Constraint + * The Constraint object. + */ + protected function getConstraint() { + if (!$this->constraint) { + $this->constraint = new Constraint($this->constraintString, \Drupal::CORE_COMPATIBILITY); + } + return $this->constraint; + } + + /** + * Determines if the provided version is compatible with this dependency. + * + * @param string $version + * The version to check, for example '4.2'. + * + * @return bool + * TRUE if compatible with the provided version, FALSE if not. + */ + public function isCompatible($version) { + return $this->getConstraint()->isCompatible($version); + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) { + @trigger_error(sprintf('Array access to %s properties is deprecated. Use accessor methods instead. See https://www.drupal.org/node/2756875', __CLASS__), E_USER_DEPRECATED); + return in_array($offset, ['name', 'project', 'original_version', 'versions'], TRUE); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) { + switch ($offset) { + case 'name': + @trigger_error(sprintf('Array access to the %s name property is deprecated. Use %s::getName() instead. See https://www.drupal.org/node/2756875', __CLASS__, __CLASS__), E_USER_DEPRECATED); + return $this->getName(); + + case 'project': + @trigger_error(sprintf('Array access to the %s project property is deprecated. Use %s::getProject() instead. See https://www.drupal.org/node/2756875', __CLASS__, __CLASS__), E_USER_DEPRECATED); + return $this->getProject(); + + case 'original_version': + @trigger_error(sprintf('Array access to the %s original_version property is deprecated. Use %s::getConstraintString() instead. See https://www.drupal.org/node/2756875', __CLASS__, __CLASS__), E_USER_DEPRECATED); + $constraint = $this->getConstraintString(); + if ($constraint) { + $constraint = ' (' . $constraint . ')'; + } + return $constraint; + + case 'versions': + @trigger_error(sprintf('Array access to the %s versions property is deprecated. See https://www.drupal.org/node/2756875', __CLASS__), E_USER_DEPRECATED); + return $this->getConstraint()->toArray(); + } + throw new \InvalidArgumentException("The $offset key is not supported"); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) { + throw new \BadMethodCallException(sprintf('%s() is not supported', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) { + throw new \BadMethodCallException(sprintf('%s() is not supported', __METHOD__)); + } + + /** + * Creates a new instance of this class from a dependency string. + * + * @param string $dependency + * A dependency string, which specifies a module or theme dependency, and + * optionally the project it comes from and a constraint string that + * determines the versions that are supported. Supported formats include: + * - 'module' + * - 'project:module' + * - 'project:module (>=version, <=version)'. + * + * @return static + */ + public static function createFromString($dependency) { + if (strpos($dependency, ':') !== FALSE) { + list($project, $dependency) = explode(':', $dependency); + } + else { + $project = ''; + } + $parts = explode('(', $dependency, 2); + $name = trim($parts[0]); + $version_string = isset($parts[1]) ? rtrim($parts[1], ") ") : ''; + return new static($name, $project, $version_string); + } + +} diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php index 01fcf5909529..7db535b350ed 100644 --- a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php +++ b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php @@ -214,7 +214,7 @@ protected function getInstalledExtensionNames() { protected function ensureRequiredDependencies(Extension $module, array $modules = []) { if (!empty($module->info['required'])) { foreach ($module->info['dependencies'] as $dependency) { - $dependency_name = ModuleHandler::parseDependency($dependency)['name']; + $dependency_name = Dependency::createFromString($dependency)->getName(); if (!isset($modules[$dependency_name]->info['required'])) { $modules[$dependency_name]->info['required'] = TRUE; $modules[$dependency_name]->info['explanation'] = $this->t('Dependency of required module @module', ['@module' => $module->info['name']]); diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 8d43a857dd4c..07b3b9107987 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -225,8 +225,8 @@ public function buildModuleDependencies(array $modules) { $graph[$module->getName()]['edges'] = []; if (isset($module->info['dependencies']) && is_array($module->info['dependencies'])) { foreach ($module->info['dependencies'] as $dependency) { - $dependency_data = static::parseDependency($dependency); - $graph[$module->getName()]['edges'][$dependency_data['name']] = $dependency_data; + $dependency_data = Dependency::createFromString($dependency); + $graph[$module->getName()]['edges'][$dependency_data->getName()] = $dependency_data; } } } @@ -697,15 +697,15 @@ protected function verifyImplementations(&$implementations, $hook) { /** * Parses a dependency for comparison by drupal_check_incompatibility(). * - * @param $dependency + * @param string $dependency * A dependency string, which specifies a module dependency, and optionally * the project it comes from and versions that are supported. Supported * formats include: * - 'module' * - 'project:module' - * - 'project:module (>=version, version)' + * - 'project:module (>=version, version)'. * - * @return + * @return array * An associative array with three keys: * - 'name' includes the name of the thing to depend on (e.g. 'foo'). * - 'original_version' contains the original version string (which can be @@ -715,51 +715,21 @@ protected function verifyImplementations(&$implementations, $hook) { * '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'. * Callers should pass this structure to drupal_check_incompatibility(). * - * @see drupal_check_incompatibility() + * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\Extension\Dependency::createFromString() instead. + * + * @see https://www.drupal.org/node/2756875 */ public static function parseDependency($dependency) { - $value = []; - // Split out the optional project name. - if (strpos($dependency, ':') !== FALSE) { - list($project_name, $dependency) = explode(':', $dependency); - $value['project'] = $project_name; - } - // We use named subpatterns and support every op that version_compare - // supports. Also, op is optional and defaults to equals. - $p_op = '(?<operation>!=|==|=|<|<=|>|>=|<>)?'; - // Core version is always optional: 8.x-2.x and 2.x is treated the same. - $p_core = '(?:' . preg_quote(\Drupal::CORE_COMPATIBILITY) . '-)?'; - $p_major = '(?<major>\d+)'; - // By setting the minor version to x, branches can be matched. - $p_minor = '(?<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)'; - $parts = explode('(', $dependency, 2); - $value['name'] = trim($parts[0]); - if (isset($parts[1])) { - $value['original_version'] = ' (' . $parts[1]; - foreach (explode(',', $parts[1]) as $version) { - if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) { - $op = !empty($matches['operation']) ? $matches['operation'] : '='; - if ($matches['minor'] == 'x') { - // Drupal considers "2.x" to mean any version that begins with - // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(), - // on the other hand, treats "x" as a string; so to - // version_compare(), "2.x" is considered less than 2.0. This - // means that >=2.x and <2.x are handled by version_compare() - // as we need, but > and <= are not. - if ($op == '>' || $op == '<=') { - $matches['major']++; - } - // Equivalence can be checked by adding two restrictions. - if ($op == '=' || $op == '==') { - $value['versions'][] = ['op' => '<', 'version' => ($matches['major'] + 1) . '.x']; - $op = '>='; - } - } - $value['versions'][] = ['op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']]; - } - } - } - return $value; + @trigger_error(__METHOD__ . ' is deprecated. Use \Drupal\Core\Extension\Dependency::createFromString() instead. See https://www.drupal.org/node/2756875', E_USER_DEPRECATED); + $dependency = Dependency::createFromString($dependency); + $result = [ + 'name' => $dependency->getName(), + 'project' => $dependency->getProject(), + 'original_version' => $dependency['original_version'], + 'versions' => $dependency['versions'], + ]; + return array_filter($result); } /** diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index ef0b240ecffc..c2ade11b7a6d 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -307,7 +307,8 @@ protected function buildRow(array $modules, Extension $module, $distribution) { } // If this module requires other modules, add them to the array. - foreach ($module->requires as $dependency => $version) { + /** @var \Drupal\Core\Extension\Dependency $dependency_object */ + foreach ($module->requires as $dependency => $dependency_object) { if (!isset($modules[$dependency])) { $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">missing</span>)', ['@module' => Unicode::ucfirst($dependency)]); $row['enable']['#disabled'] = TRUE; @@ -317,9 +318,10 @@ protected function buildRow(array $modules, Extension $module, $distribution) { $name = $modules[$dependency]->info['name']; // Disable the module's checkbox if it is incompatible with the // dependency's version. - if ($incompatible_version = drupal_check_incompatibility($version, str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $modules[$dependency]->info['version']))) { - $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">incompatible with</span> version @version)', [ - '@module' => $name . $incompatible_version, + if (!$dependency_object->isCompatible(str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $modules[$dependency]->info['version']))) { + $row['#requires'][$dependency] = $this->t('@module (@constraint) (<span class="admin-missing">incompatible with</span> version @version)', [ + '@module' => $name, + '@constraint' => $dependency_object->getConstraintString(), '@version' => $modules[$dependency]->info['version'], ]); $row['enable']['#disabled'] = TRUE; diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 6eb8c1200569..2cddcbfff963 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -803,8 +803,9 @@ function system_requirements($phase) { $requirements['php']['severity'] = REQUIREMENT_ERROR; } // Check the module's required modules. + /** @var \Drupal\Core\Extension\Dependency $requirement */ foreach ($file->requires as $requirement) { - $required_module = $requirement['name']; + $required_module = $requirement->getName(); // Check if the module exists. if (!isset($files[$required_module])) { $requirements["$module-$required_module"] = [ @@ -819,13 +820,11 @@ function system_requirements($phase) { $required_file = $files[$required_module]; $required_name = $required_file->info['name']; $version = str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $required_file->info['version']); - $compatibility = drupal_check_incompatibility($requirement, $version); - if ($compatibility) { - $compatibility = rtrim(substr($compatibility, 2), ')'); + if (!$requirement->isCompatible($version)) { $requirements["$module-$required_module"] = [ 'title' => t('Unresolved dependency'), 'description' => t('@name requires this module and version. Currently using @required_name version @version', ['@name' => $name, '@required_name' => $required_name, '@version' => $version]), - 'value' => t('@required_name (Version @compatibility required)', ['@required_name' => $required_name, '@compatibility' => $compatibility]), + 'value' => t('@required_name (Version @compatibility required)', ['@required_name' => $required_name, '@compatibility' => $requirement->getConstraintString()]), 'severity' => REQUIREMENT_ERROR, ]; continue; diff --git a/core/modules/system/system.module b/core/modules/system/system.module index e82e1ffd4824..ac27414f8567 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -5,6 +5,7 @@ * Configuration system that lets administrators modify the workings of the site. */ +use Drupal\Core\Extension\Dependency; use Drupal\Component\Render\PlainTextOutput; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Asset\AttachedAssetsInterface; @@ -21,7 +22,6 @@ use Drupal\Core\Routing\StackedRouteMatchInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Menu\MenuTreeParameters; -use Drupal\Core\Extension\ModuleHandler; use Drupal\Core\Url; use Drupal\Core\Block\BlockPluginInterface; use Drupal\user\UserInterface; @@ -1014,7 +1014,7 @@ function _system_rebuild_module_data_ensure_required($module, &$modules) { @trigger_error("_system_rebuild_module_data_ensure_required() is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. This function is no longer used in Drupal core. See https://www.drupal.org/node/2709919", E_USER_DEPRECATED); if (!empty($module->info['required'])) { foreach ($module->info['dependencies'] as $dependency) { - $dependency_name = ModuleHandler::parseDependency($dependency)['name']; + $dependency_name = Dependency::createFromString($dependency)->getName(); if (!isset($modules[$dependency_name]->info['required'])) { $modules[$dependency_name]->info['required'] = TRUE; $modules[$dependency_name]->info['explanation'] = t('Dependency of required module @module', ['@module' => $module->info['name']]); diff --git a/core/modules/system/tests/modules/update_script_test/update_script_test.module b/core/modules/system/tests/modules/update_script_test/update_script_test.module index 096f91251596..26472239e487 100644 --- a/core/modules/system/tests/modules/update_script_test/update_script_test.module +++ b/core/modules/system/tests/modules/update_script_test/update_script_test.module @@ -5,6 +5,8 @@ * This file provides testing functionality for update.php. */ +use Drupal\Core\Extension\Extension; + /** * Implements hook_cache_flush(). * @@ -16,3 +18,15 @@ function update_script_test_cache_flush() { \Drupal::messenger()->addStatus(t('hook_cache_flush() invoked for update_script_test.module.')); } + +/** + * Implements hook_system_info_alter(). + */ +function update_script_test_system_info_alter(array &$info, Extension $file, $type) { + $new_info = \Drupal::state()->get('update_script_test.system_info_alter'); + if ($new_info) { + if ($file->getName() == 'update_script_test') { + $info = $new_info + $info; + } + } +} diff --git a/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php b/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php index 29d4d08e238b..8f538795ef7d 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php @@ -142,6 +142,19 @@ public function testRequirements() { $this->assertText('This is a requirements error provided by the update_script_test module.'); $this->clickLink('try again'); $this->assertText('This is a requirements error provided by the update_script_test module.'); + + // Ensure that changes to a module's requirements that would cause errors + // are displayed correctly. + $update_script_test_config->set('requirement_type', REQUIREMENT_OK)->save(); + \Drupal::state()->set('update_script_test.system_info_alter', ['dependencies' => ['a_module_that_does_not_exist']]); + $this->drupalGet($this->updateUrl, ['external' => TRUE]); + $this->assertSession()->responseContains('a_module_that_does_not_exist (Missing)'); + $this->assertSession()->responseContains('Update script test requires this module.'); + + \Drupal::state()->set('update_script_test.system_info_alter', ['dependencies' => ['node (<7.x-0.0-dev)']]); + $this->drupalGet($this->updateUrl, ['external' => TRUE]); + $this->assertSession()->assertEscaped('Node (Version <7.x-0.0-dev required)'); + $this->assertSession()->responseContains('Update script test requires this module and version. Currently using Node version ' . \Drupal::VERSION); } /** diff --git a/core/tests/Drupal/KernelTests/Core/Common/DrupalCheckIncompatibilityTest.php b/core/tests/Drupal/KernelTests/Core/Common/DrupalCheckIncompatibilityTest.php new file mode 100644 index 000000000000..bc53e907c134 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Common/DrupalCheckIncompatibilityTest.php @@ -0,0 +1,47 @@ +<?php + +namespace Drupal\KernelTests\Core\Common; + +use Drupal\Core\Extension\Dependency; +use Drupal\KernelTests\KernelTestBase; + +/** + * Parse a predefined amount of bytes and compare the output with the expected + * value. + * + * @group Common + * @group legacy + */ +class DrupalCheckIncompatibilityTest extends KernelTestBase { + + /** + * Tests drupal_check_incompatibility(). + * + * @dataProvider providerDrupalCheckIncompatibility + * @expectedDeprecation drupal_check_incompatibility() is deprecated. Use \Drupal\Core\Extension\Dependency::isCompatible() instead. See https://www.drupal.org/node/2756875 + */ + public function testDrupalCheckIncompatibility($version_info, $version_to_check, $result) { + $this->assertSame($result, drupal_check_incompatibility($version_info, $version_to_check)); + } + + /** + * Data provider for testDrupalCheckIncompatibility. + */ + public function providerDrupalCheckIncompatibility() { + $module_data = [ + 'name' => 'views_ui', + 'original_version' => ' (8.x-1.0)', + 'versions' => [['op' => '=', 'version' => '1.0']], + ]; + + $data = []; + $data['is compatible'] = [$module_data, '1.0', NULL]; + $data['not compatible'] = [$module_data, '1.1', ' (8.x-1.0)']; + // Prove that the BC layer using ArrayAccess works with + // drupal_check_incompatibility(). + $dependency = new Dependency('views', 'drupal', '8.x-1.2'); + $data['dependency object'] = [$dependency, '1.1', ' (8.x-1.2)']; + return $data; + } + +} diff --git a/core/tests/Drupal/Tests/Component/Version/ConstraintTest.php b/core/tests/Drupal/Tests/Component/Version/ConstraintTest.php new file mode 100644 index 000000000000..f80ad800757b --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Version/ConstraintTest.php @@ -0,0 +1,107 @@ +<?php + +namespace Drupal\Tests\Component\Version; + +use Drupal\Component\Version\Constraint; +use PHPUnit\Framework\TestCase; + +/** + * @coversDefaultClass \Drupal\Component\Version\Constraint + * @group Version + */ +class ConstraintTest extends TestCase { + + /** + * @covers ::isCompatible + * @dataProvider providerIsCompatible + */ + public function testIsCompatible(Constraint $version_info, $current_version, $result) { + $this->assertSame($result, $version_info->isCompatible($current_version)); + } + + /** + * Provider for testIsCompatible. + */ + public function providerIsCompatible() { + $tests = []; + + $tests['no-dependencies'] = [new Constraint('', '8.x'), '8.1.x', TRUE]; + + // Stable version. + $stable = new Constraint('8.x-1.0', '8.x'); + $tests['(=8.x-1.0)-1.0'] = [$stable, '1.0', TRUE]; + $tests['(=8.x-1.0)-1.1'] = [$stable, '1.1', FALSE]; + $tests['(=8.x-1.0)-0.9'] = [$stable, '0.9', FALSE]; + + // Alpha version. + $alpha = new Constraint('8.x-1.1-alpha12', '8.x'); + $tests['(8.x-1.1-alpha12)-alpha12'] = [$alpha, '1.1-alpha12', TRUE]; + $tests['(8.x-1.1-alpha12)-alpha10'] = [$alpha, '1.1-alpha10', FALSE]; + $tests['(8.x-1.1-alpha12)-beta1'] = [$alpha, '1.1-beta1', FALSE]; + + // Beta version. + $beta = new Constraint('8.x-1.1-beta8', '8.x'); + $tests['(8.x-1.1-beta8)-beta8'] = [$beta, '1.1-beta8', TRUE]; + $tests['(8.x-1.1-beta8)-beta4'] = [$beta, '1.1-beta4', FALSE]; + + // RC version. + $rc = new Constraint('8.x-1.1-rc11', '8.x'); + $tests['(8.x-1.1-rc11)-rc11'] = [$rc, '1.1-rc11', TRUE]; + $tests['(8.x-1.1-rc11)-rc2'] = [$rc, '1.1-rc2', FALSE]; + + // Test greater than. + $greater = new Constraint('>8.x-1.x', '8.x'); + $tests['(>8.x-1.x)-2.0'] = [$greater, '2.0', TRUE]; + $tests['(>8.x-1.x)-1.1'] = [$greater, '1.1', FALSE]; + $tests['(>8.x-1.x)-0.9'] = [$greater, '0.9', FALSE]; + + // Test greater than or equal. + $greater_or_equal = new Constraint('>=8.x-1.0', '8.x'); + $tests['(>=8.x-1.0)-1.1'] = [$greater_or_equal, '1.1', TRUE]; + $tests['(>=8.x-1.0)-1.0'] = [$greater_or_equal, '1.0', TRUE]; + $tests['(>=8.x-1.1)-1.0'] = [new Constraint('>=8.x-1.1', '8.x'), '1.0', FALSE]; + + // Test less than. + $less = new Constraint('<8.x-1.1', '8.x'); + $tests['(<8.x-1.1)-1.1'] = [$less, '1.1', FALSE]; + $tests['(<8.x-1.1)-1.1'] = [$less, '1.0', TRUE]; + $tests['(<8.x-1.0)-1.0'] = [new Constraint('<8.x-1.0', '8.x'), '1.1', FALSE]; + + // Test less than or equal. + $less_or_equal = new Constraint('<= 8.x-1.x', '8.x'); + $tests['(<= 8.x-1.x)-2.0'] = [$less_or_equal, '2.0', FALSE]; + $tests['(<= 8.x-1.x)-1.9'] = [$less_or_equal, '1.9', TRUE]; + $tests['(<= 8.x-1.x)-1.1'] = [$less_or_equal, '1.1', TRUE]; + $tests['(<= 8.x-1.x)-0.9'] = [$less_or_equal, '0.9', TRUE]; + + // Test greater than and less than. + $less_and_greater = new Constraint('<8.x-4.x,>8.x-1.x', '8.x'); + $tests['(<8.x-4.x,>8.x-1.x)-4.0'] = [$less_and_greater, '4.0', FALSE]; + $tests['(<8.x-4.x,>8.x-1.x)-3.9'] = [$less_and_greater, '3.9', TRUE]; + $tests['(<8.x-4.x,>8.x-1.x)-2.1'] = [$less_and_greater, '2.1', TRUE]; + $tests['(<8.x-4.x,>8.x-1.x)-1.9'] = [$less_and_greater, '1.9', FALSE]; + + // Test a nonsensical greater than and less than - no compatible versions. + $less_and_greater = new Constraint('>8.x-4.x,<8.x-1.x', '8.x'); + $tests['(<8.x-4.x,>8.x-1.x)-4.0'] = [$less_and_greater, '4.0', FALSE]; + $tests['(<8.x-4.x,>8.x-1.x)-3.9'] = [$less_and_greater, '3.9', FALSE]; + $tests['(<8.x-4.x,>8.x-1.x)-2.1'] = [$less_and_greater, '2.1', FALSE]; + $tests['(<8.x-4.x,>8.x-1.x)-1.9'] = [$less_and_greater, '1.9', FALSE]; + + return $tests; + } + + /** + * @covers ::toArray + * @group legacy + * @expectedDeprecation Drupal\Component\Version\Constraint::toArray() only exists to provide a backwards compatibility layer. See https://www.drupal.org/node/2756875 + */ + public function testToArray() { + $constraint = new Constraint('<8.x-4.x,>8.x-1.x', '8.x'); + $this->assertSame([ + ['op' => '<', 'version' => '4.x'], + ['op' => '>', 'version' => '2.x'], + ], $constraint->toArray()); + } + +} diff --git a/core/tests/Drupal/Tests/ComposerIntegrationTest.php b/core/tests/Drupal/Tests/ComposerIntegrationTest.php index 8ee025a809c8..2d34dcb74c8e 100644 --- a/core/tests/Drupal/Tests/ComposerIntegrationTest.php +++ b/core/tests/Drupal/Tests/ComposerIntegrationTest.php @@ -77,6 +77,7 @@ protected function getPaths() { $this->root . '/core/lib/Drupal/Component/Transliteration', $this->root . '/core/lib/Drupal/Component/Utility', $this->root . '/core/lib/Drupal/Component/Uuid', + $this->root . '/core/lib/Drupal/Component/Version', ]; } diff --git a/core/tests/Drupal/Tests/Core/Extension/DependencyTest.php b/core/tests/Drupal/Tests/Core/Extension/DependencyTest.php new file mode 100644 index 000000000000..374a16afc6a9 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Extension/DependencyTest.php @@ -0,0 +1,103 @@ +<?php + +namespace Drupal\Tests\Core\Extension; + +use Drupal\Core\Extension\Dependency; +use Drupal\Tests\UnitTestCase; + +/** + * @coversDefaultClass \Drupal\Core\Extension\Dependency + * @group Extension + */ +class DependencyTest extends UnitTestCase { + + /** + * @covers ::createFromString + * @dataProvider providerCreateFromString + */ + public function testCreateFromString($string, $expected_name, $expected_project, $expected_constraint) { + $dependency = Dependency::createFromString($string); + $this->assertSame($expected_name, $dependency->getName()); + $this->assertSame($expected_project, $dependency->getProject()); + $this->assertSame($expected_constraint, $dependency->getConstraintString()); + } + + /** + * Data provider for testCreateFromString. + */ + public function providerCreateFromString() { + $tests = []; + $tests['module_name_only'] = ['views', 'views', '', '']; + $tests['module_and_project_names'] = ['drupal:views', 'views', 'drupal', '']; + $tests['module_and_constraint'] = ['views (<8.x-3.1)', 'views', '', '<8.x-3.1']; + $tests['module_and_project_names_and_constraint'] = ['drupal:views (>8.x-1.1)', 'views', 'drupal', '>8.x-1.1']; + return $tests; + } + + /** + * @covers ::isCompatible + */ + public function testIsCompatible() { + $dependency = new Dependency('paragraphs_demo', 'paragraphs', '>8.x-1.1'); + $this->assertFalse($dependency->isCompatible('1.1')); + $this->assertTrue($dependency->isCompatible('1.2')); + } + + /** + * @covers ::offsetExists + * @group legacy + * @expectedDeprecation Array access to Drupal\Core\Extension\Dependency properties is deprecated. Use accessor methods instead. See https://www.drupal.org/node/2756875 + */ + public function testOffsetTest() { + $dependency = new Dependency('views', 'drupal', '>8.x-1.1'); + $this->assertTrue(isset($dependency['name'])); + $this->assertFalse(isset($dependency['foo'])); + } + + /** + * @covers ::offsetGet + * @group legacy + * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency name property is deprecated. Use Drupal\Core\Extension\Dependency::getName() instead. See https://www.drupal.org/node/2756875 + * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency project property is deprecated. Use Drupal\Core\Extension\Dependency::getProject() instead. See https://www.drupal.org/node/2756875 + * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency original_version property is deprecated. Use Drupal\Core\Extension\Dependency::getConstraintString() instead. See https://www.drupal.org/node/2756875 + * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency versions property is deprecated. See https://www.drupal.org/node/2756875 + */ + public function testOffsetGet() { + $dependency = new Dependency('views', 'drupal', '>8.x-1.1'); + $this->assertSame('views', $dependency['name']); + $this->assertSame('drupal', $dependency['project']); + $this->assertSame(' (>8.x-1.1)', $dependency['original_version']); + $this->assertSame([['op' => '>', 'version' => '1.1']], $dependency['versions']); + } + + /** + * @covers ::offsetGet + * @group legacy + */ + public function testOffsetGetException() { + $dependency = new Dependency('views', 'drupal', '>8.x-1.1'); + $this->setExpectedException(\InvalidArgumentException::class, 'The does_not_exist key is not supported'); + $dependency['does_not_exist']; + } + + /** + * @covers ::offsetUnset + * @group legacy + */ + public function testOffsetUnset() { + $dependency = new Dependency('views', 'drupal', '>8.x-1.1'); + $this->setExpectedException(\BadMethodCallException::class, 'Drupal\Core\Extension\Dependency::offsetUnset() is not supported'); + unset($dependency['name']); + } + + /** + * @covers ::offsetSet + * @group legacy + */ + public function testOffsetSet() { + $dependency = new Dependency('views', 'drupal', '>8.x-1.1'); + $this->setExpectedException(\BadMethodCallException::class, 'Drupal\Core\Extension\Dependency::offsetSet() is not supported'); + $dependency['name'] = 'foo'; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Extension/DeprecatedModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/DeprecatedModuleHandlerTest.php new file mode 100644 index 000000000000..480ec5f0eead --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Extension/DeprecatedModuleHandlerTest.php @@ -0,0 +1,143 @@ +<?php + +namespace Drupal\Tests\Core\Extension; + +use Drupal\Core\Extension\ModuleHandler; +use Drupal\Tests\UnitTestCase; + +/** + * @coversDefaultClass \Drupal\Core\Extension\ModuleHandler + * @runTestsInSeparateProcesses + * + * @group Extension + * @group legacy + */ +class DeprecatedModuleHandlerTest extends UnitTestCase { + + /** + * @dataProvider dependencyProvider + * @covers ::parseDependency + * @expectedDeprecation Drupal\Core\Extension\ModuleHandler::parseDependency is deprecated. Use \Drupal\Core\Extension\Dependency::createFromString() instead. See https://www.drupal.org/node/2756875 + * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency original_version property is deprecated. Use Drupal\Core\Extension\Dependency::getConstraintString() instead. See https://www.drupal.org/node/2756875 + * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency versions property is deprecated. See https://www.drupal.org/node/2756875 + * @expectedDeprecation Drupal\Component\Version\Constraint::toArray() only exists to provide a backwards compatibility layer. See https://www.drupal.org/node/2756875 + */ + public function testDependencyParsing($dependency, $expected) { + $version = ModuleHandler::parseDependency($dependency); + $this->assertEquals($expected, $version); + } + + /** + * Provider for testing dependency parsing. + */ + public function dependencyProvider() { + return [ + ['system', ['name' => 'system']], + ['taxonomy', ['name' => 'taxonomy']], + ['views', ['name' => 'views']], + [ + 'views_ui(8.x-1.0)', + [ + 'name' => 'views_ui', + 'original_version' => ' (8.x-1.0)', + 'versions' => [['op' => '=', 'version' => '1.0']], + ], + ], + /* @todo Not supported? Fix this in + https://www.drupal.org/project/drupal/issues/3001344. + [ + 'views_ui(8.x-1.1-beta)', + [ + 'name' => 'views_ui', + 'original_version' => ' (8.x-1.1-beta)', + 'versions' => [['op' => '=', 'version' => '1.1-beta']], + ], + ],*/ + [ + 'views_ui(8.x-1.1-alpha12)', + [ + 'name' => 'views_ui', + 'original_version' => ' (8.x-1.1-alpha12)', + 'versions' => [['op' => '=', 'version' => '1.1-alpha12']], + ], + ], + [ + 'views_ui(8.x-1.1-beta8)', + [ + 'name' => 'views_ui', + 'original_version' => ' (8.x-1.1-beta8)', + 'versions' => [['op' => '=', 'version' => '1.1-beta8']], + ], + ], + [ + 'views_ui(8.x-1.1-rc11)', + [ + 'name' => 'views_ui', + 'original_version' => ' (8.x-1.1-rc11)', + 'versions' => [['op' => '=', 'version' => '1.1-rc11']], + ], + ], + [ + 'views_ui(8.x-1.12)', + [ + 'name' => 'views_ui', + 'original_version' => ' (8.x-1.12)', + 'versions' => [['op' => '=', 'version' => '1.12']], + ], + ], + [ + 'views_ui(8.x-1.x)', + [ + 'name' => 'views_ui', + 'original_version' => ' (8.x-1.x)', + 'versions' => [ + ['op' => '<', 'version' => '2.x'], + ['op' => '>=', 'version' => '1.x'], + ], + ], + ], + [ + 'views_ui( <= 8.x-1.x)', + [ + 'name' => 'views_ui', + 'original_version' => ' ( <= 8.x-1.x)', + 'versions' => [['op' => '<=', 'version' => '2.x']], + ], + ], + [ + 'views_ui(<= 8.x-1.x)', + [ + 'name' => 'views_ui', + 'original_version' => ' (<= 8.x-1.x)', + 'versions' => [['op' => '<=', 'version' => '2.x']], + ], + ], + [ + 'views_ui( <=8.x-1.x)', + [ + 'name' => 'views_ui', + 'original_version' => ' ( <=8.x-1.x)', + 'versions' => [['op' => '<=', 'version' => '2.x']], + ], + ], + [ + 'views_ui(>8.x-1.x)', + [ + 'name' => 'views_ui', + 'original_version' => ' (>8.x-1.x)', + 'versions' => [['op' => '>', 'version' => '2.x']], + ], + ], + [ + 'drupal:views_ui(>8.x-1.x)', + [ + 'project' => 'drupal', + 'name' => 'views_ui', + 'original_version' => ' (>8.x-1.x)', + 'versions' => [['op' => '>', 'version' => '2.x']], + ], + ], + ]; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php index 3adaf13507a3..9ab05d8cc2ed 100644 --- a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php @@ -496,39 +496,6 @@ public function testResetImplementations() { $module_handler->getImplementations('hook'); } - /** - * @dataProvider dependencyProvider - * @covers ::parseDependency - */ - public function testDependencyParsing($dependency, $expected) { - $version = ModuleHandler::parseDependency($dependency); - $this->assertEquals($expected, $version); - } - - /** - * Provider for testing dependency parsing. - */ - public function dependencyProvider() { - return [ - ['system', ['name' => 'system']], - ['taxonomy', ['name' => 'taxonomy']], - ['views', ['name' => 'views']], - ['views_ui(8.x-1.0)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.0)', 'versions' => [['op' => '=', 'version' => '1.0']]]], - // Not supported?. - // array('views_ui(8.x-1.1-beta)', array('name' => 'views_ui', 'original_version' => ' (8.x-1.1-beta)', 'versions' => array(array('op' => '=', 'version' => '1.1-beta')))), - ['views_ui(8.x-1.1-alpha12)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-alpha12)', 'versions' => [['op' => '=', 'version' => '1.1-alpha12']]]], - ['views_ui(8.x-1.1-beta8)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-beta8)', 'versions' => [['op' => '=', 'version' => '1.1-beta8']]]], - ['views_ui(8.x-1.1-rc11)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-rc11)', 'versions' => [['op' => '=', 'version' => '1.1-rc11']]]], - ['views_ui(8.x-1.12)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.12)', 'versions' => [['op' => '=', 'version' => '1.12']]]], - ['views_ui(8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.x)', 'versions' => [['op' => '<', 'version' => '2.x'], ['op' => '>=', 'version' => '1.x']]]], - ['views_ui( <= 8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' ( <= 8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]], - ['views_ui(<= 8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (<= 8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]], - ['views_ui( <=8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' ( <=8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]], - ['views_ui(>8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (>8.x-1.x)', 'versions' => [['op' => '>', 'version' => '2.x']]]], - ['drupal:views_ui(>8.x-1.x)', ['project' => 'drupal', 'name' => 'views_ui', 'original_version' => ' (>8.x-1.x)', 'versions' => [['op' => '>', 'version' => '2.x']]]], - ]; - } - /** * @covers ::getModuleDirectories */ -- GitLab