├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── UPDATE.md ├── composer.json ├── composer.lock ├── config ├── install │ ├── user.role.administrator.yml │ └── user.settings.yml ├── optional │ ├── block.block.bartik_account_menu.yml │ ├── block.block.bartik_branding.yml │ ├── block.block.bartik_breadcrumbs.yml │ ├── block.block.bartik_content.yml │ ├── block.block.bartik_footer.yml │ ├── block.block.bartik_help.yml │ ├── block.block.bartik_local_actions.yml │ ├── block.block.bartik_local_tasks.yml │ ├── block.block.bartik_main_menu.yml │ ├── block.block.bartik_messages.yml │ ├── block.block.bartik_page_title.yml │ ├── block.block.bartik_powered.yml │ ├── block.block.seven_breadcrumbs.yml │ ├── block.block.seven_content.yml │ ├── block.block.seven_help.yml │ ├── block.block.seven_local_actions.yml │ ├── block.block.seven_login.yml │ ├── block.block.seven_messages.yml │ ├── block.block.seven_page_title.yml │ ├── block.block.seven_primary_local_tasks.yml │ ├── block.block.seven_secondary_local_tasks.yml │ ├── block_content.type.text.yml │ ├── comment.type.comment.yml │ ├── core.entity_form_display.block_content.text.default.yml │ ├── core.entity_form_display.comment.comment.default.yml │ ├── core.entity_view_display.block_content.text.default.yml │ ├── core.entity_view_display.comment.comment.default.yml │ ├── field.field.block_content.text.body.yml │ └── field.field.comment.comment.comment_body.yml └── schema │ └── lightning.schema.yml ├── drush.services.yml ├── favicon.ico ├── help └── content_roles.md ├── lightning-logo.png ├── lightning.info.yml ├── lightning.install ├── lightning.png ├── lightning.profile ├── modules └── lightning_install │ ├── composer.json │ └── lightning_install.info.yml ├── src ├── Commands │ └── Uninstaller.php ├── Composer │ ├── AssetPackagist.php │ ├── ConfigureLegacyProject.php │ └── PatchedConstraint.php ├── Generators │ ├── SubProfileGenerator.php │ ├── install.twig │ └── profile.twig ├── LightningServiceProvider.php ├── ProxyClass │ └── RequiredModuleUninstallValidator.php ├── RequiredModuleUninstallValidator.php └── Update │ ├── Update320.php │ └── Update405.php └── tests ├── lightning_extender ├── lightning_extender.info.yml └── lightning_extender.profile ├── packages.yml ├── packages_alter.yml ├── src ├── ExistingSite │ ├── ApiTest.php │ ├── ConfigIntegrityTest.php │ └── ViewModeTest.php ├── Functional │ ├── LightningTest.php │ ├── SubprofileGeneratorTest.php │ ├── UpdatePath3xTest.php │ └── UpdatePath4xTest.php └── Kernel │ └── Update405Test.php ├── travis └── install.sh └── update.php /.gitattributes: -------------------------------------------------------------------------------- 1 | /grumphp.yml export-ignore 2 | /hooks export-ignore 3 | /tests/fixtures export-ignore 4 | /.travis.yml export-ignore 5 | /acquia-pipelines.yml export-ignore 6 | /phpunit.xml export-ignore 7 | /settings.local.php export-ignore 8 | /*.sh export-ignore 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin/ 3 | docroot/ 4 | vendor/ 5 | node_modules/ 6 | bower_components/ 7 | .sass-cache/ 8 | console/cache/ 9 | /.behat.yml 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | The information in this file has been moved to https://github.com/acquia/lightning/wiki/Changelog. 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to Lightning 2 | 3 | ### Local development 4 | **Note that these instructions won't work on old, unsupported branches of Lightning.** At the time of this writing, that includes the `8.x-1.x` and `8.x-2.x` branches, which have long since reached the end of their lives. 5 | 6 | This documentation describes how to set up Lightning (or any of its components) for development on a machine running a Unix-like operating system (e.g., Linux or macOS). We assume that: 7 | 8 | * You have Git installed in your PATH. You can confirm this by running `git --version`. 9 | * You have PHP 7.1 or later installed in your PATH. You can confirm this by running `php --version`. 10 | * You have Composer installed in your PATH. You can confirm this by running `composer --version`. You should also have Composer's global binary directory (usually `$HOME/.composer/vendor/bin`) in your PATH. 11 | * You will need `drush/drush-launcher` globally installed. To confirm this, run `drush --version`. If the command is not found, run `composer global require drush/drush-launcher`. 12 | * You will also need a database server installed. Lightning uses SQLite by default for development, since it is the most lightweight option supported by Drupal core. 13 | 14 | Now, get your Lightning code base set up: 15 | 16 | 1. Clone the git repository, e.g. `git clone git@github.com:acquia/lightning.git` 17 | 2. Enter the repository and run `composer install` to install all dependencies. 18 | 3. Install Lightning and all necessary components by running `./install-drupal.sh`. By default, this will try to install a SQLite database file called `db.sqlite` in the `docroot` directory. You can override this by passing a `DB_URL` environment variable to `install-drupal.sh`, containing the Drush-compatible URL of the database you want to use. For example: 19 | 20 | ``` 21 | DB_URL=mysql://user:password@server/drupal ./install-drupal.sh 22 | ``` 23 | 4. Run the web server. The quickest option is to use PHP's built-in server: `drush runserver 8080` 24 | 5. You should now be able to access your Lightning site at `http://localhost:8080`. 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drupal Lightning 2 | ![Lightning logo of a bolt of lightning](https://raw.githubusercontent.com/acquia/lightning/5.0.x/lightning-logo.png) 3 | 4 | ## November 2, 2021: So long and thanks for all the fish! 5 | Acquia **ended support for the Lightning distribution on November 2, 2021**, simultaneously with Drupal 8. Lightning 3, 4, and 5 no longer receive any security updates or bug fixes. It is possible to safely uninstall Lightning from your site; please see [the official announcement](https://www.acquia.com/blog/acquia-lightning-eol-2021-acquia-cms-future), [FAQ for site owners](https://support.acquia.com/hc/en-us/articles/1500006393601-Frequently-Asked-Questions-FAQ-regarding-End-of-Support-for-Acquia-Lightning), and [developer instructions](https://github.com/acquia/lightning/wiki/Uninstalling-Lightning) for more information. 6 | -------------------------------------------------------------------------------- /UPDATE.md: -------------------------------------------------------------------------------- 1 | The information in this file has moved to https://github.com/acquia/lightning/wiki/Update-Instructions. 2 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acquia/lightning", 3 | "type": "drupal-profile", 4 | "description": "The best of Drupal, curated by Acquia", 5 | "license": "GPL-2.0-or-later", 6 | "require": { 7 | "ext-dom": "*", 8 | "composer/composer": "^1.10.22 || ^2.0.13", 9 | "cweagans/composer-patches": "^1.7", 10 | "drupal/acquia_connector": "^1.24-rc3 || ^2.0-rc1 || ^3", 11 | "drupal/core": "~9.1.13", 12 | "drupal/inline_entity_form": "^1.0-rc7", 13 | "drupal/lightning_api": "^4.6", 14 | "drupal/lightning_core": "^5", 15 | "drupal/lightning_layout": "^2.10", 16 | "drupal/lightning_media": "^4.6", 17 | "drupal/lightning_workflow": "^3.16", 18 | "drupal/page_manager": "^4.0-beta6", 19 | "drupal/panelizer": "^5.0-beta3", 20 | "drupal/pendo": "^1@alpha", 21 | "drupal/profile_switcher": "^1.0-alpha5", 22 | "drupal/search_api": "^1.17" 23 | }, 24 | "require-dev": { 25 | "composer/installers": "^1.9", 26 | "drupal/core-composer-scaffold": "^9", 27 | "drupal/core-dev": "^9", 28 | "drupal/devel": "^4.1", 29 | "drush/drush": ">=9.7", 30 | "phpspec/prophecy-phpunit": "^2", 31 | "weitzman/drupal-test-traits": "dev-master" 32 | }, 33 | "config": { 34 | "preferred-install": { 35 | "drupal/core": "dist", 36 | "drupal/lightning_*": "source" 37 | } 38 | }, 39 | "extra": { 40 | "branch-alias": { 41 | "dev-8.x-4.x": "4.x-dev" 42 | }, 43 | "composer-exit-on-patch-failure": "true", 44 | "drupal-scaffold": { 45 | "file-mapping": { 46 | "[project-root]/.editorconfig": false, 47 | "[project-root]/.gitattributes": false, 48 | "[web-root]/.csslintrc": false, 49 | "[web-root]/INSTALL.txt": false, 50 | "[web-root]/example.gitignore": false, 51 | "[web-root]/modules/README.txt": false, 52 | "[web-root]/profiles/README.txt": false, 53 | "[web-root]/robots.txt": false, 54 | "[web-root]/sites/README.txt": false, 55 | "[web-root]/themes/README.txt": false, 56 | "[web-root]/web.config": false 57 | }, 58 | "locations": { 59 | "web-root": "docroot/" 60 | } 61 | }, 62 | "drush": { 63 | "services": { 64 | "drush.services.yml": "^9 || ^10" 65 | } 66 | }, 67 | "enable-patching": true, 68 | "installer-paths": { 69 | "docroot/core": [ 70 | "type:drupal-core" 71 | ], 72 | "docroot/libraries/{$name}": [ 73 | "type:drupal-library", 74 | "type:bower-asset", 75 | "type:npm-asset" 76 | ], 77 | "docroot/modules/contrib/{$name}": [ 78 | "type:drupal-module" 79 | ], 80 | "docroot/profiles/contrib/{$name}": [ 81 | "type:drupal-profile" 82 | ], 83 | "docroot/themes/contrib/{$name}": [ 84 | "type:drupal-theme" 85 | ] 86 | }, 87 | "installer-types": [ 88 | "bower-asset", 89 | "npm-asset" 90 | ], 91 | "patchLevel": { 92 | "drupal/core": "-p2" 93 | }, 94 | "patches": { 95 | "drupal/core": { 96 | "2869592 - Disabled update module shouldn't produce a status report warning": "https://www.drupal.org/files/issues/2020-02-07/2869592-remove-update-warning-34.patch", 97 | "[subprofile support] 1356276 - Allow profiles to define a base/parent profile and load them in the correct order / 2914389 - Allow profiles to exclude dependencies of their parent": "https://www.drupal.org/files/issues/2021-05-25/3143958-15-subprofile-support-9.1.x.patch", 98 | "REMOVE: 2031261 - Fix SQLite variable limit": "https://www.drupal.org/files/issues/2020-04-01/2031261-118.patch", 99 | "REMOVE: Allow installation profile to be changed from Lightning during config sync": "https://www.drupal.org/files/issues/2021-01-21/3143958-12-change-profile-config-sync-9.1.x.patch" 100 | } 101 | }, 102 | "patches-ignore": { 103 | "drupal/lightning_core": { 104 | "drupal/core": { 105 | "2869592 - Disabled update module shouldn't produce a status report warning": "https://www.drupal.org/files/issues/2869592-remove-update-warning-7.patch" 106 | } 107 | } 108 | } 109 | }, 110 | "autoload": { 111 | "psr-4": { 112 | "Acquia\\Lightning\\": "src" 113 | }, 114 | "classmap": [ 115 | "src/Composer/ConfigureLegacyProject.php" 116 | ] 117 | }, 118 | "repositories": { 119 | "drupal": { 120 | "type": "composer", 121 | "url": "https://packages.drupal.org/8" 122 | }, 123 | "assets": { 124 | "type": "composer", 125 | "url": "https://asset-packagist.org" 126 | } 127 | }, 128 | "minimum-stability": "dev", 129 | "prefer-stable": true, 130 | "scripts": { 131 | "post-install-cmd": [ 132 | "@putenv DIR=./docroot/profiles/lightning", 133 | "mkdir -p $DIR", 134 | "ln -s -f $PWD/config $DIR", 135 | "ln -s -f $PWD/drush.services.yml $DIR", 136 | "ln -s -f $PWD/favicon.ico $DIR", 137 | "ln -s -f $PWD/help $DIR", 138 | "ln -s -f $PWD/lightning-logo.png $DIR", 139 | "ln -s -f $PWD/modules $DIR", 140 | "ln -s -f $PWD/src $DIR", 141 | "ln -s -f $PWD/tests $DIR", 142 | "find $PWD -name 'lightning.*' -depth 1 -exec ln -s -f {} $DIR ';'", 143 | "cp -f phpunit.xml ./docroot/core" 144 | ], 145 | "configure-tarball": "Acquia\\Lightning\\Composer\\ConfigureLegacyProject::execute", 146 | "enable-asset-packagist": "Acquia\\Lightning\\Composer\\AssetPackagist::execute", 147 | "nuke": "rm -r -f docroot vendor", 148 | "verify-patched-constraints": "Acquia\\Lightning\\Composer\\PatchedConstraint::execute" 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /config/install/user.role.administrator.yml: -------------------------------------------------------------------------------- 1 | id: administrator 2 | label: Administrator 3 | weight: 2 4 | langcode: en 5 | is_admin: true 6 | -------------------------------------------------------------------------------- /config/install/user.settings.yml: -------------------------------------------------------------------------------- 1 | anonymous: Anonymous 2 | verify_mail: true 3 | notify: 4 | cancel_confirm: true 5 | password_reset: true 6 | status_activated: true 7 | status_blocked: false 8 | status_canceled: false 9 | register_admin_created: true 10 | register_no_approval_required: true 11 | register_pending_approval: true 12 | register: admin_only 13 | cancel_method: user_cancel_block 14 | password_reset_timeout: 86400 15 | password_strength: true 16 | langcode: en 17 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_account_menu.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | config: 5 | - system.menu.account 6 | module: 7 | - system 8 | theme: 9 | - bartik 10 | id: bartik_account_menu 11 | theme: bartik 12 | region: secondary_menu 13 | weight: 0 14 | provider: null 15 | plugin: 'system_menu_block:account' 16 | settings: 17 | id: 'system_menu_block:account' 18 | label: 'User account menu' 19 | provider: system 20 | label_display: '0' 21 | level: 1 22 | depth: 1 23 | visibility: { } 24 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_branding.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | module: 5 | - system 6 | theme: 7 | - bartik 8 | id: bartik_branding 9 | theme: bartik 10 | region: header 11 | weight: 0 12 | provider: null 13 | plugin: system_branding_block 14 | settings: 15 | id: system_branding_block 16 | label: 'Site branding' 17 | provider: system 18 | label_display: '0' 19 | use_site_logo: true 20 | use_site_name: true 21 | use_site_slogan: true 22 | visibility: { } 23 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_breadcrumbs.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | module: 5 | - system 6 | theme: 7 | - bartik 8 | id: bartik_breadcrumbs 9 | theme: bartik 10 | region: breadcrumb 11 | weight: 0 12 | provider: null 13 | plugin: system_breadcrumb_block 14 | settings: 15 | id: system_breadcrumb_block 16 | label: Breadcrumbs 17 | provider: system 18 | label_display: '0' 19 | visibility: { } 20 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_content.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | module: 5 | - system 6 | theme: 7 | - bartik 8 | id: bartik_content 9 | theme: bartik 10 | region: content 11 | weight: 0 12 | provider: null 13 | plugin: system_main_block 14 | settings: 15 | id: system_main_block 16 | label: 'Main page content' 17 | provider: system 18 | label_display: '0' 19 | visibility: { } 20 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_footer.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | config: 5 | - system.menu.footer 6 | module: 7 | - system 8 | theme: 9 | - bartik 10 | id: bartik_footer 11 | theme: bartik 12 | region: footer_fifth 13 | weight: 0 14 | provider: null 15 | plugin: 'system_menu_block:footer' 16 | settings: 17 | id: 'system_menu_block:footer' 18 | label: 'Footer menu' 19 | provider: system 20 | label_display: '0' 21 | level: 1 22 | depth: 0 23 | visibility: { } 24 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_help.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | module: 5 | - help 6 | theme: 7 | - bartik 8 | id: bartik_help 9 | theme: bartik 10 | region: content 11 | weight: -30 12 | provider: null 13 | plugin: help_block 14 | settings: 15 | id: help_block 16 | label: Help 17 | provider: help 18 | label_display: '0' 19 | visibility: { } 20 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_local_actions.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | theme: 5 | - bartik 6 | id: bartik_local_actions 7 | theme: bartik 8 | region: content 9 | weight: -20 10 | provider: null 11 | plugin: local_actions_block 12 | settings: 13 | id: local_actions_block 14 | label: 'Primary admin actions' 15 | provider: core 16 | label_display: '0' 17 | visibility: { } 18 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_local_tasks.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | theme: 5 | - bartik 6 | id: bartik_local_tasks 7 | theme: bartik 8 | region: content 9 | weight: -40 10 | provider: null 11 | plugin: local_tasks_block 12 | settings: 13 | id: local_tasks_block 14 | label: Tabs 15 | provider: core 16 | label_display: '0' 17 | primary: true 18 | secondary: true 19 | visibility: { } 20 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_main_menu.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | config: 5 | - system.menu.main 6 | module: 7 | - system 8 | theme: 9 | - bartik 10 | id: bartik_main_menu 11 | theme: bartik 12 | region: primary_menu 13 | weight: 0 14 | provider: null 15 | plugin: 'system_menu_block:main' 16 | settings: 17 | id: 'system_menu_block:main' 18 | label: 'Main navigation' 19 | provider: system 20 | label_display: '0' 21 | level: 1 22 | depth: 1 23 | visibility: { } 24 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_messages.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | module: 5 | - system 6 | theme: 7 | - bartik 8 | id: bartik_messages 9 | theme: bartik 10 | region: highlighted 11 | weight: 0 12 | provider: null 13 | plugin: system_messages_block 14 | settings: 15 | id: system_messages_block 16 | label: 'Status messages' 17 | provider: system 18 | label_display: '0' 19 | visibility: { } 20 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_page_title.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | theme: 5 | - bartik 6 | id: bartik_page_title 7 | theme: bartik 8 | region: content 9 | weight: -50 10 | provider: null 11 | plugin: page_title_block 12 | settings: 13 | id: page_title_block 14 | label: 'Page title' 15 | provider: core 16 | label_display: '0' 17 | visibility: { } 18 | -------------------------------------------------------------------------------- /config/optional/block.block.bartik_powered.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | module: 5 | - system 6 | theme: 7 | - bartik 8 | id: bartik_powered 9 | theme: bartik 10 | region: footer_fifth 11 | weight: 10 12 | provider: null 13 | plugin: system_powered_by_block 14 | settings: 15 | id: system_powered_by_block 16 | label: 'Powered by Drupal' 17 | provider: system 18 | label_display: '0' 19 | visibility: { } 20 | -------------------------------------------------------------------------------- /config/optional/block.block.seven_breadcrumbs.yml: -------------------------------------------------------------------------------- 1 | id: seven_breadcrumbs 2 | theme: seven 3 | weight: 0 4 | status: true 5 | langcode: en 6 | region: breadcrumb 7 | plugin: system_breadcrumb_block 8 | settings: 9 | id: system_breadcrumb_block 10 | label: Breadcrumbs 11 | provider: system 12 | label_display: '0' 13 | dependencies: 14 | module: 15 | - system 16 | theme: 17 | - seven 18 | visibility: { } 19 | -------------------------------------------------------------------------------- /config/optional/block.block.seven_content.yml: -------------------------------------------------------------------------------- 1 | id: seven_content 2 | theme: seven 3 | weight: 0 4 | status: true 5 | langcode: en 6 | region: content 7 | plugin: system_main_block 8 | settings: 9 | id: system_main_block 10 | label: 'Main page content' 11 | provider: system 12 | label_display: '0' 13 | dependencies: 14 | module: 15 | - system 16 | theme: 17 | - seven 18 | visibility: { } 19 | -------------------------------------------------------------------------------- /config/optional/block.block.seven_help.yml: -------------------------------------------------------------------------------- 1 | id: seven_help 2 | theme: seven 3 | weight: 0 4 | status: true 5 | langcode: en 6 | region: help 7 | plugin: help_block 8 | settings: 9 | id: help_block 10 | label: 'Help' 11 | provider: help 12 | label_display: '0' 13 | dependencies: 14 | module: 15 | - help 16 | theme: 17 | - seven 18 | visibility: { } 19 | -------------------------------------------------------------------------------- /config/optional/block.block.seven_local_actions.yml: -------------------------------------------------------------------------------- 1 | id: seven_local_actions 2 | theme: seven 3 | weight: -10 4 | status: true 5 | langcode: en 6 | region: content 7 | plugin: local_actions_block 8 | settings: 9 | id: local_actions_block 10 | label: Primary admin actions 11 | label_display: '0' 12 | dependencies: 13 | theme: 14 | - seven 15 | visibility: { } 16 | -------------------------------------------------------------------------------- /config/optional/block.block.seven_login.yml: -------------------------------------------------------------------------------- 1 | id: seven_login 2 | theme: seven 3 | weight: 10 4 | status: true 5 | langcode: en 6 | region: content 7 | plugin: user_login_block 8 | settings: 9 | id: user_login_block 10 | label: 'User login' 11 | provider: user 12 | label_display: visible 13 | dependencies: 14 | module: 15 | - user 16 | theme: 17 | - seven 18 | visibility: { } 19 | -------------------------------------------------------------------------------- /config/optional/block.block.seven_messages.yml: -------------------------------------------------------------------------------- 1 | id: seven_messages 2 | theme: seven 3 | weight: 0 4 | status: true 5 | langcode: en 6 | region: highlighted 7 | plugin: system_messages_block 8 | settings: 9 | id: system_messages_block 10 | label: 'Status messages' 11 | provider: system 12 | label_display: '0' 13 | dependencies: 14 | module: 15 | - system 16 | theme: 17 | - seven 18 | -------------------------------------------------------------------------------- /config/optional/block.block.seven_page_title.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | theme: 5 | - seven 6 | id: seven_page_title 7 | theme: seven 8 | region: header 9 | weight: -30 10 | provider: null 11 | plugin: page_title_block 12 | settings: 13 | id: page_title_block 14 | label: 'Page title' 15 | provider: core 16 | label_display: '0' 17 | visibility: { } 18 | -------------------------------------------------------------------------------- /config/optional/block.block.seven_primary_local_tasks.yml: -------------------------------------------------------------------------------- 1 | id: seven_primary_local_tasks 2 | theme: seven 3 | weight: 0 4 | status: true 5 | langcode: en 6 | region: header 7 | plugin: local_tasks_block 8 | settings: 9 | id: local_tasks_block 10 | label: Primary tabs 11 | label_display: '0' 12 | primary: true 13 | secondary: false 14 | dependencies: 15 | theme: 16 | - seven 17 | visibility: { } 18 | -------------------------------------------------------------------------------- /config/optional/block.block.seven_secondary_local_tasks.yml: -------------------------------------------------------------------------------- 1 | id: seven_secondary_local_tasks 2 | theme: seven 3 | weight: 0 4 | status: true 5 | langcode: en 6 | region: pre_content 7 | plugin: local_tasks_block 8 | settings: 9 | id: local_tasks_block 10 | label: Secondary tabs 11 | label_display: '0' 12 | primary: false 13 | secondary: true 14 | dependencies: 15 | theme: 16 | - seven 17 | visibility: { } 18 | -------------------------------------------------------------------------------- /config/optional/block_content.type.text.yml: -------------------------------------------------------------------------------- 1 | id: text 2 | label: 'Text' 3 | revision: 0 4 | description: 'A text block contains a title and a body.' 5 | langcode: en 6 | -------------------------------------------------------------------------------- /config/optional/comment.type.comment.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: { } 4 | id: comment 5 | label: 'Default comments' 6 | target_entity_type_id: node 7 | description: 'Allows commenting on content' 8 | -------------------------------------------------------------------------------- /config/optional/core.entity_form_display.block_content.text.default.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | config: 5 | - block_content.type.text 6 | - field.field.block_content.text.body 7 | module: 8 | - text 9 | id: block_content.text.default 10 | targetEntityType: block_content 11 | bundle: text 12 | mode: default 13 | content: 14 | body: 15 | type: text_textarea_with_summary 16 | weight: 0 17 | settings: 18 | rows: 9 19 | summary_rows: 3 20 | placeholder: '' 21 | third_party_settings: { } 22 | region: content 23 | info: 24 | type: string_textfield 25 | weight: -5 26 | settings: 27 | size: 60 28 | placeholder: '' 29 | third_party_settings: { } 30 | region: content 31 | hidden: { } 32 | -------------------------------------------------------------------------------- /config/optional/core.entity_form_display.comment.comment.default.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | config: 5 | - comment.type.comment 6 | - field.field.comment.comment.comment_body 7 | module: 8 | - text 9 | id: comment.comment.default 10 | targetEntityType: comment 11 | bundle: comment 12 | mode: default 13 | content: 14 | author: 15 | weight: -2 16 | region: content 17 | comment_body: 18 | type: text_textarea 19 | weight: 11 20 | region: content 21 | settings: 22 | rows: 5 23 | placeholder: '' 24 | third_party_settings: { } 25 | subject: 26 | type: string_textfield 27 | weight: 10 28 | region: content 29 | settings: 30 | size: 60 31 | placeholder: '' 32 | third_party_settings: { } 33 | hidden: { } 34 | -------------------------------------------------------------------------------- /config/optional/core.entity_view_display.block_content.text.default.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | config: 5 | - block_content.type.text 6 | - field.field.block_content.text.body 7 | module: 8 | - text 9 | id: block_content.text.default 10 | targetEntityType: block_content 11 | bundle: text 12 | mode: default 13 | content: 14 | body: 15 | type: text_default 16 | weight: 0 17 | label: hidden 18 | settings: { } 19 | third_party_settings: { } 20 | region: content 21 | hidden: { } 22 | -------------------------------------------------------------------------------- /config/optional/core.entity_view_display.comment.comment.default.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | config: 5 | - comment.type.comment 6 | - field.field.comment.comment.comment_body 7 | module: 8 | - text 9 | id: comment.comment.default 10 | targetEntityType: comment 11 | bundle: comment 12 | mode: default 13 | content: 14 | comment_body: 15 | label: hidden 16 | type: text_default 17 | weight: 0 18 | region: content 19 | settings: { } 20 | third_party_settings: { } 21 | links: 22 | weight: 100 23 | region: content 24 | hidden: { } 25 | -------------------------------------------------------------------------------- /config/optional/field.field.block_content.text.body.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | config: 5 | - block_content.type.text 6 | - field.storage.block_content.body 7 | module: 8 | - text 9 | id: block_content.text.body 10 | field_name: body 11 | entity_type: block_content 12 | bundle: text 13 | label: Body 14 | description: '' 15 | required: false 16 | translatable: true 17 | default_value: { } 18 | default_value_callback: '' 19 | settings: 20 | display_summary: false 21 | field_type: text_with_summary 22 | -------------------------------------------------------------------------------- /config/optional/field.field.comment.comment.comment_body.yml: -------------------------------------------------------------------------------- 1 | langcode: en 2 | status: true 3 | dependencies: 4 | config: 5 | - comment.type.comment 6 | - field.storage.comment.comment_body 7 | module: 8 | - text 9 | id: comment.comment.comment_body 10 | field_name: comment_body 11 | entity_type: comment 12 | bundle: comment 13 | label: Comment 14 | description: '' 15 | required: true 16 | translatable: true 17 | default_value: { } 18 | default_value_callback: '' 19 | settings: { } 20 | field_type: text_long 21 | -------------------------------------------------------------------------------- /config/schema/lightning.schema.yml: -------------------------------------------------------------------------------- 1 | user.role.*.third_party.lightning: 2 | type: mapping 3 | label: 'Lightning settings' 4 | mapping: 5 | bundled: 6 | type: boolean 7 | label: 'Bundled with Lightning' 8 | 9 | lightning.versions: 10 | type: sequence 11 | label: 'Lightning component versions' 12 | sequence: 13 | type: string 14 | label: 'Version' 15 | -------------------------------------------------------------------------------- /drush.services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | subprofile.generator: 3 | class: 'Drupal\lightning\Generators\SubProfileGenerator' 4 | arguments: 5 | - '@extension.list.module' 6 | tags: 7 | - { name: drush.generator } 8 | 9 | lightning.uninstaller: 10 | class: 'Drupal\lightning\Commands\Uninstaller' 11 | arguments: 12 | - '@module_handler' 13 | - '@theme_handler' 14 | - '@extension.list.profile' 15 | - '@file_system' 16 | - '%app.root%' 17 | - '%install_profile%' 18 | tags: 19 | - { name: drush.command } 20 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acquia/lightning/607a46eb062a653aa2f909b14b5ae02d7f15c80c/favicon.ico -------------------------------------------------------------------------------- /help/content_roles.md: -------------------------------------------------------------------------------- 1 | ## Content Roles 2 | 3 | ### Creator 4 | Creator roles are automatically created for every content type and automatically 5 | destroyed for deleted content types. The creator roles have limited permissions 6 | -- they can create content or edit their own. They *cannot* edit content created 7 | by anybody else, and they cannot delete content (even their own). They can save 8 | drafts and request review of their content, but they cannot publish it. 9 | 10 | ### Reviewer 11 | Like creator roles, reviewer roles are also automatically created for every 12 | content type and destroyed for deleted content types. Reviewers are intended to 13 | *extend* creators, meaning that you will typically grant the reviewer role *and* 14 | the creator role to users. Reviewers can edit, delete, and publish **any** 15 | content, even content they don't own. 16 | 17 | ### Administering content roles 18 | Content roles are configurable. They can be enabled or disabled. When disabled, 19 | they will not be automatically created or destroyed for any content type. To 20 | configure content roles, visit to *Manage > Configuration > System > Lightning*. 21 | 22 | It's possible to define additional content roles and change the permissions 23 | associated with them, but there is currently no UI for this. You'll need to alter the 24 | `lightning_core.settings` configuration object directly, which beyond the scope of 25 | this documentation. 26 | -------------------------------------------------------------------------------- /lightning-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acquia/lightning/607a46eb062a653aa2f909b14b5ae02d7f15c80c/lightning-logo.png -------------------------------------------------------------------------------- /lightning.info.yml: -------------------------------------------------------------------------------- 1 | name: Lightning 2 | core_version_requirement: '^8.8 || ^9' 3 | type: profile 4 | description: 'A fast and feature-rich Drupal distribution.' 5 | install: 6 | - autosave_form 7 | - block_content 8 | - breakpoint 9 | - ckeditor 10 | - config 11 | - conflict 12 | - contextual 13 | - menu_link_content 14 | - datetime 15 | - quickedit 16 | - editor 17 | - entity_block 18 | - help 19 | - history 20 | - menu_ui 21 | - node 22 | - options 23 | - path 24 | - pathauto 25 | - page_cache 26 | - redirect 27 | - taxonomy 28 | - text 29 | - dblog 30 | - shortcut 31 | - toolbar 32 | - field_ui 33 | - file 34 | - rdf 35 | - views 36 | - views_ui 37 | - diff 38 | - image_widget_crop 39 | - metatag 40 | - moderation_dashboard 41 | - moderation_sidebar 42 | - lightning_core 43 | - lightning_api 44 | - lightning_layout 45 | - lightning_media 46 | - lightning_workflow 47 | - lightning_contact_form 48 | - lightning_page 49 | - lightning_roles 50 | - lightning_search 51 | - lightning_landing_page 52 | - lightning_media_audio 53 | - lightning_media_bulk_upload 54 | - lightning_media_document 55 | - lightning_media_image 56 | - lightning_media_instagram 57 | - lightning_media_slideshow 58 | - lightning_media_twitter 59 | - lightning_media_video 60 | - lightning_scheduler 61 | - lightning_banner_block 62 | - lightning_map_block 63 | themes: 64 | - bartik 65 | - claro 66 | -------------------------------------------------------------------------------- /lightning.install: -------------------------------------------------------------------------------- 1 | getEntity('field_config', 'block_content.basic.body')->save(); 24 | 25 | $display = EntityViewDisplay::load('block_content.basic.default'); 26 | if ($display) { 27 | /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */ 28 | $display->setComponent('body', [ 29 | 'type' => 'text_default', 30 | 'weight' => 0, 31 | 'label' => 'hidden', 32 | 'settings' => [], 33 | 'third_party_settings' => [], 34 | ])->save(); 35 | } 36 | else { 37 | $config->getEntity('entity_view_display', 'block_content.basic.default')->save(); 38 | } 39 | } 40 | 41 | // Delete the `Create Landing Page` toolbar shortcut. 42 | $properties = ['link__uri' => 'internal:/admin/structure/landing-page']; 43 | $shortcuts = \Drupal::entityTypeManager()->getStorage('shortcut')->loadByProperties($properties); 44 | foreach ($shortcuts as $shortcut) { 45 | $shortcut->delete(); 46 | } 47 | 48 | // Uninstall the Media Demo Content module. 49 | \Drupal::service('module_installer')->uninstall(['lightning_media_democontent']); 50 | } 51 | 52 | /** 53 | * Removed in Lightning 8.x-2.06. 54 | * 55 | * Formerly created roles for editing content types. 56 | */ 57 | function lightning_update_8002() { 58 | } 59 | 60 | /** 61 | * Enables the lightning_core module. 62 | */ 63 | function lightning_update_8003() { 64 | Drupal::service('module_installer')->install(['lightning_core']); 65 | } 66 | 67 | /** 68 | * Enables Entity API. 69 | */ 70 | function lightning_update_8004() { 71 | /* 72 | * This ancient line is disabled in order to evade a strict coding standards 73 | * check. It should not be necessary at this point anyway, since it dates from 74 | * the days of Lightning 1.x, which has long since been end-of-life. 75 | * 76 | * eval('interface Drupal\entity\Entity\RevisionableEntityBundleInterface{}'); 77 | */ 78 | Drupal::service('module_installer')->install(['entity']); 79 | } 80 | 81 | /** 82 | * Removed in Lightning 8.x-2.04. 83 | * 84 | * Formerly installed Contact Storage. 85 | */ 86 | function lightning_update_8005() { 87 | } 88 | 89 | /** 90 | * Removed in Lightning 8.x-2.04. 91 | * 92 | * Formerly Granted permission to use contact form(s). 93 | */ 94 | function lightning_update_8006() { 95 | } 96 | 97 | /** 98 | * Uninstalls the lightning_install module. 99 | */ 100 | function lightning_update_8007() { 101 | Drupal::service('module_installer')->uninstall(['lightning_install']); 102 | } 103 | 104 | /** 105 | * Installs the profile_switcher module. 106 | */ 107 | function lightning_update_8008() { 108 | Drupal::service('module_installer')->install(['profile_switcher']); 109 | } 110 | 111 | /** 112 | * Implements hook_update_dependencies(). 113 | */ 114 | function lightning_update_dependencies() { 115 | return [ 116 | 'media_entity' => [ 117 | // Lightning 8004 must run before Media Entity 8002. 118 | 8002 => [ 119 | 'lightning' => 8004, 120 | ], 121 | ], 122 | ]; 123 | } 124 | -------------------------------------------------------------------------------- /lightning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acquia/lightning/607a46eb062a653aa2f909b14b5ae02d7f15c80c/lightning.png -------------------------------------------------------------------------------- /lightning.profile: -------------------------------------------------------------------------------- 1 | moduleExists('node')) { 33 | Drupal::configFactory() 34 | ->getEditable('system.site') 35 | ->set('page.front', '/node') 36 | ->save(TRUE); 37 | } 38 | } 39 | 40 | /** 41 | * Allows authenticated users to use shortcuts. 42 | */ 43 | function lightning_grant_shortcut_access() { 44 | if (Drupal::moduleHandler()->moduleExists('shortcut')) { 45 | user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, ['access shortcuts']); 46 | } 47 | } 48 | 49 | /** 50 | * Sets the default and administration themes. 51 | */ 52 | function lightning_set_default_theme() { 53 | Drupal::configFactory() 54 | ->getEditable('system.theme') 55 | ->set('default', 'bartik') 56 | ->set('admin', 'claro') 57 | ->save(TRUE); 58 | 59 | // Use the admin theme for creating content. 60 | if (Drupal::moduleHandler()->moduleExists('node')) { 61 | Drupal::configFactory() 62 | ->getEditable('node.settings') 63 | ->set('use_admin_theme', TRUE) 64 | ->save(TRUE); 65 | } 66 | } 67 | 68 | /** 69 | * Set the path to the logo, favicon and README file based on install directory. 70 | */ 71 | function lightning_set_logo() { 72 | $lightning_path = drupal_get_path('profile', 'lightning'); 73 | 74 | Drupal::configFactory() 75 | ->getEditable('system.theme.global') 76 | ->set('logo', [ 77 | 'path' => $lightning_path . '/lightning.png', 78 | 'url' => '', 79 | 'use_default' => FALSE, 80 | ]) 81 | ->set('favicon', [ 82 | 'mimetype' => 'image/vnd.microsoft.icon', 83 | 'path' => $lightning_path . '/favicon.ico', 84 | 'url' => '', 85 | 'use_default' => FALSE, 86 | ]) 87 | ->save(TRUE); 88 | } 89 | 90 | /** 91 | * Alters the frontpage view, if it exists. 92 | */ 93 | function lightning_alter_frontpage_view() { 94 | $front_page = Drupal::configFactory()->getEditable('views.view.frontpage'); 95 | 96 | if (!$front_page->isNew()) { 97 | $section = 'display.default.display_options.empty.area_text_custom'; 98 | $front_page 99 | ->set("$section.tokenize", TRUE) 100 | ->set("$section.content", '

Welcome to [site:name]. No front page content has been created yet.

Would you like to view the README?

') 101 | ->save(TRUE); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /modules/lightning_install/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drupal/lightning_install", 3 | "type": "drupal-module", 4 | "description": "Provides helpful under-the-hood functionality used while installing Lightning.", 5 | "license": "GPL-2.0-or-later" 6 | } 7 | -------------------------------------------------------------------------------- /modules/lightning_install/lightning_install.info.yml: -------------------------------------------------------------------------------- 1 | name: 'Install Helper' 2 | core_version_requirement: '^8.8 || ^9' 3 | type: module 4 | hidden: true 5 | package: Lightning 6 | description: 'Provides helpful under-the-hood functionality used while installing Lightning.' 7 | -------------------------------------------------------------------------------- /src/Commands/Uninstaller.php: -------------------------------------------------------------------------------- 1 | moduleHandler = $module_handler; 96 | $this->themeHandler = $theme_handler; 97 | $this->profileList = $profile_list; 98 | $this->fileSystem = $file_system; 99 | $this->appRoot = $app_root; 100 | $this->installProfile = $install_profile; 101 | } 102 | 103 | /** 104 | * Defines dynamic options if uninstalling Lightning. 105 | * 106 | * @param \Symfony\Component\Console\Command\Command $command 107 | * The command object. 108 | * 109 | * @hook option pm:uninstall 110 | */ 111 | public function options(Command $command) : void { 112 | if ($this->getUninstall()) { 113 | $command->addOption( 114 | 'profile', 115 | NULL, 116 | InputOption::VALUE_REQUIRED, 117 | 'The profile to switch to.', 118 | 'minimal' 119 | ); 120 | $command->addOption( 121 | 'composer', 122 | NULL, 123 | InputOption::VALUE_REQUIRED, 124 | 'The path of the project-level composer.json.', 125 | $this->locateProjectFile() 126 | ); 127 | } 128 | } 129 | 130 | /** 131 | * Runs a Drush command, with the --yes option. 132 | * 133 | * @param string $command 134 | * The command name (e.g., 'status'). 135 | * @param array $arguments 136 | * (optional) Arguments to pass to the command. 137 | */ 138 | private function drush(string $command, array $arguments = []) : void { 139 | $alias = $this->siteAliasManager()->getSelf(); 140 | 141 | $this->processManager() 142 | ->drush($alias, $command, $arguments, ['yes' => NULL]) 143 | ->mustRun(); 144 | } 145 | 146 | /** 147 | * Returns the profile being uninstalled. 148 | * 149 | * @return string|null 150 | * If an uninstall of Lightning or Headless Lightning is being attempted, 151 | * the machine name of the profile being uninstalled. NULL otherwise. 152 | */ 153 | private function getUninstall() : ?string { 154 | $modules = array_intersect($this->input()->getArgument('modules'), [ 155 | 'lightning', 156 | 'headless_lightning', 157 | ]); 158 | return reset($modules) ?: NULL; 159 | } 160 | 161 | /** 162 | * Validates the pm:uninstall command if Lightning is being uninstalled. 163 | * 164 | * @param \Consolidation\AnnotatedCommand\CommandData $data 165 | * The current command data. 166 | * 167 | * @hook validate pm:uninstall 168 | * 169 | * @throws \RuntimeException 170 | * Thrown if: 171 | * - The user is trying to uninstall Lightning (or Headless Lightning) at 172 | * the same time as other extension(s). 173 | * - Any installed extensions are physically located inside the Lightning 174 | * profile directory. 175 | * - Any profiles (installed or not) are using Lightning or Headless 176 | * Lightning as their immediate parent, and the user declines to modify 177 | * them automatically. 178 | */ 179 | public function validate(CommandData $data) : void { 180 | $profile = $this->getUninstall(); 181 | if ($profile) { 182 | $info = $this->profileList->getExtensionInfo($profile); 183 | $this->io()->title('Welcome to the ' . $info['name'] . ' uninstaller!'); 184 | 185 | if (count($data->input()->getArgument('modules')) > 1) { 186 | throw new \RuntimeException('You cannot uninstall ' . $info['name'] . ' and other extensions at the same time.'); 187 | } 188 | 189 | // Ensure that there are no installed modules or themes in the Lightning 190 | // profile directory. 191 | $extensions = $this->getExtensionsInProfileDirectory(); 192 | if ($extensions) { 193 | $error = sprintf('The following modules and/or themes are located inside the Lightning profile directory. They must be moved elsewhere before Lightning can be uninstalled: %s', implode(', ', $extensions)); 194 | throw new \RuntimeException($error); 195 | } 196 | 197 | // Ensure that there are no other profiles available that use the profile 198 | // as a parent. If there are, offer to automatically fix them, and error 199 | // out if the user declines. 200 | $children = $this->getChildren($profile); 201 | if ($children) { 202 | $warning = sprintf('The following install profiles use %s as a base profile. They must stand alone, or use a different base profile, before Lightning can be uninstalled: %s', $info['name'], implode(', ', $children)); 203 | $this->io()->warning($warning); 204 | 205 | $fix_it = $this->confirm('These profiles can be automatically decoupled from ' . $info['name'] . '. Should I do that now?', TRUE); 206 | if ($fix_it) { 207 | array_walk($children, [$this, 'decoupleProfile']); 208 | } 209 | else { 210 | throw new \RuntimeException('These profiles must be decoupled from ' . $info['name'] . ' before uninstallation can continue.'); 211 | } 212 | } 213 | } 214 | } 215 | 216 | /** 217 | * Performs required actions before Lightning is uninstalled. 218 | * 219 | * @hook pre-command pm:uninstall 220 | */ 221 | public function preCommand() : void { 222 | if ($this->getUninstall()) { 223 | if ($this->installProfile === 'lightning' || $this->installProfile === 'headless_lightning') { 224 | // The lightning_install module was created to prevent broken builds of 225 | // Lightning (created by drupal.org's legacy packaging system) from 226 | // being installed. 227 | $this->drush('pm:uninstall', ['lightning_install']); 228 | 229 | $profile = $this->input()->getOption('profile'); 230 | $this->boldlySay("Switching to $profile profile..."); 231 | $this->drush('pm:enable', ['profile_switcher']); 232 | $this->drush('switch:profile', [$profile]); 233 | } 234 | $this->alterProject(); 235 | } 236 | } 237 | 238 | /** 239 | * Performs required actions after Lightning is uninstalled. 240 | * 241 | * @hook post-command pm:uninstall 242 | */ 243 | public function postCommand() : void { 244 | $profile = $this->getUninstall(); 245 | if ($profile) { 246 | $this->drush('pm:uninstall', ['profile_switcher']); 247 | 248 | $info = $this->profileList->getExtensionInfo($profile); 249 | $this->io()->success([ 250 | "Congrats, " . $info['name'] . " has been uninstalled!", 251 | "You should now commit code and configuration changes, and deploy them to your hosting environment.", 252 | ]); 253 | } 254 | } 255 | 256 | /** 257 | * Returns installed extensions in the Lightning profile directory. 258 | * 259 | * @return string[] 260 | * The names of installed extensions in the Lightning profile directory. 261 | * If there are any, Lightning cannot be uninstalled. There shouldn't 262 | * normally be any -- Lightning doesn't ship with any runtime modules -- 263 | * but it's wise to check anyway in case the current site has an exotic 264 | * set-up. 265 | */ 266 | private function getExtensionsInProfileDirectory() : array { 267 | $extensions = array_merge( 268 | $this->moduleHandler->getModuleList(), 269 | $this->themeHandler->listInfo() 270 | ); 271 | 272 | $profile_path = $extensions['lightning']->getPath(); 273 | unset($extensions['lightning']); 274 | // The lightning_install module is a special module that was created to 275 | // prevent installation of broken builds of Lightning created by the legacy 276 | // drupal.org packaging system. This utility uninstalls it along with the 277 | // profile. 278 | unset($extensions['lightning_install']); 279 | 280 | $filter = function (Extension $extension) use ($profile_path) : bool { 281 | return strpos($extension->getPath(), $profile_path) !== FALSE; 282 | }; 283 | $extensions = array_filter($extensions, $filter); 284 | return array_keys($extensions); 285 | } 286 | 287 | /** 288 | * Lists all profiles that have a specific profile as their parent. 289 | * 290 | * @param string $parent 291 | * The name of the parent profile. 292 | * 293 | * @return string[] 294 | * The machine names of all profiles, installed or not, that have the given 295 | * parent. 296 | */ 297 | private function getChildren(string $parent) : array { 298 | $children = []; 299 | foreach ($this->profileList->getAllAvailableInfo() as $name => $info) { 300 | if (isset($info['base profile']) && $info['base profile'] === $parent) { 301 | $children[] = $name; 302 | } 303 | } 304 | return $children; 305 | } 306 | 307 | /** 308 | * Uncouples a profile from its parent. 309 | * 310 | * This will modify the profile's info file to remove the dependency on 311 | * the parent, then copy all of the parent's default configuration into the 312 | * profile's optional config directory. Existing config is preserved, as are 313 | * any info file customizations. 314 | * 315 | * @param string $name 316 | * The machine name of the sub-profile. 317 | */ 318 | private function decoupleProfile(string $name) : void { 319 | $target = $this->readInfo($name); 320 | $parent_key = $target['base profile']; 321 | $parent = $this->readInfo($parent_key); 322 | 323 | $io = $this->io(); 324 | $io->section("Decoupling $name from " . $parent['name']); 325 | unset($target['base profile']); 326 | 327 | // This strips out the project prefix from a dependency. For example, this 328 | // will convert 'drupal:views' to just 'views'. 329 | $map = function (string $name) : string { 330 | $name = explode(':', $name, 2); 331 | return end($name); 332 | }; 333 | 334 | $exclude = array_map($map, $target['exclude'] ?? []); 335 | unset($target['exclude']); 336 | 337 | $install = array_map($map, $target['install'] ?? []); 338 | // Add all of the parent's dependencies, except for excluded ones. 339 | $install = array_merge($install, $parent['install']); 340 | $target['install'] = $this->arrayDiff($install, $exclude); 341 | 342 | // Add all of the parent's themes, except for excluded ones. 343 | $themes = array_merge($target['themes'] ?? [], $parent['themes']); 344 | $target['themes'] = $this->arrayDiff($themes, $exclude); 345 | 346 | // If the parent is listed as an explicit dependency, remove that. 347 | if (isset($target['dependencies'])) { 348 | $target['dependencies'] = $this->arrayDiff($target['dependencies'], [$parent_key]); 349 | } 350 | 351 | $destination = $this->profileList->getPathname($name); 352 | $success = file_put_contents($destination, Yaml::encode($target)); 353 | if ($success) { 354 | $this->say("Updated $destination."); 355 | } 356 | else { 357 | throw new IOException("Unable to write to $destination."); 358 | } 359 | 360 | $this->copyConfiguration($parent_key, $name); 361 | $io->success("$name has been decoupled from " . $parent['name'] . "."); 362 | } 363 | 364 | /** 365 | * Returns the difference between two arrays. 366 | * 367 | * @param array $a 368 | * An array of values. 369 | * @param array $b 370 | * Another array of values. 371 | * 372 | * @return array 373 | * The items which are in $a but not $b, numerically re-indexed. All 374 | * duplicate values will be removed. 375 | */ 376 | private function arrayDiff(array $a, array $b) : array { 377 | $c = array_diff($a, $b); 378 | $c = array_unique($c); 379 | return array_values($c); 380 | } 381 | 382 | /** 383 | * Reads the info file of a profile. 384 | * 385 | * @param string $name 386 | * The machine name of the profile. 387 | * 388 | * @return array 389 | * The parsed profile info. 390 | */ 391 | private function readInfo(string $name) : array { 392 | $info = $this->profileList->getPathname($name); 393 | $info = file_get_contents($info); 394 | return Yaml::decode($info); 395 | } 396 | 397 | /** 398 | * Copies all config from one profile into another. 399 | * 400 | * @param string $source 401 | * The profile from which the config should be copied. 402 | * @param string $target 403 | * The profile into which the config should be copied. 404 | */ 405 | private function copyConfiguration(string $source, string $target) : void { 406 | $destination_dir = $this->profileList->getPath($target) . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY; 407 | $this->fileSystem->prepareDirectory($destination_dir, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); 408 | 409 | $info = $this->profileList->getExtensionInfo($source); 410 | $this->boldlySay("Copying " . $info['name'] . " configuration to $target..."); 411 | 412 | foreach ($this->getConfigurationToCopy($source) as $name => $path) { 413 | $destination = sprintf('%s/%s.%s', $destination_dir, $name, FileStorage::getFileExtension()); 414 | $this->say($destination); 415 | 416 | try { 417 | $this->fileSystem->copy($path, $destination, FileSystemInterface::EXISTS_ERROR); 418 | } 419 | catch (FileExistsException $e) { 420 | $this->io()->note($e->getMessage()); 421 | } 422 | } 423 | } 424 | 425 | /** 426 | * Lists all config that ships with the a profile. 427 | * 428 | * @param string $profile 429 | * The name of the profile. 430 | * 431 | * @return string[] 432 | * An array of config that ships with the given profile. The keys will be 433 | * the config names, and the values will be the paths of the config files, 434 | * relative to the Drupal root. 435 | */ 436 | private function getConfigurationToCopy(string $profile) : array { 437 | $base_dir = $this->profileList->getPath($profile); 438 | 439 | $directories = array_filter([ 440 | $base_dir . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY, 441 | $base_dir . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY, 442 | ], 'is_dir'); 443 | 444 | $list = []; 445 | foreach ($directories as $dir) { 446 | $storage = new FileStorage($dir); 447 | foreach ($storage->listAll() as $name) { 448 | $list[$name] = $storage->getFilePath($name); 449 | } 450 | } 451 | return $list; 452 | } 453 | 454 | /** 455 | * Returns the location of the project-level composer.json. 456 | * 457 | * @return string 458 | * The path of the project-level composer.json. 459 | */ 460 | private function locateProjectFile() : string { 461 | $finder = new DrupalFinder(); 462 | if ($finder->locateRoot($this->appRoot) == FALSE) { 463 | throw new \LogicException("Could not locate the Drupal root."); 464 | } 465 | 466 | $target = $finder->getComposerRoot() . DIRECTORY_SEPARATOR . 'composer.json'; 467 | assert(file_exists($target), "Expected $target to exist, but it does not."); 468 | 469 | return $target; 470 | } 471 | 472 | /** 473 | * Alters the project-level composer.json to uninstall Lightning. 474 | */ 475 | private function alterProject() : void { 476 | $target = $this->input()->getOption('composer'); 477 | 478 | $this->boldlySay("Modifying $target..."); 479 | $this->io()->listing([ 480 | 'Ensuring direct Lightning dependencies are required', 481 | 'Ensuring required repositories are present', 482 | 'Adding required patches', 483 | 'Checking patcher configuration', 484 | 'Checking installer configuration', 485 | 'Checking scaffold configuration', 486 | ]); 487 | 488 | $file = new JsonFile($target); 489 | $target = $file->read(); 490 | 491 | // Read Lightning's composer.json, since we will need to merge in many 492 | // default values from it. 493 | $source = new JsonFile(__DIR__ . '/../../composer.json'); 494 | $source = $source->read(); 495 | $source += [ 496 | 'extra' => [], 497 | ]; 498 | // Sanity check that we have the correct composer.json. 499 | assert($source['name'] === 'acquia/lightning'); 500 | 501 | $data = $this->mergeCanadian($target, [ 502 | 'require' => $this->getRequirements($target, $source), 503 | 'extra' => [ 504 | 'composer-exit-on-patch-failure' => $extra['composer-exit-on-patch-failure'] ?? TRUE, 505 | 'drupal-scaffold' => [ 506 | 'locations' => [ 507 | 'web-root' => $this->getDrupalRoot($target, $source) . '/', 508 | ], 509 | ], 510 | 'enable-patching' => $source['extra']['enable-patching'] ?? TRUE, 511 | 'installer-paths' => $this->getPaths($target, $source), 512 | 'installer-types' => $this->getPackageTypes($target), 513 | 'patchLevel' => $source['extra']['patchLevel'] ?? [], 514 | 'patches' => $source['extra']['patches'] ?? [], 515 | 'patches-ignore' => $source['extra']['patches-ignore'] ?? [], 516 | ], 517 | 'repositories' => $this->getRepositories($target), 518 | ]); 519 | 520 | // If the project requires Headless Lightning, we need to switch it to 521 | // Headless Lightning 2 to ensure that the modules which are part of 522 | // Headless Lightning will continue to be present after uninstall. 523 | if (isset($data['require']['acquia/headless_lightning'])) { 524 | $data['require']['acquia/headless_lightning'] = 'dev-eol'; 525 | } 526 | 527 | // Delete any empty arrays, since they will be encoded as empty arrays and 528 | // may therefore break the composer.json schema. 529 | // @todo Handle this recursively. 530 | $data = array_filter($data, function ($item) { 531 | return is_array($item) ? (bool) $item : TRUE; 532 | }); 533 | 534 | $file->write($data); 535 | } 536 | 537 | /** 538 | * Returns the combined requirements for the target package. 539 | * 540 | * @param array $target 541 | * The target package's configuration. 542 | * @param array $source 543 | * The source package's configuration. 544 | * 545 | * @return array 546 | * The combined requirements to add to the target package. The keys will 547 | * be package names and the values will be version constraints. 548 | */ 549 | private function getRequirements(array $target, array $source) : array { 550 | $requirements = []; 551 | // Lightning requires composer/composer in order for this uninstaller to 552 | // alter composer.json correctly. When that's done, we don't need it 553 | // anymore. 554 | unset($source['require']['composer/composer']); 555 | // The target package's existing dependencies should supersede any 556 | // dependencies defined by the source package (Lightning). 557 | $requirements += ($target['require'] ?? []); 558 | $requirements += ($source['require'] ?? []); 559 | 560 | // If the target package is not using the deprecated scaffold plugin, use 561 | // the one that ships with Drupal core. On the other hand, if the target 562 | // package *is* using the deprecated plugin, they are on their own. 563 | if (empty($requirements['drupal-composer/drupal-scaffold'])) { 564 | $requirements += [ 565 | 'drupal/core-composer-scaffold' => $requirements['drupal/core'], 566 | ]; 567 | } 568 | return $requirements; 569 | } 570 | 571 | /** 572 | * Returns the package types to expose to the Composer installers extender. 573 | * 574 | * @param array $target 575 | * The target package's configuration. 576 | * 577 | * @return string[] 578 | * The package types to expose to the Composer installers extender plugin 579 | * (oomphinc/composer-installers-extender), if available. 580 | */ 581 | private function getPackageTypes(array $target) : array { 582 | $installer_types = $target['extra']['installer-types'] ?? []; 583 | 584 | // Ensure that npm-asset and bower-asset are known package types. 585 | array_push($installer_types, 'npm-asset', 'bower-asset'); 586 | return array_unique($installer_types); 587 | } 588 | 589 | /** 590 | * Returns the combined installer paths for the target package. 591 | * 592 | * @param array $target 593 | * The target package's configuration. 594 | * @param array $source 595 | * The source package's configuration. 596 | * 597 | * @return array[] 598 | * An array of paths to be used by the composer/installers plugin. 599 | */ 600 | private function getPaths(array $target, array $source) : array { 601 | $root_dir = $this->getDrupalRoot($target, $source); 602 | // If we don't know where Drupal core is installed, we cannot possibly 603 | // determine where modules, themes, etc. should go. 604 | if (empty($root_dir)) { 605 | throw new \LogicException("Cannot determine the Drupal root."); 606 | } 607 | 608 | $path_map = $this->getPathMap($target, $source); 609 | $path_map += [ 610 | 'type:drupal-module' => $root_dir . '/modules/contrib/{$name}', 611 | 'type:drupal-custom-module' => $root_dir . '/modules/custom/{$name}', 612 | 'type:drupal-profile' => $root_dir . '/profiles/contrib/{$name}', 613 | 'type:drupal-theme' => $root_dir . '/themes/contrib/{$name}', 614 | 'type:drupal-custom-theme' => $root_dir . '/themes/custom/{$name}', 615 | 'type:drupal-library' => $root_dir . '/libraries/{$name}', 616 | 'type:npm-asset' => $root_dir . '/libraries/{$name}', 617 | 'type:bower-asset' => $root_dir . '/libraries/{$name}', 618 | ]; 619 | // If the target package uses Headless Lightning, it should be treated as 620 | // a normal module. 621 | if (isset($target['require']['acquia/headless_lightning'])) { 622 | $path_map += [ 623 | 'acquia/headless_lightning' => $path_map['type:drupal-module'], 624 | ]; 625 | } 626 | 627 | $paths = []; 628 | foreach ($path_map as $package => $location) { 629 | $paths[$location][] = $package; 630 | } 631 | return $paths; 632 | } 633 | 634 | /** 635 | * Returns the combined repositories for the target package. 636 | * 637 | * @param array $target 638 | * The target package's configuration. 639 | * 640 | * @return array[] 641 | * An array of Composer repository definitions to add to the target package. 642 | */ 643 | private function getRepositories(array $target) : array { 644 | $repositories = []; 645 | 646 | $source_repositories = [ 647 | 'https://packages.drupal.org/8', 648 | 'https://asset-packagist.org', 649 | ]; 650 | 651 | $target_repositories = []; 652 | foreach (($target['repositories'] ?? []) as $repository) { 653 | if ($repository['type'] === 'composer') { 654 | $target_repositories[] = $repository['url']; 655 | } 656 | } 657 | 658 | // Ensure that the two repositories listed in $source_repositories are 659 | // added to the target package's repositories. 660 | $repositories_to_add = array_diff($source_repositories, $target_repositories); 661 | 662 | foreach ($repositories_to_add as $url) { 663 | $repositories[] = [ 664 | 'type' => 'composer', 665 | 'url' => $url, 666 | ]; 667 | } 668 | return $repositories; 669 | } 670 | 671 | /** 672 | * Returns a map of locations where packages will be installed. 673 | * 674 | * @param array $target 675 | * The target package's configuration. 676 | * @param array $source 677 | * The source package's configuration. 678 | * 679 | * @return string[] 680 | * A map where the keys are the package, or package type, to install (e.g., 681 | * 'drupal/dropzonejs' or 'type:drupal-theme') and the values are the 682 | * location where that package or package type will be installed, relative 683 | * to the target package. 684 | */ 685 | private function getPathMap(array $target, array $source) : array { 686 | // Try to get the installer-paths configuration from the target package, 687 | // falling back to the source package (Lightning) in the unlikely event 688 | // that the target package has not configured this. 689 | $extra = isset($target['extra']['installer-paths']) 690 | ? $target['extra'] 691 | : $source['extra']; 692 | 693 | $path_map = []; 694 | foreach ($extra['installer-paths'] as $location => $packages) { 695 | foreach ($packages as $package) { 696 | $path_map[$package] = $location; 697 | } 698 | } 699 | return $path_map; 700 | } 701 | 702 | /** 703 | * Returns the path to the Drupal root, relative to the target package. 704 | * 705 | * @param array $target 706 | * The target package's configuration. 707 | * @param array $source 708 | * The source package (i.e., Lightning)'s configuration. 709 | * 710 | * @return string|null 711 | * The path to the Drupal root, relative to the target package, e.g., 712 | * 'docroot', or NULL if it cannot be determined. 713 | */ 714 | private function getDrupalRoot(array $target, array $source) : ?string { 715 | $path_map = $this->getPathMap($target, $source); 716 | 717 | // We expect that the path map has an install location for Drupal core. If 718 | // it doesn't, that's a pretty major error condition; in such a case, it's 719 | // not clear how their code base could even be working. Maybe it's a bizarre 720 | // set-up (symlink jungle?) that we don't support. 721 | $core_location = $path_map['drupal/core'] ?? $path_map['type:drupal-core']; 722 | return $core_location ? dirname($core_location) : NULL; 723 | } 724 | 725 | /** 726 | * Recursively merges two associative arrays, preserving existing items. 727 | * 728 | * @param array $a 729 | * The array which $b will be merged into. 730 | * @param array $b 731 | * The array to merge into $a. 732 | * 733 | * @return array 734 | * The merged array. 735 | */ 736 | private function mergeCanadian(array $a, array $b) : array { 737 | $a += $b; 738 | foreach ($a as $k => $v) { 739 | if (is_array($v) && isset($b[$k]) && is_array($b[$k])) { 740 | $a[$k] = $this->mergeCanadian($a[$k], $b[$k]); 741 | } 742 | } 743 | return $a; 744 | } 745 | 746 | /** 747 | * Wrapper around ::say() which displays the text in bold. 748 | * 749 | * @param string $text 750 | * The text to display. 751 | */ 752 | private function boldlySay(string $text) : void { 753 | $this->writeln("$text"); 754 | } 755 | 756 | } 757 | -------------------------------------------------------------------------------- /src/Composer/AssetPackagist.php: -------------------------------------------------------------------------------- 1 | exists()) { 31 | $info = $file->read(); 32 | 33 | if (isset($info['require']['acquia/lightning'])) { 34 | return $file; 35 | } 36 | } 37 | chdir('..'); 38 | array_pop($dir); 39 | } while ($dir); 40 | 41 | throw new \RuntimeException('Could not locate the root package.'); 42 | } 43 | 44 | /** 45 | * Executes the script. 46 | * 47 | * @param \Composer\Script\Event $event 48 | * The script event. 49 | */ 50 | public static function execute(Event $event) { 51 | $io = $event->getIO(); 52 | 53 | // Search upwards for a composer.json which depends on acquia/lightning. 54 | $io->write('Searching for root package...'); 55 | 56 | $file = static::getRootPackage(); 57 | 58 | $package = $file->read(); 59 | 60 | // Add the Asset Packagist repository if it does not already exist. 61 | if (isset($package['repositories'])) { 62 | $repository_key = NULL; 63 | 64 | foreach ($package['repositories'] as $key => $repository) { 65 | if ($repository['type'] == 'composer' && strpos($repository['url'], 'https://asset-packagist.org') === 0) { 66 | $repository_key = $key; 67 | break; 68 | } 69 | } 70 | 71 | if (is_null($repository_key)) { 72 | $package['repositories']['asset-packagist'] = [ 73 | 'type' => 'composer', 74 | 'url' => 'https://asset-packagist.org', 75 | ]; 76 | } 77 | } 78 | 79 | // oomphinc/composer-installers-extender is required by Lightning and 80 | // depends on composer/installers, so it does not need to be specifically 81 | // included. 82 | unset( 83 | $package['require']['composer/installers'], 84 | $package['require']['oomphinc/composer-installers-extender'] 85 | ); 86 | 87 | $package['extra']['installer-types'][] = 'bower-asset'; 88 | $package['extra']['installer-types'][] = 'npm-asset'; 89 | $package['extra']['installer-paths']['docroot/libraries/{$name}'][] = 'type:bower-asset'; 90 | $package['extra']['installer-paths']['docroot/libraries/{$name}'][] = 'type:npm-asset'; 91 | 92 | $file->write($package); 93 | $io->write('Successfully updated your root composer.json file. Switch back to your project root and run "composer update".'); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/Composer/ConfigureLegacyProject.php: -------------------------------------------------------------------------------- 1 | getArguments(); 21 | 22 | $target = new JsonFile($arguments[0] . '/composer.json'); 23 | $project = $target->read(); 24 | 25 | $project['extra']['installer-paths']['libraries/{$name}'] = [ 26 | 'type:drupal-library', 27 | 'type:bower-asset', 28 | 'type:npm-asset', 29 | ]; 30 | $project['extra']['installer-types'] = ['bower-asset', 'npm-asset']; 31 | $project['extra']['patchLevel']['drupal/core'] = '-p2'; 32 | $project['extra']['enable-patching'] = TRUE; 33 | 34 | // Composer doesn't like empty sections of composer.json, so 35 | // filter those out before we write the configuration. 36 | $project = array_filter($project); 37 | $target->write($project); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/Composer/PatchedConstraint.php: -------------------------------------------------------------------------------- 1 | getComposer()->getPackage(); 25 | $patched_dependencies = static::getPatchedDependencyConstraints($root_package); 26 | $error = []; 27 | 28 | /** @var \Composer\Package\Link $package */ 29 | foreach ($patched_dependencies as $package) { 30 | if (static::packageIsUnpinned($package)) { 31 | $error[] = $package->getTarget() . ': ' . $package->getPrettyConstraint(); 32 | } 33 | } 34 | if (!empty($error)) { 35 | array_unshift($error, 'The following dependencies are patched but don\'t have pinned dependency constraints:'); 36 | $event->getIO()->writeError($error); 37 | return FALSE; 38 | } 39 | else { 40 | $event->getIO()->write('Patched dependencies have constraints that are properly pinned.'); 41 | } 42 | } 43 | 44 | /** 45 | * Filters the requires section to packages that are patched. 46 | * 47 | * @param \Composer\Package\RootPackageInterface $root_package 48 | * The root composer.json package. 49 | * 50 | * @return \Composer\Package\Link 51 | * List of required packages that are patched. 52 | */ 53 | protected static function getPatchedDependencyConstraints(RootPackageInterface $root_package) { 54 | $required = $root_package->getRequires(); 55 | $extra = $root_package->getExtra(); 56 | $patched = $extra['patches']; 57 | return array_intersect_key($required, $patched); 58 | } 59 | 60 | /** 61 | * Determines if a given package's constraint is pinned or not. 62 | * 63 | * @param \Composer\Package\Link $package 64 | * The package to check. 65 | * 66 | * @return bool 67 | * True if the constraint appears to be unpinned. 68 | */ 69 | protected static function packageIsUnpinned(Link $package) { 70 | if ($package->getTarget() == 'drupal/core') { 71 | // Bail out if the patched package is drupal/core since we release with 72 | // each version of core and always ensure core patches still apply. 73 | return FALSE; 74 | } 75 | $constraint = $package->getPrettyConstraint(); 76 | if (preg_match('/[\^~*|]/', $constraint)) { 77 | // If ^, ~, *, or | characters are being used, the dependency is not 78 | // pinned to a specific release. 79 | return TRUE; 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/Generators/SubProfileGenerator.php: -------------------------------------------------------------------------------- 1 | moduleList = $module_list; 60 | 61 | // Drush doesn't support setting $this->destination to 'profiles/custom', 62 | // so we need to use a callback function instead. This is ignored if 63 | // the --directory option is set; in that case, the profile will be 64 | // generated at $custom_directory/$machine_name. 65 | // @see \Drush\Commands\generate\Helper\InputHandler::collectVars() 66 | // @see \Drush\Commands\generate\Helper\InputHandler::getDirectory() 67 | $this->setDestination(function () { 68 | return 'profiles/custom'; 69 | }); 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | protected function interact(InputInterface $input, OutputInterface $output) { 76 | $io = new DrushStyle($input, $output); 77 | 78 | $questions['name'] = new Question('Profile Name'); 79 | $questions['name']->setValidator([Utils::class, 'validateRequired']); 80 | $questions['machine_name'] = new Question('Profile Machine Name (enter for default)'); 81 | $questions['machine_name']->setValidator([Utils::class, 'validateMachineName']); 82 | $questions['description'] = new Question('Enter the description (optional)'); 83 | $questions['install'] = new Question('Additional modules to include (optional), separated by commas (e.g. context, rules, file_entity)', NULL); 84 | $questions['install']->setNormalizer([static::class, 'toArray']); 85 | 86 | $vars = &$this->collectVars($input, $output, $questions); 87 | 88 | if ($io->confirm('Do you want to exclude any components of Lightning?', FALSE)) { 89 | $filter = function ($name) { 90 | return strpos($name, 'lightning_') === 0; 91 | }; 92 | $modules = array_filter($this->moduleList->getAllAvailableInfo(), $filter, ARRAY_FILTER_USE_KEY); 93 | 94 | $map = function (array $info) { 95 | return $info['name']; 96 | }; 97 | $modules = array_map($map, $modules); 98 | 99 | $questions['exclude'] = new ChoiceQuestion('Lightning components to exclude (optional), entered as keys separated by commas (e.g. lightning_media, lightning_layout)', $modules); 100 | $questions['exclude']->setMultiselect(TRUE); 101 | 102 | $this->collectVars($input, $output, $questions); 103 | } 104 | 105 | $info_array = [ 106 | 'name' => $vars['name'], 107 | 'type' => 'profile', 108 | 'description' => '', 109 | 'core_version_requirement' => '^8.8 || ^9', 110 | 'install' => [], 111 | 'themes' => [ 112 | 'bartik', 'seven', 113 | ], 114 | 'base profile' => 'lightning', 115 | 'exclude' => [], 116 | ]; 117 | 118 | if ($vars['description']) { 119 | $info_array['description'] = $vars['description']; 120 | } 121 | if ($vars['install']) { 122 | $info_array['install'] = $vars['install']; 123 | } 124 | if ($vars['exclude']) { 125 | $info_array['exclude'] = is_string($vars['exclude']) 126 | ? static::toArray($vars['exclude']) 127 | : $vars['exclude']; 128 | } 129 | 130 | $info_array = array_filter($info_array); 131 | 132 | $this->addFile() 133 | ->path("{machine_name}/{machine_name}.info.yml") 134 | ->content(Yaml::encode($info_array)); 135 | 136 | $this->addFile() 137 | ->path("{machine_name}/{machine_name}.install") 138 | ->template('install.twig'); 139 | 140 | $this->addFile() 141 | ->path("{machine_name}/{machine_name}.profile") 142 | ->template('profile.twig'); 143 | } 144 | 145 | /** 146 | * Converts a comma-separated string to an array of trimmed values. 147 | * 148 | * @param string $string 149 | * The comma-separated string to split up. 150 | * 151 | * @return string[] 152 | * The comma-separated values, trimmed of white space. 153 | */ 154 | public static function toArray($string) { 155 | return $string ? array_map('trim', explode(',', $string)) : []; 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/Generators/install.twig: -------------------------------------------------------------------------------- 1 | getDefinition('required_module_uninstall_validator') 23 | ->setClass(RequiredModuleUninstallValidator::class); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/ProxyClass/RequiredModuleUninstallValidator.php: -------------------------------------------------------------------------------- 1 | container = $container; 52 | $this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id; 53 | } 54 | 55 | /** 56 | * Lazy loads the real service from the container. 57 | * 58 | * @return object 59 | * Returns the constructed real service. 60 | */ 61 | protected function lazyLoadItself() 62 | { 63 | if (!isset($this->service)) { 64 | $this->service = $this->container->get($this->drupalProxyOriginalServiceId); 65 | } 66 | 67 | return $this->service; 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function validate($module) 74 | { 75 | return $this->lazyLoadItself()->validate($module); 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function setStringTranslation(\Drupal\Core\StringTranslation\TranslationInterface $translation) 82 | { 83 | return $this->lazyLoadItself()->setStringTranslation($translation); 84 | } 85 | 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/RequiredModuleUninstallValidator.php: -------------------------------------------------------------------------------- 1 | appRoot = $app_root; 39 | 40 | if ($translation) { 41 | $this->setStringTranslation($translation); 42 | } 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public static function create(ContainerInterface $container) { 49 | return new static( 50 | $container->get('app.root'), 51 | $container->get('string_translation') 52 | ); 53 | } 54 | 55 | /** 56 | * Converts sub-profile info keys to the 8.6.x API. 57 | * 58 | * @param \Symfony\Component\Console\Style\StyleInterface $io 59 | * The I/O handler. 60 | * 61 | * @update 62 | * 63 | * @ask Do you want to update all sub-profiles to be Drupal 8.6 compatible? 64 | */ 65 | public function updateProfiles(StyleInterface $io) { 66 | $discovery = new ExtensionDiscovery($this->appRoot); 67 | 68 | $profiles = $discovery->scan('profile'); 69 | foreach ($profiles as $profile) { 70 | $info_file = $profile->getPathname(); 71 | 72 | if (is_writable($info_file)) { 73 | $info = file_get_contents($info_file); 74 | 75 | if (strstr($info, 'base profile:')) { 76 | $info = Yaml::decode($info); 77 | 78 | if (is_array($info['base profile'])) { 79 | $info['base profile'] = $info['base profile']['name']; 80 | } 81 | if (isset($info['dependencies'])) { 82 | $info['install'] = $info['dependencies']; 83 | unset($info['dependencies']); 84 | } 85 | 86 | $exclude = []; 87 | if (isset($info['excluded_dependencies'])) { 88 | $exclude = array_merge($exclude, $info['excluded_dependencies']); 89 | unset($info['excluded_dependencies']); 90 | } 91 | if (isset($info['excluded_themes'])) { 92 | $exclude = array_merge($exclude, $info['excluded_themes']); 93 | unset($info['excluded_themes']); 94 | } 95 | if ($exclude) { 96 | $info['exclude'] = $exclude; 97 | } 98 | file_put_contents($info_file, Yaml::encode($info)); 99 | 100 | $message = $this->t('Updated @profile.', [ 101 | '@profile' => $profile->getName(), 102 | ]); 103 | $io->success((string) $message); 104 | } 105 | } 106 | else { 107 | $message = $this->t('Cannot write to @path, skipping.', [ 108 | '@path' => $info_file, 109 | ]); 110 | $io->warning((string) $message); 111 | } 112 | } 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/Update/Update405.php: -------------------------------------------------------------------------------- 1 | moduleInstaller = $module_installer; 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public static function create(ContainerInterface $container) { 37 | return new static( 38 | $container->get('module_installer') 39 | ); 40 | } 41 | 42 | /** 43 | * Enables the Autosave Form and Conflict modules. 44 | * 45 | * @update 46 | * 47 | * @ask Do you want to enable the Autosave Form and Conflict modules? 48 | */ 49 | public function enableAutosaveForm() { 50 | $this->moduleInstaller->install(['autosave_form', 'conflict']); 51 | } 52 | 53 | /** 54 | * Enables the Redirect module. 55 | * 56 | * @update 57 | * 58 | * @ask Do you want to enable the Redirect module? 59 | */ 60 | public function enableRedirect() { 61 | $this->moduleInstaller->install(['redirect']); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /tests/lightning_extender/lightning_extender.info.yml: -------------------------------------------------------------------------------- 1 | name: 'Lightning Extender' 2 | type: profile 3 | core_version_requirement: '^8.8 || ^9' 4 | install: 5 | - ban 6 | themes: 7 | - bartik 8 | - seven 9 | 'base profile': lightning 10 | exclude: 11 | - lightning_search 12 | -------------------------------------------------------------------------------- /tests/lightning_extender/lightning_extender.profile: -------------------------------------------------------------------------------- 1 | container->get('config.factory'); 23 | $config_factory->getEditable('lightning_api.settings') 24 | ->set('entity_json', TRUE) 25 | ->save(); 26 | 27 | // If the samlauth module is installed, ensure that it is configured (in 28 | // this case, using its own test data) to avoid errors when creating user 29 | // accounts in this test. 30 | if ($this->container->get('module_handler')->moduleExists('samlauth')) { 31 | $path = $this->container->get('extension.list.module') 32 | ->getPath('samlauth'); 33 | $data = file_get_contents("$path/test_resources/samlauth.authentication.yml"); 34 | $data = Yaml::decode($data); 35 | 36 | $this->container->get('config.factory') 37 | ->getEditable('samlauth.authentication') 38 | ->setData($data) 39 | ->save(); 40 | } 41 | } 42 | 43 | /** 44 | * Tests viewing a configuration entity as JSON via the API. 45 | */ 46 | public function testViewConfigEntityAsJson() { 47 | $assert_session = $this->assertSession(); 48 | $page = $this->getSession()->getPage(); 49 | 50 | $account = $this->createUser([], NULL, TRUE); 51 | $this->drupalLogin($account); 52 | 53 | $this->drupalGet('/admin/structure/contact'); 54 | $page->clickLink('View JSON'); 55 | $assert_session->statusCodeEquals(200); 56 | 57 | $this->drupalGet('/admin/structure/media'); 58 | $page->clickLink('View JSON'); 59 | $assert_session->statusCodeEquals(200); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /tests/src/ExistingSite/ConfigIntegrityTest.php: -------------------------------------------------------------------------------- 1 | container->get('module_handler')->moduleExists('samlauth')) { 30 | $path = $this->container->get('extension.list.module') 31 | ->getPath('samlauth'); 32 | $data = file_get_contents("$path/test_resources/samlauth.authentication.yml"); 33 | $data = Yaml::decode($data); 34 | 35 | $this->container->get('config.factory') 36 | ->getEditable('samlauth.authentication') 37 | ->setData($data) 38 | ->save(); 39 | } 40 | } 41 | 42 | /** 43 | * Tests configuration values were set correctly during installation. 44 | */ 45 | public function testConfig() { 46 | $assert_session = $this->assertSession(); 47 | 48 | // Assert that all install tasks have done what they should do. 49 | // @see lightning_install_tasks() 50 | $this->assertSame('/node', $this->config('system.site')->get('page.front')); 51 | $this->assertSame(UserInterface::REGISTER_ADMINISTRATORS_ONLY, $this->config('user.settings')->get('register')); 52 | $this->assertTrue(Role::load(Role::AUTHENTICATED_ID)->hasPermission('access shortcuts')); 53 | $theme_config = $this->config('system.theme'); 54 | $this->assertSame('bartik', $theme_config->get('default')); 55 | $this->assertSame('claro', $theme_config->get('admin')); 56 | $this->assertTrue($this->config('node.settings')->get('use_admin_theme')); 57 | $theme_global = $this->config('system.theme.global'); 58 | $this->assertStringContainsString('/lightning/lightning.png', $theme_global->get('logo.path')); 59 | $this->assertStringContainsString('/lightning/favicon.ico', $theme_global->get('favicon.path')); 60 | /** @var \Drupal\views\ViewEntityInterface $view */ 61 | $view = View::load('frontpage'); 62 | $this->assertInstanceOf(View::class, $view); 63 | $display = &$view->getDisplay('default'); 64 | $this->assertTrue($display['display_options']['empty']['area_text_custom']['tokenize']); 65 | $this->assertStringContainsString('/lightning/README.md', $display['display_options']['empty']['area_text_custom']['content']); 66 | 67 | // lightning_core_update_8002() marks a couple of core view modes as 68 | // internal. 69 | $view_modes = EntityViewMode::loadMultiple([ 70 | 'node.rss', 71 | 'node.search_index', 72 | ]); 73 | /** @var \Drupal\Core\Entity\EntityViewModeInterface $view_mode */ 74 | foreach ($view_modes as $view_mode) { 75 | $this->assertTrue($view_mode->getThirdPartySetting('lightning_core', 'internal')); 76 | } 77 | 78 | // All users should be able to view media items. 79 | $this->assertPermissions('anonymous', 'view media'); 80 | $this->assertPermissions('authenticated', 'view media'); 81 | // Media creators can use bulk upload. 82 | $this->assertPermissions('media_creator', 'dropzone upload files'); 83 | 84 | $this->assertEntityExists('node_type', [ 85 | 'page', 86 | 'landing_page', 87 | ]); 88 | $this->assertEntityExists('user_role', [ 89 | 'landing_page_creator', 90 | 'landing_page_reviewer', 91 | 'layout_manager', 92 | 'media_creator', 93 | 'media_manager', 94 | 'page_creator', 95 | 'page_reviewer', 96 | ]); 97 | $this->assertEntityExists('crop_type', 'freeform'); 98 | $this->assertEntityExists('image_style', 'crop_freeform'); 99 | 100 | // Assert that the editorial workflow exists and has the review state and 101 | // transition. 102 | $workflow = Workflow::load('editorial'); 103 | $this->assertInstanceOf(Workflow::class, $workflow); 104 | /** @var \Drupal\workflows\WorkflowTypeInterface $type_plugin */ 105 | $type_plugin = $workflow->getTypePlugin(); 106 | // getState() throws an exception if the state does not exist. 107 | $type_plugin->getState('review'); 108 | // getTransition() throws an exception if the transition does not exist. 109 | /** @var \Drupal\workflows\TransitionInterface $transition */ 110 | $transition = $type_plugin->getTransition('review'); 111 | $this->assertEquals('review', $transition->to()->id()); 112 | $from = array_keys($transition->from()); 113 | $this->assertContainsAll(['draft', 'review'], $from); 114 | $this->assertNotContains('published', $from); 115 | 116 | $creator_permissions = [ 117 | 'use text format rich_text', 118 | 'access image_browser entity browser pages', 119 | ]; 120 | $this->assertPermissions('page_creator', $creator_permissions); 121 | $this->assertPermissions('landing_page_creator', $creator_permissions); 122 | $this->assertPermissions('layout_manager', [ 123 | 'administer node display', 124 | 'configure any layout', 125 | ]); 126 | 127 | $node_types = \Drupal::entityQuery('node_type')->execute(); 128 | 129 | foreach ($node_types as $node_type) { 130 | $this->assertPermissions("{$node_type}_creator", [ 131 | "create $node_type content", 132 | "edit own $node_type content", 133 | "view $node_type revisions", 134 | 'view own unpublished content', 135 | 'create url aliases', 136 | 'access in-place editing', 137 | 'access contextual links', 138 | 'access toolbar', 139 | ]); 140 | $this->assertPermissions("{$node_type}_reviewer", [ 141 | 'access content overview', 142 | "edit any $node_type content", 143 | "delete any $node_type content", 144 | ]); 145 | } 146 | 147 | // Assert that bundled content types have meta tags enabled. 148 | $this->assertMetatag(['page', 'landing_page']); 149 | 150 | // Assert that Lightning configuration pages are accessible to users who 151 | // have an administrative role. 152 | $this->assertForbidden('/admin/config/system/lightning'); 153 | $this->assertForbidden('/admin/config/system/lightning/api'); 154 | $this->assertForbidden('/admin/config/system/lightning/layout'); 155 | $this->assertForbidden('/admin/config/system/lightning/media'); 156 | 157 | $account = $this->createUser([], NULL, TRUE); 158 | $this->drupalLogin($account); 159 | 160 | $this->assertAllowed('/admin/config/system/lightning'); 161 | $assert_session->linkByHrefExists('/admin/config/system/lightning/api'); 162 | $assert_session->linkByHrefExists('/admin/config/system/lightning/layout'); 163 | $assert_session->linkByHrefExists('/admin/config/system/lightning/media'); 164 | $this->assertAllowed('/admin/config/system/lightning/api'); 165 | $this->assertAllowed('/admin/config/system/lightning/api/keys'); 166 | $this->assertAllowed('/admin/config/system/lightning/layout'); 167 | $this->assertAllowed('/admin/config/system/lightning/media'); 168 | } 169 | 170 | /** 171 | * Data provider for testModeratedContentTypes(). 172 | * 173 | * @return array[] 174 | * Sets of arguments to pass to the test method. 175 | */ 176 | public function providerModeratedContentTypes() { 177 | return [ 178 | ['page', 'page_creator'], 179 | ['page', 'administrator'], 180 | ['landing_page', 'landing_page_creator'], 181 | ['landing_page', 'administrator'], 182 | ]; 183 | } 184 | 185 | /** 186 | * Tests that moderated content types do not show a Published checkbox. 187 | * 188 | * @param string $node_type 189 | * The machine name of the content type to test. 190 | * @param string $role 191 | * The machine name of the user role to log in with. 192 | * 193 | * @dataProvider providerModeratedContentTypes 194 | */ 195 | public function testModeratedContentTypes($node_type, $role) { 196 | $assert_session = $this->assertSession(); 197 | 198 | $account = $this->createUser(); 199 | $account->addRole($role); 200 | $account->save(); 201 | 202 | $this->drupalLogin($account); 203 | $this->drupalGet("/node/add/$node_type"); 204 | $assert_session->statusCodeEquals(200); 205 | $assert_session->buttonExists('Save'); 206 | $assert_session->fieldNotExists('status[value]'); 207 | $assert_session->buttonNotExists('Save and publish'); 208 | $assert_session->buttonNotExists('Save as unpublished'); 209 | } 210 | 211 | /** 212 | * Asserts that meta tags are enabled for specific content types. 213 | * 214 | * @param string[] $node_types 215 | * The node type IDs to check. 216 | */ 217 | private function assertMetatag(array $node_types) { 218 | $assert = $this->assertSession(); 219 | 220 | $permissions = array_map( 221 | function ($node_type) { 222 | return "create $node_type content"; 223 | }, 224 | $node_types 225 | ); 226 | $account = $this->createUser($permissions); 227 | $this->drupalLogin($account); 228 | 229 | foreach ($node_types as $node_type) { 230 | $this->assertAllowed("/node/add/$node_type"); 231 | $assert->fieldExists('field_meta_tags[0][basic][title]'); 232 | $assert->fieldExists('field_meta_tags[0][basic][description]'); 233 | } 234 | $this->drupalLogout(); 235 | } 236 | 237 | /** 238 | * Asserts the existence of an entity. 239 | * 240 | * @param string $entity_type 241 | * The entity type ID. 242 | * @param mixed|mixed[] $id 243 | * The entity ID, or a set of IDs. 244 | */ 245 | private function assertEntityExists($entity_type, $id) { 246 | $this->assertContainsAll( 247 | (array) $id, 248 | \Drupal::entityQuery($entity_type)->execute() 249 | ); 250 | } 251 | 252 | /** 253 | * Asserts that a user role has a set of permissions. 254 | * 255 | * @param \Drupal\user\RoleInterface|string $role 256 | * The user role, or its ID. 257 | * @param string|string[] $permissions 258 | * The permission(s) to check. 259 | */ 260 | private function assertPermissions($role, $permissions) { 261 | if (is_string($role)) { 262 | $role = Role::load($role); 263 | } 264 | $this->assertContainsAll((array) $permissions, $role->getPermissions()); 265 | } 266 | 267 | /** 268 | * Asserts that a haystack contains a set of needles. 269 | * 270 | * @param mixed[] $needles 271 | * The needles expected to be in the haystack. 272 | * @param mixed[] $haystack 273 | * The haystack. 274 | */ 275 | private function assertContainsAll(array $needles, array $haystack) { 276 | $diff = array_diff($needles, $haystack); 277 | $this->assertSame([], $diff); 278 | } 279 | 280 | /** 281 | * Asserts that the current user can access a Drupal route. 282 | * 283 | * @param string $path 284 | * The route path to visit. 285 | */ 286 | private function assertAllowed($path) { 287 | $this->drupalGet($path); 288 | $this->assertSession()->statusCodeEquals(200); 289 | } 290 | 291 | /** 292 | * Asserts that the current user cannot access a Drupal route. 293 | * 294 | * @param string $path 295 | * The route path to visit. 296 | */ 297 | private function assertForbidden($path) { 298 | $this->drupalGet($path); 299 | $this->assertSession()->statusCodeEquals(403); 300 | } 301 | 302 | /** 303 | * Returns a config object by its name. 304 | * 305 | * @param string $name 306 | * The name of the config object to return. 307 | * 308 | * @return \Drupal\Core\Config\Config 309 | * The config object. 310 | */ 311 | private function config($name) { 312 | return $this->container->get('config.factory')->getEditable($name); 313 | } 314 | 315 | } 316 | -------------------------------------------------------------------------------- /tests/src/ExistingSite/ViewModeTest.php: -------------------------------------------------------------------------------- 1 | 'node.foobaz', 24 | 'label' => 'Foobaz', 25 | 'targetEntityType' => 'node', 26 | ]); 27 | $view_mode 28 | ->setThirdPartySetting('lightning_core', 'internal', TRUE) 29 | ->setThirdPartySetting('lightning_core', 'description', 'Behold, my glorious view mode.') 30 | ->save(); 31 | } 32 | 33 | /** 34 | * Tests that users are informed about internal view modes. 35 | */ 36 | public function testInternalViewMode() { 37 | $assert_session = $this->assertSession(); 38 | $page = $this->getSession()->getPage(); 39 | 40 | $account = $this->createUser(['administer node display']); 41 | $this->drupalLogin($account); 42 | 43 | $this->drupalGet('/admin/structure/types/manage/page/display'); 44 | $page->checkField('Foobaz'); 45 | $page->pressButton('Save'); 46 | $assert_session->elementTextContains('css', '.messages--status', 'The Foobaz mode now uses custom display settings.'); 47 | $page->find('css', '.messages--status')->clickLink('configure them'); 48 | $assert_session->elementTextContains('css', '.messages--warning', "This display is internal and will not be seen by normal users."); 49 | $assert_session->pageTextContains('Behold, my glorious view mode.'); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function tearDown() { 56 | EntityViewMode::load('node.foobaz')->delete(); 57 | parent::tearDown(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /tests/src/Functional/LightningTest.php: -------------------------------------------------------------------------------- 1 | assertNotEmpty($root); 42 | 43 | // Symlink the sub-profile into a place where Drupal will be able to find 44 | // it. The symlink is deleted in tearDown(). If the symlink cannot be 45 | // created, fail the test. 46 | $target = __DIR__ . '/../../' . $this->profile; 47 | $this->assertDirectoryIsReadable($target); 48 | 49 | $link = "$root/profiles/$this->profile"; 50 | $this->assertDirectoryIsWritable(dirname($link)); 51 | 52 | // symlink() is called strangely in order to evade a too-strict coding 53 | // standards check. 54 | $success = call_user_func('symlink', $target, $link); 55 | $this->assertTrue($success, "Could not symlink $link to $target"); 56 | $this->assertDirectoryIsReadable($target); 57 | 58 | parent::setUp(); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | protected function tearDown() { 65 | unlink("$this->root/profiles/$this->profile"); 66 | parent::tearDown(); 67 | } 68 | 69 | /** 70 | * Tests integrated functionality of the Lightning profile. 71 | * 72 | * Because it takes aeons to install the Lightning profile, or any of its 73 | * descendants, this test only has one public test method, with private helper 74 | * methods covering specific test scenarios. This is done purely for 75 | * performance reasons. 76 | */ 77 | public function testLightning() { 78 | // Test that the sub-profile was installed... 79 | $this->assertSame('lightning_extender', $this->container->getParameter('install_profile')); 80 | 81 | $module_list = $this->container->get('extension.list.module')->getAllInstalledInfo(); 82 | // ...and that the changes it makes are reflected in the system. 83 | $this->assertArrayHasKey('ban', $module_list); 84 | $this->assertArrayNotHasKey('lightning_search', $module_list); 85 | 86 | // Lightning's configuration overrides should have taken effect. 87 | $this->assertSame(UserInterface::REGISTER_ADMINISTRATORS_ONLY, $this->config('user.settings')->get('register')); 88 | 89 | $this->doModerationDashboardTest(); 90 | $this->doTextBlockTest(); 91 | } 92 | 93 | /** 94 | * Tests that the moderation dashboard works. 95 | */ 96 | private function doModerationDashboardTest() { 97 | $account = $this->drupalCreateUser(['use moderation dashboard']); 98 | $this->drupalLogin($account); 99 | $this->drupalGet('/user/' . $account->id() . '/moderation/dashboard'); 100 | $this->assertSession()->statusCodeEquals(200); 101 | 102 | $this->drupalLogout(); 103 | } 104 | 105 | /** 106 | * Tests the 'text' custom block type that ships with Lightning. 107 | */ 108 | private function doTextBlockTest() { 109 | $assert_session = $this->assertSession(); 110 | 111 | // Assert that basic blocks expose a Body field. 112 | $account = $this->createUser(['administer blocks']); 113 | $this->drupalLogin($account); 114 | 115 | $this->drupalGet('/block/add/text'); 116 | $assert_session->statusCodeEquals(200); 117 | $assert_session->fieldExists('Body'); 118 | 119 | $this->drupalLogout(); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /tests/src/Functional/SubprofileGeneratorTest.php: -------------------------------------------------------------------------------- 1 | [ 36 | 'answers' => [ 37 | 'install' => ['consumers'], 38 | ], 39 | ], 40 | 'with exclusions' => [ 41 | 'answers' => [ 42 | 'exclude' => 'lightning_search, lightning_media_instagram', 43 | ], 44 | ], 45 | 'inclusions and exclusions' => [ 46 | 'answers' => [ 47 | 'install' => ['consumers', 'book'], 48 | 'exclude' => 'lightning_media_instagram', 49 | ], 50 | ], 51 | 'default answers' => [ 52 | 'answers' => [], 53 | ], 54 | 'specific machine name and description' => [ 55 | 'answers' => [ 56 | 'machine_name' => 'something_different', 57 | 'description' => 'This profile rules.', 58 | ], 59 | ], 60 | ]; 61 | } 62 | 63 | /** 64 | * Tests the lightning-subprofile generator for Drush. 65 | * 66 | * @param array $answers 67 | * The answers to the generator's questions, keyed by the questions' machine 68 | * names. 69 | * 70 | * @see \Drupal\lightning\Generators\SubProfileGenerator::interact() 71 | * 72 | * @dataProvider provider 73 | */ 74 | public function test(array $answers = []) { 75 | $drupal_root = $this->getDrupalRoot(); 76 | $this->assertFileExists("$drupal_root/../composer.json", 'A composer.json file must exist in the directory above the Drupal root.'); 77 | 78 | // We need Drush to run commands defined by the Lightning profile, but we 79 | // don't want to actually install the Lightning profile into the test site 80 | // because it takes freaking forever. So, this hack is a quick way to expose 81 | // our generator to Drush. 82 | $this->config('core.extension') 83 | ->set('module.lightning', 0) 84 | ->set('profile', 'lightning') 85 | ->save(); 86 | 87 | $answers += [ 88 | 'name' => 'Wizards', 89 | 'machine_name' => 'wizards', 90 | // Ideally, these would be empty strings, but it seems that Symfony 91 | // Console (or possibly Drush) mangles empty strings in JSON-encoded data, 92 | // results in the --answers option containing invalid JSON. Setting these 93 | // to NULL is a workaround, but it achieves the desired result. 94 | 'description' => NULL, 95 | 'install' => NULL, 96 | 'exclude' => NULL, 97 | ]; 98 | $options = [ 99 | 'answers' => Json::encode($answers), 100 | // Generate the profile in the test site's directory, so that it will be 101 | // automatically cleaned up when the test is done. 102 | 'directory' => "$drupal_root/$this->siteDirectory/profiles/custom", 103 | ]; 104 | 105 | // The generator will ask if we want to exclude any components. We need to 106 | // explicitly answer the prompt either way, since we are running the command 107 | // quasi-interactively. 108 | if ($answers['exclude']) { 109 | $options['yes'] = NULL; 110 | } 111 | else { 112 | $options['no'] = NULL; 113 | } 114 | 115 | $machine_name = $answers['machine_name']; 116 | // DrushTestTrait::drush() will invoke the command with the --no-interaction 117 | // option, bypassing the generator's interact() method -- which contains all 118 | // the logic we want to test! The SHELL_INTERACTIVE environment variable 119 | // forces Symfony Console to run the method anyway. 120 | $this->drush('generate', ['lightning-subprofile'], $options, NULL, "$drupal_root/..", 0, NULL, [ 121 | 'SHELL_INTERACTIVE' => 1, 122 | ]); 123 | 124 | // Ensure that the new profile is picked up by the extension system. Because 125 | // ExtensionDiscovery does not expect the available extensions to change in 126 | // the course of a single request, and has no public method to reset its 127 | // internal static cache, we need to pry open its static $files property 128 | // and force-reset it. 129 | $reflector = new \ReflectionClass('\Drupal\Core\Extension\ExtensionDiscovery'); 130 | $property = $reflector->getProperty('files'); 131 | $property->setAccessible(TRUE); 132 | $property->setValue([]); 133 | 134 | $extension = $this->container->get('extension.list.profile') 135 | ->reset() 136 | ->get($machine_name); 137 | 138 | // Get the raw info for the generated profile. The profile list's 139 | // getExtensionInfo() method would merge in default values from the 140 | // extension system and ancestral profiles, which interfere with these 141 | // assertions. 142 | $info = file_get_contents($extension->getPathname()); 143 | $info = Yaml::decode($info); 144 | 145 | // Assert the constant values. 146 | $this->assertSame($answers['name'], $info['name']); 147 | $this->assertSame('profile', $info['type']); 148 | $this->assertArrayNotHasKey('core', $info); 149 | $this->assertNotEmpty($info['core_version_requirement']); 150 | $this->assertSame(['bartik', 'seven'], $info['themes']); 151 | $this->assertSame('lightning', $info['base profile']); 152 | 153 | // Assert the stuff that can change depending on user input. 154 | if ($answers['description']) { 155 | $this->assertSame($answers['description'], $info['description']); 156 | } 157 | else { 158 | $this->assertArrayNotHasKey('description', $info); 159 | } 160 | 161 | if ($answers['install']) { 162 | $this->assertSame($answers['install'], $info['install']); 163 | } 164 | else { 165 | $this->assertArrayNotHasKey('install', $info); 166 | } 167 | 168 | if ($answers['exclude']) { 169 | $exclude = SubProfileGenerator::toArray($answers['exclude']); 170 | 171 | foreach ($exclude as $module) { 172 | $this->assertContains($module, $info['exclude']); 173 | } 174 | } 175 | else { 176 | $this->assertArrayNotHasKey('exclude', $info); 177 | } 178 | 179 | // Load up the new profile to ensure it's valid PHP and includes an install 180 | // hook. 181 | $module_handler = $this->container->get('module_handler'); 182 | $module_handler->addProfile($machine_name, $extension->getPath()); 183 | $module_handler->load($machine_name); 184 | $module_handler->loadInclude($machine_name, 'install'); 185 | $this->assertTrue(function_exists($machine_name . '_install')); 186 | 187 | // Ensure the .profile file at least includes a comment. 188 | $file = $module_handler->getModule($machine_name)->getExtensionPathname(); 189 | $this->assertNotEmpty(file_get_contents($file)); 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /tests/src/Functional/UpdatePath3xTest.php: -------------------------------------------------------------------------------- 1 | databaseDumpFiles = [ 22 | __DIR__ . '/../../fixtures/3.4.0.php.gz', 23 | ]; 24 | } 25 | 26 | /** 27 | * Tests updating from Lightning 3.4.0 via the UI. 28 | */ 29 | public function test() { 30 | require_once __DIR__ . '/../../update.php'; 31 | $this->getRandomGenerator()->image('public://star_2.png', '16x16', '16x16'); 32 | $this->runUpdates(); 33 | $this->drush('update:lightning', [], ['yes' => NULL]); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /tests/src/Functional/UpdatePath4xTest.php: -------------------------------------------------------------------------------- 1 | databaseDumpFiles = [ 23 | __DIR__ . '/../../fixtures/4.1.0-beta1.php.gz', 24 | ]; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function replaceUser1() { 31 | // When the parent method re-saves the user account, an obscure code path 32 | // through Layout Builder and Lightning Media is triggered, resulting in 33 | // the use of an old media source plugin ID that will not exist after the 34 | // updates are run. Normally, Lightning Media accounts for this by aliasing 35 | // the old plugin ID, but that is only done if the kernel is an 36 | // UpdateKernel...which is NOT the case in this test's memory space. So, 37 | // although this is dirty and brittle, I don't know of a better way around 38 | // it; we simply need to have a (mocked) UpdateKernel in place when the 39 | // media source plugin definition cache is rebuilt. 40 | $kernel = $this->container->get('kernel'); 41 | $this->container->set('kernel', $this->prophesize(UpdateKernel::class)->reveal()); 42 | 43 | // Clear the cached plugin definitions, then rebuild them. The fact that 44 | // an UpdateKernel is present means that the old media source plugin ID 45 | // should be aliased and cached correctly. 46 | // @see lightning_media_instagram_media_source_info_alter() 47 | $plugin_manager = $this->container->get('plugin.manager.media.source'); 48 | $plugin_manager->clearCachedDefinitions(); 49 | $plugin_manager->getDefinitions(); 50 | 51 | // Restore the real kernel and proceed merrily on our way. 52 | $this->container->set('kernel', $kernel); 53 | parent::replaceUser1(); 54 | } 55 | 56 | /** 57 | * Tests updating from Lightning 4.1.0-beta1 via the UI. 58 | */ 59 | public function test() { 60 | require_once __DIR__ . '/../../update.php'; 61 | $this->getRandomGenerator()->image('public://star.png', '16x16', '16x16'); 62 | $this->runUpdates(); 63 | $this->drush('update:lightning', [], ['yes' => NULL]); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /tests/src/Kernel/Update405Test.php: -------------------------------------------------------------------------------- 1 | container->get('module_handler'); 26 | 27 | $this->assertFalse($moduleHandler->moduleExists('autosave_form')); 28 | $this->assertFalse($moduleHandler->moduleExists('conflict')); 29 | $this->assertFalse($moduleHandler->moduleExists('redirect')); 30 | 31 | $update = Update405::create($this->container); 32 | $update->enableAutosaveForm(); 33 | $update->enableRedirect(); 34 | 35 | $moduleHandler = $this->container->get('module_handler'); 36 | 37 | $this->assertTrue($moduleHandler->moduleExists('autosave_form')); 38 | $this->assertTrue($moduleHandler->moduleExists('conflict')); 39 | $this->assertTrue($moduleHandler->moduleExists('redirect')); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /tests/travis/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # NAME 4 | # install.sh - Install Travis CI dependencies 5 | # 6 | # SYNOPSIS 7 | # install.sh 8 | # 9 | # DESCRIPTION 10 | # Creates the test fixture. 11 | 12 | cd "$(dirname "$0")" 13 | 14 | # Reuse ORCA's own includes. 15 | source ../../../orca/bin/travis/_includes.sh 16 | 17 | # Handle the special case of scanning for deprecations in contrib dependencies. 18 | # We need to ensure that the components are included as part of the SUT, 19 | # but none of the other Acquia product modules are. 20 | if [[ "$ORCA_JOB" == "DEPRECATED_CODE_SCAN_W_CONTRIB" ]]; then 21 | export ORCA_PACKAGES_CONFIG=../lightning/tests/packages.yml 22 | orca fixture:init -f --sut="$ORCA_SUT_NAME" --dev --no-site-install 23 | exit 0 24 | fi 25 | 26 | # When testing the SUT in isolation using dev package versions, treat the 27 | # components as part of the SUT, to be installed in an isolated (SUT-only) 28 | # fixture. 29 | if [[ "$ORCA_JOB" == "ISOLATED_TEST_ON_CURRENT_DEV" ]]; then 30 | export ORCA_PACKAGES_CONFIG=../lightning/tests/packages.yml 31 | orca fixture:init -f --sut="$ORCA_SUT_NAME" --dev --profile=lightning 32 | else 33 | # Run ORCA's standard installation script. 34 | ../../../orca/bin/travis/install.sh 35 | fi 36 | 37 | # If there is no fixture, there's nothing else for us to do. 38 | [[ -d "$ORCA_FIXTURE_DIR" ]] || exit 0 39 | 40 | # Add testing dependencies. 41 | composer -d"$ORCA_FIXTURE_DIR" require --dev weitzman/drupal-test-traits:dev-master phpspec/prophecy-phpunit:^2 42 | -------------------------------------------------------------------------------- /tests/update.php: -------------------------------------------------------------------------------- 1 | getEditable('core.extension') 12 | ->clear('module.libraries') 13 | ->clear('module.openapi_redoc') 14 | ->save(); 15 | 16 | Drupal::configFactory() 17 | ->getEditable('libraries.settings') 18 | ->delete(); 19 | 20 | foreach (['default', 'embedded'] as $view_mode) { 21 | Drupal::configFactory() 22 | ->getEditable("core.entity_view_display.media.instagram.$view_mode") 23 | ->set('content.embed_code.settings.hidecaption', NULL) 24 | ->save(); 25 | } 26 | 27 | Drupal::keyValue('system.schema')->deleteMultiple([ 28 | 'libraries', 29 | 'openapi_redoc', 30 | ]); 31 | 32 | $view = View::load('media'); 33 | $display = &$view->getDisplay('default'); 34 | $display['display_options']['fields']['media_bulk_form']['plugin_id'] = 'bulk_form'; 35 | $view->save(); 36 | 37 | Drupal::service('extension.list.profile')->reset(); 38 | --------------------------------------------------------------------------------