diff --git a/sites/all/modules/contrib/admin/domain/.travis.yml b/sites/all/modules/contrib/admin/domain/.travis.yml new file mode 100644 index 000000000..b77fae3a0 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/.travis.yml @@ -0,0 +1,99 @@ +language: php +sudo: false + +matrix: + fast_finish: true + include: + - env: DRUPAL=8.4.x + php: 7.0 + - env: DRUPAL=8.4.x + php: 5.6 + - env: DRUPAL=8.4.x + php: 5.5 + - env: DRUPAL=8.5.x + php: 7.1 + - env: DRUPAL=8.5.x + php: 7.0 + - env: DRUPAL=8.5.x + php: 5.6 + - env: DRUPAL=8.5.x + php: 5.5 + +addons: + hosts: + - example.com + - one.example.com + - two.example.com + - three.example.com + - four.example.com + - five.example.com + +cache: + directories: + - $HOME/.composer/cache + +env: + global: + # add composer's global bin directory to the path + # see: https://github.com/drush-ops/drush#install---composer + - PATH="$PATH:$HOME/.composer/vendor/bin" + +mysql: + database: domain + username: root + encoding: utf8 + +notifications: + email: false + +before_install: + # Remove Xdebug. Not an issue for PHP 7. + - phpenv config-rm xdebug.ini || true + + - composer self-update + + # Install Drush. + - composer global require --no-interaction drush/drush:8.* + +install: + # Optimize MySQL timeout and max packet size. + - echo "mysql.connect_timeout=3000" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - echo "default_socket_timeout=3000" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - mysql -e 'create database domain' + - mysql -e "SET GLOBAL wait_timeout = 36000;" + - mysql -e "SET GLOBAL max_allowed_packet = 134209536;" + +before_script: + + # Remember the current rules test directory for later use in the Drupal installation. + - TESTDIR=$(pwd) + # Navigate out of module directory to prevent blown stack by recursive module lookup. + - cd .. + + # Download Drupal 8 core. + - travis_retry drush dl drupal-$DRUPAL --drupal-project-rename=drupal + - cd drupal + + # Make the module appear in the correct place + - ln -s $TESTDIR modules/domain + + # Install drupal default profile + - /usr/bin/env PHP_OPTIONS="-d sendmail_path=$(which true)" drush --yes --verbose site-install minimal --db-url=mysql://root:@127.0.0.1/domain + - drush --yes en simpletest domain domain_access domain_alias domain_config domain_source + - drush cr + + # Start a web server on port 8080 in the background. + - nohup php -S 0.0.0.0:8080 > /dev/null 2>&1 & + + # Wait until the web server is responding. + - until curl -s example.com:8080; do true; done > /dev/null + +script: + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_access + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_alias + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain_alias + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_config + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_content + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_source diff --git a/sites/all/modules/contrib/admin/domain/CHANGELOG.md b/sites/all/modules/contrib/admin/domain/CHANGELOG.md new file mode 100644 index 000000000..687fc5135 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/CHANGELOG.md @@ -0,0 +1,126 @@ +Changelog +===== + +31-JUL-2016 8.x-1.0-alpha1 +22-AUG-2016 8.x-1.0-alpha2 +30-AUG-2016 8.x-1.0-alpha3 +01-SEP-2016 8.x-1.0-alpha4 +25-OCT-2016 8.x-1.0-alpha5 +20-NOV-2016 8.x-1.0-alpha6 +06-DEC-2016 Adds the domain_alpha module to handle critical pre-release updates. +13-DEC-2016 8.x-1.0-alpha7 +12-MAR-2017 8.x-1.0-alpha8 +23-APR-2017 8.x-1.0-alpha9 +01-DEC-2017 8.x-1.0-alpha10 +19-DEC-2017 8.x-1.0-alpha11 + +Status +==== + +The 8.x-1.x version is a ground up rewrite of a module originally written for +Drupal 5. + +The following feature sets are considered critical for each release stage. Items +marked with [x] are considered complete. + +# Alpha +- [x] Domain entity for storage and configuration +- [x] Domain negotiation for active requests +- [x] API for modifying active domain requests +- [x] API for modifying paths per domain +- [x] Mechanism for access restriction inactive domains +- [x] Form for adding / editing / deleting domain records +- [x] Validation of domain record patterns +- [x] Domain server information block +- [x] Domain switcher block +- [x] Domain context for block visibility +- [x] Entity reference support, with an API for altering entity lists +- [x] Alias registration and negotiation +- [x] Alias validation +- [x] Configuration overrides per domain +- [x] Language-aware configuration overrides +- [x] Enable node access control via domain references (Domain Access) +- [x] Allow nodes to be assigned to domains +- [x] Allow users to be assigned as domain editors +- [x] Set permissions for content editing based on domain +- [x] Restrict publication options based on user domains +- [x] Support the all affiliates concept of publication +- [x] Support the all affiliates concept for editors +- [x] Test and document cross-domain logins +- [x] Make language optional for domain overrides +- [x] Use domain source for path rewrites +- [x] Finish the path alter behavior +- [x] Catalog @TODO items as issues +- [x] Drupal cache system makes it difficult to serve different homepage for each domain +- [x] Provide common views support for attached fields +- [x] Recreate the Domain Content module with bulk operations +- [x] Add help text to domain overview screen +- [x] Views argument handler to show proper title. +- [x] Ensure unique numeric ids for use with node access +- [x] Invalidate cache on Domain save +- [x] Invalidate render cache on Alias save +- [x] DomainConfigOverrider returns empty $overrides +- [x] Allow non-ascii domains and aliases +- [x] The domain_validate hook needs tests +- [x] Allow partial config loading + +# Beta +- [x] Actions for domain operations +- [x] Drush support for domain operations +- [x] Replace / inject the storage manager in DomainAliasLoader. +- [x] Replace / inject the storage manager in domainStorage. +- [x] Write tests for Domain Content. +- [x] Views access handler for domain content. +- [x] Restrict Domain Source options using JS +- [x] Handle site name overrides -- perhaps as a new field? +- [x] Restore the `domain_grant_all` permission? +- [x] Domain token support +- [x] Module configurations + - [x] Allow configuration of access-exempt paths for inactive domains + - [x] www prefix handling + - [x] Add domain-specific CSS classes + - [x] Path matching for URL rewrites? + - [x] Allow non-ascii characters in domains +- [x] Recreate the Domain Nav module +- [x] Allow selective access to domain record editing +- [x] Allow access to actions based on assigned domain permissions +- [x] Tests for all module hooks +- [x] Proper tests for domain record validation +- [x] Check test logic in testDomainAliasNegotiator() +- [x] Test that sort logic in DomainAliasLoader matches what is documented +- [x] Error handling in DomainAliasForm +- [x] Error checking in DomainAliasController +- [x] Deprecated methods in DomainAliasController +- [x] Error reporting in `domain_alias_domain_request_alter()` +- [x] Ensure completeness of DomainAccessPermissionsTest +- [x] Check module setup behavior in tests +- [x] Make all affiliates default value configurable +- [x] Review drupalUserIsLoggedIn() hack +- [x] Review DomainNegotiatorTest for completeness +- [x] Review core note in DomainEntityReferenceTest +- [x] Expand DomainActionsTest +- [x] DomainViewBuilder review +- [x] Dependency Injection in DomainValidator +- [x] Inject the module handler service in DomainListBuilder::getOperations() +- [x] `drush_domain_generate_domains()` has odd counting logic +- [x] Separate permissions for Domain Alias +- [x] Check loader logic in the DomainSource PathProcessor +- [x] Check loader logic in Domain Access `node_access` +- [x] Check id logic in Domain Alias list controller +- [x] Check domain responses on configuration forms +- [ ] Remove deprecated `entity_get_form_display` +- [ ] Implement theme functions or twig templates where proper +- [ ] Advanced drush integration / complete labelled tasks +- [ ] Test cron handling +- [ ] Caching strategies in DomainNegotiator +- [ ] Caching strategies in DomainConfigOverrides +- [ ] Cache in the DomainAccessManager +- [o] Recreate the Domain Theme module -- see https://www.drupal.org/project/domain_theme_switch + +# Final +- [ ] Security review +- [ ] Provide an upgrade path from 6.x +- [ ] Provide an upgrade path from 7.x-3.x +- [x] Remove calls to deprecated methods / classes +- [ ] Remove unnecessary use statements +- [ ] Support Tour module diff --git a/sites/all/modules/contrib/admin/domain/LICENSE b/sites/all/modules/contrib/admin/domain/LICENSE new file mode 100644 index 000000000..6b5dc9742 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/LICENSE @@ -0,0 +1,339 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Domain module for Drupal port to Drupal 8. + Copyright (C) 2013 Ken Rickard + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/contrib/admin/domain/LICENSE.txt b/sites/all/modules/contrib/admin/domain/LICENSE.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/contrib/admin/domain/README.md b/sites/all/modules/contrib/admin/domain/README.md new file mode 100644 index 000000000..67f3c8d85 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/README.md @@ -0,0 +1,244 @@ +Domain +====== + +The Domain module suite lets you share users, content, and configuration across a group of domains from a single installation and database. + +Current Status +------ + +Domain module for Drupal port to Drupal 8, under active development. + +Active branch is 8-x.1-x. Begin any forks from there. + +The underlying API is stable, and it's currently usable for access control. +The configuration supports manual editing. Themes should work. Views and Bulk +Operations are not yet supported. + +For a complete feature status list, see [CHANGELOG.md](https://github.com/agentrickard/domain/blob/8.x-1.x/CHANGELOG.md) + +Included modules +------- + +* *Domain* + The core module. Domain provides means for registering multiple domains within a + single Drupal installation. It allows users to be assigned as domain administrators, + provides a Block and Views display context, and creates a default entity reference + field for use by other modules. + +* *Domain Access* + Provides node access controls based on domains. (This module contains much of the + Drupal 7 functionality). It allows users to be assigned as editors of content per-domain, + sets content visibility rules, and provides Views integration for content. + +* *Domain Alias* + Allows multiple hostnames to be pointed to a single registered domain. These aliases + can include wildcards (such as *.example.com) and may be configured to redirect to + their canonical domain. Domain Alias also allows developers to register aliases per + `environment`, so that different hosts are used consistently across development + environments. See the README file for Domain Alias for more information. + +* *Domain Alpha* + Provides limited alpha-to-alpha updates. Recommended. + +* *Domain Config* + Provides a means for changing configuration settings on a per-domain basis. See the + README for Domain Config for more information. + +* *Domain Content* + Provides content overview pages on a per-domain basis, so that editors may review + content assigned to specific domains. This module is a series of Views. + +* *Domain Source* + Allows content to be assigned a canonical domain when writing URLs. Domain Source will + ensure that content that appears on multiple domains always links to one URL. See + the module's README for more information. + + +Alpha release updates +------ + +A limited set of updates are provided for major architecture changes during the +alpha release. + +You can run these updates by enabling the `domain_alpha` module and running +Drupal's database updates. The updates and affected versions are: + +* Update 8001: Affects versions of Alpha6 or lower. + +Implementation Notes +====== + +Cross-domain logins +------ + +To use cross-domain logins, you must now set the *cookie_domain* value in +*sites/default/services.yml*. + +To do so, clone `default.services.yml` to `services.yml` and change the +`cookie_domain` value to match the root hostname of your sites. Note that +cross-domain login requires the sharing of a top-level domain, so a setting like +`.example.com` will work for all `example.com` subdomains. + +Example: + +``` +cookie_domain: '.example.com' +``` + +See https://www.drupal.org/node/2391871. + +Cross-Site HTTP requests (CORS) +------ +As of Drupal 8.2, it's possible to allow a particular site to enable CORS for responses +served by Drupal. + +In the case of Domain, allowing CORS may remove AJAX errors caused when using some forms, +particularly entity references, when the AJAX request goes to another domain. + +This feature is not enabled by default because there are security consequences. See +https://www.drupal.org/node/2715637 for more information and instructions. + +To enable CORS for all domains, copy `default.services.yml` to `services.yml` and enable +the following lines: + +``` + # Configure Cross-Site HTTP requests (CORS). + # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS + # for more information about the topic in general. + # Note: By default the configuration is disabled. + cors.config: + enabled: false + # Specify allowed headers, like 'x-allowed-header'. + allowedHeaders: [] + # Specify allowed request methods, specify ['*'] to allow all possible ones. + allowedMethods: [] + # Configure requests allowed from specific origins. + allowedOrigins: ['*'] + # Sets the Access-Control-Expose-Headers header. + exposedHeaders: false + # Sets the Access-Control-Max-Age header. + maxAge: false + # Sets the Access-Control-Allow-Credentials header. + supportsCredentials: false +``` + +The key here is setting `enabled` to `false`. + +Trusted host settings +------ + +If using the trusted host security setting in Drupal 8, be sure to add each domain +and alias to the pattern list. For example: + +``` +$settings['trusted_host_patterns'] = array( + '^.+\.example\.org$', + '^myexample\.com$', + '^myexample\.dev$', + '^localhost$', +); +``` + +We *strongly encourage* the use of trusted host settings. When Domain issues a redirect, +it will check the domain hostname against these settings. Any redirect that does not +match the trusted host settings will be denied and throw an error. + +See https://www.drupal.org/node/1992030 for more information. + +Configuring domain records +------- +To create a domain record, you must provide the following information: + +* A unique *hostname*, which may include a port. (Therefore, example.com and +example.com:8080 are considered different.) The hostname may only contain +alphanumeric characters, dashes, dots, and one colon. If you wish to use +international domain names, toggle the `Allow non-ASCII characters in domains + and aliases.` setting. +* A *machine_name* that must be unique. This value will be autogenerated and +cannot be edited once created. +* A *name* to be used in lists of domains. +* A URL scheme, used for writing links to the domain. The scheme may be `http`, +`https`, or `variable`. If `variable` is used, the scheme will be inherited from +the server or request settings. This option is good if your test environments +do not have secure certificates but your production environment does. +* A *status* indicating `active` or `inactive`. Inactive domains may only be +viewed by users with permission to `view inactive domains` all other users will +be redirected to the default domain (see below). +* The *weight* to be used when sorting domains. These values autoincrement as +new domains are created. +* Whether the domain is the *default* or not. Only one domain can be set as + `default`. The default domain is used for redirects in cases where other + domains are either restricted (inactive) or fail to load. This value can be + reassigned after domains are created. + +Domain records are *configuration entities*, which means they are not stored in +the database nor accessible to Views by default. They are, however, exportable +as part of your configuration. + +Domains and caching +------ + +If some variable changes are not picked up when the page renders, you may need +add domain-sensitivity to the site's cache. + +To do so, clone `default.services.yml` to `services.yml` (if you have not +already done so) and change the `required_cache_contexts` value to: + +```YAML + required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions', 'url.site'] +``` + +The addition of `url.site` should provide the domain context that the cache +layer requires. + +For developers, see also the information in the Domain Alias README. + +Contributing +==== + +If you'd like to contribute, please do. Github forks and pull requests are preferable. +If you prefer a patch-based workflow, you can attach patches to GitHub issues or Drupal.org +issues. If you open a Drupal.org issue, please link to it from the appropriate GitHub +issue. + +The GitHub issues are grouped under three milestones: + +1. Alpha -- items required for a test release. When this is complete, we will roll an +alpha1 release on Drupal.org. +2. Beta -- items considered critical features for a release. When complete, we will roll +a beta release on Drupal.org. +3. Final -- items required for a stable, secure release on Drupal.org. + +We would like to tackle issues in that order, but feel free to work on what motivates you. + +Testing +==== + +@zerolab built a Travis definition file for automated testing! That means all pull requests will automatically run tests! + +[![Build Status](https://travis-ci.org/agentrickard/domain.svg?branch=8.x-1.x)](https://travis-ci.org/agentrickard/domain) + +The module does have solid test coverage, and complete coverage is required for release. +Right now, we mostly use SimpleTest, because it is most familiar, and much of our +testing is about browser and http behavior. + +If you file a pull request or patch, please (at a minimum) run the existing tests to check +for failures. Writing additional tests will greatly speed completion, as I won't commit +code without test coverage. + +New tests should be written as Functional, Kernel, or Unit tests. Conversion patches that +move Simpletests to Functional tests are welcome. + +Because Domain requires varying http host requests to test, we can't normally use the Drupal.org +testing infrastructure. (This may change, but we are not counting on it.) + +To setup a proper environment locally, you need multiple or wilcard domains configured to +point to your drupal instance. I use variants of `example.com` for local tests. See +`DomainTestBase` for documentation. Domain testing should work with root hosts other than +`example.com`, though we also expect to find the subdomains `one.*, two.*, three.*, four.*, five.*` +in most test cases. See `DomainTestBase::domainCreateTestDomains()` for the logic. + +When running tests, you normally need to be on the default domain. + +If anyone is capable of building a vagrant box to simplify testing, that would be ideal. + diff --git a/sites/all/modules/contrib/admin/domain/composer.json b/sites/all/modules/contrib/admin/domain/composer.json new file mode 100644 index 000000000..c5d5ff1d6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/composer.json @@ -0,0 +1,17 @@ +{ + "name": "drupal/domain", + "description": "Creates domain records within a Drupal installation.", + "type": "drupal-module", + "license": "GPL-2.0+", + "authors": [ + { + "name": "Ken Rickard", + "email": "agentrickard@gmail.com" + } + ], + "support": { + "issues": "http://drupal.org/project/issues/domain", + "irc": "http://irc.freenode.org/drupal-contribute", + "source": "http://cgit.drupalcode.org/domain" + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/domain.settings.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/domain.settings.yml new file mode 100644 index 000000000..946b224db --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/domain.settings.yml @@ -0,0 +1,4 @@ +allow_non_ascii: false +www_prefix: false +login_paths: '/user/login\r\n/user/password' +css_classes: '' diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/field.field.user.user.field_domain_admin.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/field.field.user.user.field_domain_admin.yml new file mode 100644 index 000000000..e70c002b4 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/field.field.user.user.field_domain_admin.yml @@ -0,0 +1,24 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_domain_admin + module: + - user +id: user.user.field_domain_admin +field_name: field_domain_admin +entity_type: user +bundle: user +label: 'Domain administrator' +description: 'Select the domains this user may administer.' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'domain:domain' + target_bundles: null + sort: + field: weight + direction: ASC +field_type: entity_reference diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/field.storage.user.field_domain_admin.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/field.storage.user.field_domain_admin.yml new file mode 100644 index 000000000..86ec5b253 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/field.storage.user.field_domain_admin.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - domain + - user +id: user.field_domain_admin +field_name: field_domain_admin +entity_type: user +type: entity_reference +settings: + target_type: domain +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_default_action.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_default_action.yml new file mode 100644 index 000000000..c7bf8b9c2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_default_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - domain +id: domain_default_action +label: 'Set default domain record' +type: domain +plugin: domain_default_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_delete_action.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_delete_action.yml new file mode 100644 index 000000000..8f1e94d57 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_delete_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - domain +id: domain_delete_action +label: 'Delete domain record' +type: domain +plugin: domain_delete_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_disable_action.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_disable_action.yml new file mode 100644 index 000000000..b9817b947 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_disable_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - domain +id: domain_disable_action +label: 'Disable domain record' +type: domain +plugin: domain_disable_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_enable_action.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_enable_action.yml new file mode 100644 index 000000000..c23ede79b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_enable_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - domain +id: domain_enable_action +label: 'Enable domain record' +type: domain +plugin: domain_enable_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain/config/schema/domain.schema.yml b/sites/all/modules/contrib/admin/domain/domain/config/schema/domain.schema.yml new file mode 100644 index 000000000..b068fdf2e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/schema/domain.schema.yml @@ -0,0 +1,85 @@ +# Schema for the configuration files of the Domain module. +domain.settings: + type: config_object + label: 'Domain settings' + mapping: + allow_non_ascii: + type: boolean + label: 'Allow non-ASCII characters' + www_prefix: + type: boolean + label: 'Ignore www prefix when negotiating domains' + login_paths: + type: text + label: 'Paths that should be accessible for inactive domains' + css_classes: + type: text + label: 'Custom css classes to apply to the body tag of each domain' + +domain.record.*: + type: config_entity + label: 'Domain record' + mapping: + id: + type: string + label: 'ID' + uuid: + type: string + label: 'ID' + domain_id: + type: integer + label: 'Domain ID' + hostname: + type: string + label: 'Hostname' + name: + type: label + label: 'Name' + scheme: + type: string + label: 'Scheme' + status: + type: boolean + label: 'Status' + weight: + type: integer + label: 'Weight' + is_default: + type: boolean + label: 'Default domain' + +action.configuration.domain_default_action: + type: action_configuration_default + label: 'Set default domain record' +action.configuration.domain_delete_action: + type: action_configuration_default + label: 'Delete domain record' +action.configuration.domain_disable_action: + type: action_configuration_default + label: 'Disable domain record' +action.configuration.domain_enable_action: + type: action_configuration_default + label: 'Enable domain record' + +block.settings.domain_nav_block: + type: block_settings + label: 'Domain navigation block' + mapping: + link_options: + type: string + label: 'Link paths' + link_theme: + type: string + label: 'Link theme' + link_label: + type: string + label: 'Link text' + +condition.plugin.domain: + type: condition.plugin + mapping: + domains: + type: sequence + sequence: + type: string + diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.api.php b/sites/all/modules/contrib/admin/domain/domain/domain.api.php new file mode 100644 index 000000000..0b98801e6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.api.php @@ -0,0 +1,138 @@ +addProperty('myvar', 'mydomainvar'); + } +} + +/** + * Allows modules to modify the inbound domain request. + * + * When using this hook, first check $domain->getMatchType(), which returns a + * numeric constant indicating the type of match derived by the caller or by + * earlier returns of this hook (such as domain_alias_request_alter()). + * Use this value to determine if the request needs to be overridden. Valid + * types are DomainNegotiator::DOMAIN_MATCH_NONE, + * DomainNegotiator::DOMAIN_MATCH_EXACT, DomainNegotiator::DOMAIN_MATCH_ALIAS. + * + * To issue a redirect, as in the case of Domain Alias, set a redirect + * property to a valid response code (301 or 302). + * + * @param \Drupal\domain\DomainInterface $domain + * A domain object defined by Drupal\domain\DomainInterface. + */ +function hook_domain_request_alter(\Drupal\domain\DomainInterface &$domain) { + // Add a special case to the example domain. + if ($domain->getMatchType() == \Drupal\domain\DomainNegotiator::DOMAIN_MATCH_EXACT && $domain->id() == 'example_com') { + // Do something here. + $domain->addProperty('foo', 'Bar'); + } +} + +/** + * Adds administrative operations for the domain overview form. + * + * These operations are only available to users who can administer the domain. + * That access check happens prior to this hook being called. If your use-case + * requires additional permission checking, you should provide it before + * returning any values. + * + * @param \Drupal\domain\DomainInterface $domain + * A domain record object. + * @param \Drupal\Core\Session\AccountInterface $account + * The user account performing the operation. + * + * @return array + * An array of operations which uses a unique string key and requires the + * elements 'title' and 'url'; the 'query' value is optional, and used + * for link-actions with tokens + */ +function hook_domain_operations(\Drupal\domain\DomainInterface $domain, \Drupal\Core\Session\AccountInterface $account) { + $operations = []; + // Check permissions. + if ($account->hasPermission('view domain aliases') || $account->hasPermission('administer domain aliases')) { + // Add aliases to the list. + $id = $domain->id(); + $operations['domain_alias'] = array( + 'title' => t('Aliases'), + 'url' => Url::fromRoute('domain_alias.admin', array('domain' => $id)), + 'weight' => 60, + ); + } + return $operations; +} + +/** + * Alter the validation step of a domain record. + * + * This hook allows modules to change or extend how domain validation + * happens. Most useful for international domains or other special cases + * where a site wants to restrict domain creation is some manner. + * + * NOTE: This does not apply to Domain Alias records. + * + * @param array &$error_list + * The list of current validation errors. Modify this value by reference. + * If you return an empty array or NULL, the domain is considered valid. + * @param string $hostname + * The HTTP_HOST string value being validated, such as one.example.com. + * Note that this is checked for uniqueness separately. This value is not + * modifiable. + * + * No return value. Modify $error_list by reference. Return an empty array + * or NULL to validate this domain. + * + * @see domain_valid_domain() + */ +function hook_domain_validate_alter(&$error_list, $hostname) { + // Only allow TLDs to be .org for our site. + if (substr($hostname, -4) != '.org') { + $error_list[] = t('Only .org domains may be registered.'); + } +} + +/** + * Alter the list of domains that may be referenced. + * + * Note that this hook does not fire for users with the 'administer domains' + * permission. + * + * @param \Drupal\Core\Entity\Query\QueryInterface $query + * An entity query prepared by DomainSelection::buildEntityQuery(). + * @param \Drupal\Core\Session\AccountInterface $account + * The account of the user viewing the reference list. + * @param array $context + * A keyed array passing two items: + * - entity_type The type of entity (e.g. node, user) that requested the list. + * - bundle The entity subtype (e.g. 'article' or 'page'). + * - field_type The access field group used for this selection. Groups are + * 'editor' for assigning editorial permissions (as in Domain Access) + * 'admin' for assigning administrative permissions for a specific domain. + * Most contributed modules will use 'editor'. + * + * No return value. Modify the $query object via methods. + */ +function hook_domain_references_alter($query, $account, $context) { + // Remove the default domain from non-admins when editing nodes. + if ($context['entity_type'] == 'node' && $context['field_type'] == 'editor' && !$account->hasPermission('edit assigned domains')) { + $default = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultId(); + $query->condition('id', $default, '<>'); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.drush.inc b/sites/all/modules/contrib/admin/domain/domain/domain.drush.inc new file mode 100644 index 000000000..48f620dff --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.drush.inc @@ -0,0 +1,641 @@ + 'List active domains for the site.', + 'examples' => array( + 'drush domain-list', + 'drush domains', + ), + 'aliases' => array('domains'), + ); + $items['domain-add'] = array( + 'description' => 'Add a new domain to the site.', + 'examples' => array( + 'drush domain-add example.com \'My Test Site\'', + 'drush domain-add example.com \'My Test Site\' --inactive=1 --https==1', + 'drush domain-add example.com \'My Test Site\' --weight=10', + 'drush domain-add example.com \'My Test Site\' --validate=1', + ), + 'arguments' => array( + 'hostname' => 'The domain hostname to register (e.g. example.com).', + 'name' => 'The name of the site (e.g. Domain Two).', + ), + 'options' => array( + 'inactive' => 'Set the domain to inactive status if set.', + 'https' => 'Use https protocol for this domain if set.', + 'weight' => 'Set the order (weight) of the domain.', + 'is_default' => 'Set this domain as the default domain.', + 'validate' => 'Force a check of the URL response before allowing registration.', + ), + ); + $items['domain-delete'] = array( + 'description' => 'Delete a domain from the site.', + 'examples' => array( + 'drush domain-delete example.com', + 'drush domain-delete 1', + ), + 'arguments' => array( + 'domain' => 'The numeric id or hostname of the domain to delete.', + ), + ); + $items['domain-test'] = array( + 'description' => 'Tests domains for proper response. If run from a subfolder, you must specify the --uri.', + 'examples' => array( + 'drush domain-test', + 'drush domain-test example.com', + 'drush domain-test 1', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to test. If no value is passed, all domains are tested.', + ), + 'options' => array( + 'base_path' => 'The subdirectory name if Drupal is installed in a folder other than server root.', + ), + ); + $items['domain-default'] = array( + 'description' => 'Sets the default domain. If run from a subfolder, you must specify the --uri.', + 'examples' => array( + 'drush domain-default example.com', + 'drush domain-default 1', + 'drush domain-default 1 --validate=1', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to make default.', + ), + 'options' => array( + 'validate' => 'Force a check of the URL response before allowing registration.', + ), + ); + $items['domain-disable'] = array( + 'description' => 'Sets a domain status to off.', + 'examples' => array( + 'drush domain-disable example.com', + 'drush domain-disable 1', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to disable.', + ), + ); + $items['domain-enable'] = array( + 'description' => 'Sets a domain status to on.', + 'examples' => array( + 'drush domain-disable example.com', + 'drush domain-disable 1', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to enable.', + ), + ); + $items['domain-name'] = array( + 'description' => 'Changes a domain label.', + 'examples' => array( + 'drush domain-name example.com Foo', + 'drush domain-name 1 Foo', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to relabel.', + 'name' => 'The name to use for the domain.', + ), + ); + $items['domain-machine-name'] = array( + 'description' => 'Changes a domain name.', + 'examples' => array( + 'drush domain-machine-name example.com foo', + 'drush domain-machine-name 1 foo', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to rename.', + 'name' => 'The machine-readable name to use for the domain.', + ), + ); + $items['domain-scheme'] = array( + 'description' => 'Changes a domain scheme.', + 'examples' => array( + 'drush domain-scheme example.com https', + 'drush domain-scheme 1 https', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to change.', + 'scheme' => 'The URL schema (http or https) to use for the domain.', + ), + ); + $items['generate-domains'] = array( + 'description' => 'Generate domains for testing.', + 'arguments' => array( + 'primary' => 'The primary domain to use. This will be created and used for *.example.com hostnames.', + ), + 'options' => array( + 'count' => 'The count of extra domains to generate. Default is 15.', + 'empty' => 'Pass empty=1 to truncate the {domain} table before creating records.' + ), + 'examples' => array( + 'drush domain-generate example.com', + 'drush domain-generate example.com --count=25', + 'drush domain-generate example.com --count=25 --empty=1', + 'drush gend', + 'drush gend --count=25', + 'drush gend --count=25 --empty=1', + ), + 'aliases' => array('gend'), + ); + return $items; +} + +/** + * Implements hook_drush_help(). + */ +function domain_drush_help($section) { + $items = domain_drush_command(); + $name = str_replace('domain:', '', $section); + if (isset($items[$name])) { + return dt($items[$name]['description']); + } +} + +/** + * Shows the domain list. + */ +function drush_domain_list() { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultipleSorted(NULL, TRUE); + + if (empty($domains)) { + drush_print(dt('No domains have been created. Use drush domain-add to create one.')); + return; + } + + $header = array( + 'weight' => dt('Weight'), + 'name' => dt('Name'), + 'hostname' => dt('Hostname'), + 'scheme' => dt('Scheme'), + 'status' => dt('Status'), + 'is_default' => dt('Default'), + 'domain_id' => dt('Domain Id'), + 'id' => dt('Machine name'), + ); + $rows = array(array_values($header)); + foreach ($domains as $domain) { + $row = array(); + foreach ($header as $key => $name) { + $row[] = Html::escape($domain->get($key)); + } + $rows[] = $row; + } + + drush_print_table($rows, TRUE); +} + +/** + * Generates a list of domains for testing. + * + * In my environment, I name hostnames one.* two.* up to ten. I also use + * foo.* bar.* and baz.*. We also want a non-hostname here and use + * myexample.com. + * + * The script may also add test1, test2, test3 up to any number to test a + * large number of domains. This test is mostly for UI testing. + * + * @param $primary + * The root domain to use for domain creation. + * + * @return + * A list of the domains created. + */ +function drush_domain_generate_domains($primary = 'example.com') { + // Check the number of domains to create. + $count = drush_get_option('count'); + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + if (empty($count)) { + $count = 15; + } + // Ensure we don't duplicate any domains. + $existing = array(); + if (!empty($domains)) { + foreach ($domains as $domain) { + $existing[] = $domain->getHostname(); + } + } + // Set up one.* and so on. + $names = array( + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine', + 'ten', + 'foo', + 'bar', + 'baz', + ); + // Set the creation array. + $new = array($primary); + foreach ($names as $name) { + $new[] = $name . '.' . $primary; + } + // Include a non hostname. + $new[] = 'my' . $primary; + // Filter against existing so we can count correctly. + $prepared = array(); + foreach ($new as $key => $value) { + if (!in_array($value, $existing)) { + $prepared[] = $value; + } + } + // Add any test domains that have numeric prefixes. We don't expect these URLs to work, + // and mainly use these for testing the user interface. + $needed = $count - count($prepared); + for ($i = 1; $i <= $needed; $i++) { + $prepared[] = 'test' . $i . '.' . $primary; + } + + // Get the initial item weight for sorting. + $start_weight = count($domains); + $prepared = array_slice($prepared, 0, $count); + + // Create the domains. + foreach ($prepared as $key => $item) { + $hostname = strtolower($item); + $values = array( + 'name' => ($item != $primary) ? ucwords(str_replace(".$primary", '', $item)) : \Drupal::config('system.site')->get('name'), + 'hostname' => $hostname, + 'scheme' => 'http', + 'status' => 1, + 'weight' => ($item != $primary) ? $key + $start_weight + 1 : -1, + 'is_default' => 0, + 'id' => \Drupal::service('entity_type.manager')->getStorage('domain')->createMachineName($hostname), + ); + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->create($values); + domain_drush_create($domain); + } + + // If nothing created, say so. + if (empty($new)) { + drush_print(dt('No new domains were created.')); + } +} + +/** + * Validates the domain generation script. + * + * @param $primary + * The root domain to use for domain creation. + */ +function drush_domain_generate_domains_validate($primary = 'example.com') { + if ($empty = drush_get_option('empty')) { + db_query("TRUNCATE TABLE {domain}"); + } + return; + // TODO: Update this validation. + $error = domain_valid_domain($primary); + if (!empty($error)) { + return drush_set_error('domain', $error); + } +} + +/** + * Adds a new domain. + * + * @param string $hostname + * The domain name to register. + * @param string $name + * The name to use for this domain. + */ +function drush_domain_add($hostname, $name) { + $records_count = \Drupal::entityTypeManager()->getStorage('domain')->getQuery()->count()->execute(); + $start_weight = $records_count + 1; + $hostname = strtolower($hostname); + /** @var \Drupal\domain\DomainStorageInterface $domain_storage */ + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $values = array( + 'hostname' => $hostname, + 'name' => $name, + 'status' => (!drush_get_option('invalid')) ? 1 : 0, + 'scheme' => (!drush_get_option('https')) ? 'http' : 'https', + 'weight' => ($weight = drush_get_option('weight')) ? $weight : $start_weight + 1, + 'is_default' => ($is_default = drush_get_option('is_default')) ? $is_default : 0, + 'id' => $domain_storage->createMachineName($hostname), + 'validate_url' => (drush_get_option('validate')) ? 1 : 0, + ); + $domain = $domain_storage->create($values); + domain_drush_create($domain); +} + +/** + * Validates the domain add command arguments. + * + * @param string $hostname + * The domain name to register. + * @param string $name + * The name to use for this domain. + * + * @return bool + * TRUE when validation passed, FALSE otherwise. + */ +function drush_domain_add_validate($hostname, $name) { + $errors = domain_drush_validate_domain($hostname); + if (!empty($errors)) { + return drush_set_error('domain', $errors); + } + elseif (\Drupal::service('entity_type.manager')->getStorage('domain')->loadByHostname($hostname)) { + return drush_set_error('domain', dt('The hostname is already registered.')); + } + return TRUE; +} + +/** + * Creates a domain record. + * + * @param Drupal\domain\DomainInterface $domain + * A domain entity. + */ +function domain_drush_create(DomainInterface $domain) { + if ($error = domain_drush_check_response($domain)) { + drush_set_error('hostname', $error); + } + else { + $domain->save(); + if ($domain->getDomainId()) { + drush_print(dt('Created @name at @domain.', array('@name' => $domain->label(), '@domain' => $domain->getHostname()))); + } + else { + drush_print(dt('The request could not be completed.')); + } + } +} + +/** + * Runs a check to ensure that the domain is responsive. + * + * @param Drupal\domain\DomainInterface $domain + * A domain entity. + * + * @return string + * An error message if the domain url does not validate. Else empty. + */ +function domain_drush_check_response(DomainInterface $domain) { + // Check the domain response. First, clear the path value. + if ($domain->validate_url) { + $domain->setPath(); + try { + $response = $domain->getResponse(); + } + // We cannot know which Guzzle Exception class will be returned; be generic. + catch (RequestException $e) { + watchdog_exception('domain', $e); + // File a general server failure. + $domain->setResponse(500); + } + // If validate_url is set, then we must receive a 200 response. + if ($domain->getResponse() != 200) { + if (empty($response)) { + $response = 500; + } + return dt('The server request to @url returned a @response response. To proceed, disable the test of the server response by leaving off the --validate flag.', ['@url' => $domain->getPath(), '@response' => $response]); + } + } +} + +/** + * Validates a domain. + * + * @param $hostname + * The domain name to validate for syntax and uniqueness. + * + * @return array + * An array of errors encountered. + * + * @see domain_validate() + */ +function domain_drush_validate_domain($hostname) { + /** @var \Drupal\domain\DomainValidatorInterface $validator */ + $validator = \Drupal::service('domain.validator'); + return $validator->validate($hostname); +} + +/** + * Deletes a domain record. + * + * @param $argument + * The domain_id to delete. Pass 'all' to delete all records. + */ +function drush_domain_delete($argument = NULL) { + if (is_null($argument)) { + drush_set_error('domain', dt('You must specify a domain to delete.')); + } + if ($argument == 'all') { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + if (empty($domains)) { + drush_print(dt('There are no domains to delete.')); + return; + } + $content = drush_choice(array(1 => dt('Delete all domains')), dt('This action may not be undone. Continue?'), '!value'); + if (empty($content)) { + return; + } + } + // Resolve the domain. + elseif ($domain = drush_domain_get_from_argument($argument)) { + if ($domain->isDefault()) { + return drush_set_error('domain', dt('The primary domain may not be deleted. Use drush domain-default to set a new default domain.')); + } + $domains = [$domain]; + } + else { + return; + } + foreach ($domains as $domain) { + $domain->delete(); + drush_print(dt('Domain record @domain deleted.', array('@domain' => $domain->getHostname()))); + } + + return; + + // TODO: Set options for re-assigning content. + $list = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + $options = array('0' => dt('Do not reassign')); + foreach ($list as $data) { + if ($data->id() != $domain->id()) { + $options[$data->getDomainId()] = $data->getHostname(); + } + } + $content = drush_choice($options, dt('Reassign content to:'), '!value'); + if (empty($content)) { + return; + } + $users = drush_choice($options, dt('Reassign users to:'), '!value'); + if (empty($users)) { + return; + } + $values['domain_access'] = (!empty($content)) ? $content : 'none'; + $values['domain_editor'] = (!empty($content)) ? $users : 'none'; + + domain_delete($domain, $values); + + drush_print(dt('Domain record deleted.')); +} + +/** + * Tests a domain record for the proper HTTP response. + * + * @param $argument + * The domain_id to test. Passing no value tests all records. + */ +function drush_domain_test($argument = NULL) { + // TODO: This won't work in a subdirectory without a parameter. + if ($base_path = drush_get_option('base_path')) { + $GLOBALS['base_path'] = '/' . $base_path . '/'; + } + if (is_null($argument)) { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + } + else { + if ($domain = drush_domain_get_from_argument($argument)) { + $domains = array($domain); + } + else { + return; + } + } + foreach ($domains as $domain) { + if ($domain->getResponse() != 200) { + drush_print(dt('Fail: !error. Please pass a --uri parameter or a --base_path to retest.' , array('!error' => $domain->getResponse()))); + } + else { + drush_print(dt('Success: !url tested successfully.', array('!url' => $domain->getPath()))); + } + } +} + +/** + * Sets the default domain id. + */ +function drush_domain_default($argument) { + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + $validate = (drush_get_option('validate')) ? 1 : 0; + $domain->addProperty('validate_url', $validate); + if ($error = domain_drush_check_response($domain)) { + drush_set_error('domain', $error); + } + else { + $domain->saveDefault(); + drush_print(dt('!domain set to primary domain.', array('!domain' => $domain->getHostname()))); + } + } +} + +/** + * Disables a domain. + */ +function drush_domain_disable($argument) { + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + if ($domain->status()) { + $domain->disable(); + drush_print(dt('!domain has been disabled.', array('!domain' => $domain->getHostname()))); + } + else { + drush_print(dt('!domain is already disabled.', array('!domain' => $domain->getHostname()))); + } + } +} + +/** + * Enables a domain. + */ +function drush_domain_enable($argument) { + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + if (!$domain->status()) { + $domain->enable(); + drush_print(dt('!domain has been enabled.', array('!domain' => $domain->getHostname()))); + } + else { + drush_print(dt('!domain is already enabled.', array('!domain' => $domain->getHostname()))); + } + } +} + +/** + * Changes a domain name. + */ +function drush_domain_name($argument, $name) { + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + $domain->saveProperty('name', $name); + } +} + +/** + * Changes a domain machine_name. + */ +function drush_domain_machine_name($argument, $machine_name) { + $machine_name = \Drupal::service('entity_type.manager')->getStorage('domain')->createMachineName($machine_name); + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + $results = \Drupal::entityTypeManager() + ->getStorage('domain') + ->loadByProperties(array('machine_name' => $machine_name)); + foreach ($results as $result) { + if ($result->id() == $machine_name) { + drush_print(dt('The machine_name @machine_name is being used by domain @hostname.', array('@machine_name' => $machine_name, '@hostname' => $result->getHostname()))); + return; + } + } + $domain->saveProperty('id', $machine_name); + } +} + +/** + * Changes a domain scheme. + */ +function drush_domain_scheme($argument) { + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + $content = drush_choice(array(1 => dt('http'), 2 => dt('https')), dt('Select the default http scheme:'), '!value'); + if (empty($content)) { + return; + } + $scheme = 'http'; + if ($content == 2) { + $scheme = 'https'; + } + $domain->saveProperty('scheme', $scheme); + } +} + +/** + * Converts a domain string or domain_id to a $domain array. + * + * On failure, throws a drush error. + */ +function drush_domain_get_from_argument($argument) { + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->load($argument); + if (!$domain) { + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->loadByHostname($argument); + } + if (!$domain) { + drush_set_error('domain', dt('Domain record not found.')); + return NULL; + } + return $domain; +} diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.info.yml b/sites/all/modules/contrib/admin/domain/domain/domain.info.yml new file mode 100644 index 000000000..04fa37fe4 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.info.yml @@ -0,0 +1,15 @@ +name: Domain +description: 'Creates domain records within a Drupal installation.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - options +configure: domain.admin + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.install b/sites/all/modules/contrib/admin/domain/domain/domain.install new file mode 100644 index 000000000..9f986a5ad --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.install @@ -0,0 +1,38 @@ +setComponent(DOMAIN_ADMIN_FIELD, array( + 'type' => 'options_buttons', + 'weight' => 50, + )) + ->save(); +} diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.libraries.yml b/sites/all/modules/contrib/admin/domain/domain/domain.libraries.yml new file mode 100644 index 000000000..6c70e1411 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.libraries.yml @@ -0,0 +1,7 @@ +drupal.domain: + version: VERSION + js: + js/domain.js: {} + dependencies: + - core/jquery + - core/drupal diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.links.action.yml b/sites/all/modules/contrib/admin/domain/domain/domain.links.action.yml new file mode 100644 index 000000000..e65a9b02c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.links.action.yml @@ -0,0 +1,5 @@ +entity.domain_add: + route_name: entity.domain.add + title: 'Add domain record' + appears_on: + - domain.admin diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.links.menu.yml b/sites/all/modules/contrib/admin/domain/domain/domain.links.menu.yml new file mode 100644 index 000000000..50e3f3971 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.links.menu.yml @@ -0,0 +1,18 @@ +domain.admin: + title: Domains + route_name: domain.admin + parent: system.admin_config + description: 'Domain configuration' + weight: 0 +domain.admin_list: + title: Domain records + route_name: domain.admin + parent: domain.admin + description: 'Manage the domain records used by the site.' + weight: -10 +domain.settings: + title: Domain settings + route_name: domain.settings + parent: domain.admin + description: 'Domain module settings' + weight: 0 diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.links.task.yml b/sites/all/modules/contrib/admin/domain/domain/domain.links.task.yml new file mode 100644 index 000000000..e66821094 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.links.task.yml @@ -0,0 +1,8 @@ +domain.admin: + title: Domain records + route_name: domain.admin + base_route: domain.admin +domain.settings: + title: Domain settings + route_name: domain.settings + base_route: domain.admin diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.module b/sites/all/modules/contrib/admin/domain/domain/domain.module new file mode 100644 index 000000000..a4a0b6c73 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.module @@ -0,0 +1,162 @@ +getPath(), ['absolute' => TRUE]); +} + +/** + * Implements hook_entity_load(). + * + * The $domain->path and $domain->uri properties are derived from data in the + * {domain} table. We use the hook system to load that data to indicate that + * the data is not native to the object. + * + * This action is performed in hook_entity_load(), which precedes the running + * of hook_domain_load() and ensures that our data is present for other modules. + */ +function domain_entity_load(array $entities, $entity_type) { + if ($entity_type == 'domain') { + foreach ($entities as $domain) { + $domain->setPath(); + $domain->setUrl(); + } + } +} + +/** + * Implements hook_help(). + */ +function domain_help($route_name, RouteMatchInterface $route_match) { + switch ($route_name) { + case 'domain.admin': + $output = t('

The following domains have been created for your site. The currently active domain + is shown in boldface. You may click on a domain to change the currently active domain. +

'); + return $output; + } +} + +/** + * Implements hook_token_info(). + */ +function domain_token_info() { + return \Drupal::service('domain.token')->getTokenInfo(); +} + +/** + * Implements hook_tokens(). + */ +function domain_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) { + return \Drupal::service('domain.token')->getTokens($type, $tokens, $data, $options, $bubbleable_metadata); +} + + +/** + * Implements hook_preprocess_HOOK() for html.html.twig. + */ +function domain_preprocess_html(array &$variables) { + // Add class to body tag, if set. + $config = \Drupal::config('domain.settings'); + if ($string = $config->get('css_classes')) { + $token = \Drupal::token(); + // Prepare the classes proparly, with one class per string. + $classes = explode(' ', trim($string)); + foreach ($classes as $class) { + // Ensure no leading or trailing space. + $class = trim($class); + if (!empty($class)) { + $variables['attributes']['class'][] = Html::getClass($token->replace($class)); + } + } + } +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\user\UserForm. + * + * Handle settings that the user cannot access. + */ +function domain_form_user_form_alter(&$form, &$form_state, $form_id) { + // Add the options hidden from the user silently to the form. + $manager = \Drupal::service('domain.element_manager'); + $form = $manager->setFormOptions($form, $form_state, DOMAIN_ADMIN_FIELD); +} + +/** + * Implements hook_domain_references_alter(). + */ +function domain_domain_references_alter($query, $account, $context) { + // Restrict domains by assignment, being sure only to act on the admin field. + if ($context['field_type'] == 'admin' && $context['entity_type'] == 'user') { + if ($account->hasPermission('administer domains')) { + // Do nothing. + } + elseif ($account->hasPermission('assign domain administrators')) { + $allowed = \Drupal::service('domain.element_manager')->getFieldValues($account, DOMAIN_ADMIN_FIELD); + $query->condition('id', array_keys($allowed), 'IN'); + } + else { + // Remove all options. + $query->condition('id', '-no-possible-match-'); + } + } +} + +/** + * Implements hook_views_data_alter. + */ +function domain_views_data_alter(array &$data) { + $table = 'user__' . DOMAIN_ADMIN_FIELD; + // Since domains are not stored in the database, relationships cannot be used. + unset($data[$table][DOMAIN_ADMIN_FIELD]['relationship']); +} + +/** + * Implements hook_theme(). + */ +function domain_theme() { + return [ + 'domain_nav_block' => [ + 'render element' => 'items', + ], + ]; +} + +/** + * Prepares variables for block templates. + * + * Default template: domain-nav-block.html.twig. + * + * @param array $variables + * An associative array containing: + * - items: An array of labels and urls for use in the list. + * Properties used: 'label', 'url', 'active' + */ +function template_preprocess_domain_nav_block(&$variables) { + $variables['items'] = $variables['items']['#items']; +} diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.permissions.yml b/sites/all/modules/contrib/admin/domain/domain/domain.permissions.yml new file mode 100644 index 000000000..c3b2d2f80 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.permissions.yml @@ -0,0 +1,32 @@ +administer domains: + title: 'Administer all domain records' + description: 'View, create, edit, and delete domain records. Allows all permissions for the module.' + restrict access: true +access inactive domains: + title: 'Access inactive domains' + description: 'Access domain URLs for domains marked as inactive.' + restrict access: true +assign domain administrators: + title: 'Assign additional administrators to assigned domains' + restrict access: true +create domains: + title: 'Create domain records' + restrict access: true +edit assigned domains: + title: 'Edit assigned domain records' + restrict access: true +delete assigned domains: + title: 'Delete assigned domain records' + restrict access: true +use domain nav block: + title: 'Access the domain navigation block' +use domain switcher block: + title: 'Access the domain switcher block' + restrict access: true +view assigned domains: + title: 'View assigned domains in the administration list' +view domain list: + title: 'View all registered domains in the administration list' +view domain information: + title: 'View debugging information for domain handling' + restrict access: true diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.routing.yml b/sites/all/modules/contrib/admin/domain/domain/domain.routing.yml new file mode 100644 index 000000000..c230a9994 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.routing.yml @@ -0,0 +1,55 @@ +domain.admin: + path: '/admin/config/domain' + defaults: + _entity_list: 'domain' + _title: 'Domains' + _title_context: 'With modules' + requirements: + _custom_access: 'Drupal\domain\Access\DomainListCheck::viewDomainList' + +domain.settings: + path: '/admin/config/domain/settings' + defaults: + _title: 'Domain settings' + _form: '\Drupal\domain\Form\DomainSettingsForm' + requirements: + _permission: 'administer domains' + +entity.domain.add: + path: '/admin/config/domain/add' + defaults: + _entity_form: domain.edit + _title: 'Add domain record' + options: + _admin_route: TRUE + requirements: + _entity_create_access: domain + +entity.domain.edit_form: + path: '/admin/config/domain/edit/{domain}' + defaults: + _entity_form: domain.edit + _title: 'Edit domain record' + options: + _admin_route: TRUE + requirements: + _entity_access: domain.update + +entity.domain.delete_form: + path: '/admin/config/domain/delete/{domain}' + defaults: + _entity_form: domain.delete + _title: 'Delete' + options: + _admin_route: TRUE + requirements: + _entity_access: domain.delete + +domain.inline_action: + path: '/admin/config/domain/{op}/{domain}' + defaults: + _controller: 'Drupal\domain\Controller\DomainController::ajaxOperation' + requirements: + _entity_access: domain.update + _csrf_token: 'TRUE' + op: 'enable|disable|default' diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.services.yml b/sites/all/modules/contrib/admin/domain/domain/domain.services.yml new file mode 100644 index 000000000..8b87c6a5f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.services.yml @@ -0,0 +1,41 @@ +services: + access_check.domain: + class: Drupal\domain\Access\DomainAccessCheck + tags: + - { name: access_check } + arguments: ['@domain.negotiator', '@config.factory', '@path.matcher'] + access_check.domain_route: + class: Drupal\domain\Access\DomainRouteCheck + tags: + - { name: access_check, applies_to: _domain } + arguments: ['@domain.negotiator'] + domain.current_domain_context: + class: Drupal\domain\ContextProvider\CurrentDomainContext + arguments: ['@domain.negotiator'] + tags: + - { name: 'context_provider' } + domain.element_manager: + class: Drupal\domain\DomainElementManager + arguments: ['@entity_type.manager'] + domain.negotiator: + class: Drupal\domain\DomainNegotiator + arguments: ['@request_stack', '@module_handler', '@entity_type.manager', '@config.factory'] + domain.subscriber: + class: Drupal\domain\EventSubscriber\DomainSubscriber + tags: + - { name: event_subscriber } + arguments: ['@domain.negotiator', '@entity_type.manager', '@access_check.domain', '@current_user'] + domain.token: + class: Drupal\domain\DomainToken + arguments: ['@entity_type.manager', '@domain.negotiator'] + domain.validator: + class: Drupal\domain\DomainValidator + arguments: ['@module_handler', '@config.factory', '@http_client', '@entity_type.manager'] + # @deprecated and will be removed for 8.x-1.0 release. + domain.creator: + class: Drupal\domain\DomainCreator + arguments: ['@domain.loader', '@domain.negotiator'] + # @deprecated and will be removed for 8.x-1.0 release. + domain.loader: + class: Drupal\domain\DomainLoader + arguments: ['@config.typed', '@config.factory'] diff --git a/sites/all/modules/contrib/admin/domain/domain/js/domain.js b/sites/all/modules/contrib/admin/domain/domain/js/domain.js new file mode 100644 index 000000000..be71eceeb --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/js/domain.js @@ -0,0 +1,42 @@ +/** + * @file + * Attaches behaviors for the Domain module. + */ +(function ($) { + + "use strict"; + + /** + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.domainSettingsSummaries = { + attach: function () { + // The drupalSetSummary method required for this behavior is not available + // on the Blocks administration page, so we need to make sure this + // behavior is processed only if drupalSetSummary is defined. + if (typeof jQuery.fn.drupalSetSummary === 'undefined') { + return; + } + + // There may be an easier way to do this. Right now, we just copy code + // from block module. + function checkboxesSummary(context) { + var vals = []; + var $checkboxes = $(context).find('input[type="checkbox"]:checked + label'); + var il = $checkboxes.length; + for (var i = 0; i < il; i++) { + vals.push($($checkboxes[i]).html()); + } + if (!vals.length) { + vals.push(Drupal.t('Not restricted')); + } + return vals.join(', '); + } + + $('[data-drupal-selector="edit-visibility-domain"]').drupalSetSummary(checkboxesSummary); + + } + }; + +})(jQuery); diff --git a/sites/all/modules/contrib/admin/domain/domain/migration_templates/d7_domain.yml b/sites/all/modules/contrib/admin/domain/domain/migration_templates/d7_domain.yml new file mode 100644 index 000000000..ced2c7198 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/migration_templates/d7_domain.yml @@ -0,0 +1,17 @@ +id: d7_domain +label: Domain Records +migration_tags: + - Drupal 7 +source: + plugin: d7_domain +process: + id: machine_name + name: sitename + hostname: subdomain + weight: weight + is_default: is_default + scheme: scheme + path: subdomain + status: valid +destination: + plugin: entity:domain diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php new file mode 100644 index 000000000..a3cf475c6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php @@ -0,0 +1,93 @@ +domainNegotiator = $negotiator; + $this->configFactory = $config_factory; + $this->pathMatcher = $path_matcher; + } + + /** + * {@inheritdoc} + */ + public function applies(Route $route) { + return $this->checkPath($route->getPath()); + } + + /** + * {@inheritdoc} + */ + public function checkPath($path) { + $allowed_paths = $this->configFactory->get('domain.settings')->get('login_paths'); + return !$this->pathMatcher->matchPath($path, $allowed_paths); + } + + /** + * {@inheritdoc} + */ + public function access(AccountInterface $account) { + /** @var \Drupal\domain\DomainInterface $domain */ + $domain = $this->domainNegotiator->getActiveDomain(); + // Is the domain allowed? + // No domain, let it pass. + if (empty($domain)) { + return AccessResult::allowed()->setCacheMaxAge(0); + } + // Active domain, let it pass. + if ($domain->status()) { + return AccessResult::allowed()->setCacheMaxAge(0); + } + // Inactive domain, require permissions. + else { + $permissions = array('administer domains', 'access inactive domains'); + $operator = 'OR'; + return AccessResult::allowedIfHasPermissions($account, $permissions, $operator)->setCacheMaxAge(0); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainListCheck.php b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainListCheck.php new file mode 100644 index 000000000..a270f9c9b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainListCheck.php @@ -0,0 +1,28 @@ +hasPermission('administer domains') || $account->hasPermission('view domain list') || $account->hasPermission('view assigned domains')) { + return AccessResult::allowed(); + } + return AccessResult::forbidden(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainRouteCheck.php b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainRouteCheck.php new file mode 100644 index 000000000..d17a873e1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainRouteCheck.php @@ -0,0 +1,82 @@ +domainNegotiator = $negotiator; + } + + /** + * {@inheritdoc} + */ + public function applies(Route $route) { + return $route->hasRequirement($this->requirementsKey); + } + + /** + * Checks access to a route with a _domain requirement. + * + * @param \Symfony\Component\Routing\Route $route + * The route to check against. + * @param \Drupal\Core\Session\AccountInterface $account + * The currently logged in account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + * + * @see \Drupal\domain\Plugin\views\access\Domain + */ + public function access(Route $route, AccountInterface $account) { + // Requirements just allow strings, so this might be a comma separated list. + $string = $route->getRequirement($this->requirementsKey); + $domain = $this->domainNegotiator->getActiveDomain(); + // Since only one domain can be active per request, we only suport OR logic. + $allowed = array_filter(array_map('trim', explode('+', $string))); + if (!empty($domain) && in_array($domain->id(), $allowed)) { + return AccessResult::allowed()->addCacheContexts(['url.site']); + } + // If there is no allowed domain, give other access checks a chance. + return AccessResult::neutral()->addCacheContexts(['url.site']); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/ContextProvider/CurrentDomainContext.php b/sites/all/modules/contrib/admin/domain/domain/src/ContextProvider/CurrentDomainContext.php new file mode 100644 index 000000000..8c61ad582 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/ContextProvider/CurrentDomainContext.php @@ -0,0 +1,65 @@ +negotiator = $negotiator; + } + + /** + * {@inheritdoc} + */ + public function getRuntimeContexts(array $unqualified_context_ids) { + // Load the current domain. + $current_domain = $this->negotiator->getActiveDomain(); + // Set the context. + $context = new Context(new ContextDefinition('entity:domain', $this->t('Active domain')), $current_domain); + + // Allow caching. + $cacheability = new CacheableMetadata(); + $cacheability->setCacheContexts(['url.site']); + $context->addCacheableDependency($cacheability); + + // Prepare the result. + $result = [ + 'entity:domain' => $context, + ]; + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getAvailableContexts() { + return $this->getRuntimeContexts([]); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainController.php b/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainController.php new file mode 100644 index 000000000..c245e6977 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainController.php @@ -0,0 +1,76 @@ +saveDefault(); + $message = $this->t('Domain record set as default'); + if ($domain->isDefault()) { + $success = TRUE; + } + break; + + case 'enable': + $domain->enable(); + $message = $this->t('Domain record has been enabled.'); + if ($domain->status()) { + $success = TRUE; + } + break; + + case 'disable': + $domain->disable(); + $message = $this->t('Domain record has been disabled.'); + if (!$domain->status()) { + $success = TRUE; + } + break; + } + + // Set a message. + if ($success) { + drupal_set_message($message); + } + else { + drupal_set_message($this->t('The operation failed.')); + } + + // Return to the invoking page. + $url = Url::fromRoute('domain.admin', array(), array('absolute' => TRUE)); + return new RedirectResponse($url->toString(), 302); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainControllerBase.php b/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainControllerBase.php new file mode 100644 index 000000000..f102bb69e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainControllerBase.php @@ -0,0 +1,59 @@ +domainStorage = $domain_storage; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager')->getStorage('domain'), + $container->get('entity_type.manager') + ); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainAccessControlHandler.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainAccessControlHandler.php new file mode 100644 index 000000000..bffdff3a3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainAccessControlHandler.php @@ -0,0 +1,119 @@ +entityTypeManager = $entity_type_manager; + $this->domainElementManager = $domain_element_manager; + $this->userStorage = $user_storage; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity_type.manager'), + $container->get('domain.element_manager'), + $container->get('entity_type.manager')->getStorage('user') + ); + } + + /** + * {@inheritdoc} + */ + public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account = NULL) { + $account = $this->prepareUser($account); + // Check the global permission. + if ($account->hasPermission('administer domains')) { + return AccessResult::allowed(); + } + // @TODO: This may not be relevant. + if ($operation == 'create' && $account->hasPermission('create domains')) { + return AccessResult::allowed(); + } + // For view, we allow admins unless the domain is inactive. + $is_admin = $this->isDomainAdmin($entity, $account); + if ($operation == 'view' && ($entity->status() || $account->hasPermission('access inactive domains')) && ($is_admin || $account->hasPermission('view domain list'))) { + return AccessResult::allowed(); + } + // For other operations, check that the user is a domain admin. + if ($operation == 'update' && $account->hasPermission('edit assigned domains') && $is_admin) { + return AccessResult::allowed(); + } + if ($operation == 'delete' && $account->hasPermission('delete assigned domains') && $is_admin) { + return AccessResult::allowed(); + } + return AccessResult::forbidden(); + } + + /** + * Checks if a user can administer a specific domain. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to retrieve field data from. + * @param \Drupal\Core\Session\AccountInterface + * The user account. + * + * @return boolean + */ + public function isDomainAdmin(EntityInterface $entity, AccountInterface $account) { + $user = $this->userStorage->load($account->id()); + $user_domains = $this->domainElementManager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + return isset($user_domains[$entity->id()]); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainCreator.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainCreator.php new file mode 100644 index 000000000..73ea320c0 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainCreator.php @@ -0,0 +1,81 @@ +loader = $loader; + $this->negotiator = $negotiator; + } + + /** + * {@inheritdoc} + */ + public function createDomain(array $values = array()) { + $default = $this->loader->loadDefaultId(); + $domains = $this->loader->loadMultiple(); + if (empty($values)) { + $values['hostname'] = $this->createHostname(); + $values['name'] = \Drupal::config('system.site')->get('name'); + } + $values += array( + 'scheme' => \Drupal::service('entity_type.manager')->getStorage('domain')->getDefaultScheme(), + 'status' => 1, + 'weight' => count($domains) + 1, + 'is_default' => (int) empty($default), + ); + $domain = \Drupal::entityTypeManager()->getStorage('domain')->create($values); + + return $domain; + } + + /** + * {@inheritdoc} + */ + public function createHostname() { + return $this->negotiator->negotiateActiveHostname(); + } + + /** + * {@inheritdoc} + */ + public function createMachineName($hostname = NULL) { + if (empty($hostname)) { + $hostname = $this->createHostname(); + } + return preg_replace('/[^a-z0-9_]/', '_', $hostname); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainCreatorInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainCreatorInterface.php new file mode 100644 index 000000000..3eca77b37 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainCreatorInterface.php @@ -0,0 +1,47 @@ +entityTypeManager = $entity_type_manager; + $this->domainStorage = $entity_type_manager->getStorage('domain'); + } + + /** + * @inheritdoc + */ + public function setFormOptions(array $form, FormStateInterface $form_state, $field_name, $hide_on_disallow = FALSE) { + // There are cases, such as Entity Browser, where the form is partially + // invoked, but without our fields. + if (!isset($form[$field_name])) { + return $form; + } + $fields = $this->fieldList($field_name); + $disallowed = $this->disallowedOptions($form_state, $form[$field_name]); + $empty = empty($form[$field_name]['widget']['#options']); + + // If the domain form element is set as a group, and the field is not assigned to + // another group, then move it. See domain_access_form_node_form_alter(). + if (isset($form['domain']) && !isset($form[$field_name]['#group'])) { + $form[$field_name]['#group'] = 'domain'; + } + + // Check for domains the user cannot access or the absence of any options. + if (!empty($disallowed) || $empty) { + // @TODO: Potentially show this information to users with permission. + $form[$field_name . '_disallowed'] = array( + '#type' => 'value', + '#value' => $disallowed, + ); + $form['domain_hidden_fields'] = array( + '#type' => 'value', + '#value' => $fields, + ); + if ($hide_on_disallow || $empty) { + $form[$field_name]['#access'] = FALSE; + } + elseif (!empty($disallowed)) { + $form[$field_name]['widget']['#description'] .= $this->listDisallowed($disallowed); + } + // Call our submit function to merge in values. + // Account for all the submit buttons on the node form. + $buttons = ['preview', 'delete']; + $submit = $this->getSubmitHandler(); + foreach ($form['actions'] as $key => $action) { + if (!in_array($key, $buttons) && isset($form['actions'][$key]['#submit']) && !in_array($submit, $form['actions'][$key]['#submit'])) { + array_unshift($form['actions'][$key]['#submit'], $submit); + } + } + } + + return $form; + } + + /** + * @inheritdoc + */ + public static function submitEntityForm(array &$form, FormStateInterface $form_state) { + $fields = $form_state->getValue('domain_hidden_fields'); + foreach ($fields as $field) { + $entity_values = []; + $values = $form_state->getValue($field . '_disallowed'); + if (!empty($values)) { + $info = $form_state->getBuildInfo(); + $node = $form_state->getFormObject()->getEntity(); + $entity_values = $form_state->getValue($field); + } + foreach ($values as $value) { + $entity_values[]['target_id'] = $value; + } + // Prevent a fatal error caused by passing a NULL value. + // See https://www.drupal.org/node/2841962. + if (!empty($entity_values)) { + $form_state->setValue($field, $entity_values); + } + } + } + + /** + * @inheritdoc + */ + public function disallowedOptions(FormStateInterface $form_state, $field) { + $options = []; + $info = $form_state->getBuildInfo(); + $entity = $form_state->getFormObject()->getEntity(); + $entity_values = $this->getFieldValues($entity, $field['widget']['#field_name']); + if (isset($field['widget']['#options'])) { + $options = array_diff_key($entity_values, $field['widget']['#options']); + } + return array_keys($options); + } + + /** + * @inheritdoc + */ + public function fieldList($field_name) { + static $fields = []; + $fields[] = $field_name; + // Return only unique field names. AJAX requests can result in duplicates. + // See https://www.drupal.org/project/domain/issues/2930934. + return array_unique($fields); + } + + /** + * @inheritdoc + */ + public function getFieldValues($entity, $field_name) { + // @TODO: static cache. + $list = []; + // @TODO In tests, $entity is returning NULL. + if (is_null($entity)) { + return $list; + } + // Get the values of an entity. + $values = $entity->hasField($field_name) ? $entity->get($field_name) : NULL; + // Must be at least one item. + if (!empty($values)) { + foreach ($values as $item) { + if ($target = $item->getValue()) { + if ($domain = $this->domainStorage->load($target['target_id'])) { + $list[$domain->id()] = $domain->getDomainId(); + } + } + } + } + return $list; + } + + /** + * @inheritdoc + */ + public function getSubmitHandler() { + return '\\Drupal\\domain\\DomainElementManager::submitEntityForm'; + } + + /** + * Lists the disallowed domains in the user interface. + * + * @param array $disallowed + * An array of domain ids. + * + * @return string + * A string suitable for display. + */ + public function listDisallowed(array $disallowed) { + $domains = $this->domainStorage->loadMultiple($disallowed); + // @TODO: Proper theme function here. + $string = $this->t('The following domains are currently assigned and cannot be changed:'); + foreach ($domains as $domain) { + $items[] = $domain->label(); + } + $build = array( + '#theme' => 'item_list', + '#items' => $items, + ); + $string .= render($build); + return '
' . $string . '
'; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainElementManagerInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainElementManagerInterface.php new file mode 100644 index 000000000..31c8e47d8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainElementManagerInterface.php @@ -0,0 +1,105 @@ +domainStorage = $domain_storage; + $this->renderer = $renderer; + $this->validator = $validator; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager')->getStorage('domain'), + $container->get('renderer'), + $container->get('domain.validator'), + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + /** @var \Drupal\domain\Entity\Domain $domain */ + $domain = $this->entity; + + // Create defaults if this is the first domain. + $count_existing = $this->domainStorage->getQuery()->count()->execute(); + if (!$count_existing) { + $domain->addProperty('hostname', $this->domainStorage->createHostname()); + $domain->addProperty('name', $this->config('system.site')->get('name')); + } + $form['domain_id'] = array( + '#type' => 'value', + '#value' => $domain->getDomainId(), + ); + $form['hostname'] = array( + '#type' => 'textfield', + '#title' => $this->t('Hostname'), + '#size' => 40, + '#maxlength' => 80, + '#default_value' => $domain->getCanonical(), + '#description' => $this->t('The canonical hostname, using the full subdomain.example.com format. Leave off the http:// and the trailing slash and do not include any paths.
If this domain uses a custom http(s) port, you should specify it here, e.g.: subdomain.example.com:1234
The hostname may contain only lowercase alphanumeric characters, dots, dashes, and a colon (if using alternative ports).'), + ); + $form['id'] = array( + '#type' => 'machine_name', + '#default_value' => !empty($domain->id()) ? $domain->id() : '', + '#disabled' => !empty($domain->id()), + '#machine_name' => array( + 'source' => array('hostname'), + 'exists' => array($this->domainStorage, 'load'), + ), + ); + $form['name'] = array( + '#type' => 'textfield', + '#title' => $this->t('Name'), + '#size' => 40, + '#maxlength' => 80, + '#default_value' => $domain->label(), + '#description' => $this->t('The human-readable name is shown in domain lists and may be used as the title tag.'), + ); + // Do not use the :// suffix when storing data. + $add_suffix = FALSE; + $form['scheme'] = array( + '#type' => 'radios', + '#title' => $this->t('Domain URL scheme'), + '#options' => array('http' => 'http://', 'https' => 'https://', 'variable' => 'Variable'), + '#default_value' => $domain->getRawScheme(), + '#description' => $this->t('This URL scheme will be used when writing links and redirects to this domain and its resources. Selecting Variable will inherit the current scheme of the web request.'), + ); + $form['status'] = array( + '#type' => 'radios', + '#title' => $this->t('Domain status'), + '#options' => array(1 => $this->t('Active'), 0 => $this->t('Inactive')), + '#default_value' => (int) $domain->status(), + '#description' => $this->t('"Inactive" domains are only accessible to user roles with that assigned permission.'), + ); + $form['weight'] = array( + '#type' => 'weight', + '#title' => $this->t('Weight'), + '#delta' => $count_existing + 1, + '#default_value' => $domain->getWeight(), + '#description' => $this->t('The sort order for this record. Lower values display first.'), + ); + $form['is_default'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Default domain'), + '#default_value' => $domain->isDefault(), + '#description' => $this->t('If a URL request fails to match a domain record, the settings for this domain will be used. Only one domain can be default.'), + ); + $form['validate_url'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Test server response'), + '#default_value' => TRUE, + '#description' => $this->t('Validate that url of the host is accessible to Drupal before saving.'), + ); + $required = $this->validator->getRequiredFields(); + foreach ($form as $key => $element) { + if (in_array($key, $required)) { + $form[$key]['#required'] = TRUE; + } + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\domain\DomainInterface $entity */ + $entity = $this->entity; + $hostname = $entity->getHostname(); + $errors = $this->validator->validate($hostname); + if (!empty($errors)) { + // Render errors to display as message. + $message = [ + '#theme' => 'item_list', + '#items' => $errors, + ]; + $message = $this->renderer->renderPlain($message); + $form_state->setErrorByName('hostname', $message); + } + // Validate if the same hostname exists. + // Do not use domain loader because it may change hostname. + $existing = $this->domainStorage->loadByProperties(['hostname' => $hostname]); + $existing = reset($existing); + // If we have already registered a hostname, make sure we don't create a duplicate. + // We cannot check id() here, as the machine name is editable. + if ($existing && $existing->getDomainId() != $entity->getDomainId()) { + $form_state->setErrorByName('hostname', $this->t('The hostname is already registered.')); + } + + // Check the domain response. First, clear the path value. + $entity->setPath(); + // Check the response. + $response = $this->validator->checkResponse($entity); + // If validate_url is set, then we must receive a 200 response. + if ($entity->validate_url && $response != 200) { + if (empty($response)) { + $response = 500; + } + $form_state->setErrorByName('hostname', $this->t('The server request to @url returned a @response response. To proceed, disable the Test server response in the form.', ['@url' => $entity->getPath(), '@response' => $response])); + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $status = parent::save($form, $form_state); + if ($status == SAVED_NEW) { + drupal_set_message($this->t('Domain record created.')); + } + else { + drupal_set_message($this->t('Domain record updated.')); + } + $form_state->setRedirect('domain.admin'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainInterface.php new file mode 100644 index 000000000..3b9e8ffb9 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainInterface.php @@ -0,0 +1,241 @@ +get('entity_type.manager')->getStorage($entity_type->id()), + $container->get('current_user'), + $container->get('redirect.destination'), + $container->get('entity_type.manager'), + $container->get('module_handler'), + $container->get('domain.element_manager') + ); + } + + /** + * Constructs a new EntityListBuilder object. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\domain\DomainStorageInterface $domain_storage + * The domain storage class. + * @param \Drupal\Core\Session\AccountInterface $account + * The active user account. + * @param \Drupal\Core\Routing\RedirectDestinationInterface $destination + * The redirect destination helper. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + * @param \Drupal\domain\DomainElementManager $domain_element_manager + * The domain field element manager. + */ + public function __construct(EntityTypeInterface $entity_type, DomainStorageInterface $domain_storage, AccountInterface $account, RedirectDestinationInterface $destination_handler, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, DomainElementManager $domain_element_manager) { + parent::__construct($entity_type, $domain_storage); + $this->entityTypeId = $entity_type->id(); + $this->domainStorage = $domain_storage; + $this->entityType = $entity_type; + $this->currentUser = $account; + $this->destinationHandler = $destination_handler; + $this->entityTypeManager = $entity_type_manager; + $this->accessHandler = $this->entityTypeManager->getAccessControlHandler('domain'); + $this->moduleHandler = $module_handler; + $this->domainElementManager = $domain_element_manager; + $this->userStorage = $this->entityTypeManager->getStorage('user'); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'domain_admin_overview_form'; + } + + /** + * {@inheritdoc} + */ + public function getOperations(EntityInterface $entity) { + $operations = parent::getOperations($entity); + $destination = $this->destinationHandler->getAsArray(); + $default = $entity->isDefault(); + $id = $entity->id(); + + // If the user cannot edit domains, none of these actions are permitted. + $access = $this->accessHandler->checkAccess($entity, 'update'); + if ($access->isForbidden()) { + return $operations; + } + + $super_admin = $this->currentUser->hasPermission('administer domains'); + if ($super_admin || $this->currentUser->hasPermission('access inactive domains')) { + if ($entity->status() && !$default) { + $operations['disable'] = array( + 'title' => $this->t('Disable'), + 'url' => Url::fromRoute('domain.inline_action', array('op' => 'disable', 'domain' => $id)), + 'weight' => 50, + ); + } + elseif (!$default) { + $operations['enable'] = array( + 'title' => $this->t('Enable'), + 'url' => Url::fromRoute('domain.inline_action', array('op' => 'enable', 'domain' => $id)), + 'weight' => 40, + ); + } + } + if (!$default && $super_admin) { + $operations['default'] = array( + 'title' => $this->t('Make default'), + 'url' => Url::fromRoute('domain.inline_action', array('op' => 'default', 'domain' => $id)), + 'weight' => 30, + ); + } + if (!$default && $this->accessHandler->checkAccess($entity, 'delete')->isAllowed()) { + $operations['delete'] = array( + 'title' => $this->t('Delete'), + 'url' => Url::fromRoute('entity.domain.delete_form', array('domain' => $id)), + 'weight' => 20, + ); + } + $operations += $this->moduleHandler->invokeAll('domain_operations', array($entity, $this->currentUser)); + foreach ($operations as $key => $value) { + if (isset($value['query']['token'])) { + $operations[$key]['query'] += $destination; + } + } + /** @var DomainInterface $default */ + $default = $this->domainStorage->loadDefaultDomain(); + + // Deleting the site default domain is not allowed. + if ($default && $id == $default->id()) { + unset($operations['delete']); + } + return $operations; + } + + /** + * {@inheritdoc} + */ + public function buildHeader() { + $header['label'] = $this->t('Name'); + $header['hostname'] = $this->t('Hostname'); + $header['status'] = $this->t('Status'); + $header['is_default'] = $this->t('Default'); + $header += parent::buildHeader(); + if (!$this->currentUser->hasPermission('administer domains')) { + unset($header['weight']); + } + return $header; + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + // If the user cannot view the domain, none of these actions are permitted. + $admin = $this->accessHandler->checkAccess($entity, 'view'); + if ($admin->isForbidden()) { + return; + } + + $row['label'] = $this->getLabel($entity); + $row['hostname'] = array('#markup' => $entity->getLink()); + if ($entity->isActive()) { + $row['hostname']['#prefix'] = ''; + $row['hostname']['#suffix'] = ''; + } + $row['status'] = array('#markup' => $entity->status() ? $this->t('Active') : $this->t('Inactive')); + $row['is_default'] = array('#markup' => ($entity->isDefault() ? $this->t('Yes') : $this->t('No'))); + $row += parent::buildRow($entity); + + if (!$this->currentUser->hasPermission('administer domains')) { + unset($row['weight']); + } + + return $row; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + $form[$this->entitiesKey]['#domains'] = $this->entities; + $form['actions']['submit']['#value'] = $this->t('Save configuration'); + // Only super-admins may sort domains. + if (!$this->currentUser->hasPermission('administer domains')) { + $form['actions']['submit']['#access'] = FALSE; + unset($form['#tabledrag']); + } + // Delta is set after each row is loaded. + $count = count($this->domainStorage->loadMultiple()) + 1; + foreach (Element::children($form['domains']) as $key) { + if (isset($form['domains'][$key]['weight'])) { + $form['domains'][$key]['weight']['#delta'] = $count; + } + } + return $form; + } + + /** + * {@inheritdoc} + * + * Overrides the parent method to prevent saving bad data. + * + * @link https://www.drupal.org/project/domain/issues/2925798 + * @link https://www.drupal.org/project/domain/issues/2925629 + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + foreach ($form_state->getValue($this->entitiesKey) as $id => $value) { + if (isset($this->entities[$id]) && $this->entities[$id]->get($this->weightKey) != $value['weight']) { + // Reset weight properly. + $this->entities[$id]->set($this->weightKey, $value['weight']); + // Do not allow accidental hostname rewrites. + $this->entities[$id]->set('hostname', $this->entities[$id]->getCanonical()); + $this->entities[$id]->save(); + } + } + } + + + /** + * Internal sort method for form weights. + */ + private function sortByWeight($a, $b) { + if ($a['weight'] < $b['weight']) { + return 0; + } + return 1; + } + + /** + * {@inheritdoc} + * + * Builds the entity listing as a form with pagination. This method overrides both + * Drupal\Core\Config\Entity\DraggableListBuilder::render() and + * Drupal\Core\Entity\EntityListBuilder::render(). + */ + public function render() { + // Build the default form, which includes weights. + $form = $this->formBuilder()->getForm($this); + + // Only add the pager if a limit is specified. + if ($this->limit) { + $form['pager'] = array( + '#type' => 'pager', + ); + } + return $form; + } + + /** + * {@inheritdoc} + * + * Loads entity IDs using a pager sorted by the entity weight. The default behavior when + * using a limit is to sort by id. + * + * We also have to limit by assigned domains of the active user. + * + * See Drupal\Core\Entity\EntityListBuilder::getEntityIds() + * + * @return array + * An array of entity IDs. + */ + protected function getEntityIds() { + $query = $this->getStorage()->getQuery() + ->sort($this->entityType->getKey('weight')); + + // If the user cannot administer domains, we must filter the query further by + // assigned IDs. We don't have to check permissions here, because that is handled by + // the route system and buildRow(). There are two permissions that allow users to view + // the entire list. + if (!$this->currentUser->hasPermission('administer domains') && !$this->currentUser->hasPermission('view domain list')) { + $user = $this->userStorage->load($this->currentUser->id()); + $allowed = $this->domainElementManager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $query->condition('id', array_keys($allowed), 'IN'); + } + + // Only add the pager if a limit is specified. + if ($this->limit) { + $query->pager($this->limit); + } + return $query->execute(); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainLoader.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainLoader.php new file mode 100644 index 000000000..b626e7fa5 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainLoader.php @@ -0,0 +1,162 @@ +typedConfig = $typed_config; + $this->configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public function loadSchema() { + $fields = $this->typedConfig->getDefinition('domain.record.*'); + return isset($fields['mapping']) ? $fields['mapping'] : array(); + } + + /** + * {@inheritdoc} + */ + public function load($id, $reset = FALSE) { + $controller = $this->getStorage(); + if ($reset) { + $controller->resetCache(array($id)); + } + return $controller->load($id); + } + + /** + * {@inheritdoc} + */ + public function loadDefaultId() { + $result = $this->loadDefaultDomain(); + if (!empty($result)) { + return $result->id(); + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function loadDefaultDomain() { + $result = $this->getStorage()->loadByProperties(array('is_default' => TRUE)); + if (!empty($result)) { + return current($result); + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function loadMultiple(array $ids = NULL, $reset = FALSE) { + $controller = $this->getStorage(); + if ($reset) { + $controller->resetCache($ids); + } + return $controller->loadMultiple($ids); + } + + /** + * {@inheritdoc} + */ + public function loadMultipleSorted(array $ids = NULL) { + $domains = $this->loadMultiple($ids); + uasort($domains, array($this, 'sort')); + return $domains; + } + + /** + * {@inheritdoc} + */ + public function loadByHostname($hostname) { + $hostname = $this->prepareHostname($hostname); + $result = $this->getStorage()->loadByProperties(array('hostname' => $hostname)); + if (empty($result)) { + return NULL; + } + return current($result); + } + + /** + * {@inheritdoc} + */ + public function loadOptionsList() { + $list = array(); + foreach ($this->loadMultipleSorted() as $id => $domain) { + $list[$id] = $domain->label(); + } + return $list; + } + + /** + * {@inheritdoc} + */ + public function sort(DomainInterface $a, DomainInterface $b) { + return $a->getWeight() > $b->getWeight(); + } + + /** + * Loads the storage controller. + * + * We use the loader very early in the request cycle. As a result, if we try + * to inject the storage container, we hit a circular dependency. Using this + * method at least keeps our code easier to update. + */ + protected function getStorage() { + $storage = \Drupal::entityTypeManager()->getStorage('domain'); + return $storage; + } + + /** + * {@inheritdoc} + */ + public function prepareHostname($hostname) { + // Strip www. prefix off the hostname. + $ignore_www = $this->configFactory->get('domain.settings')->get('www_prefix'); + if ($ignore_www && substr($hostname, 0, 4) == 'www.') { + $hostname = substr($hostname, 4); + } + return $hostname; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainLoaderInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainLoaderInterface.php new file mode 100644 index 000000000..5bacd4b28 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainLoaderInterface.php @@ -0,0 +1,118 @@ + label for use in forms. + */ + public function loadOptionsList(); + + /** + * Sorts domains by weight. + * + * For use by loadMultipleSorted(). + * + * @param DomainInterface $a + * The first Domain object to sort. + * @param DomainInterface $b + * The Domain object to compare against. + * + * @return bool + * Wether the first domain weight is greater or not. + */ + public function sort(DomainInterface $a, DomainInterface $b); + + /** + * Gets the entity field schema for domain records. + * + * @return array + * An array representing the field schema of the object. + */ + public function loadSchema(); + + /** + * Removes www. prefix from a hostname, if set. + * + * @param string $hostname + * A hostname. + * + * @return string + * The cleaned hostname. + */ + public function prepareHostname($hostname); + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php new file mode 100644 index 000000000..a45644448 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php @@ -0,0 +1,223 @@ +requestStack = $requestStack; + $this->moduleHandler = $module_handler; + $this->entityTypeManager = $entity_type_manager; + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + $this->configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public function setRequestDomain($httpHost, $reset = FALSE) { + // @TODO: Investigate caching methods. + $this->setHttpHost($httpHost); + // Try to load a direct match. + if ($domain = $this->domainStorage->loadByHostname($httpHost)) { + // If the load worked, set an exact match flag for the hook. + $domain->setMatchType(self::DOMAIN_MATCH_EXACT); + } + // If a straight load fails, create a base domain for checking. This data + // is required for hook_domain_request_alter(). + else { + $values = array('hostname' => $httpHost); + /** @var \Drupal\domain\Entity\DomainInterface $domain */ + $domain = $this->domainStorage->create($values); + $domain->setMatchType(self::DOMAIN_MATCH_NONE); + } + + // Make sure all modules are loaded and can alter the found domains. + // See https://www.drupal.org/node/2896434#comment-12267208. + $this->moduleHandler->reload(); + foreach ($this->moduleHandler->getImplementations('domain_request_alter') as $module) { + $this->moduleHandler->load($module); + } + // Now check with modules (like Domain Alias) that register alternate + // lookup systems with the main module. + $this->moduleHandler->alter('domain_request', $domain); + + // We must have registered a valid id, else the request made no match. + if (!empty($domain->id())) { + $this->setActiveDomain($domain); + } + // Fallback to default domain if no match. + elseif ($domain = $this->domainStorage->loadDefaultDomain()) { + $this->moduleHandler->alter('domain_request', $domain); + $domain->setMatchType(self::DOMAIN_MATCH_NONE); + if (!empty($domain->id())) { + $this->setActiveDomain($domain); + } + } + } + + /** + * {@inheritdoc} + */ + public function setActiveDomain(DomainInterface $domain) { + // @TODO: caching + $this->domain = $domain; + } + + /** + * Determine the active domain. + */ + protected function negotiateActiveDomain() { + $httpHost = $this->negotiateActiveHostname(); + $this->setRequestDomain($httpHost); + return $this->domain; + } + + /** + * {@inheritdoc} + */ + public function getActiveDomain($reset = FALSE) { + if ($reset) { + $this->negotiateActiveDomain(); + } + return $this->domain; + } + + /** + * {@inheritdoc} + */ + public function getActiveId() { + return $this->domain->id(); + } + + /** + * {@inheritdoc} + */ + public function negotiateActiveHostname() { + if ($request = $this->requestStack->getCurrentRequest()) { + $httpHost = $request->getHttpHost(); + } + else { + $httpHost = $_SERVER['HTTP_HOST']; + } + $hostname = !empty($httpHost) ? $httpHost : 'localhost'; + return $this->domainStorage->prepareHostname($hostname); + } + + /** + * {@inheritdoc} + */ + public function setHttpHost($httpHost) { + $this->httpHost = $httpHost; + } + + /** + * {@inheritdoc} + */ + public function getHttpHost() { + return $this->httpHost; + } + + /** + * {@inheritdoc} + */ + public function isRegisteredDomain($hostname) { + // Direct hostname match always passes. + if ($domain = $this->domainStorage->loadByHostname($hostname)) { + return TRUE; + } + // Check for registered alias matches. + $values = array('hostname' => $httpHost); + /** @var \Drupal\domain\Entity\DomainInterface $domain */ + $domain = $this->domainStorage->create($values); + $domain->setMatchType(self::DOMAIN_MATCH_NONE); + + // Now check with modules (like Domain Alias) that register alternate + // lookup systems with the main module. + $this->moduleHandler->alter('domain_request', $domain); + + // We must have registered a valid id, else the request made no match. + if (!empty($domain->id())) { + return TRUE; + } + return FALSE; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiatorInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiatorInterface.php new file mode 100644 index 000000000..c1c7cc437 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiatorInterface.php @@ -0,0 +1,91 @@ +getRequestContext()->getCompleteBaseUrl(); + return !UrlHelper::isExternal($url) || UrlHelper::externalIsLocal($url, $base_url) || $this->externalIsRegistered($url, $base_url); + } + + /** + * {@inheritdoc} + */ + protected function isSafe($url) { + return $this->isLocal($url); + } + + /** + * Returns the request context. + * + * @return \Drupal\Core\Routing\RequestContext + */ + protected function getRequestContext() { + if (!isset($this->requestContext)) { + $this->requestContext = \Drupal::service('router.request_context'); + } + return $this->requestContext; + } + + /** + * Sets the request context. + * + * @param \Drupal\Core\Routing\RequestContext $request_context + * The request context. + * + * @return $this + */ + public function setRequestContext(RequestContext $request_context) { + $this->requestContext = $request_context; + + return $this; + } + + /** + * Determines if an external URL points to this domain-aware installation. + * + * This method replaces the logic in + * Drupal\Component\Utility\UrlHelper::externalIsLocal(). Since that class is not + * directly extendable, we have to replace it. + * + * @param string $url + * A string containing an external URL, such as "http://example.com/foo". + * + * @return bool + * TRUE if the URL has the same domain and base path. + * @param string $base_url + * The base URL string to check against, such as "http://example.com/" + * + * @throws \InvalidArgumentException + * Exception thrown when $url is not fully qualified. + */ + public static function externalIsRegistered($url, $base_url) { + $url_parts = parse_url($url); + $base_parts = parse_url($base_url); + + if (empty($url_parts['host'])) { + throw new \InvalidArgumentException('A path was passed when a fully qualified domain was expected.'); + } + + // Check that the host name is registered with trusted hosts. + $trusted = self::checkTrustedHost($url_parts['host']); + if (!$trusted) { + return FALSE; + } + + // Check that the requested $url is registered. + $negotiator = \Drupal::service('domain.negotiator'); + $registered_domain = $negotiator->isRegisteredDomain($url_parts['host']); + + if (!isset($url_parts['path']) || !isset($base_parts['path'])) { + return $registered_domain; + } + else { + // When comparing base paths, we need a trailing slash to make sure a + // partial URL match isn't occurring. Since base_path() always returns + // with a trailing slash, we don't need to add the trailing slash here. + return ($registered_domain && stripos($url_parts['path'], $base_parts['path']) === 0); + } + } + + /** + * Checks that a host is registered with trusted_host_patterns. + * + * This method is cribbed from Symfony's Request::getHost() method. + * + * @param string $host + * The hostname to check. + * + * @return bool + * TRUE if the hostname matches the trusted_host_patterns. FALSE otherwise. + * It is the caller's responsibility to deal with this result securely. + */ + public static function checkTrustedHost($host) { + // See Request::setTrustedHosts(); + if (!isset(self::$trustedHostPatterns)) { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('#%s#i', $hostPattern); + }, Settings::get('trusted_host_patterns', [])); + // Reset the trusted host match array. + self::$trustedHosts = []; + } + + // Trim and remove port number from host. Host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // In the original Symfony code, hostname validation runs here. We have removed that + // portion because Domains are already validated on creation. + if (count(self::$trustedHostPatterns) > 0) { + // To avoid host header injection attacks, you should provide a list of trusted host patterns + if (in_array($host, self::$trustedHosts)) { + return TRUE; + } + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + return TRUE; + } + } + return FALSE; + } + // In cases where trusted_host_patterns are not set, allow all. This is flagged as a + // security issue by Drupal core in the Reports UI. + return TRUE; + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainStorage.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainStorage.php new file mode 100644 index 000000000..7a6f744cf --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainStorage.php @@ -0,0 +1,205 @@ +configFactory = $config_factory; + $this->uuidService = $uuid_service; + $this->languageManager = $language_manager; + $this->typedConfig = $typed_config; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('config.factory'), + $container->get('uuid'), + $container->get('language_manager'), + $container->get('config.typed') + ); + } + + /** + * {@inheritdoc} + */ + public function loadSchema() { + $fields = $this->typedConfig->getDefinition('domain.record.*'); + return isset($fields['mapping']) ? $fields['mapping'] : array(); + } + + /** + * {@inheritdoc} + */ + public function loadDefaultId() { + $result = $this->loadDefaultDomain(); + if (!empty($result)) { + return $result->id(); + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function loadDefaultDomain() { + $result = $this->loadByProperties(array('is_default' => TRUE)); + if (!empty($result)) { + return current($result); + } + return NULL; + } + + + /** + * {@inheritdoc} + */ + public function loadMultipleSorted(array $ids = NULL) { + $domains = $this->loadMultiple($ids); + uasort($domains, array($this, 'sort')); + return $domains; + } + + /** + * {@inheritdoc} + */ + public function loadByHostname($hostname) { + $hostname = $this->prepareHostname($hostname); + $result = $this->loadByProperties(array('hostname' => $hostname)); + if (empty($result)) { + return NULL; + } + return current($result); + } + + /** + * {@inheritdoc} + */ + public function loadOptionsList() { + $list = array(); + foreach ($this->loadMultipleSorted() as $id => $domain) { + $list[$id] = $domain->label(); + } + return $list; + } + + /** + * {@inheritdoc} + */ + public function sort(DomainInterface $a, DomainInterface $b) { + return $a->getWeight() > $b->getWeight(); + } + + /** + * {@inheritdoc} + */ + public function prepareHostname($hostname) { + // Strip www. prefix off the hostname. + $ignore_www = $this->configFactory->get('domain.settings')->get('www_prefix'); + if ($ignore_www && substr($hostname, 0, 4) == 'www.') { + $hostname = substr($hostname, 4); + } + return $hostname; + } + + /** + * {@inheritdoc} + */ + public function create(array $values = []) { + $default = $this->loadDefaultId(); + $domains = $this->loadMultiple(); + if (empty($values)) { + $values['hostname'] = $this->createHostname(); + $values['name'] = \Drupal::config('system.site')->get('name'); + } + $values += array( + 'scheme' => $this->getDefaultScheme(), + 'status' => 1, + 'weight' => count($domains) + 1, + 'is_default' => (int) empty($default), + ); + $domain = parent::create($values); + + return $domain; + } + + /** + * {@inheritdoc} + */ + public function createHostname() { + // We cannot inject the negotiator due to dependencies. + return \Drupal::service('domain.negotiator')->negotiateActiveHostname(); + } + + /** + * {@inheritdoc} + */ + public function createMachineName($hostname = NULL) { + if (empty($hostname)) { + $hostname = $this->createHostname(); + } + return preg_replace('/[^a-z0-9_]/', '_', $hostname); + } + + /** + * {@inheritdoc} + */ + public function getDefaultScheme() { + // Use the foundation request if possible/ + $request = \Drupal::request(); + if (!empty($request)) { + $scheme = $request->getScheme(); + } + // Else use the server variable. + elseif (!empty($_SERVER['https'])) { + $scheme = 'https'; + } + // Else fall through to default. + else { + $scheme = 'http'; + } + return $scheme; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainStorageInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainStorageInterface.php new file mode 100644 index 000000000..110bce7fd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainStorageInterface.php @@ -0,0 +1,124 @@ + label for use in forms. + */ + public function loadOptionsList(); + + /** + * Sorts domains by weight. + * + * For use by loadMultipleSorted(). + * + * @param DomainInterface $a + * The first Domain object to sort. + * @param DomainInterface $b + * The Domain object to compare against. + * + * @return bool + * Wether the first domain weight is greater or not. + */ + public function sort(DomainInterface $a, DomainInterface $b); + + /** + * Gets the entity field schema for domain records. + * + * @return array + * An array representing the field schema of the object. + */ + public function loadSchema(); + + /** + * Removes www. prefix from a hostname, if set. + * + * @param string $hostname + * A hostname. + * + * @return string + * The cleaned hostname. + */ + public function prepareHostname($hostname); + + /** + * Gets the hostname of the active request. + * + * @return string + * The hostname string of the current request. + */ + public function createHostname(); + + /** + * Creates a machine-name string from the hostname. + * + * This string is the primary key of the entity. + * + * @param string $hostname + * The hostname of the domain record. If empty, the current request will be + * used. + * + * @return string + * A string containing A-Z, a-z, 0-9, and _ characters. + */ + public function createMachineName($hostname = NULL); + + /** + * Returns the default http/https scheme for the site. + * + * This function helps us account for variable schemes across environments. + * + * @return $scheme + * A string representation of s scheme (http|https). + */ + public function getDefaultScheme(); + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainToken.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainToken.php new file mode 100644 index 000000000..7b4f23d01 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainToken.php @@ -0,0 +1,187 @@ +entityTypeManager = $entity_type_manager; + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + $this->negotiator = $negotiator; + } + + /** + * Implements hook_token_info(). + */ + public function getTokenInfo() { + // Domain token types. + $info['types']['domain'] = array( + 'name' => $this->t('Domains'), + 'description' => $this->t('Tokens related to domains.'), + 'needs-data' => 'domain', + ); + // These two types require the Token contrib module. + $info['types']['current-domain'] = array( + 'name' => $this->t('Current domain'), + 'description' => $this->t('Tokens related to the current domain.'), + 'type' => 'domain', + ); + $info['types']['default-domain'] = array( + 'name' => $this->t('Default domain'), + 'description' => $this->t('Tokens related to the default domain.'), + 'type' => 'domain', + ); + + // Domain tokens. + $info['tokens']['domain']['id'] = array( + 'name' => $this->t('Domain id'), + 'description' => $this->t('The domain\'s numeric ID.'), + ); + $info['tokens']['domain']['machine-name'] = array( + 'name' => $this->t('Domain machine name'), + 'description' => $this->t('The domain machine identifier.'), + ); + $info['tokens']['domain']['path'] = array( + 'name' => $this->t('Domain path'), + 'description' => $this->t('The base URL for the domain.'), + ); + $info['tokens']['domain']['name'] = array( + 'name' => $this->t('Domain name'), + 'description' => $this->t('The domain name.'), + ); + $info['tokens']['domain']['url'] = array( + 'name' => $this->t('Domain URL'), + 'description' => $this->t('The domain\'s URL for the current page request.'), + ); + $info['tokens']['domain']['hostname'] = array( + 'name' => $this->t('Domain hostname'), + 'description' => $this->t('The domain hostname.'), + ); + $info['tokens']['domain']['scheme'] = array( + 'name' => $this->t('Domain scheme'), + 'description' => $this->t('The domain scheme.'), + ); + $info['tokens']['domain']['status'] = array( + 'name' => $this->t('Domain status'), + 'description' => $this->t('The domain status.'), + ); + $info['tokens']['domain']['weight'] = array( + 'name' => $this->t('Domain weight'), + 'description' => $this->t('The domain weight.'), + ); + $info['tokens']['domain']['is_default'] = array( + 'name' => $this->t('Domain default'), + 'description' => $this->t('The domain is the default domain.'), + ); + + return $info; + } + + /** + * Implements hook_tokens(). + */ + public function getTokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) { + $replacements = array(); + + $domain = NULL; + + // Based on the type, get the proper domain context. + switch ($type) { + case 'domain': + if (!empty($data['domain'])) { + $domain = $data['domain']; + } + else { + $domain = $this->negotiator->getActiveDomain(); + } + break; + case 'current-domain': + $domain = $this->negotiator->getActiveDomain(); + break; + case 'default-domain': + $domain = $this->domainStorage->loadDefaultDomain(); + break; + } + + // Set the token information. + if (!empty($domain)) { + $callbacks = $this->getCallbacks(); + foreach ($tokens as $name => $original) { + if (isset($callbacks[$name])) { + $replacements[$original] = $domain->{$callbacks[$name]}(); + $bubbleable_metadata->addCacheableDependency($domain); + } + } + } + + return $replacements; + } + + /** + * Maps tokens to their entity callbacks. + * + * We assume that the token will call an instance of DomainInterface. + * + * @return array + */ + public function getCallbacks() { + return [ + 'id' => 'getDomainId', + 'machine-name' => 'id', + 'path' => 'getPath', + 'name' => 'label', + 'hostname' => 'getHostname', + 'scheme' => 'getScheme', + 'status' => 'status', + 'weight' => 'getWeight', + 'is_default' => 'isDefault', + 'url' => 'getUrl', + ]; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php new file mode 100644 index 000000000..f0f8a3da5 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php @@ -0,0 +1,154 @@ +moduleHandler = $module_handler; + $this->configFactory = $config_factory; + $this->httpClient = $http_client; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public function validate($hostname) { + $error_list = array(); + // Check for at least one dot or the use of 'localhost'. + // Note that localhost can specify a port. + $localhost_check = explode(':', $hostname); + if (substr_count($hostname, '.') == 0 && $localhost_check[0] != 'localhost') { + $error_list[] = $this->t('At least one dot (.) is required, except when using localhost.'); + } + // Check for one colon only. + if (substr_count($hostname, ':') > 1) { + $error_list[] = $this->t('Only one colon (:) is allowed.'); + } + // If a colon, make sure it is only followed by numbers. + elseif (substr_count($hostname, ':') == 1) { + $parts = explode(':', $hostname); + $port = (int) $parts[1]; + if (strcmp($port, $parts[1])) { + $error_list[] = $this->t('The port protocol must be an integer.'); + } + } + // The domain cannot begin or end with a period. + if (substr($hostname, 0, 1) == '.') { + $error_list[] = $this->t('The domain must not begin with a dot (.)'); + } + // The domain cannot begin or end with a period. + if (substr($hostname, -1) == '.') { + $error_list[] = $this->t('The domain must not end with a dot (.)'); + } + // Check for valid characters, unless using non-ASCII domains. + $config = $this->configFactory->get('domain.settings'); + $non_ascii = $config->get('allow_non_ascii'); + if (!$non_ascii) { + $pattern = '/^[a-z0-9\.\-:]*$/i'; + if (!preg_match($pattern, $hostname)) { + $error_list[] = $this->t('Only alphanumeric characters, dashes, and a colon are allowed.'); + } + } + // Check for lower case. + if ($hostname != Unicode::strtolower($hostname)) { + $error_list[] = $this->t('Only lower-case characters are allowed.'); + } + // Check for 'www' prefix if redirection / handling is + // enabled under global domain settings. + // Note that www prefix handling must be set explicitly in the UI. + // See http://drupal.org/node/1529316 and http://drupal.org/node/1783042 + if ($config->get('www_prefix') && (substr($hostname, 0, strpos($hostname, '.')) == 'www')) { + $error_list[] = $this->t('WWW prefix handling: Domains must be registered without the www. prefix.'); + } + + // Allow modules to alter this behavior. + $this->moduleHandler->alter('domain_validate', $error_list, $hostname); + + return $error_list; + } + + /** + * {@inheritdoc} + */ + public function checkResponse(DomainInterface $domain) { + $url = $domain->getPath() . drupal_get_path('module', 'domain') . '/tests/200.png'; + try { + // GuzzleHttp no longer allows for bogus URL calls. + $request = $this->httpClient->get($url); + } + // We cannot know which Guzzle Exception class will be returned; be generic. + catch (RequestException $e) { + watchdog_exception('domain', $e); + // File a general server failure. + $domain->setResponse(500); + return; + } + // Expected result (i.e. no exception thrown.) + $domain->setResponse($request->getStatusCode()); + + return $domain->getResponse(); + } + + /** + * {@inheritdoc} + */ + public function getRequiredFields() { + return array('hostname', 'name', 'scheme', 'status', 'weight'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainValidatorInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainValidatorInterface.php new file mode 100644 index 000000000..b1c349c20 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainValidatorInterface.php @@ -0,0 +1,45 @@ +getStorage('domain'); + $default = $domain_storage->loadDefaultId(); + $count = $storage_controller->getQuery()->count()->execute(); + $values += array( + 'scheme' => empty($GLOBALS['is_https']) ? 'http' : 'https', + 'status' => 1, + 'weight' => $count + 1, + 'is_default' => (int) empty($default), + ); + // Note that we have not created a domain_id, which is only used for + // node access control and will be added on save. + } + + /** + * {@inheritdoc} + */ + public function isActive() { + $negotiator = \Drupal::service('domain.negotiator'); + /** @var self $domain */ + $domain = $negotiator->getActiveDomain(); + if (empty($domain)) { + return FALSE; + } + return ($this->id() == $domain->id()); + } + + /** + * {@inheritdoc} + */ + public function addProperty($name, $value) { + if (!isset($this->{$name})) { + $this->{$name} = $value; + } + } + + /** + * {@inheritdoc} + */ + public function isDefault() { + return (bool) $this->is_default; + } + + /** + * {@inheritdoc} + */ + public function isHttps() { + return (bool) ($this->getScheme(FALSE) == 'https'); + } + + /** + * {@inheritdoc} + */ + public function saveDefault() { + if (!$this->isDefault()) { + // Swap the current default. + /** @var self $default */ + if ($default = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultDomain()) { + $default->is_default = 0; + $default->setHostname($default->getCanonical()); + $default->save(); + } + // Save the new default. + $this->is_default = 1; + $this->setHostname($this->getCanonical()); + $this->save(); + } + else { + drupal_set_message($this->t('The selected domain is already the default.'), 'warning'); + } + } + + /** + * {@inheritdoc} + */ + public function enable() { + $this->setStatus(TRUE); + $this->setHostname($this->getCanonical()); + $this->save(); + } + + /** + * {@inheritdoc} + */ + public function disable() { + if (!$this->isDefault()) { + $this->setStatus(FALSE); + $this->setHostname($this->getCanonical()); + $this->save(); + } + else { + drupal_set_message($this->t('The default domain cannot be disabled.'), 'warning'); + } + } + + /** + * {@inheritdoc} + */ + public function saveProperty($name, $value) { + if (isset($this->{$name})) { + $this->{$name} = $value; + $this->setHostname($this->getCanonical()); + $this->save(); + drupal_set_message($this->t('The @key attribute was set to @value for domain @hostname.', array( + '@key' => $name, + '@value' => $value, + '@hostname' => $this->hostname, + ))); + } + else { + drupal_set_message($this->t('The @key attribute does not exist.', array('@key' => $name))); + } + } + + /** + * {@inheritdoc} + */ + public function setPath() { + $this->path = $this->getScheme() . $this->getHostname() . base_path(); + } + + /** + * {@inheritdoc} + */ + public function setUrl() { + $uri = \Drupal::request()->getRequestUri(); + $this->url = $this->getScheme() . $this->getHostname() . $uri; + } + + /** + * {@inheritdoc} + */ + public function getPath() { + if (!isset($this->path)) { + $this->setPath(); + } + return $this->path; + } + + /** + * Returns the raw path of the domain object, without the base url. + */ + public function getRawPath() { + return $this->getScheme() . $this->getHostname(); + } + + /** + * Builds a link from a known internal path. + * + * @param string $path + * A Drupal-formatted internal path, starting with /. Note that it is the + * caller's responsibility to handle the base_path(). + * + * @return string + * The built link. + */ + public function buildUrl($path) { + return $this->getRawPath() . $path; + } + + /** + * {@inheritdoc} + */ + public function getUrl() { + if (!isset($this->url)) { + $this->setUrl(); + } + return $this->url; + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + parent::preSave($storage); + // Sets the default domain properly. + /** @var self $default */ + $default = $storage->loadDefaultDomain(); + if (!$default) { + $this->is_default = 1; + } + elseif ($this->is_default && $default->getDomainId() != $this->getDomainId()) { + // Swap the current default. + $default->is_default = 0; + $default->save(); + } + // Ensures we have a proper domain_id. + if ($this->isNew()) { + $this->createDomainId(); + } + // Prevent duplicate hostname. + $hostname = $this->getHostname(); + // Do not use domain loader because it may change hostname. + $existing = $storage->loadByProperties(['hostname' => $hostname]); + $existing = reset($existing); + if ($existing && $this->getDomainId() != $existing->getDomainId()) { + throw new ConfigValueException("The hostname ($hostname) is already registered."); + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + // Invalidate cache tags relevant to domains. + \Drupal::service('cache_tags.invalidator')->invalidateTags(['rendered', 'url.site']); + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageInterface $storage, array $entities) { + parent::postDelete($storage, $entities); + foreach ($entities as $entity) { + $actions = $storage->loadMultiple([ + 'domain_default_action.' . $entity->id(), + 'domain_delete_action.' . $entity->id(), + 'domain_disable_action.' . $entity->id(), + 'domain_enable_action.' . $entity->id(), + ]); + foreach ($actions as $action) { + $action->delete(); + } + } + // Invalidate cache tags relevant to domains. + \Drupal::service('cache_tags.invalidator')->invalidateTags(['rendered', 'url.site']); + } + + /** + * {@inheritdoc} + */ + public function createDomainId() { + // We cannot reliably use sequences (1, 2, 3) because those can be different + // across environments. Instead, we use the crc32 hash function to create a + // unique numeric id for each domain. In some systems (Windows?) we have + // reports of crc32 returning a negative number. Issue #2794047. + // If we don't use hash(), then crc32() returns different results for 32- and 64-bit + // systems. On 32-bit systems, the number returned may also be too large for PHP. + // See #2908236. + $id = hash('crc32', $this->id()); + $id = abs(hexdec(substr($id, 0, -2))); + $this->createNumericId($id); + } + + /** + * Creates a unique numeric id for use in the {node_access} table. + * + * @param integer $id + * An integer to ue as the numeric id. + */ + public function createNumericId($id) { + // Ensure that this value is unique. + $storage = \Drupal::entityTypeManager()->getStorage('domain'); + $result = $storage->loadByProperties(array('domain_id' => $id)); + if (empty($result)) { + $this->domain_id = $id; + } + else { + $id++; + $this->createNumericId($id); + } + } + + /** + * {@inheritdoc} + */ + public function getScheme($add_suffix = TRUE) { + $scheme = $this->scheme; + if ($scheme == 'variable') { + $scheme = \Drupal::service('entity_type.manager')->getStorage('domain')->getDefaultScheme(); + } + elseif ($scheme != 'https') { + $scheme = 'http'; + } + $scheme .= ($add_suffix) ? '://' : ''; + + return $scheme; + } + + /** + * {@inheritdoc} + */ + public function getRawScheme() { + return $this->scheme; + } + + /** + * {@inheritdoc} + */ + public function getResponse() { + if (empty($this->response)) { + $validator = \Drupal::service('domain.validator'); + $validator->checkResponse($this); + } + return $this->response; + } + + /** + * {@inheritdoc} + */ + public function setResponse($response) { + $this->response = $response; + } + + /** + * {@inheritdoc} + */ + public function getLink($current_path = TRUE) { + $options = array('absolute' => TRUE, 'https' => $this->isHttps()); + if ($current_path) { + $url = Url::fromUri($this->getUrl(), $options); + } + else { + $url = Url::fromUri($this->getPath(), $options); + } + + return Link::fromTextAndUrl($this->getCanonical(), $url)->toString(); + } + + /** + * {@inheritdoc} + */ + public function getRedirect() { + return $this->redirect; + } + + /** + * {@inheritdoc} + */ + public function setRedirect($code = 302) { + $this->redirect = $code; + } + + /** + * {@inheritdoc} + */ + public function getHostname() { + return $this->hostname; + } + + /** + * {@inheritdoc} + */ + public function setHostname($hostname) { + $this->hostname = $hostname; + } + + /** + * {@inheritdoc} + */ + public function getDomainId() { + return $this->domain_id; + } + + /** + * {@inheritdoc} + */ + public function getWeight() { + return $this->weight; + } + + /** + * {@inheritdoc} + */ + public function setMatchType($match_type = DomainNegotiator::DOMAIN_MATCH_EXACT) { + $this->matchType = $match_type; + } + + /** + * {@inheritdoc} + */ + public function getMatchType() { + return $this->matchType; + } + + /** + * {@inheritdoc} + */ + public function getPort() { + $ports = explode(':', $this->getHostname()); + if (isset($ports[1])) { + return ':' . $ports[1]; + } + return ''; + } + + /** + * {@inheritdoc} + */ + public function setCanonical($hostname = NULL) { + if (is_null($hostname)) { + $this->canonical = $this->getHostname(); + } + else { + $this->canonical = $hostname; + } + } + + /** + * {@inheritdoc} + */ + public function getCanonical() { + if (empty($this->canonical)) { + $this->setCanonical(); + } + return $this->canonical; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/EventSubscriber/DomainSubscriber.php b/sites/all/modules/contrib/admin/domain/domain/src/EventSubscriber/DomainSubscriber.php new file mode 100644 index 000000000..244619d97 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/EventSubscriber/DomainSubscriber.php @@ -0,0 +1,140 @@ +domainNegotiator = $negotiator; + $this->entityTypeManager = $entity_type_manager; + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + $this->accessCheck = $access_check; + $this->account = $account; + } + + /** + * Sets the domain context of the request. + * + * This method also determines the redirect status for the http request. + * + * Specifically, here we determine if a redirect is required. That happens + * in one of two cases: an unauthorized request to an inactive domain is made; + * a domain alias is set to redirect to its primary domain record. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event + * The Event to process. + * + * @see domain_alias_domain_request_alter + */ + public function onKernelRequestDomain(GetResponseEvent $event) { + // Negotiate the request and set domain context. + /** @var \Drupal\domain\DomainInterface $domain */ + if ($domain = $this->domainNegotiator->getActiveDomain(TRUE)) { + $hostname = $domain->getHostname(); + $domain_url = $domain->getUrl(); + if ($domain_url) { + $redirect_status = $domain->getRedirect(); + $path = trim($event->getRequest()->getPathInfo(), '/'); + // If domain negotiation asked for a redirect, issue it. + if (is_null($redirect_status) && $this->accessCheck->checkPath($path)) { + // Else check for active domain or inactive access. + /** @var \Drupal\Core\Access\AccessResult $access */ + $access = $this->accessCheck->access($this->account); + // If the access check fails, reroute to the default domain. + // Note that Allowed, Neutral, and Failed are the options here. + // We insist on Allowed. + if (!$access->isAllowed()) { + /** @var \Drupal\domain\DomainInterface $default */ + $default = $this->domainStorage->loadDefaultDomain(); + $domain_url = $default->getUrl(); + $redirect_status = 302; + $hostname = $default->getHostname(); + } + } + } + if (!empty($redirect_status)) { + // Pass a redirect if necessary. + if (DomainRedirectResponse::checkTrustedHost($hostname)) { + $response = new TrustedRedirectResponse($domain_url, $redirect_status); + } + else { + // If the redirect is not to a registered hostname, reject the request. + $response = new Response('The provided host name is not a valid redirect.', 401); + } + $event->setResponse($response); + } + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + // This needs to fire very early in the stack, before accounts are cached. + $events[KernelEvents::REQUEST][] = array('onKernelRequestDomain', 50); + return $events; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainDeleteForm.php b/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainDeleteForm.php new file mode 100644 index 000000000..d556a7c49 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainDeleteForm.php @@ -0,0 +1,45 @@ +t('Are you sure you want to delete %name?', array('%name' => $this->entity->label())); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('domain.admin'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->entity->delete(); + drupal_set_message($this->t('Domain %label has been deleted.', array('%label' => $this->entity->label()))); + \Drupal::logger('domain')->notice('Domain %label has been deleted.', array('%label' => $this->entity->label())); + $form_state->setRedirectUrl($this->getCancelUrl()); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainSettingsForm.php b/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainSettingsForm.php new file mode 100644 index 000000000..f5169aa6e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainSettingsForm.php @@ -0,0 +1,92 @@ +config('domain.settings'); + $form['allow_non_ascii'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Allow non-ASCII characters in domains and aliases'), + '#default_value' => $config->get('allow_non_ascii'), + '#description' => $this->t('Domains may be registered with international character sets. Note that not all DNS server respect non-ascii characters.'), + ); + $form['www_prefix'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Ignore www prefix when negotiating domains'), + '#default_value' => $config->get('www_prefix'), + '#description' => $this->t('Domain negotiation will ignore any www prefixes for all requests.'), + ); + // Get the usable tokens for this field. + foreach (\Drupal::service('domain.token')->getCallbacks() as $key => $callback) { + $patterns[] = "[domain:$key]"; + } + $form['css_classes'] = array( + '#type' => 'textfield', + '#size' => 80, + '#title' => $this->t('Custom CSS classes'), + '#default_value' => $config->get('css_classes'), + '#description' => $this->t('Enter any CSS classes that should be added to the <body> tag. Available replacement patterns are: ' . implode(', ', $patterns)), + ); + $form['login_paths'] = array( + '#type' => 'textarea', + '#rows' => 5, + '#columns' => 40, + '#title' => $this->t('Paths that should be accessible for inactive domains'), + '#default_value' => $config->get('login_paths', "/user/login\r\n/user/password"), + '#description' => $this->t('Inactive domains are only accessible to users with permission. + Enter any paths that should be accessible, one per line. Normally, only the + login path will be allowed.'), + ); + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $config = $this->config('domain.settings'); + foreach ($this->settingsKeys() as $key) { + $config->set($key, $form_state->getValue($key)); + } + $config->save(); + parent::submitForm($form, $form_state); + } + + /** + * Returns an array of settings keys. + */ + public function settingsKeys() { + return [ + 'allow_non_ascii', + 'www_prefix', + 'login_paths', + 'css_classes', + ]; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DefaultDomain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DefaultDomain.php new file mode 100644 index 000000000..f44253bef --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DefaultDomain.php @@ -0,0 +1,47 @@ +saveDefault(); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $objects) { + foreach ($objects as $object) { + if ($object instanceof DomainInterface) { + $object->saveDefault(); + } + } + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + $access_result = AccessResult::allowedIfHasPermission($account, 'administer domains'); + return $return_as_object ? $access_result : $access_result->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DeleteDomain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DeleteDomain.php new file mode 100644 index 000000000..0450a6d88 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DeleteDomain.php @@ -0,0 +1,47 @@ +delete(); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $objects) { + foreach ($objects as $object) { + if ($object instanceof DomainInterface) { + $object->delete(); + } + } + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + $access_result = AccessResult::allowedIfHasPermission($account, 'administer domains'); + return $return_as_object ? $access_result : $access_result->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DisableDomain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DisableDomain.php new file mode 100644 index 000000000..f227f0604 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DisableDomain.php @@ -0,0 +1,47 @@ +disable(); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $objects) { + foreach ($objects as $object) { + if ($object instanceof DomainInterface) { + $object->disable(); + } + } + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + $access_result = AccessResult::allowedIfHasPermission($account, 'administer domains'); + return $return_as_object ? $access_result : $access_result->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/EnableDomain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/EnableDomain.php new file mode 100644 index 000000000..0b9deab27 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/EnableDomain.php @@ -0,0 +1,47 @@ +enable(); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $objects) { + foreach ($objects as $object) { + if ($object instanceof DomainInterface) { + $object->enable(); + } + } + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + $access_result = AccessResult::allowedIfHasPermission($account, 'administer domains'); + return $return_as_object ? $access_result : $access_result->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainBlockBase.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainBlockBase.php new file mode 100644 index 000000000..ccc8d0643 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainBlockBase.php @@ -0,0 +1,20 @@ +getSetting('link_options') == 'home') { + return ['url.site']; + } + return ['url']; + } + + /** + * {@inheritdoc} + */ + public function blockForm($form, FormStateInterface $form_state) { + $defaults = $this->defaultConfiguration(); + $elements['link_options'] = [ + '#title' => $this->t('Link paths'), + '#type' => 'radios', + '#required' => TRUE, + '#options' => ['active' => $this->t('Link to active url'), 'home' => $this->t('Link to site home page')], + '#default_value' => !empty($this->configuration['link_options']) ? $this->configuration['link_options'] : $defaults['link_options'], + '#description' => $this->t('Determines how links to each domain will be written. Note that some paths may not be accessible on all domains.'), + ]; + $options = array( + 'select' => t('JavaScript select list'), + 'menus' => t('Menu-style tab links'), + 'ul' => t('Unordered list of links'), + ); + $elements['link_theme'] = array( + '#type' => 'radios', + '#title' => t('Link theme'), + '#default_value' => !empty($this->configuration['link_theme']) ? $this->configuration['link_theme'] : $defaults['link_theme'], + '#options' => $options, + '#description' => $this->t('Select how to display the block output.'), + ); + $options = array( + 'name' => t('The domain display name'), + 'hostname' => t('The raw hostname'), + 'url' => t('The domain base URL'), + ); + $elements['link_label'] = array( + '#type' => 'radios', + '#title' => t('Link text'), + '#default_value' => !empty($this->configuration['link_label']) ? $this->configuration['link_label'] : $defaults['link_label'], + '#options' => $options, + '#description' => $this->t('Select the text to display for each link.'), + ); + return $elements; + } + + /** + * {@inheritdoc} + */ + public function blockSubmit($form, FormStateInterface $form_state) { + // Process the block's submission handling if no errors occurred only. + if (!$form_state->getErrors()) { + foreach (array_keys($this->defaultConfiguration()) as $element) { + $this->configuration[$element] = $form_state->getValue($element); + } + } + } + + /** + * Overrides \Drupal\block\BlockBase::access(). + */ + public function access(AccountInterface $account, $return_as_object = FALSE) { + $access = AccessResult::allowedIfHasPermissions($account, array('administer domains', 'use domain nav block'), 'OR'); + return $return_as_object ? $access : $access->isAllowed(); + } + + /** + * Build the output. + */ + public function build() { + /** @var \Drupal\domain\DomainInterface $active_domain */ + $active_domain = \Drupal::service('domain.negotiator')->getActiveDomain(); + $access_handler = \Drupal::service('entity_type.manager')->getAccessControlHandler('domain'); + $account = \Drupal::currentUser(); + + // Determine the visible domain list. + $items = []; + $add_path = ($this->getSetting('link_options') == 'active'); + /** @var \Drupal\domain\DomainInterface $domain */ + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultipleSorted() as $domain) { + // Set the URL. + $options = ['absolute' => TRUE, 'https' => ($domain->getScheme() == 'https')]; + if ($add_path) { + $url = Url::fromUri($domain->getUrl(), $options); + } + else { + $url = Url::fromUri($domain->getPath(), $options); + } + // Set the label text. + $label = $domain->label(); + if ($this->getSetting('link_label') == 'hostname') { + $label = $domain->getHostname(); + } + elseif ($this->getSetting('link_label') == 'url') { + $label = $domain->getPath(); + } + // Handles menu links. + if ($domain->status() || $account->hasPermission('access inactive domains')) { + if ($this->getSetting('link_theme') == 'menus') { + // @TODO: active trail isn't working properly, likely because this + // isn't really a menu. + $items[] = [ + 'title' => $label, + 'url' => $url, + 'attributes' => [], + 'below' => [], + 'is_expanded' => FALSE, + 'is_collapsed' => FALSE, + 'in_active_trail' => $domain->isActive(), + ]; + } + elseif ($this->getSetting('link_theme') == 'select') { + $items[] = [ + 'label' => $label, + 'url' => $url->toString(), + 'active' => $domain->isActive(), + ]; + } + else { + $items[] = ['#markup' => Link::fromTextAndUrl($label, $url)->toString()]; + } + } + } + + // Set the proper theme. + switch ($this->getSetting('link_theme')) { + case 'select': + $build['#theme'] = 'domain_nav_block'; + $build['items'] = $items; + break; + case 'menus': + // Map the $items params to what menu.html.twig expects. + $build['#items'] = $items; + $build['#menu_name'] = 'domain-nav'; + $build['#sorted'] = TRUE; + $build['#theme'] = 'menu__' . strtr($build['#menu_name'], '-', '_'); + break; + case 'ul': + default: + $build['#theme'] = 'item_list'; + $build['#items'] = $items; + break; + } + + return $build; + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'link_options' => 'home', + 'link_theme' => 'ul', + 'link_label' => 'name', + ]; + } + + /** + * Gets the configuration for the block, loading defaults if not set. + * + * @param $key + * The setting key to retrieve, a string + * + * @return + *. The setting value, a string. + */ + public function getSetting($key) { + if (isset($this->settings[$key])) { + return $this->settings[$key]; + } + $defaults = $this->defaultConfiguration(); + if (isset($this->configuration[$key])) { + $this->settings[$key] = $this->configuration[$key]; + } + else { + $this->settings[$key] = $defaults[$key]; + } + return $this->settings[$key]; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainServerBlock.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainServerBlock.php new file mode 100644 index 000000000..6929c273a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainServerBlock.php @@ -0,0 +1,126 @@ +isAllowed(); + } + + /** + * Build the output. + * + * @TODO: abstract or theme this function? + */ + public function build() { + /** @var \Drupal\domain\DomainInterface $domain */ + $domain = \Drupal::service('domain.negotiator')->getActiveDomain(); + if (!$domain) { + return array( + '#markup' => $this->t('No domain record could be loaded.'), + ); + } + $header = array($this->t('Server'), $this->t('Value')); + $rows[] = array( + $this->t('HTTP_HOST request'), + Html::escape($_SERVER['HTTP_HOST']), + ); + // Check the response test. + $domain->getResponse(); + $check = \Drupal::service('entity_type.manager')->getStorage('domain')->loadByHostname($_SERVER['HTTP_HOST']); + $match = $this->t('Exact match'); + if (!$check) { + // Specific check for Domain Alias. + if (isset($domain->alias)) { + $match = $this->t('ALIAS: Using alias %id', array('%id' => $domain->alias->getPattern())); + } + else { + $match = $this->t('FALSE: Using default domain.'); + } + } + $rows[] = array( + $this->t('Domain match'), + $match, + ); + $www = \Drupal::config('domain.settings')->get('www_prefix'); + $rows[] = array( + $this->t('Strip www prefix'), + !empty($www) ? $this->t('On') : $this->t('Off'), + ); + $list = $domain->toArray(); + ksort($list); + foreach ($list as $key => $value) { + if (is_null($value)) { + $value = $this->t('NULL'); + } + elseif ($value === TRUE) { + $value = $this->t('TRUE'); + } + elseif ($value === FALSE) { + $value = $this->t('FALSE'); + } + elseif ($key == 'status' || $key == 'is_default') { + $value = empty($value) ? $this->t('FALSE') : $this->t('TRUE'); + } + $rows[] = array( + Html::escape($key), + !is_array($value) ? Html::escape($value) : $this->printArray($value), + ); + } + return array( + '#theme' => 'table', + '#rows' => $rows, + '#header' => $header, + ); + } + + /** + * Prints array data for the server block. + * + * @param array $array + * An array of data. Note that we support two levels of nesting. + * + * @return string + * A suitable output string. + */ + public function printArray(array $array) { + $items = array(); + foreach ($array as $key => $val) { + if (!is_array($val)) { + $value = Html::escape($val); + } + else { + $list = array(); + foreach ($val as $k => $v) { + $list[] = $this->t('@key : @value', array('@key' => $k, '@value' => $v)); + } + $value = implode('
', $list); + } + $items[] = $this->t('@key : @value', array('@key' => $key, '@value' => $value)); + } + $variables['domain_server'] = array( + '#theme' => 'item_list', + '#items' => $items, + ); + return render($variables); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainSwitcherBlock.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainSwitcherBlock.php new file mode 100644 index 000000000..1f45c9f0d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainSwitcherBlock.php @@ -0,0 +1,52 @@ +isAllowed(); + } + + /** + * Build the output. + * + * @TODO: abstract or theme this function? + */ + public function build() { + /** @var \Drupal\domain\DomainInterface $active_domain */ + $active_domain = \Drupal::service('domain.negotiator')->getActiveDomain(); + $items = array(); + /** @var \Drupal\domain\DomainInterface $domain */ + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultipleSorted() as $domain) { + $string = $domain->getLink(); + if (!$domain->status()) { + $string .= '*'; + } + if ($domain->id() == $active_domain->id()) { + $string = '' . $string . ''; + } + $items[] = array('#markup' => $string); + } + return array( + '#theme' => 'item_list', + '#items' => $items, + ); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainTokenBlock.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainTokenBlock.php new file mode 100644 index 000000000..f8f9965fc --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainTokenBlock.php @@ -0,0 +1,83 @@ +isAllowed(); + } + + /** + * Build the output. + * + * @TODO: abstract or theme this function? + */ + public function build() { + /** @var \Drupal\domain\DomainInterface $domain */ + $domain = \Drupal::service('domain.negotiator')->getActiveDomain(); + if (!$domain) { + return array( + '#markup' => $this->t('No domain record could be loaded.'), + ); + } + $header = array($this->t('Token'), $this->t('Value')); + return array( + '#theme' => 'table', + '#rows' => $this->renderTokens($domain), + '#header' => $header, + ); + } + + /** + * Generates available tokens for printing. + * + * @param Drupal\domain\DomainInterface $domain + * The active domain request. + * @return array + * An array keyed by token name, with value of replacement value. + */ + private function renderTokens(DomainInterface $domain) { + $rows = array(); + $token = \Drupal::token(); + $tokens = $token->getInfo(); + // The 'domain' token is supported by core. The others by Token module, + // so we cannot assume that Token module is present. + $domain_tokens = ['domain', 'current-domain', 'default-domain']; + foreach ($domain_tokens as $key) { + if (isset($tokens['tokens'][$key])) { + $data = []; + // Pass domain data to the default handler. + if ($key == 'domain') { + $data['domain'] = $domain; + } + foreach ($tokens['tokens'][$key] as $name => $info) { + $string = "[$key:$name]"; + $rows[] = [ + $string, + $token->replace($string, $data), + ]; + } + } + } + return $rows; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php new file mode 100644 index 000000000..95cc9ed8f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php @@ -0,0 +1,145 @@ +domainNegotiator = $domain_negotiator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $container->get('domain.negotiator'), + $configuration, + $plugin_id, + $plugin_definition + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form['domains'] = array( + '#type' => 'checkboxes', + '#title' => $this->t('When the following domains are active'), + '#default_value' => $this->configuration['domains'], + '#options' => array_map('\Drupal\Component\Utility\Html::escape', \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList()), + '#description' => $this->t('If you select no domains, the condition will evaluate to TRUE for all requests.'), + '#attached' => array( + 'library' => array( + 'domain/drupal.domain', + ), + ), + ); + return parent::buildConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array( + 'domains' => array(), + ) + parent::defaultConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['domains'] = array_filter($form_state->getValue('domains')); + parent::submitConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function summary() { + // Use the domain labels. They will be sanitized below. + $domains = array_intersect_key(\Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(), $this->configuration['domains']); + if (count($domains) > 1) { + $domains = implode(', ', $domains); + } + else { + $domains = reset($domains); + } + if ($this->isNegated()) { + return $this->t('Active domain is not @domains', array('@domains' => $domains)); + } + else { + return $this->t('Active domain is @domains', array('@domains' => $domains)); + } + } + + /** + * {@inheritdoc} + */ + public function evaluate() { + $domains = $this->configuration['domains']; + if (empty($domains) && !$this->isNegated()) { + return TRUE; + } + // If the context did not load, derive from the request. + if (!$domain = $this->getContextValue('entity:domain')) { + $domain = $this->domainNegotiator->getActiveDomain(); + } + // No context found? + if (empty($domain)) { + return FALSE; + } + // NOTE: The context system handles negation for us. + return (bool) in_array($domain->id(), $domains); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = parent::getCacheContexts(); + $contexts[] = 'url.site'; + return $contexts; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/EntityReferenceSelection/DomainAdminSelection.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/EntityReferenceSelection/DomainAdminSelection.php new file mode 100644 index 000000000..bd47b8a4d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/EntityReferenceSelection/DomainAdminSelection.php @@ -0,0 +1,36 @@ +currentUser->hasPermission('administer domains')) { + return $query; + } + // Can this user access inactive domains? + if (!$this->currentUser->hasPermission('access inactive domains')) { + $query->condition('status', 1); + } + // Filter domains by the user's assignments, which are controlled by other + // modules. Those modules must know what type of entity they are dealing + // with, so look up the entity type and bundle. + $info = $query->getMetaData('entity_reference_selection_handler'); + + if (!empty($info->configuration['entity'])) { + $context['entity_type'] = $info->configuration['entity']->getEntityTypeId(); + $context['bundle'] = $info->configuration['entity']->bundle(); + $context['field_type'] = $this->field_type; + + // Load the current user. + $account = User::load($this->currentUser->id()); + // Run the alter hook. + $this->moduleHandler->alter('domain_references', $query, $account, $context); + } + + return $query; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $selection_handler_settings = $this->configuration['handler_settings']; + + // Merge-in default values. + $selection_handler_settings += array( + // For the 'target_bundles' setting, a NULL value is equivalent to "allow + // entities from any bundle to be referenced" and an empty array value is + // equivalent to "no entities from any bundle can be referenced". + 'target_bundles' => NULL, + 'sort' => array( + 'field' => 'weight', + 'direction' => 'ASC', + ), + 'auto_create' => FALSE, + 'default_selection' => 'current', + ); + + $form['target_bundles'] = array( + '#type' => 'value', + '#value' => NULL, + ); + + $fields = array( + 'weight' => $this->t('Weight'), + 'label' => $this->t('Name'), + 'hostname' => $this->t('Hostname'), + ); + + $form['sort']['field'] = array( + '#type' => 'select', + '#title' => $this->t('Sort by'), + '#options' => $fields, + '#ajax' => FALSE, + '#default_value' => $selection_handler_settings['sort']['field'], + ); + + $form['sort']['direction'] = array( + '#type' => 'select', + '#title' => $this->t('Sort direction'), + '#required' => TRUE, + '#options' => array( + 'ASC' => $this->t('Ascending'), + 'DESC' => $this->t('Descending'), + ), + '#default_value' => $selection_handler_settings['sort']['direction'], + ); + + return $form; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/migrate/source/d7/DomainRecord.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/migrate/source/d7/DomainRecord.php new file mode 100644 index 000000000..4ceaa42f2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/migrate/source/d7/DomainRecord.php @@ -0,0 +1,56 @@ +select('domain', 'd')->fields('d', $fields); + } + + /** + * {@inheritdoc} + */ + public function fields() { + return [ + 'domain_id' => $this->t('Domain ID.'), + 'subdomain' => $this->t('Subdomain.'), + 'sitename' => $this->t('Sitename.'), + 'scheme' => $this->t('Scheme.'), + 'valid' => $this->t('Valid.'), + 'weight' => $this->t('Weight.'), + 'is_default' => $this->t('Is default.'), + 'machine_name' => $this->t('Machine name.'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getIds() { + return ['domain_id' => ['type' => 'int']]; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/views/access/Domain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/views/access/Domain.php new file mode 100644 index 000000000..63376a960 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/views/access/Domain.php @@ -0,0 +1,189 @@ +domainStorage = $domain_storage; + $this->domainNegotiator = $domain_negotiator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager')->getStorage('domain'), + $container->get('domain.negotiator') + ); + } + + /** + * {@inheritdoc} + */ + public function access(AccountInterface $account) { + $id = $this->domainNegotiator->getActiveId(); + $options = array_filter($this->options['domain']); + return isset($options[$id]); + } + + /** + * {@inheritdoc} + */ + public function alterRouteDefinition(Route $route) { + if ($this->options['domain']) { + $route->setRequirement('_domain', (string) implode('+', $this->options['domain'])); + } + } + + /** + * {@inheritdoc} + */ + public function summaryTitle() { + $count = count($this->options['domain']); + if ($count < 1) { + return $this->t('No domain(s) selected'); + } + elseif ($count > 1) { + return $this->t('Multiple domains'); + } + else { + $domains = $this->domainStorage->loadOptionsList(); + $domain = reset($this->options['domain']); + return $domains[$domain]; + } + } + + /** + * {@inheritdoc} + */ + protected function defineOptions() { + $options = parent::defineOptions(); + $options['domain'] = array('default' => array()); + + return $options; + } + + /** + * {@inheritdoc} + */ + public function buildOptionsForm(&$form, FormStateInterface $form_state) { + parent::buildOptionsForm($form, $form_state); + $form['domain'] = array( + '#type' => 'checkboxes', + '#title' => $this->t('Domain'), + '#default_value' => $this->options['domain'], + '#options' => $this->domainStorage->loadOptionsList(), + '#description' => $this->t('Only the checked domain(s) will be able to access this display.'), + ); + } + + /** + * {@inheritdoc} + */ + public function validateOptionsForm(&$form, FormStateInterface $form_state) { + $domain = $form_state->getValue(array('access_options', 'domain')); + $domain = array_filter($domain); + + if (!$domain) { + $form_state->setError($form['domain'], $this->t('You must select at least one domain if type is "by domain"')); + } + + $form_state->setValue(array('access_options', 'domain'), $domain); + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + $dependencies = parent::calculateDependencies(); + + foreach (array_keys($this->options['domain']) as $id) { + if ($domain = $this->domainStorage->load($id)) { + $dependencies[$domain->getConfigDependencyKey()][] = $domain->getConfigDependencyName(); + } + } + + return $dependencies; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['url.site']; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return []; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Tests/DomainTestBase.php b/sites/all/modules/contrib/admin/domain/domain/src/Tests/DomainTestBase.php new file mode 100644 index 000000000..51cc0f3c4 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Tests/DomainTestBase.php @@ -0,0 +1,102 @@ +base_hostname or the + * domainCreateTestDomains() method. + */ + public $base_hostname; + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('domain', 'node'); + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Create Basic page and Article node types. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + } + + // Set the base hostname for domains. + $this->setBaseHostname(); + } + + /** + * Returns whether a given user account is logged in. + * + * @param \Drupal\user\UserInterface $account + * The user account object to check. + * + * @return bool + */ + protected function drupalUserIsLoggedIn($account) { + // @TODO: This is a temporary hack for the test login fails when setting $cookie_domain. + if (!isset($account->session_id)) { + return (bool) $account->id(); + } + // The session ID is hashed before being stored in the database. + // @see \Drupal\Core\Session\SessionHandler::read() + return (bool) db_query("SELECT sid FROM {users_field_data} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => Crypt::hashBase64($account->session_id)))->fetchField(); + } + + /** + * Login a user on a specific domain. + * + * @param \Drupal\domain\DomainInterface $domain + * The domain to log the user into. + * @param \Drupal\user\UserInterface $account + * The user account to login. + */ + public function domainLogin(DomainInterface $domain, UserInterface $account) { + if ($this->loggedInUser) { + $this->drupalLogout(); + } + + // Login. + $url = $domain->getPath() . 'user/login'; + $edit = ['name' => $account->getAccountName(), 'pass' => $account->passRaw]; + $this->drupalPostForm($url, $edit, t('Log in')); + + // @see WebTestBase::drupalUserIsLoggedIn() + if (isset($this->sessionId)) { + $account->session_id = $this->sessionId; + } + $pass = $this->assert($this->drupalUserIsLoggedIn($account), new FormattableMarkup('User %name successfully logged in.', array('%name' => $account->getUsername())), 'User login'); + if ($pass) { + $this->loggedInUser = $account; + $this->container->get('current_user')->setAccount($account); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/templates/domain-nav-block.html.twig b/sites/all/modules/contrib/admin/domain/domain/templates/domain-nav-block.html.twig new file mode 100644 index 000000000..d2d4c0aca --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/templates/domain-nav-block.html.twig @@ -0,0 +1,32 @@ +{# +/** + * @file + * Custom theme implementation for a select-and-go menu list. + * + * Available variables: + * - items: A list of items containing an array of strings. + * - label: the label text + * - url: the link url string + * - active: indicates if the domain is currently active. + * + * @ingroup themeable + */ +#} +{%- if items -%} +
+
+ +
+
+{%- else -%} + {{- empty -}} +{%- endif -%} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/200.png b/sites/all/modules/contrib/admin/domain/domain/tests/200.png new file mode 100644 index 000000000..d6152bd3c Binary files /dev/null and b/sites/all/modules/contrib/admin/domain/domain/tests/200.png differ diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/config/optional/views.view.domain_views_access.yml b/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/config/optional/views.view.domain_views_access.yml new file mode 100644 index 000000000..148f0ff5c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/config/optional/views.view.domain_views_access.yml @@ -0,0 +1,172 @@ +langcode: en +status: true +dependencies: + config: + - domain.record.example_com + - domain.record.one_example_com + module: + - domain + - user +id: domain_views_access +label: 'Domain views access' +module: views +description: '' +tag: '' +base_table: users_field_data +base_field: uid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: domain + options: + domain: + example_com: example_com + one_example_com: one_example_com + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: some + options: + items_per_page: 1 + offset: 0 + style: + type: default + row: + type: fields + fields: + name: + id: name + table: users_field_data + field: name + entity_type: user + entity_field: name + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: user_name + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: + status: + value: true + table: users_field_data + field: status + plugin_id: boolean + entity_type: user + entity_field: status + id: status + expose: + operator: '' + group: 1 + sorts: { } + title: 'Domain views access' + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.site + tags: { } + block_1: + display_plugin: block + id: block_1 + display_title: Block + position: 2 + display_options: + display_extenders: { } + pager: + type: some + options: + items_per_page: 5 + offset: 1 + defaults: + pager: false + header: false + header: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.site + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: domain-views-access + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.site + tags: { } diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.info.yml b/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.info.yml new file mode 100644 index 000000000..a7bb7e659 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.info.yml @@ -0,0 +1,16 @@ +name: "Domain module hook tests" +description: "Support module for domain hook testing." +type: module +package: Testing +# version: VERSION +# core: 8.x +hidden: TRUE + +dependencies: + - domain + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.module b/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.module new file mode 100644 index 000000000..21bf62d73 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.module @@ -0,0 +1,60 @@ +addProperty('foo', 'bar'); + } +} + +/** + * Implements hook_domain_validate_alter(). + */ +function domain_test_domain_validate_alter(&$error_list, $hostname) { + // Deliberate test fail. + if ($hostname == 'fail.example.com') { + $error_list[] = 'Fail.example.com cannot be registered'; + } +} + +/** + * Implements hook_domain_request_alter(). + */ +function domain_test_domain_request_alter(DomainInterface &$domain) { + $domain->addProperty('foo1', 'bar1'); +} + +/** + * Implements hook_domain_operations + */ +function domain_test_domain_operations(DomainInterface $domain) { + $operations = []; + // Add aliases to the list. + $id = $domain->id(); + $operations['domain_test'] = array( + 'title' => t('Test'), + 'url' => Url::fromRoute('entity.domain.edit_form', array('domain' => $id)), + 'weight' => 80, + ); + return $operations; +} + +/** + * Implements hook_domain_references_alter + */ +function domain_test_domain_references_alter($query, $account, $context) { + if ($context['entity_type'] == 'node') { + $test = 'Test string'; + $query->addMetadata('domain_test', $test); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/Condition/DomainConditionTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/Condition/DomainConditionTest.php new file mode 100644 index 000000000..c0830285a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/Condition/DomainConditionTest.php @@ -0,0 +1,88 @@ +manager = $this->container->get('plugin.manager.condition'); + + // Create test domains. + $this->domainCreateTestDomains(5); + + // Get two sample domains. + $this->domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $this->test_domain = array_shift($this->domains); + $this->not_domain = array_shift($this->domains); + } + + /** + * Test the domain condition. + */ + public function testConditions() { + // Grab the domain condition and configure it to check against one domain. + $condition = $this->manager->createInstance('domain') + ->setConfig('domains', array($this->test_domain->id() => $this->test_domain->id())) + ->setContextValue('entity:domain', $this->not_domain); + $this->assertFalse($condition->execute(), 'Domain request condition fails on wrong domain.'); + + // Grab the domain condition and configure it to check against a null set. + $condition = $this->manager->createInstance('domain') + ->setConfig('domains', array($this->test_domain->id() => $this->test_domain->id())) + ->setContextValue('entity:domain', NULL); + $this->assertFalse($condition->execute(), 'Domain request condition fails when no context present.'); + + // Grab the domain condition and configure it to check against itself. + $condition = $this->manager->createInstance('domain') + ->setConfig('domains', array($this->test_domain->id() => $this->test_domain->id())) + ->setContextValue('entity:domain', $this->test_domain); + $this->assertTrue($condition->execute(), 'Domain request condition succeeds on matching domain.'); + + // Check for the proper summary. + // Summaries require an extra space due to negate handling in summary(). + $this->assertEqual($condition->summary(), 'Active domain is ' . $this->test_domain->label()); + + // Check the negated summary. + $condition->setConfig('negate', TRUE); + $this->assertEqual($condition->summary(), 'Active domain is not ' . $this->test_domain->label()); + + // Check the negated condition. + $this->assertFalse($condition->execute(), 'Domain request condition fails when condition negated.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainActionsTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainActionsTest.php new file mode 100644 index 000000000..756307d45 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainActionsTest.php @@ -0,0 +1,103 @@ +admin_user = $this->drupalCreateUser(array('administer domains', 'access administration pages')); + $this->drupalLogin($this->admin_user); + + $path = 'admin/config/domain'; + + // Create test domains. + $this->domainCreateTestDomains(4); + + // Visit the domain overview administration page. + $this->drupalGet($path); + $this->assertResponse(200); + + // Test the domains. + $storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $domains = $storage->loadMultiple(); + $this->assertTrue(count($domains) == 4, 'Four domain records found.'); + + // Check the default domain. + $default = $storage->loadDefaultId(); + $key = 'example_com'; + $this->assertTrue($default == $key, 'Default domain set correctly.'); + + // Test some text on the page. + foreach ($domains as $domain) { + $name = $domain->label(); + $this->assertText($name, 'Name found properly.'); + } + // Test the list of actions. + $actions = array('delete', 'disable', 'default'); + foreach ($actions as $action) { + $this->assertRaw("/domain/{$action}/", 'Actions found properly.'); + } + // Check that all domains are active. + $this->assertNoRaw('Inactive', 'Inactive domain not found.'); + + // Disable a domain and test the enable link. + $this->clickLink('Disable', 0); + $this->assertRaw('Inactive', 'Inactive domain found.'); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + foreach ($storage->loadMultiple() as $domain) { + if ($domain->id() == 'one_example_com') { + $this->assertEmpty($domain->status(), 'One domain inactive.'); + } + else { + $this->assertNotEmpty($domain->status(), 'Other domains active.'); + } + } + + // Test the list of actions. + $actions = array('enable', 'delete', 'disable', 'default'); + foreach ($actions as $action) { + $this->assertRaw("/domain/{$action}/", 'Actions found properly.'); + } + // Re-enable the domain. + $this->clickLink('Enable', 0); + $this->assertNoRaw('Inactive', 'Inactive domain not found.'); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + foreach ($storage->loadMultiple() as $domain) { + $this->assertNotEmpty($domain->status(), 'All domains active.'); + } + + // Set a new default domain. + $this->clickLink('Make default', 0); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + // Check the default domain. + $storage->resetCache(); + $default = $storage->loadDefaultId(); + $key = 'one_example_com'; + $this->assertTrue($default == $key, 'Default domain set correctly.'); + + } + +} + diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainAdminElementTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainAdminElementTest.php new file mode 100644 index 000000000..5fac62914 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainAdminElementTest.php @@ -0,0 +1,142 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainAccessElement() { + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer users', + 'administer domains', + )); + $this->drupalLogin($admin); + + $this->drupalGet('admin/people/create'); + $this->assertSession()->statusCodeEquals(200); + + // Create a user through the form. + $this->fillField('name', 'testuser'); + $this->fillField('mail', 'test@example.com'); + $this->fillField('pass[pass1]', 'test'); + $this->fillField('pass[pass2]', 'test'); + + // We expect to find 5 domain options. We set two as selected. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $count = 0; + $ids = ['example_com', 'one_example_com', 'two_example_com']; + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if (in_array($domain->id(), $ids)) { + $this->checkField($locator); + } + } + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + $storage = \Drupal::entityTypeManager()->getStorage('user'); + $user = $storage->load(3); + // Check that two values are set. + $manager = \Drupal::service('domain.element_manager'); + $values = $manager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 3, 'User saved with three domain records.'); + + // Now login as a user with limited rights. + $account = $this->drupalCreateUser(array( + 'administer users', + 'assign domain administrators', + )); + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account->id(), $ids, DOMAIN_ADMIN_FIELD); + $tester = $storage->load($account->id()); + $values = $manager->getFieldValues($tester, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + $storage->resetCache(array($account->id())); + $this->drupalLogin($account); + + $this->drupalGet('user/' . $user->id() . '/edit'); + $this->assertSession()->statusCodeEquals(200); + + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if ($domain->id() == 'example_com') { + $this->checkField($locator); + } + elseif ($domain->id() == 'one_example_com') { + $this->uncheckField($locator); + } + else { + $this->assertSession()->fieldNotExists($locator); + } + } + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Now, check the user. + $storage->resetCache(array($user->id())); + $user = $storage->load($user->id()); + // Check that two values are set. + $values = $manager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + + // Test the case presented in https://www.drupal.org/node/2841962. + $config = \Drupal::configFactory()->getEditable('user.settings'); + $config->set('verify_mail', 0); + $config->set('register', USER_REGISTER_VISITORS); + $config->save(); + $this->drupalLogout(); + $this->drupalGet('user/register'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->responseNotContains('Domain administrator'); + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->assertSession()->fieldNotExists($locator); + } + // Create a user through the form. + $this->fillField('name', 'testuser2'); + $this->fillField('mail', 'test2@example.com'); + // In 8.3, this field is not present? + if (!empty($this->findField('pass[pass1]'))) { + $this->fillField('pass[pass1]', 'test'); + $this->fillField('pass[pass2]', 'test'); + } + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainCSSTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainCSSTest.php new file mode 100644 index 000000000..c3f67a39c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainCSSTest.php @@ -0,0 +1,77 @@ +install(array('bartik')); + } + + /** + * Tests the handling of an inbound request. + */ + public function testDomainNegotiator() { + // No domains should exist. + $this->domainTableIsEmpty(); + + // Create four new domains programmatically. + $this->domainCreateTestDomains(4); + + // The test runner doesn't use a theme that contains the preprocess hook, + // so set to use Bartik. + $config = $this->config('system.theme'); + $config->set('default', 'bartik')->save(); + + // Test the response of the default home page. + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple() as $domain) { + $this->drupalGet($domain->getPath()); + $text = 'id() . '-class'); + $this->assertRaw($text, 'Custom CSS present.' . $text); + } + + // Set the css classes. + $config = $this->config('domain.settings'); + $config->set('css_classes', '[domain:machine-name]-class [domain:name]-class')->save(); + // Test the response of the default home page. + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple() as $domain) { + // The render cache trips up this test. In production, it may be + // necessary to add the url.site cache context. See README.md. + drupal_flush_all_caches(); + $this->drupalGet($domain->getPath()); + $text = 'id() . '"'; + $this->assertRaw($string, 'Found the domain option'); + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + + // Try to post a node, assigned to the first two domains. + $edit['title[0][value]'] = 'Test node'; + $edit["field_domain[{$one}]"] = TRUE; + $edit["field_domain[{$two}]"] = TRUE; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + $this->assertResponse(200); + $node = \Drupal::entityTypeManager()->getStorage('node')->load(1); + $values = $node->get('field_domain'); + + // Get the expected value count. + $this->assertTrue(count($values) == 2, 'Node saved with two domain records.'); + + } + + /** + * Creates a simple field for testing on the article content type. + * + * Note: This code is a model for auto-creation of fields. + */ + public function domainCreateTestField() { + $label = 'domain'; + $name = 'field_' . $label; + + $storage = array( + 'field_name' => $name, + 'entity_type' => 'node', + 'type' => 'entity_reference', + 'cardinality' => -1, + 'settings' => array( + 'target_type' => 'domain', + ), + ); + $field_storage_config = \Drupal::entityTypeManager()->getStorage('field_storage_config')->create($storage); + $field_storage_config->save(); + + $field = array( + 'field_name' => $name, + 'entity_type' => 'node', + 'label' => 'Domain test field', + 'bundle' => 'article', + 'settings' => array( + 'handler_settings' => array( + 'sort' => array('field' => 'weight', 'direction' => 'ASC'), + ), + ), + ); + $field_config = \Drupal::entityTypeManager()->getStorage('field_config')->create($field); + $field_config->save(); + + // Tell the form system how to behave. + entity_get_form_display('node', 'article', 'default') + ->setComponent($name, array( + 'type' => 'options_buttons', + )) + ->save(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainFormsTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainFormsTest.php new file mode 100644 index 000000000..be5575712 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainFormsTest.php @@ -0,0 +1,79 @@ +admin_user = $this->drupalCreateUser(array('administer domains', 'create domains')); + $this->drupalLogin($this->admin_user); + + $storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + + // No domains should exist. + $this->domainTableIsEmpty(); + + // Visit the main domain administration page. + $this->drupalGet('admin/config/domain'); + + // Check for the add message. + $this->assertText('There is no Domain record yet.', 'Text for no domains found.'); + // Visit the add domain administration page. + $this->drupalGet('admin/config/domain/add'); + + // Make a POST request on admin/config/domain/add. + $edit = $this->domainPostValues(); + $this->drupalPostForm('admin/config/domain/add', $edit, 'Save'); + + // Did it save correctly? + $default_id = $storage->loadDefaultId(); + $this->assertTrue(!empty($default_id), 'Domain record saved via form.'); + + // Does it load correctly? + $storage->resetCache([$default_id]); + $new_domain = $storage->load($default_id); + $this->assertTrue($new_domain->id() == $default_id, 'Domain loaded properly.'); + + // Has a UUID been set? + $this->assertTrue(!empty($new_domain->uuid()), 'Entity UUID set properly.'); + + // Visit the edit domain administration page. + $editUrl = 'admin/config/domain/edit/' . $new_domain->id(); + $this->drupalGet($editUrl); + + // Update the record. + $edit = []; + $edit['name'] = 'Foo'; + $edit['validate_url'] = 0; + $this->drupalPostForm($editUrl, $edit, 'Save'); + + // Check that the update succeeded. + $storage->resetCache([$default_id]); + $domain = $storage->load($default_id); + $this->assertTrue($domain->label() == 'Foo', 'Domain record updated via form.'); + + // Visit the delete domain administration page. + $deleteUrl = 'admin/config/domain/delete/' . $new_domain->id(); + $this->drupalGet($deleteUrl); + + // Delete the record. + $this->drupalPostForm($deleteUrl, array(), 'Delete'); + $storage->resetCache([$default_id]); + $domain = $storage->load($default_id); + $this->assertTrue(empty($domain), 'Domain record deleted.'); + + // No domains should exist. + $this->domainTableIsEmpty(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainGetResponseTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainGetResponseTest.php new file mode 100644 index 000000000..7360d894a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainGetResponseTest.php @@ -0,0 +1,44 @@ +domainTableIsEmpty(); + + // Create a new domain programmatically. + $this->domainCreateTestDomains(); + + // Check the created domain based on it's known id value. + $key = 'example_com'; + /** @var \Drupal\domain\Entity\Domain $domain */ + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->load($key); + + // Our testing server should be able to access the test PNG file. + $this->assertTrue($domain->getResponse() == 200, 'Server returned a 200 response.'); + + // Now create a bad domain. + $values = array( + 'hostname' => 'foo.bar', + 'id' => 'foo_bar', + 'name' => 'Foo', + ); + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->create($values); + + $domain->save(); + $this->assertTrue($domain->getResponse() == 500, 'Server test returned a 500 response.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainInactiveTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainInactiveTest.php new file mode 100644 index 000000000..c7d075f98 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainInactiveTest.php @@ -0,0 +1,98 @@ +config('system.site'); + $site_config->set('page.front', '/node')->save(); + + // Create four new domains programmatically. + $this->domainCreateTestDomains(4); + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + + // Grab a known domain for testing. + $domain = $domains['two_example_com']; + $this->drupalGet($domain->getPath()); + $this->assertTrue($domain->status(), 'Tested domain is set to active.'); + $this->assertTrue($domain->getPath() == $this->getUrl(), 'Loaded the active domain.'); + + // Disable the domain and test for redirect. + $domain->disable(); + $default = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultDomain(); + // Our postSave() cache tag clear should allow this to work properly. + $this->drupalGet($domain->getPath()); + + $this->assertFalse($domain->status(), 'Tested domain is set to inactive.'); + $this->assertTrue($default->getPath() == $this->getUrl(), 'Redirected an inactive domain to the default domain.'); + + // Check to see if the user can login. + $url = $domain->getPath() . 'user/login'; + $this->drupalGet($url); + $this->assertResponse(200, 'Request to login on inactive domain allowed.'); + // Check to see if the user can reset password. + $url = $domain->getPath() . 'user/password'; + $this->drupalGet($url); + $this->assertResponse(200, 'Request to reset password on inactive domain allowed.'); + + // Try to access with the proper permission. + user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, array('access inactive domains')); + // Must flush cache because we did not resave the domain. + drupal_flush_all_caches(); + $this->assertFalse($domain->status(), 'Tested domain is set to inactive.'); + $this->drupalGet($domain->getPath()); + + // Set up two additional domains. + $domain2 = $domains['one_example_com']; + $domain3 = $domains['three_example_com']; + + // Check against trusted host patterns. + $settings['settings']['trusted_host_patterns'] = (object) [ + 'value' => ['^' . $this->prepareTrustedHostname($domain->getHostname()) . '$', + '^' . $this->prepareTrustedHostname($domain2->getHostname()) . '$'], + 'required' => TRUE, + ]; + $this->writeSettings($settings); + + // Revoke the permission change. + user_role_revoke_permissions(RoleInterface::ANONYMOUS_ID, array('access inactive domains')); + + $domain2->saveDefault(); + + // Test the trusted host, which should redirect to default. + $this->drupalGet($domain->getPath()); + $this->assertTrue($domain2->getPath() == $this->getUrl(), 'Redirected from the inactive domain.'); + $this->assertResponse(200, 'Request to trusted host allowed.'); + + // The redirect is cached, so we must flush cache to test again. + drupal_flush_all_caches(); + + // Test another inactive domain that is not trusted. + // Disable the domain and test for redirect. + $domain3->saveDefault(); + $this->drupalGet($domain->getPath()); + $this->assertRaw('The provided host name is not a valid redirect.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListBuilderTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListBuilderTest.php new file mode 100644 index 000000000..1d43fbd5e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListBuilderTest.php @@ -0,0 +1,213 @@ +domainCreateTestDomains(150); + } + + /** + * Basic test setup. + */ + public function testDomainListBuilder() { + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($admin); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Check that links are printed. + foreach ($this->getPaginatedDomains() as $domain) { + $href = 'admin/config/domain/edit/' . $domain->id(); + $this->assertSession()->linkByHrefExists($href, 0, 'Link found ' . $href); + $this->assertSession()->assertEscaped($domain->label()); + // Check for pagination. + $this->checkPagination(); + } + + // Go to page 2. + $this->clickLink('Next'); + foreach ($this->getPaginatedDomains(1) as $domain) { + $href = 'admin/config/domain/edit/' . $domain->id(); + $this->assertSession()->linkByHrefExists($href, 0, 'Link found ' . $href); + $this->assertSession()->assertEscaped($domain->label()); + // Check for pagination. + $this->checkPagination(); + } + + // Now login as a user with limited rights. + $account = $this->drupalCreateUser(array( + 'create article content', + 'edit any article content', + 'edit assigned domains', + 'view domain list', + )); + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account->id(), $ids, DOMAIN_ADMIN_FIELD); + $user_storage = \Drupal::entityTypeManager()->getStorage('user'); + $user = $user_storage->load($account->id()); + $manager = \Drupal::service('domain.element_manager'); + $values = $manager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + + $this->drupalLogin($account); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Check that links are printed. + $path = 'admin/config/domain'; + $this->drupalGet($path); + foreach ($this->getPaginatedDomains() as $domain) { + $href = 'admin/config/domain/edit/' . $domain->id(); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + $this->assertSession()->assertEscaped($domain->label()); + } + else { + $this->assertSession()->linkByHrefNotExists($href, 'Link not found'); + $this->assertSession()->assertEscaped($domain->label()); + } + // Check for pagination. + $this->checkPagination(); + } + + // Check access to the pages/routes. + foreach ($this->getPaginatedDomains() as $domain) { + $path = 'admin/config/domain/edit/' . $domain->id(); + $this->drupalGet($path); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->statusCodeEquals(200); + } + else { + $this->assertSession()->statusCodeEquals(403); + } + } + + // Go to page 2. + $this->drupalGet('admin/config/domain'); + $this->clickLink('Next'); + foreach ($this->getPaginatedDomains(1) as $domain) { + $href = 'admin/config/domain/edit/' . $domain->id(); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + $this->assertSession()->assertEscaped($domain->label()); + } + else { + $this->assertSession()->linkByHrefNotExists($href, 'Link not found'); + $this->assertSession()->assertEscaped($domain->label()); + } + // Check for pagination. + $this->checkPagination(); + } + + // Now login as a user with more limited rights. + $account2 = $this->drupalCreateUser(array( + 'create article content', + 'edit any article content', + 'edit assigned domains', + 'view assigned domains', + )); + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account2->id(), $ids, DOMAIN_ADMIN_FIELD); + $user_storage = \Drupal::entityTypeManager()->getStorage('user'); + $user = $user_storage->load($account2->id()); + $manager = \Drupal::service('domain.element_manager'); + $values = $manager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + + $this->drupalLogin($account2); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Check that domains are listed and links are printed. + $path = 'admin/config/domain'; + $this->drupalGet($path); + foreach ($this->getPaginatedDomains() as $domain) { + $href = 'admin/config/domain/edit/' . $domain->id(); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + $this->assertSession()->assertEscaped($domain->label()); + } + else { + $this->assertSession()->linkByHrefNotExists($href, 'Link not found'); + $this->assertSession()->assertNoEscaped($domain->label()); + } + // Check for pagination. + $this->checkNoPagination(); + } + + // Check access to the pages/routes. + foreach ($this->getPaginatedDomains() as $domain) { + $path = 'admin/config/domain/edit/' . $domain->id(); + $this->drupalGet($path); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->statusCodeEquals(200); + } + else { + $this->assertSession()->statusCodeEquals(403); + } + } + + } + + /** + * Returns an array of domains, paginated and sorted by weight. + * + * @param int $page + * The page number to return. + */ + private function getPaginatedDomains($page = 0) { + $limit = 50; + $offset = $page * $limit; + return array_slice($this->getDomainsSorted(), $offset, $limit); + } + + /** + * Checks that pagination links appear, as expected. + */ + private function checkPagination() { + foreach (['?page=0', '?page=1', '?page=2'] as $href) { + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + } + } + + /** + * Checks that pagination links do not appear, as expected. + */ + private function checkNoPagination() { + foreach (['?page=0', '?page=1', '?page=2'] as $href) { + $this->assertSession()->linkByHrefNotExists($href, 0, 'Link not found'); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListWeightTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListWeightTest.php new file mode 100644 index 000000000..bcb242a17 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListWeightTest.php @@ -0,0 +1,110 @@ +domainCreateTestDomains(60); + } + + /** + * Basic test setup. + */ + public function testDomainWeight() { + // Test the default sort values. Should be 1 to 60. + $domains = $this->getDomainsSorted(); + $i = 1; + foreach ($domains as $domain) { + $this->assert($domain->getWeight() == $i, 'Weight set to ' . $i); + $i++; + } + // The last domain should be test59_example_com. + $this->assert($domain->id() == 'test59_example_com', 'Last domain is test59'); + $domains_old = $domains; + + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($admin); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Set one weight to 61. + $locator = 'edit-domains-one-example-com-weight'; + $this->fillField($locator, 61); + + // Save the form. + $this->pressButton('edit-submit'); + + $domains = $this->getDomainsSorted(); + $i = 1; + foreach ($domains as $domain) { + // Weights should be the same one page 1 except for the one we changed. + if ($domain->id() == 'one_example_com') { + $this->assert($domain->getWeight() == 61, 'Weight set to 61 ' . $domain->getWeight()); + } + else { + $this->assert($domain->getWeight() == $domains_old[$domain->id()]->getWeight(). 'Weights unchanged'); + } + $i++; + } + // The last domain should be one_example_com. + $this->assert($domain->id() == 'one_example_com', 'Last domain is one'); + + // Go to page two. + $this->clickLink('Next'); + $this->assertSession()->statusCodeEquals(200); + // Set one weight to 2. + $locator = 'edit-domains-one-example-com-weight'; + $this->fillField($locator, 2); + // Save the form. + $this->pressButton('edit-submit'); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Go to page two. + $this->clickLink('Next'); + $this->assertSession()->statusCodeEquals(200); + + // Check the domain sort order. + $domains = $this->getDomainsSorted(); + $i = 1; + foreach ($domains as $domain) { + if ($domain->id() == 'one_example_com') { + $this->assert($domain->getWeight() == 2, 'Weight set to 2'); + } + else { + $this->assert($domain->getWeight() == $domains_old[$domain->id()]->getWeight(). 'Weights unchanged'); + } + } + // The last domain should be test59_example_com. + $this->assert($domain->id() == 'test59_example_com', 'Last domain is test59' . $domain->id()); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNavBlockTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNavBlockTest.php new file mode 100644 index 000000000..dbb88bc2b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNavBlockTest.php @@ -0,0 +1,97 @@ +domainCreateTestDomains(4); + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + + // Place the nav block. + $block = $this->drupalPlaceBlock('domain_nav_block'); + + // Let the anon user view the block. + user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, array('use domain nav block')); + + // Load the homepage. All links should appear. + $this->drupalGet(''); + // Confirm domain links. + foreach ($domains as $id => $domain) { + $this->findLink($domain->label()); + } + + // Disable one of the domains. One link should not appear. + $disabled = $domains['one_example_com']; + $disabled->disable(); + + // Load the homepage. + $this->drupalGet(''); + // Confirm domain links. + foreach ($domains as $id => $domain) { + if ($id != 'one_example_com') { + $this->findLink($domain->label()); + } + else { + $this->assertNoRaw($domain->label()); + } + } + // Let the anon user view diabled domains. All links should appear. + user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, array('access inactive domains')); + + // Load the homepage. + $this->drupalGet(''); + // Confirm domain links. + foreach ($domains as $id => $domain) { + $this->findLink($domain->label()); + } + + // Now update the configuration and test again. + $this->config('block.block.' . $block->id()) + ->set('settings.link_options', 'active') + ->set('settings.link_label', 'hostname') + ->save(); + + // Load the the login page. + $this->drupalGet('user/login'); + // Confirm domain links. + foreach ($domains as $id => $domain) { + $this->findLink($domain->getHostname()); + $this->assertRaw($domain->buildUrl('/user/login')); + } + + // Now update the configuration and test again. + $this->config('block.block.' . $block->id()) + ->set('settings.link_options', 'home') + ->set('settings.link_theme', 'menu') + ->set('settings.link_label', 'url') + ->save(); + + // Load the the login page. + $this->drupalGet('user/login'); + // Confirm domain links. + foreach ($domains as $id => $domain) { + $this->findLink($domain->getPath()); + $this->assertRaw($domain->getPath()); + } + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNegotiatorTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNegotiatorTest.php new file mode 100644 index 000000000..d0f3a9cfa --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNegotiatorTest.php @@ -0,0 +1,46 @@ +domainTableIsEmpty(); + + // Create four new domains programmatically. + $this->domainCreateTestDomains(4); + + // Since we cannot read the service request, we place a block + // which shows the current domain information. + $this->drupalPlaceBlock('domain_server_block'); + + // To get around block access, let the anon user view the block. + user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, array('view domain information')); + + // Test the response of the default home page. + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple() as $domain) { + $this->drupalGet($domain->getPath()); + $this->assertRaw($domain->label(), 'Loaded the proper domain.'); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainReferencesTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainReferencesTest.php new file mode 100644 index 000000000..4bfb345ac --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainReferencesTest.php @@ -0,0 +1,216 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainReferences() { + // Create an admin user. This will be user 2. + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer users', + 'administer domains', + 'assign domain editors', + )); + $this->drupalLogin($admin); + + $this->drupalGet('admin/people/create'); + $this->assertSession()->statusCodeEquals(200); + + // Create a user through the form. This will be user 3. + $this->fillField('name', 'testuser'); + $this->fillField('mail', 'test@example.com'); + $this->fillField('pass[pass1]', 'test'); + $this->fillField('pass[pass2]', 'test'); + + // We expect to find 5 domain options. We set three as selected. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + + $ids = ['example_com', 'one_example_com', 'two_example_com']; + $edit_ids = ['example_com', 'one_example_com']; + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if (in_array($domain->id(), $ids)) { + $this->checkField($locator); + } + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if (in_array($domain->id(), $edit_ids)) { + $this->checkField($locator); + } + } + + // Find the all affiliates field. + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->findField($locator); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Load our test user. + $storage = \Drupal::entityTypeManager()->getStorage('user'); + $testuser = $storage->load(3); + // Check that three values are set. + $manager = \Drupal::service('domain.element_manager'); + $values = $manager->getFieldValues($testuser, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 3, 'User saved with three domain admin records.'); + // Check that no access fields are set. + $values = $manager->getFieldValues($testuser, DOMAIN_ACCESS_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain access records.'); + + // Now login as a user with limited rights. This is user 4. + $account = $this->drupalCreateUser(array( + 'administer users', + 'assign domain administrators', + )); + // Set some domain assignments for this user. + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account->id(), $ids, DOMAIN_ADMIN_FIELD); + $limited_admin = $storage->load($account->id()); + $values = $manager->getFieldValues($limited_admin, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain admin records.'); + // Check that no access fields are set. + $values = $manager->getFieldValues($limited_admin, DOMAIN_ACCESS_FIELD); + $this->assert(count($values) == 0, 'User saved with no domain access records.'); + + // Now edit user 3 as user 4 with limited rights. + $this->drupalLogin($account); + $this->drupalGet('user/' . $testuser->id() . '/edit'); + $this->assertSession()->statusCodeEquals(200); + + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if ($domain->id() == 'example_com') { + $this->checkField($locator); + } + elseif ($domain->id() == 'one_example_com') { + $this->uncheckField($locator); + } + else { + $this->assertSession()->fieldNotExists($locator); + } + // No Domain Access field rights exist for this user. + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + $this->assertSession()->fieldNotExists($locator); + } + + // The all affiliates field should not be present.. + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->assertSession()->fieldNotExists($locator); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Now, check the user. + $storage->resetCache(array($testuser->id())); + $testuser = $storage->load($testuser->id()); + // Check that two values are set. + $values = $manager->getFieldValues($testuser, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain admin records.'); + // Check that no access fields are set. + $values = $manager->getFieldValues($testuser, DOMAIN_ACCESS_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain access records.'); + + // Now login as a user with different limited rights. This is user 5. + $new_account = $this->drupalCreateUser(array( + 'administer users', + 'assign domain administrators', + 'assign domain editors', + )); + $ids = ['example_com', 'one_example_com']; + $new_ids = ['one_example_com', 'four_example_com']; + $this->addDomainsToEntity('user', $new_account->id(), $ids, DOMAIN_ADMIN_FIELD); + $this->addDomainsToEntity('user', $new_account->id(), $new_ids, DOMAIN_ACCESS_FIELD); + + $new_admin = $storage->load($new_account->id()); + $values = $manager->getFieldValues($new_admin, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain admin records.'); + $values = $manager->getFieldValues($new_admin, DOMAIN_ACCESS_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain access records.'); + + // Now edit the user as someone with limited rights. + $storage->resetCache(array($new_admin->id())); + $this->drupalLogin($new_account); + + $this->drupalGet('user/' . $testuser->id() . '/edit'); + $this->assertSession()->statusCodeEquals(200); + + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if ($domain->id() == 'example_com') { + $this->checkField($locator); + } + elseif ($domain->id() == 'one_example_com') { + $this->uncheckField($locator); + } + else { + $this->assertSession()->fieldNotExists($locator); + } + // Some Domain Access field rights exist for this user. This adds + // one to the count. + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + if (in_array($domain->id(), $new_ids)) { + $this->findField($locator); + $this->checkField($locator); + } + else { + $this->assertSession()->fieldNotExists($locator); + } + } + + // The all affiliates field should not be present.. + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->assertSession()->fieldNotExists($locator); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Now, check the user. + $storage->resetCache(array($testuser->id())); + $testuser = $storage->load($testuser->id()); + // Check that two values are set. + $values = $manager->getFieldValues($testuser, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain admin records.'); + $values = $manager->getFieldValues($testuser, DOMAIN_ACCESS_FIELD); + $this->assert(count($values) == 3, 'User saved with three domain access records.'); + + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTestBase.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTestBase.php new file mode 100644 index 000000000..b1ab00a3d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTestBase.php @@ -0,0 +1,218 @@ +base_hostname or the + * domainCreateTestDomains() method. + */ + public $base_hostname; + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('domain', 'node'); + + /** + * We use the standard profile for testing. + * + * @var string + */ + protected $profile = 'standard'; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Set the base hostname for domains. + $this->base_hostname = \Drupal::service('entity_type.manager')->getStorage('domain')->createHostname(); + } + + /** + * The methods below are brazenly copied from Rules module. They are all + * helper methods that make writing tests a bit easier. + */ + + /** + * Finds link with specified locator. + * + * @param string $locator + * Link id, title, text or image alt. + * + * @return \Behat\Mink\Element\NodeElement|null + * The link node element. + */ + public function findLink($locator) { + return $this->getSession()->getPage()->findLink($locator); + } + + /** + * Confirms absence of link with specified locator. + * + * @param string $locator + * Link id, title, text or image alt. + * + * @return \Behat\Mink\Element\NodeElement|null + * The link node element. + */ + public function findNoLink($locator) { + return empty($this->getSession()->getPage()->hasLink($locator)); + } + + /** + * Finds field (input, textarea, select) with specified locator. + * + * @param string $locator + * Input id, name or label. + * + * @return \Behat\Mink\Element\NodeElement|null + * The input field element. + */ + public function findField($locator) { + return $this->getSession()->getPage()->findField($locator); + } + + /** + * Finds button with specified locator. + * + * @param string $locator + * Button id, value or alt. + * + * @return \Behat\Mink\Element\NodeElement|null + * The button node element. + */ + public function findButton($locator) { + return $this->getSession()->getPage()->findButton($locator); + } + + /** + * Presses button with specified locator. + * + * @param string $locator + * Button id, value or alt. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + */ + public function pressButton($locator) { + $this->getSession()->getPage()->pressButton($locator); + } + + /** + * Fills in field (input, textarea, select) with specified locator. + * + * @param string $locator + * Input id, name or label. + * @param string $value + * Value. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * + * @see \Behat\Mink\Element\NodeElement::setValue + */ + public function fillField($locator, $value) { + $this->getSession()->getPage()->fillField($locator, $value); + } + + /** + * Checks checkbox with specified locator. + * + * @param string $locator input id, name or label + * + * @throws ElementNotFoundException + */ + public function checkField($locator) { + $this->getSession()->getPage()->checkField($locator); + } + + /** + * Unchecks checkbox with specified locator. + * + * @param string $locator input id, name or label + * + * @throws ElementNotFoundException + */ + public function uncheckField($locator) { + $this->getSession()->getPage()->uncheckField($locator); + } + + /** + * Selects option from select field with specified locator. + * + * @param string $locator input id, name or label + * @param string $value option value + * @param Boolean $multiple select multiple options + * + * @throws ElementNotFoundException + * + * @see NodeElement::selectOption + */ + public function selectFieldOption($locator, $value, $multiple = false) { + $this->getSession()->getPage()->selectFieldOption($locator, $value, $multiple); + } + + /** + * Returns whether a given user account is logged in. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The user account object to check. + * + * @return bool + */ + protected function drupalUserIsLoggedIn(AccountInterface $account) { + // @TODO: This is a temporary hack for the test login fails when setting $cookie_domain. + if (!isset($account->session_id)) { + return (bool) $account->id(); + } + // The session ID is hashed before being stored in the database. + // @see \Drupal\Core\Session\SessionHandler::read() + return (bool) db_query("SELECT sid FROM {users_field_data} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => Crypt::hashBase64($account->session_id)))->fetchField(); + } + + /** + * Login a user on a specific domain. + * + * @param \Drupal\domain\DomainInterface $domain + * The domain to log the user into. + * @param \Drupal\Core\Session\AccountInterface $account + * The user account to login. + */ + public function domainLogin(DomainInterface $domain, AccountInterface $account) { + // Due to a quirk in session handling that we cannot directly access, it + // works if we login, then logout, and then login to a specific domain. + $this->drupalLogin($account); + if ($this->loggedInUser) { + $this->drupalLogout(); + } + + // Login. + $url = $domain->getPath() . 'user/login'; + $this->submitForm([ + 'name' => $account->getUsername(), + 'pass' => $account->passRaw, + ], t('Log in')); + + // @see BrowserTestBase::drupalUserIsLoggedIn() + $account->sessionId = $this->getSession()->getCookie($this->getSessionName()); + $this->assertTrue($this->drupalUserIsLoggedIn($account), 'User successfully logged in.'); + + $this->loggedInUser = $account; + $this->container->get('current_user')->setAccount($account); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTokenTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTokenTest.php new file mode 100644 index 000000000..b21ee6fd7 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTokenTest.php @@ -0,0 +1,76 @@ +domainTableIsEmpty(); + + // Create four new domains programmatically. + $this->domainCreateTestDomains(4); + + // Since we cannot read the service request, we place a block + // which shows the current domain token information. + $this->drupalPlaceBlock('domain_token_block'); + + // To get around block access, let the anon user view the block. + user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, array('view domain information')); + + // Test the response of the default home page. + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple() as $domain) { + $this->drupalGet($domain->getPath()); + $this->assertRaw($domain->label(), 'Loaded the proper domain.'); + $this->assertRaw('Token', 'Token values printed.'); + foreach ($this->tokenList() as $token => $callback) { + $this->assertRaw("$token", "$token found correctly."); + // The URL token is sensitive to the path, which is /user, but that + // does not come across when making the callback outside of a request + // context. + $value = $domain->{$callback}(); + if ($token == '[domain:url]') { + $value = str_replace('user', '', $value); + if (substr($value, -1) != '/') { + $value .= '/'; + } + } + $this->assertRaw('' . $value . '', 'Value set correctly to ' . $value); + } + } + } + + /** + * Gets the list of tokens and value callbacks used by the test. + * + * @return array + * An array keyed by token string, with value of expected domain value. + */ + private function tokenList() { + $tokens = []; + foreach (\Drupal::service('domain.token')->getCallbacks() as $key => $callback) { + $name = "[domain:$key]"; + $tokens[$name] = $callback; + } + return $tokens; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainValidatorTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainValidatorTest.php new file mode 100644 index 000000000..96394e9f3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainValidatorTest.php @@ -0,0 +1,91 @@ +domainTableIsEmpty(); + $validator = \Drupal::service('domain.validator'); + $storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + + // Create a domain. + $this->domainCreateTestDomains(1, 'foo.com'); + // Check the created domain based on its known id value. + $key = 'foo.com'; + /** @var \Drupal\domain\Entity\Domain $domain */ + $domain = $storage->loadByHostname($key); + $this->assertTrue(!empty($domain), 'Test domain created.'); + + // Valid hostnames to test. Valid is the boolean value. + $hostnames = [ + 'localhost' => 1, + 'example.com' => 1, + 'www.example.com' => 1, // see www-prefix test, below. + 'one.example.com' => 1, + 'example.com:8080' => 1, + 'example.com::8080' => 0, // only one colon. + 'example.com:abc' => 0, // no letters after a colon. + '.example.com' => 0, // cannot begin with a dot. + 'example.com.' => 0, // cannot end with a dot. + 'EXAMPLE.com' => 0, // lowercase only. + 'éxample.com' => 0, // ascii-only. + ]; + foreach ($hostnames as $hostname => $valid) { + $errors = $validator->validate($hostname); + if ($valid) { + $this->assertTrue(empty($errors), 'Validation correct with no errors.'); + } + else { + $this->assertTrue(!empty($errors), 'Validation correct with proper errors.'); + } + } + // Test duplicate hostname creation. + $test_hostname = 'foo.com'; + $test_domain = $storage->create([ + 'hostname' => $test_hostname, + 'name' => 'Test domain', + 'id' => 'test_domain', + ]); + try { + $test_domain->save(); + $this->fail('Duplicate hostname validation'); + } + catch (ConfigValueException $e) { + $expected_message = "The hostname ($test_hostname) is already registered."; + $this->assertEqual($expected_message, $e->getMessage()); + } + // Test the two configurable options. + $config = $this->config('domain.settings'); + $config->set('www_prefix', true); + $config->set('allow_non_ascii', true); + $config->save(); + // Valid hostnames to test. Valid is the boolean value. + $hostnames = [ + 'www.example.com' => 0, // no www-prefix allowed + 'éxample.com' => 1, // ascii-only allowed. + ]; + foreach ($hostnames as $hostname => $valid) { + $errors = $validator->validate($hostname); + if ($valid) { + $this->assertTrue(empty($errors),'Validation test correct with no errors.'); + } + else { + $this->assertTrue(!empty($errors), 'Validation test correct with errors.'); + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainViewsAccessTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainViewsAccessTest.php new file mode 100644 index 000000000..eae994075 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainViewsAccessTest.php @@ -0,0 +1,84 @@ +domainCreateTestDomains(5); + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + // Enable the views. + $this->enableViewsTestModule(); + // Create a user. To test the area output was more difficult, so we just + // configured two views. The page shows the first, admin, user, and the + // block will show this new user name. + $this->user = $this->drupalCreateUser(array('administer domains', 'create domains')); + // Place the view block. + $this->drupalPlaceBlock('views_block:domain_views_access-block_1'); + + // The block and page should be visible on example_com and one_example_com. + $allowed = ['example_com', 'one_example_com']; + + foreach ($domains as $domain) { + $path = $domain->getPath() . 'domain-views-access'; + $this->DrupalGet($path); + if (in_array($domain->id(), $allowed)) { + $this->assertResponse('200', 'Access allowed'); + $this->assertRaw('admin'); + $this->assertRaw($this->user->getUsername()); + } + else { + $this->assertResponse('403', 'Access denied'); + $this->assertNoRaw('admin'); + $this->assertNoRaw($this->user->getUsername()); + } + // Test the block on another page. + $this->drupalGet($domain->getPath()); + if (in_array($domain->id(), $allowed)) { + $this->assertRaw($this->user->getUsername()); + } + else { + $this->assertNoRaw($this->user->getUsername()); + } + } + } + + /** + * Sets up the domain_test module. + * + * Because the schema of domain_test.module is dependent on the test + * using it, it cannot be enabled normally. + */ + protected function enableViewsTestModule() { + \Drupal::service('module_installer')->install(array('domain_test')); + $this->resetAll(); + $this->rebuildContainer(); + $this->container->get('module_handler')->reload(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainHookTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainHookTest.php new file mode 100644 index 000000000..2853f9685 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainHookTest.php @@ -0,0 +1,160 @@ +domainCreateTestDomains(); + + // Get the services. + $this->domainStorage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $this->currentUser = \Drupal::service('current_user'); + $this->moduleHandler = \Drupal::service('module_handler'); + } + + /** + * Tests domain loading. + */ + public function testHookDomainLoad() { + // Check the created domain based on its known id value. + $domain = $this->domainStorage->load($this->key); + + // Internal hooks. + $path = $domain->getPath(); + $url = $domain->getUrl(); + $this->assertTrue(isset($path), new FormattableMarkup('The path property was set to %path by hook_entity_load.', array('%path' => $path))); + $this->assertTrue(isset($url), new FormattableMarkup('The url property was set to %url by hook_entity_load.', array('%url' => $url))); + + // External hooks. + $this->assertTrue($domain->foo == 'bar', 'The foo property was set to bar by hook_domain_load.'); + } + + /** + * Tests domain validation. + */ + public function testHookDomainValidate() { + $validator = \Drupal::service('domain.validator'); + // Test a good domain. + $errors = $validator->validate('one.example.com'); + $this->assertEmpty($errors, 'No errors returned for example.com'); + + // Test our hook implementation, which denies fail.example.com explicitly. + $errors = $validator->validate('fail.example.com'); + $this->assertNotEmpty($errors, 'Errors returned for fail.example.com'); + $this->assertTrue(current($errors) == 'Fail.example.com cannot be registered', 'Error message returned correctly.'); + } + + /** + * Tests domain request alteration. + */ + public function testHookDomainRequestAlter() { + // Set the request. + $negotiator = \Drupal::service('domain.negotiator'); + $negotiator->setRequestDomain($this->base_hostname); + + // Check that the property was added by our hook. + $domain = $negotiator->getActiveDomain(); + $this->assertTrue($domain->foo1 == 'bar1', 'The foo1 property was set to bar1 by hook_domain_request_alter'); + } + + /** + * Tests domain operations hook. + */ + public function testHookDomainOperations() { + $domain = $this->domainStorage->load($this->key); + + // Set the request. + $operations = $this->moduleHandler->invokeAll('domain_operations', array($domain, $this->currentUser)); + + // Test that our operations were added by the hook. + $this->assertTrue(isset($operations['domain_test']), 'Domain test operation loaded.'); + } + + /** + * Tests domain references alter hook. + */ + public function testHookDomainReferencesAlter() { + $domain = $this->domainStorage->load($this->key); + + // Set the request. + $manager = \Drupal::service('entity.manager'); + $target_type = 'domain'; + + // Build a node entity selection query. + $query = $manager->getStorage($target_type)->getQuery(); + $context = [ + 'entity_type' => 'node', + 'bundle' => 'article', + 'field_type' => 'editor', + ]; + + // Run the alteration, which should add metadata to the query for nodes. + $this->moduleHandler->alter('domain_references', $query, $this->currentUser, $context); + $this->assertTrue($query->getMetaData('domain_test') == 'Test string', 'Domain test query altered.'); + + // Build a user entity selection query. + $query = $manager->getStorage($target_type)->getQuery(); + $context = [ + 'entity_type' => 'user', + 'bundle' => 'user', + 'field_type' => 'admin', + ]; + + // Run the alteration, which does not add metadata for user queries. + $this->moduleHandler->alter('domain_references', $query, $this->currentUser, $context); + $this->assertEmpty($query->getMetaData('domain_test'), 'Domain test query not altered.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainVariableSchemeTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainVariableSchemeTest.php new file mode 100644 index 000000000..ad8979fcd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainVariableSchemeTest.php @@ -0,0 +1,72 @@ +domainCreateTestDomains(); + + // Get the services. + $this->domainStorage = \Drupal::service('entity_type.manager')->getStorage('domain'); + } + + /** + * Tests domain loading. + */ + public function testDomainScheme() { + // Set our testing parameters. + $default_scheme = \Drupal::request()->getScheme(); + $alt_scheme = ($default_scheme == 'https') ? 'http' : 'https'; + $add_suffix = FALSE; + + // Our created domain should have a scheme that matches the request. + $domain = $this->domainStorage->load($this->key); + $this->assertTrue($domain->getScheme($add_suffix) == $default_scheme); + + // Swtich the scheme and see if that works. + $domain->set('scheme', $alt_scheme); + $domain->save(); + $domain = $this->domainStorage->load($this->key); + $this->assertTrue($domain->getScheme($add_suffix) == $alt_scheme); + + // Set the scheme to variable, and that should match the default. + $domain->set('scheme', 'variable'); + $domain->save(); + $this->assertTrue($domain->getScheme($add_suffix) == $default_scheme); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Traits/DomainTestTrait.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Traits/DomainTestTrait.php new file mode 100644 index 000000000..314fefeef --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Traits/DomainTestTrait.php @@ -0,0 +1,158 @@ +getStorage('domain')->loadMultiple(NULL, TRUE); + if (empty($base_hostname)) { + $base_hostname = $this->base_hostname; + } + // Note: these domains are rigged to work on my test server. + // For proper testing, yours should be set up similarly, but you can pass a + // $list array to change the default. + if (empty($list)) { + $list = array('', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'); + } + for ($i = 0; $i < $count; $i++) { + if ($i === 0) { + $hostname = $base_hostname; + $machine_name = 'example.com'; + $name = 'Example'; + } + elseif (!empty($list[$i])) { + $hostname = $list[$i] . '.' . $base_hostname; + $machine_name = $list[$i] . '.example.com'; + $name = 'Test ' . ucfirst($list[$i]); + } + // These domains are not setup and are just for UX testing. + else { + $hostname = 'test' . $i . '.' . $base_hostname; + $machine_name = 'test' . $i . '.example.com'; + $name = 'Test ' . $i; + } + // Create a new domain programmatically. + $values = array( + 'hostname' => $hostname, + 'name' => $name, + 'id' => \Drupal::service('entity_type.manager')->getStorage('domain')->createMachineName($machine_name), + ); + $domain = \Drupal::entityTypeManager()->getStorage('domain')->create($values); + $domain->save(); + } + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + } + + /** + * Adds a test domain to an entity. + * + * @param string $entity_type + * The entity type being acted upon. + * @param int $entity_id + * The entity id. + * @param array $ids + * An array of ids to add. + * @param string $field + * The name of the domain field used to attach to the entity. + */ + public function addDomainsToEntity($entity_type, $entity_id, $ids, $field) { + if ($entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id)) { + $entity->set($field, $ids); + $entity->save(); + } + } + + /** + * Returns an uncached list of all domains. + * + * @return array + * An array of domain entities. + */ + public function getDomains() { + \Drupal::service('entity_type.manager')->getStorage('domain')->resetCache(); + return \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + } + + /** + * Returns an uncached list of all domains, sorted by weight. + * + * @return array + * An array of domain entities. + */ + public function getDomainsSorted() { + \Drupal::service('entity_type.manager')->getStorage('domain')->resetCache(); + return \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultipleSorted(); + } + + /** + * Converts a domain hostname to a trusted host pattern. + * + * @param $hostname + * A hostname string. + * + * @return + * A regex-safe hostname, without delimiters. + */ + public function prepareTrustedHostname($hostname) { + $hostname = strtolower(preg_replace('/:\d+$/', '', trim($hostname))); + return preg_quote($hostname); + } + + /** + * Set the base hostname for this test. + */ + public function setBaseHostname() { + $this->base_hostname = \Drupal::service('entity_type.manager')->getStorage('domain')->createHostname(); + } + + /** + * Reusable test function for checking initial / empty table status. + */ + public function domainTableIsEmpty() { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + $this->assertTrue(empty($domains), 'No domains have been created.'); + $default_id = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultId(); + $this->assertTrue(empty($default_id), 'No default domain has been set.'); + } + + /** + * Creates domain record for use with POST request tests. + */ + public function domainPostValues() { + $edit = array(); + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->create(); + $required = \Drupal::service('domain.validator')->getRequiredFields(); + foreach ($required as $key) { + $edit[$key] = $domain->get($key); + } + // URL validation has issues on Travis, so only do it when requested. + $edit['validate_url'] = 0; + $edit['id'] = \Drupal::service('entity_type.manager')->getStorage('domain')->createMachineName($edit['hostname']); + return $edit; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/domain_access.settings.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/domain_access.settings.yml new file mode 100644 index 000000000..2f245f810 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/domain_access.settings.yml @@ -0,0 +1 @@ +node_advanced_tab: true diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_access.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_access.yml new file mode 100644 index 000000000..d4cdf636b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_access.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - domain + - node + enforced: + module: + - domain_access +id: node.field_domain_access +field_name: field_domain_access +entity_type: node +type: entity_reference +settings: + target_type: domain +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_all_affiliates.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_all_affiliates.yml new file mode 100644 index 000000000..392f68940 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_all_affiliates.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + module: + - domain + - node + enforced: + module: + - domain_access +id: node.field_domain_all_affiliates +field_name: field_domain_all_affiliates +entity_type: node +type: boolean +settings: { } +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_access.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_access.yml new file mode 100644 index 000000000..25d0b0944 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_access.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - domain + - user + enforced: + module: + - domain_access +id: user.field_domain_access +field_name: field_domain_access +entity_type: user +type: entity_reference +settings: + target_type: domain +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_all_affiliates.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_all_affiliates.yml new file mode 100644 index 000000000..e8f9888fc --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_all_affiliates.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + module: + - domain + - user + enforced: + module: + - domain_access +id: user.field_domain_all_affiliates +field_name: field_domain_all_affiliates +entity_type: user +type: boolean +settings: { } +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_all_action.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_all_action.yml new file mode 100644 index 000000000..d932320f8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_all_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - node +id: domain_access_all_action +label: 'Assign content to all affiliates' +type: node +plugin: domain_access_all_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_all_action.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_all_action.yml new file mode 100644 index 000000000..926e2d55b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_all_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - node +id: domain_access_edit_all_action +label: 'Assign editors to all affiliates' +type: user +plugin: domain_access_edit_all_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_none_action.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_none_action.yml new file mode 100644 index 000000000..33e7adf10 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_none_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - node +id: domain_access_edit_none_action +label: 'Remove editors from all affiliates' +type: user +plugin: domain_access_edit_none_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_none_action.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_none_action.yml new file mode 100644 index 000000000..4accb23cd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_none_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - node +id: domain_access_none_action +label: 'Remove content from all affiliates' +type: node +plugin: domain_access_none_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/schema/domain_access.schema.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/schema/domain_access.schema.yml new file mode 100644 index 000000000..dcdcc11da --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/schema/domain_access.schema.yml @@ -0,0 +1,47 @@ +domain_access.settings: + type: config_object + label: 'Domain Access settings' + mapping: + node_advanced_tab: + type: boolean + label: 'Move domain access field to a tab in the advanced settings on nodes.' +action.configuration.domain_access_all_action: + type: action_configuration_default + label: 'Assign content to all affiliates' +action.configuration.domain_access_edit_all_action: + type: action_configuration_default + label: 'Assign editors to all affiliates' +action.configuration.domain_access_edit_none_action: + type: action_configuration_default + label: 'Remove editors from all affiliates' +action.configuration.domain_access_none_action: + type: action_configuration_default + label: 'Remove content from all affiliates' +action.configuration.domain_access_add_action: + type: mapping + label: 'Configuration for the add domain(s) to content action' + mapping: + domain_id: + type: string + label: 'The ID(s) of the domain(s) to add' +action.configuration.domain_access_remove_action: + type: mapping + label: 'Configuration for the remove domain(s) from content action' + mapping: + domain_id: + type: string + label: 'The ID(s) of the domain(s) to remove' +action.configuration.domain_access_add_editor_action: + type: mapping + label: 'Configuration for the add domain(s) to users action' + mapping: + domain_id: + type: string + label: 'The ID(s) of the domain(s) to add' +action.configuration.domain_access_remove_editor_action: + type: mapping + label: 'Configuration for the remove domain(s) from users action' + mapping: + domain_id: + type: string + label: 'The ID(s) of the domain(s) to remove' diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml new file mode 100644 index 000000000..2a6f1bac6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml @@ -0,0 +1,15 @@ +name: Domain Access +description: 'Domain-based access control for content.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + - node + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.install b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.install new file mode 100644 index 000000000..8633b4dec --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.install @@ -0,0 +1,36 @@ +getStorage('node_type')->loadMultiple(); + foreach ($node_types as $type => $info) { + $list[$type] = 'node'; + } + // Install our fields. + foreach ($list as $bundle => $entity_type) { + domain_access_confirm_fields($entity_type, $bundle); + } + // Install our actions. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + domain_access_domain_insert($domain); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.menu.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.menu.yml new file mode 100644 index 000000000..92ad9905b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.menu.yml @@ -0,0 +1,6 @@ +domain_access.settings: + title: Domain Access settings + route_name: domain_access.settings + parent: domain.admin + description: 'Domain Access settings' + weight: 10 diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.task.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.task.yml new file mode 100644 index 000000000..02cc8734d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.task.yml @@ -0,0 +1,4 @@ +domain_access.settings: + title: Domain Access settings + route_name: domain_access.settings + base_route: domain.admin diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.module b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.module new file mode 100755 index 000000000..b27340b46 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.module @@ -0,0 +1,735 @@ +getActiveDomain(); + + if (empty($active)) { + $active = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultDomain(); + } + + // No domains means no permissions. + if (empty($active)) { + return $grants; + } + + $id = $active->getDomainId(); + // Advanced grants for edit/delete require permissions. + /** @var \Drupal\user\UserInterface $user */ + $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id()); + $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($user); + // Grants for view are simple. Use the active domain and all affiliates. + // Note that "X to any domain" is a global permission designed for admins. + if ($op == 'view') { + $grants['domain_id'][] = $id; + $grants['domain_site'][] = 0; + if ($user->hasPermission('view unpublished domain content')) { + if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) { + $grants['domain_unpublished'][] = $id; + } + } + } + elseif ($op == 'update' && $user->hasPermission('edit domain content')) { + if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) { + $grants['domain_id'][] = $id; + } + } + elseif ($op == 'delete' && $user->hasPermission('delete domain content')) { + if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) { + $grants['domain_id'][] = $id; + } + } + return $grants; +} + +/** + * Implements hook_node_access_records(). + */ +function domain_access_node_access_records(NodeInterface $node) { + $grants = array(); + // Create grants for each translation of the node. See the report at + // https://www.drupal.org/node/2825419 for the logic here. Note that right + // now, grants may not be the same for all languages. + $translations = $node->getTranslationLanguages(); + foreach ($translations as $langcode => $language) { + $translation = $node->getTranslation($langcode); + // If there are no domains set, use the current one. + $domains = \Drupal::service('domain_access.manager')->getAccessValues($translation); + /** @var \Drupal\domain\DomainInterface $active */ + if (empty($domains) && $active = \Drupal::service('domain.negotiator')->getActiveDomain()) { + $domains[$active->id()] = $active->getDomainId(); + } + foreach ($domains as $id => $domainId) { + /** @var \Drupal\domain\DomainInterface $domain */ + if ($domain = \Drupal::service('entity_type.manager')->getStorage('domain')->load($id)) { + $grants[] = array( + 'realm' => ($translation->isPublished()) ? 'domain_id' : 'domain_unpublished', + 'gid' => $domain->getDomainId(), + 'grant_view' => 1, + 'grant_update' => 1, + 'grant_delete' => 1, + 'langcode' => $langcode, + ); + } + } + // Set the domain_site grant. + if (!empty($translation->get(DOMAIN_ACCESS_ALL_FIELD)->value) && $translation->isPublished()) { + $grants[] = array( + 'realm' => 'domain_site', + 'gid' => 0, + 'grant_view' => 1, + 'grant_update' => 0, + 'grant_delete' => 0, + 'langcode' => $langcode, + ); + } + // Because of language translation, we must save a record for each language. + // even if that record adds no permissions, as this one does. + else { + $grants[] = array( + 'realm' => 'domain_site', + 'gid' => 1, + 'grant_view' => 0, + 'grant_update' => 0, + 'grant_delete' => 0, + 'langcode' => $langcode, + ); + } + } + return $grants; +} + +/** + * Implements hook_ENTITY_TYPE_presave(). + * + * Fires only if Devel Generate module is present, to assign test nodes to + * domains. + */ +function domain_access_node_presave(EntityInterface $node) { + domain_access_presave_generate($node); +} + +/** + * Implements hook_ENTITY_TYPE_presave(). + * + * Fires only if Devel Generate module is present, to assign test nodes to + * domains. + */ +function domain_access_user_presave(EntityInterface $account) { + domain_access_presave_generate($account); +} + +/** + * Handles presave operations for devel generate. + */ +function domain_access_presave_generate(EntityInterface $entity) { + // There is a core bug https://www.drupal.org/node/2609252 that causes a + // fatal database errors if the boolean DOMAIN_ACCESS_ALL_FIELD is set when + // a user cannot access the field. See domain_access_entity_field_access(). + // To overcome this issue, we cast the boolean to integer, which prevents the + // failure. + $value = (int) $entity->get(DOMAIN_ACCESS_ALL_FIELD)->value; + $entity->set(DOMAIN_ACCESS_ALL_FIELD, $value); + + // Handle devel module settings. + $exists = \Drupal::moduleHandler()->moduleExists('devel_generate'); + $values = []; + if ($exists && isset($entity->devel_generate)) { + // If set by the form. + if (isset($entity->devel_generate['domain_access'])) { + $selection = array_filter($entity->devel_generate['domain_access']); + if (isset($selection['random-selection'])) { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $values[DOMAIN_ACCESS_FIELD] = array_rand($domains, ceil(rand(1, count($domains)))); + } + else { + $values[DOMAIN_ACCESS_FIELD] = array_keys($selection); + } + } + if (isset($entity->devel_generate['domain_all'])) { + $selection = $entity->devel_generate['domain_all']; + if ($selection == 'random-selection') { + $values[DOMAIN_ACCESS_ALL_FIELD] = rand(0, 1); + } + else { + $values[DOMAIN_ACCESS_ALL_FIELD] = ($selection = 'yes' ? 1 : 0); + } + } + foreach ($values as $name => $value) { + $entity->set($name, $value); + } + } + +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Add options for domains when using Devel Generate. + */ +function domain_access_form_devel_generate_form_content_alter(&$form, &$form_state, $form_id) { + // Add our element to the Devel generate form. + $form['submit']['#weight'] = 10; + $list = ['random-selection' => t('Random selection')]; + $list += \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(); + $form['domain_access'] = array( + '#title' => t('Domains'), + '#type' => 'checkboxes', + '#options' => $list, + '#weight' => 2, + '#multiple' => TRUE, + '#size' => count($list) > 5 ? 5 : count($list), + '#default_value' => ['random-selection'], + '#description' => t('Sets the domains for created nodes. Random selection overrides other choices.'), + ); + $form['domain_all'] = array( + '#title' => t('Send to all affiliates'), + '#type' => 'radios', + '#options' => [ + 'random-selection' => t('Random selection'), + 'yes' => t('Yes'), + 'no' => t('No'), + ], + '#default_value' => 'random-selection', + '#weight' => 3, + '#description' => t('Sets visibility across all affiliates.'), + ); +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Add options for domains when using Devel Generate. + */ +function domain_access_form_devel_generate_form_user_alter(&$form, &$form_state, $form_id) { + domain_access_form_devel_generate_form_content_alter($form, $form_state, $form_id); +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm. + * + * Move Domain Access fields to an advanced tab like other node settings. + */ +function domain_access_form_node_form_alter(&$form, FormState $form_state, $form_id) { + $move_enabled = \Drupal::config('domain_access.settings')->get('node_advanced_tab'); + if ( + $move_enabled && isset($form[DOMAIN_ACCESS_FIELD]) && + isset($form[DOMAIN_ACCESS_ALL_FIELD]) && + empty($form[DOMAIN_ACCESS_FIELD]['#group']) && + empty($form[DOMAIN_ACCESS_ALL_FIELD]['#group']) + ) { + // Move to the tabs on the entity form + $form[DOMAIN_ACCESS_FIELD]['#group'] = 'domain'; + $form[DOMAIN_ACCESS_ALL_FIELD]['#group'] = 'domain'; + $form['domain'] = [ + '#type' => 'details', + '#title' => t('Domain settings'), + '#group' => 'advanced', + '#attributes' => [ + 'class' => ['node-form-options'] + ], + '#attached' => [ + 'library' => ['node/drupal.node'], + ], + '#weight' => 100, + '#optional' => TRUE + ]; + } + // Add the options hidden from the user silently to the form. + $manager = \Drupal::service('domain.element_manager'); + $form = $manager->setFormOptions($form, $form_state, DOMAIN_ACCESS_FIELD); +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\user\UserForm. + * + * Handle settings that the user cannot access. + */ +function domain_access_form_user_form_alter(&$form, &$form_state, $form_id) { + // Add the options hidden from the user silently to the form. + $manager = \Drupal::service('domain.element_manager'); + $form = $manager->setFormOptions($form, $form_state, DOMAIN_ACCESS_FIELD); +} + +/** + * Implements hook_domain_references_alter(). + */ +function domain_access_domain_references_alter($query, $account, $context) { + // Restrict domains by editorial assignment. + if ($context['field_type'] != 'editor') { + return; + } + switch ($context['entity_type']) { + case 'node': + if ($account->hasPermission('publish to any domain')) { + break; + } + elseif ($account->hasPermission('publish to any assigned domain')) { + if (!empty($account->get(DOMAIN_ACCESS_ALL_FIELD)->value)) { + break; + } + $allowed = \Drupal::service('domain_access.manager')->getAccessValues($account); + $query->condition('id', array_keys($allowed), 'IN'); + } + else { + // Remove all options. + $query->condition('id', '-no-possible-match-'); + } + break; + + case 'user': + if ($account->hasPermission('assign editors to any domain')) { + // Do nothing. + } + elseif ($account->hasPermission('assign domain editors')) { + if (!empty($account->get(DOMAIN_ACCESS_ALL_FIELD)->value)) { + break; + } + $allowed = \Drupal::service('domain_access.manager')->getAccessValues($account); + $query->condition('id', array_keys($allowed), 'IN'); + } + else { + // Remove all options. + $query->condition('id', '-no-possible-match-'); + } + break; + + default: + // No action taken. + break; + } +} + +/** + * Implements hook_node_access(). + */ +function domain_access_node_access(NodeInterface $node, $op, AccountInterface $account) { + static $active_domain; + if (!isset($active_domain)) { + // Ensure that the loader has run. In some tests, the kernel event has not. + $active = \Drupal::service('domain.negotiator')->getActiveDomain(); + if (empty($active)) { + $active = \Drupal::service('domain.negotiator')->getActiveDomain(TRUE); + } + $active_domain = $active; + } + // Check to see that we have a valid active domain. + // Without one, we cannot assert an opinion about access. + if (empty($active_domain->getDomainId())) { + return AccessResult::neutral(); + } + + $type = $node->bundle(); + $manager = \Drupal::service('domain_access.manager'); + $allowed = FALSE; + + // In order to access update or delete, the user must be able to View. + if ($op == 'view' && $manager->checkEntityAccess($node, $account)) { + /** @var \Drupal\user\UserInterface $user */ + if ($node->isPublished()) { + $allowed = TRUE; + } + elseif ($account->hasPermission('view unpublished domain content')) { + $allowed = TRUE; + } + } + + if ($op == 'update') { + if ($account->hasPermission('update ' . $type . ' content on assigned domains') && $manager->checkEntityAccess($node, $account)) { + $allowed = TRUE; + } + elseif ($account->hasPermission('edit domain content') && $manager->checkEntityAccess($node, $account)) { + $allowed = TRUE; + } + } + + if ($op == 'delete') { + if ($account->hasPermission('delete ' . $type . ' content on assigned domains') && $manager->checkEntityAccess($node, $account)) { + $allowed = TRUE; + } + elseif ($account->hasPermission('delete domain content') && $manager->checkEntityAccess($node, $account)) { + $allowed = TRUE; + } + } + + if ($allowed) { + return AccessResult::allowed() + ->cachePerPermissions() + ->cachePerUser() + ->addCacheableDependency($node); + } + + // No opinion. + return AccessResult::neutral(); +} + +/** + * Implements hook_node_create_access(). + * + * @link https://www.drupal.org/node/2348203 + */ +function domain_access_node_create_access(AccountInterface $account, $context, $entity_bundle) { + // Check to see that we have a valid active domain. + // Without one, we cannot assert an opinion about access. + /** @var \Drupal\domain\DomainInterface $active */ + if ($active = \Drupal::service('domain.negotiator')->getActiveDomain()) { + $id = $active->getDomainId(); + } + else { + return AccessResult::neutral(); + } + // Load the full user record. + $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id()); + $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($user); + if (($account->hasPermission('create ' . $entity_bundle . ' content on assigned domains') + || $account->hasPermission('create domain content')) + && in_array($id, $user_domains)) { + // Note the cache context here! + return AccessResult::allowed()->addCacheContexts(['user.permissions', 'url.site']); + } + // No opinion. + return AccessResult::neutral(); +} + +/** + * Implements hook_entity_field_access(). + */ +function domain_access_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { + // Hide the domain access fields from the entity add/edit forms + // when the user cannot access them. + if ($operation != 'edit') { + return AccessResult::neutral(); + } + + // The entity the field is attached to. + $entity = $items->getEntity(); + + if ($field_definition->getName() == DOMAIN_ACCESS_FIELD) { + if ($entity instanceof AccountInterface) { + $access = AccessResult::allowedIfHasPermissions($account, [ + 'assign domain editors', + 'assign editors to any domain', + ], 'OR'); + } + elseif ($entity instanceof NodeInterface) { + // Treat any other entity as content. + $access = AccessResult::allowedIfHasPermissions($account, [ + 'publish to any domain', + 'publish to any assigned domain', + ], 'OR'); + } + // allowedIfHasPermissions returns allowed() or neutral(). + // In this case, we want it to be forbidden, + // if user doesn't have the permissions above. + if (isset($access) && !$access->isAllowed()) { + return AccessResult::forbidden(); + } + } + // Check permissions on the All Affiliates field. + elseif ($field_definition->getName() == DOMAIN_ACCESS_ALL_FIELD) { + if ($entity instanceof AccountInterface) { + return AccessResult::forbiddenIf(!$account->hasPermission('assign editors to any domain')); + } + elseif ($entity instanceof NodeInterface) { + // Treat any other entity as content. + return AccessResult::forbiddenIf(!$account->hasPermission('publish to any domain')); + } + } + + return AccessResult::neutral(); +} + +/** + * Implements hook_ENTITY_TYPE_insert(). + * + * Creates our fields when new node types are created. + */ +function domain_access_node_type_insert(EntityInterface $entity) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ + if (!$entity->isSyncing()) { + // Do not fire hook when config sync in progress. + domain_access_confirm_fields('node', $entity->id()); + } +} + +/** + * Creates our fields for an entity bundle. + * + * @param string $entity_type + * The entity type being created. Node and user are supported. + * @param string $bundle + * The bundle being created. + * @param array $text + * The text to use for the field. Keys are: + * 'name' -- the lower-case, human-readable name of the entity. + * 'label' -- the form label for the all affiliates field. + * 'description' -- the help text for the all affiliates field. + * + * If calling this function for entities other than user or node, it is the + * caller's responsibility to provide this text. + * + * This function is here for convenience during installation. It is not really + * an API function. Modules wishing to add fields to non-node entities must + * provide their own field storage. See the field storage YML sample in + * tests/modules/domain_access_test for an example of field storage definitions. + * + * @see domain_access_node_type_insert() + * @see domain_access_install() + */ +function domain_access_confirm_fields($entity_type, $bundle, $text = array()) { + // We have reports that importing config causes this function to fail. + try { + $text['node'] = [ + 'name' => 'content', + 'label' => 'Send to all affiliates', + 'description' => 'Make this content available on all domains.', + ]; + $text['user'] = [ + 'name' => 'user', + 'label' => 'Editor for all affiliates', + 'description' => 'Make this user an editor on all domains.', + ]; + + $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_FIELD; + + $field_storage = \Drupal::entityTypeManager()->getStorage('field_config'); + if (!$field = $field_storage->load($id)) { + $field = array( + 'field_name' => DOMAIN_ACCESS_FIELD, + 'entity_type' => $entity_type, + 'label' => 'Domain Access', + 'bundle' => $bundle, + // Users should not be required to be a domain editor. + 'required' => $entity_type !== 'user', + 'description' => 'Select the affiliate domain(s) for this ' . $text[$entity_type]['name'], + 'default_value_callback' => 'Drupal\domain_access\DomainAccessManager::getDefaultValue', + 'settings' => array( + 'handler' => 'default:domain', + // Handler_settings are deprecated but seem to be necessary here. + 'handler_settings' => [ + 'target_bundles' => NULL, + 'sort' => ['field' => 'weight', 'direction' => 'ASC'], + ], + 'target_bundles' => NULL, + 'sort' => ['field' => 'weight', 'direction' => 'ASC'], + ), + ); + $field_config = $field_storage->create($field); + $field_config->save(); + } + // Assign the all affiliates field. + $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_ALL_FIELD; + if (!$field = $field_storage->load($id)) { + $field = array( + 'field_name' => DOMAIN_ACCESS_ALL_FIELD, + 'entity_type' => $entity_type, + 'label' => $text[$entity_type]['label'], + 'bundle' => $bundle, + 'required' => FALSE, + 'description' => $text[$entity_type]['description'], + ); + $field_config = $field_storage->create($field); + $field_config->save(); + } + // Tell the form system how to behave. Default to radio buttons. + // @TODO: This function is deprecated, but using the OO syntax is causing + // test fails. + entity_get_form_display($entity_type, $bundle, 'default') + ->setComponent(DOMAIN_ACCESS_FIELD, array( + 'type' => 'options_buttons', + 'weight' => 40, + )) + ->setComponent(DOMAIN_ACCESS_ALL_FIELD, array( + 'type' => 'boolean_checkbox', + 'settings' => array('display_label' => 1), + 'weight' => 41, + )) + ->save(); + } + catch (Exception $e) { + \Drupal::logger('domain_access')->notice('Field installation failed.'); + } +} + +/** + * Implements hook_views_data_alter(). + */ +function domain_access_views_data_alter(array &$data) { + $table = 'node__' . DOMAIN_ACCESS_FIELD; + $data[$table][DOMAIN_ACCESS_FIELD]['field']['id'] = 'domain_access_field'; + $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['filter']['id'] = 'domain_access_filter'; + $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['argument']['id'] = 'domain_access_argument'; + + // Current domain filter. + $data[$table]['current_all'] = array( + 'title' => t('Current domain'), + 'group' => t('Domain'), + 'filter' => array( + 'field' => DOMAIN_ACCESS_FIELD . '_target_id', + 'id' => 'domain_access_current_all_filter', + 'title' => t('Available on current domain'), + 'help' => t('Filters out nodes not available on current domain (published to current domain or all affiliates).'), + ), + ); + // Since domains are not stored in the database, relationships cannot be used. + unset($data[$table][DOMAIN_ACCESS_FIELD]['relationship']); + + // Set the user data. + $table = 'user__' . DOMAIN_ACCESS_FIELD; + $data[$table][DOMAIN_ACCESS_FIELD]['field']['id'] = 'domain_access_field'; + $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['filter']['id'] = 'domain_access_filter'; + $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['argument']['id'] = 'domain_access_argument'; + + // Since domains are not stored in the database, relationships cannot be used. + unset($data[$table][DOMAIN_ACCESS_FIELD]['relationship']); +} + +/** + * Implements hook_ENTITY_TYPE_insert(). + */ +function domain_access_domain_insert($entity) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ + if ($entity->isSyncing()) { + // Do not fire hook when config sync in progress. + return; + } + $id = 'domain_access_add_action.' . $entity->id(); + $controller = \Drupal::entityTypeManager()->getStorage('action'); + if (!$controller->load($id)) { + /** @var \Drupal\system\Entity\Action $action */ + $action = $controller->create(array( + 'id' => $id, + 'type' => 'node', + 'label' => t('Add selected content to the @label domain', array('@label' => $entity->label())), + 'configuration' => array( + 'domain_id' => $entity->id(), + ), + 'plugin' => 'domain_access_add_action', + )); + $action->trustData()->save(); + } + $remove_id = 'domain_access_remove_action.' . $entity->id(); + if (!$controller->load($remove_id)) { + /** @var \Drupal\system\Entity\Action $action */ + $action = $controller->create(array( + 'id' => $remove_id, + 'type' => 'node', + 'label' => t('Remove selected content from the @label domain', array('@label' => $entity->label())), + 'configuration' => array( + 'domain_id' => $entity->id(), + ), + 'plugin' => 'domain_access_remove_action', + )); + $action->trustData()->save(); + } + $id = 'domain_access_add_editor_action.' . $entity->id(); + if (!$controller->load($id)) { + /** @var \Drupal\system\Entity\Action $action */ + $action = $controller->create(array( + 'id' => $id, + 'type' => 'user', + 'label' => t('Add editors to the @label domain', array('@label' => $entity->label())), + 'configuration' => array( + 'domain_id' => $entity->id(), + ), + 'plugin' => 'domain_access_add_editor_action', + )); + $action->trustData()->save(); + } + $remove_id = 'domain_access_remove_editor_action.' . $entity->id(); + if (!$controller->load($remove_id)) { + /** @var \Drupal\system\Entity\Action $action */ + $action = $controller->create(array( + 'id' => $remove_id, + 'type' => 'user', + 'label' => t('Remove editors from the @label domain', array('@label' => $entity->label())), + 'configuration' => array( + 'domain_id' => $entity->id(), + ), + 'plugin' => 'domain_access_remove_editor_action', + )); + $action->trustData()->save(); + } +} + +/** + * Implements hook_ENTITY_TYPE_delete(). + */ +function domain_access_domain_delete(EntityInterface $entity) { + $controller = \Drupal::entityTypeManager()->getStorage('action'); + $actions = $controller->loadMultiple(array( + 'domain_access_add_action.' . $entity->id(), + 'domain_access_remove_action.' . $entity->id(), + 'domain_access_add_editor_action.' . $entity->id(), + 'domain_access_remove_editor_action.' . $entity->id(), + )); + foreach ($actions as $action) { + $action->delete(); + } +} + +/** + * Implements hook_form_alter(). + * + * Find forms that contain the domain access field and allow those to handle + * default values properly. Note that here we just care if the form saves an + * entity. We then pass that entity to a helper function. + * + * @see domain_access_default_form_values(). + */ +function domain_access_form_alter(&$form, &$form_state, $form_id) { + if ($object = $form_state->getFormObject() && !empty($object) && is_callable([$object, 'getEntity']) && $entity = $object->getEntity()) { + domain_access_default_form_values($form, $form_state, $entity); + } +} + +/** + * Defines default values for domain access field. + * + * This function is a workaround for a core bug. When the domain access field + * is not accessible to some users, the existing values are not preserved. + * + * @see domain_access_entity_field_access(). + */ +function domain_access_default_form_values(&$form, &$form_state, $entity) { + // Set domain access default value when the user does not have access + // to edit the field. This seems to work fine for all affiliates, which + // suggests a core bug in entity reference handling. + if (!$entity->isNew() && + isset($form['field_domain_access']) && + !$form['field_domain_access']['#access'] && + empty($form['field_domain_access']['widget']['#default_value']) + ) { + // Set the default values correctly. + $values = \Drupal::service('domain_access.manager')->getAccessValues($entity); + $form['field_domain_access']['widget']['#default_value'] = array_keys($values); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.permissions.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.permissions.yml new file mode 100644 index 000000000..b05ebf58b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.permissions.yml @@ -0,0 +1,2 @@ +permission_callbacks: + - Drupal\domain_access\DomainAccessPermissions::permissions diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.routing.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.routing.yml new file mode 100644 index 000000000..fb3071dab --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.routing.yml @@ -0,0 +1,7 @@ +domain_access.settings: + path: '/admin/config/domain/domain_access' + defaults: + _title: 'Domain Access settings' + _form: '\Drupal\domain_access\Form\DomainAccessSettingsForm' + requirements: + _permission: 'administer domains' diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.services.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.services.yml new file mode 100644 index 000000000..bb9fefb32 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.services.yml @@ -0,0 +1,9 @@ +services: + domain_access.manager: + class: Drupal\domain_access\DomainAccessManager + arguments: ['@domain.negotiator'] + access_check.domain_access_views: + class: Drupal\domain_access\Access\DomainAccessViewsAccess + arguments: ['@entity_type.manager', '@domain_access.manager'] + tags: + - { name: access_check, applies_to: _domain_access_views } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Access/DomainAccessViewsAccess.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Access/DomainAccessViewsAccess.php new file mode 100644 index 000000000..c9f3ceb7b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Access/DomainAccessViewsAccess.php @@ -0,0 +1,101 @@ +entityTypeManager = $entity_type_manager; + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + $this->userStorage = $this->entityTypeManager->getStorage('user'); + $this->manager = $manager; + } + + /** + * {@inheritdoc} + */ + public function access(Route $route, AccountInterface $account, $arg_0 = NULL) { + // Permissions are stored on the route defaults. + $permission = $route->getDefault('domain_permission'); + $allPermission = $route->getDefault('domain_all_permission'); + + // Users with this permission can see any domain content lists, and it is + // required to view all affiliates. + if ($account->hasPermission($allPermission)) { + return AccessResult::allowed(); + } + + // Load the domain from the passed argument. In testing, this passed NULL + // in some instances. + if (!is_null($arg_0)) { + $domain = $this->domainStorage->load($arg_0); + } + + // Domain found, check user permissions. + if (!empty($domain)) { + if ($this->manager->hasDomainPermissions($account, $domain, [$permission])) { + return AccessResult::allowed(); + } + } + return AccessResult::forbidden(); + } + + /** + * {@inheritdoc} + */ + public function applies(Route $route) { + return $route->hasRequirement($this->requirementsKey); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManager.php b/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManager.php new file mode 100644 index 000000000..7cf30b4a5 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManager.php @@ -0,0 +1,140 @@ +negotiator = $negotiator; + } + + /** + * @inheritdoc + */ + public static function getAccessValues(EntityInterface $entity, $field_name = DOMAIN_ACCESS_FIELD) { + // @TODO: static cache. + $list = []; + // @TODO In tests, $entity is returning NULL. + if (is_null($entity)) { + return $list; + } + // Get the values of an entity. + $values = $entity->hasField($field_name) ? $entity->get($field_name) : NULL; + // Must be at least one item. + if (!empty($values)) { + foreach ($values as $item) { + if ($target = $item->getValue()) { + if ($domain = \Drupal::entityTypeManager()->getStorage('domain')->load($target['target_id'])) { + $list[$domain->id()] = $domain->getDomainId(); + } + } + } + } + return $list; + } + + /** + * @inheritdoc + */ + public static function getAllValue(EntityInterface $entity) { + return $entity->hasField(DOMAIN_ACCESS_ALL_FIELD) ? $entity->get(DOMAIN_ACCESS_ALL_FIELD)->value : NULL; + } + + /** + * @inheritdoc + */ + public function checkEntityAccess(EntityInterface $entity, AccountInterface $account) { + $entity_domains = $this->getAccessValues($entity); + $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id()); + if (!empty($this->getAllValue($user)) && !empty($entity_domains)) { + return TRUE; + } + $user_domains = $this->getAccessValues($user); + return (bool) !empty(array_intersect($entity_domains, $user_domains)); + } + + /** + * @inheritdoc + */ + public static function getDefaultValue(FieldableEntityInterface $entity, FieldDefinitionInterface $definition) { + $item = []; + if (!$entity->isNew()) { + // If set, ensure we do not drop existing data. + foreach (self::getAccessValues($entity) as $id) { + $item[] = $id; + } + } + // When creating a new entity, populate if required. + elseif ($entity->getFieldDefinition(DOMAIN_ACCESS_FIELD)->isRequired()) { + /** @var \Drupal\domain\DomainInterface $active */ + if ($active = \Drupal::service('domain.negotiator')->getActiveDomain()) { + $item[0]['target_uuid'] = $active->uuid(); + } + } + return $item; + } + + /** + * @inheritdoc + */ + public function hasDomainPermissions(AccountInterface $account, DomainInterface $domain, array $permissions, $conjunction = 'AND') { + // Assume no access. + $access = FALSE; + + // In the case of multiple AND permissions, assume access and then deny if + // any check fails. + if ($conjunction == 'AND' && !empty($permissions)) { + $access = TRUE; + foreach ($permissions as $permission) { + if (!($permission_access = $account->hasPermission($permission))) { + $access = FALSE; + break; + } + } + } + // In the case of multiple OR permissions, assume deny and then allow if any + // check passes. + else { + foreach ($permissions as $permission) { + if ($permission_access = $account->hasPermission($permission)) { + $access = TRUE; + break; + } + } + } + // Validate that the user is assigned to the domain. If not, deny. + $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id()); + $allowed = $this->getAccessValues($user); + if (!isset($allowed[$domain->id()]) && empty($this->getAllValue($user))) { + $access = FALSE; + } + + return $access; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManagerInterface.php b/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManagerInterface.php new file mode 100644 index 000000000..d95933a36 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManagerInterface.php @@ -0,0 +1,85 @@ + array( + 'title' => $this->t('Assign additional editors to assigned domains'), + 'restrict access' => TRUE, + ), + 'assign editors to any domain' => array( + 'title' => $this->t('Assign additional editors to any domains'), + 'restrict access' => TRUE, + ), + 'publish to any domain' => array( + 'title' => $this->t('Publish to any domain'), + ), + 'publish to any assigned domain' => array( + 'title' => $this->t('Publish content to any assigned domain'), + ), + 'create domain content' => array( + 'title' => $this->t('Create any content on assigned domains'), + ), + 'edit domain content' => array( + 'title' => $this->t('Edit any content on assigned domains'), + ), + 'delete domain content' => array( + 'title' => $this->t('Delete any content on assigned domains'), + ), + 'view unpublished domain content' => array( + 'title' => $this->t('View unpublished content on assigned domains'), + 'restrict access' => TRUE, + ), + ); + + // Generate standard node permissions for all applicable node types. + foreach (NodeType::loadMultiple() as $type) { + $permissions += $this->nodePermissions($type); + } + + return $permissions; + } + + /** + * Helper method to generate standard node permission list for a given type. + * + * Shamelessly lifted from node_list_permissions(). + * + * @param NodeType $type + * The node type object. + * + * @return array + * An array of permission names and descriptions. + */ + private function nodePermissions(NodeType $type) { + // Build standard list of node permissions for this type. + $id = $type->id(); + $perms = array( + "create $id content on assigned domains" => array( + 'title' => $this->t('%type_name: Create new content on assigned domains', array('%type_name' => $type->label())), + ), + "update $id content on assigned domains" => array( + 'title' => $this->t('%type_name: Edit any content on assigned domains', array('%type_name' => $type->label())), + ), + "delete $id content on assigned domains" => array( + 'title' => $this->t('%type_name: Delete any content on assigned domains', array('%type_name' => $type->label())), + ), + ); + + return $perms; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Form/DomainAccessSettingsForm.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Form/DomainAccessSettingsForm.php new file mode 100644 index 000000000..f7cb2310a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Form/DomainAccessSettingsForm.php @@ -0,0 +1,53 @@ +config('domain_access.settings'); + $form['node_advanced_tab'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Move Domain Access fields to advanced node settings.'), + '#default_value' => $config->get('node_advanced_tab'), + '#description' => $this->t('When checked the Domain Access fields will be shown as a tab in the advanced settings on node edit form. However, if you have placed the fields in a field group already, they will not be moved.'), + ); + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->config('domain_access.settings') + ->set('node_advanced_tab', $form_state->getValue('node_advanced_tab')) + ->save(); + + parent::submitForm($form, $form_state); + } + + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessActionBase.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessActionBase.php new file mode 100644 index 000000000..8bc8d000c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessActionBase.php @@ -0,0 +1,100 @@ +entityType = $entity_type; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity.manager')->getDefinition('domain') + ); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array( + 'domain_id' => '', + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(); + $form['domain_id'] = array( + '#type' => 'checkboxes', + '#title' => t('Domain'), + '#options' => $domains, + '#default_value' => $this->configuration['id'], + '#required' => TRUE, + ); + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['domain_id'] = $form_state->getValue('domain_id'); + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + if (!empty($this->configuration['domain_id'])) { + $prefix = $this->entityType->getConfigPrefix() . '.'; + $this->addDependency('config', $prefix . $this->configuration['domain_id']); + } + return $this->dependencies; + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\user\UserInterface $object */ + // @TODO: fix this logic. + $access = $object->access('update', $account, TRUE); + + return $return_as_object ? $access : $access->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAdd.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAdd.php new file mode 100644 index 000000000..5d388405e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAdd.php @@ -0,0 +1,31 @@ +configuration['domain_id']; + $node_domains = \Drupal::service('domain_access.manager')->getAccessValues($entity); + + // Add domain assignment if not present. + if ($entity !== FALSE && !isset($node_domains[$id])) { + $node_domains[$id] = $id; + $entity->set(DOMAIN_ACCESS_FIELD, array_keys($node_domains)); + $entity->save(); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAddEditor.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAddEditor.php new file mode 100644 index 000000000..e6e963de9 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAddEditor.php @@ -0,0 +1,31 @@ +configuration['domain_id']; + $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($entity); + + // Skip adding the role to the user if they already have it. + if ($entity !== FALSE && !isset($user_domains[$id])) { + $user_domains[$id] = $id; + $entity->set(DOMAIN_ACCESS_FIELD, array_keys($user_domains)); + $entity->save(); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAll.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAll.php new file mode 100644 index 000000000..8ccd91397 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAll.php @@ -0,0 +1,24 @@ +set(DOMAIN_ACCESS_ALL_FIELD, 1); + $entity->save(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditAll.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditAll.php new file mode 100644 index 000000000..dc2dfa772 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditAll.php @@ -0,0 +1,24 @@ +set(DOMAIN_ACCESS_ALL_FIELD, 1); + $entity->save(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditNone.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditNone.php new file mode 100644 index 000000000..ee3ba9855 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditNone.php @@ -0,0 +1,24 @@ +set(DOMAIN_ACCESS_ALL_FIELD, 0); + $entity->save(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessNone.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessNone.php new file mode 100644 index 000000000..53d670a2b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessNone.php @@ -0,0 +1,24 @@ +set(DOMAIN_ACCESS_ALL_FIELD, 0); + $entity->save(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemove.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemove.php new file mode 100644 index 000000000..4bbfe4a4e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemove.php @@ -0,0 +1,31 @@ +configuration['domain_id']; + $node_domains = \Drupal::service('domain_access.manager')->getAccessValues($entity); + + // Remove domain assignment if present. + if ($entity !== FALSE && isset($node_domains[$id])) { + unset($node_domains[$id]); + $entity->set(DOMAIN_ACCESS_FIELD, array_keys($node_domains)); + $entity->save(); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemoveEditor.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemoveEditor.php new file mode 100644 index 000000000..0c2e27905 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemoveEditor.php @@ -0,0 +1,31 @@ +configuration['domain_id']; + $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($entity); + + // Skip adding the role to the user if they already have it. + if ($entity !== FALSE && isset($user_domains[$id])) { + unset($user_domains[$id]); + $entity->set(DOMAIN_ACCESS_FIELD, array_keys($user_domains)); + $entity->save(); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessContent.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessContent.php new file mode 100644 index 000000000..7111e2389 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessContent.php @@ -0,0 +1,169 @@ +domainStorage = $domain_storage; + $this->userStorage = $user_storage; + $this->manager = $manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager')->getStorage('domain'), + $container->get('entity_type.manager')->getStorage('user'), + $container->get('domain_access.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function summaryTitle() { + return $this->t('Domain editor'); + } + + /** + * {@inheritdoc} + */ + public function access(AccountInterface $account) { + // Users with this permission can see any domain content lists, and it is + // required to view all affiliates. + if ($account->hasPermission($this->allPermission)) { + return TRUE; + } + + // The routine below determines what domain (if any) was passed to the View. + if (isset($this->view->element['#arguments'])) { + foreach ($this->view->element['#arguments'] as $value) { + if ($domain = $this->domainStorage->load($value)) { + break; + } + } + } + + // Domain found, check user permissions. + if (!empty($domain)) { + return $this->manager->hasDomainPermissions($account, $domain, [$this->permission]); + } + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function alterRouteDefinition(Route $route) { + if ($domains = $this->domainStorage->loadMultiple()) { + $list = array_keys($domains); + } + $list[] = 'all_affiliates'; + $route->setRequirement('_domain_access_views', (string) implode('+', $list)); + $route->setDefault('domain_permission', $this->permission); + $route->setDefault('domain_all_permission', $this->allPermission); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['user']; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return []; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessEditor.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessEditor.php new file mode 100644 index 000000000..f5cefb183 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessEditor.php @@ -0,0 +1,26 @@ +getStorage('domain')->load($this->argument)) { + return $domain->label(); + } + return parent::title(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/field/DomainAccessField.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/field/DomainAccessField.php new file mode 100644 index 000000000..30456088e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/field/DomainAccessField.php @@ -0,0 +1,50 @@ +options['settings']['link'])) { + foreach ($items as &$item) { + $object = $item['raw']; + $entity = $object->getEntity(); + $url = $entity->toUrl()->toString(); + $domain = $item['rendered']['#options']['entity']; + $item['rendered']['#type'] = 'markup'; + $item['rendered']['#markup'] = '' . $domain->label() . ''; + } + uasort($items, array($this, 'sort')); + } + + return $items; + } + + /** + * Sort the domain list, if possible. + */ + private function sort($a, $b) { + $domainA = isset($a['rendered']['#options']['entity']) ? $a['rendered']['#options']['entity'] : 0; + $domainB = isset($b['rendered']['#options']['entity']) ? $b['rendered']['#options']['entity'] : 0; + if ($domainA !== 0) { + return $domainA->getWeight() > $domainB->getWeight(); + } + // We don't have a domain object so sort as best we can. + return $a['rendered']['#plain_text'] > $b['rendered']['#plain_text']; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessCurrentAllFilter.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessCurrentAllFilter.php new file mode 100644 index 000000000..d23f6e2af --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessCurrentAllFilter.php @@ -0,0 +1,73 @@ +value_value = t('Available on current domain'); + } + + /** + * {@inheritdoc} + */ + public function getValueOptions() { + $this->valueOptions = array(1 => $this->t('Yes'), 0 => $this->t('No')); + } + + /** + * {@inheritdoc} + */ + protected function operators() { + return array(); + } + + /** + * {@inheritdoc} + */ + public function query() { + $this->ensureMyTable(); + // @TODO: Proper abstraction of table and field name. + $all_table = $this->query->ensureTable('node__field_domain_all_affiliates'); + $current_domain = \Drupal::service('domain.negotiator')->getActiveId(); + if (empty($this->value)) { + // @TODO proper handling of NULL? + $where = "$this->tableAlias.$this->realField <> '$current_domain'"; + $where = '(' . $where . " OR $this->tableAlias.$this->realField IS NULL)"; + $where = '(' . $where . " AND ($all_table.field_domain_all_affiliates_value = 0 OR $all_table.field_domain_all_affiliates_value IS NULL))"; + } + else { + $where = "($this->tableAlias.$this->realField = '$current_domain' OR $all_table.field_domain_all_affiliates_value = 1)"; + } + $this->query->addWhereExpression($this->options['group'], $where); + // This filter causes duplicates. + $this->query->options['distinct'] = TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = parent::getCacheContexts(); + + $contexts[] = 'url.site'; + + return $contexts; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessFilter.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessFilter.php new file mode 100644 index 000000000..0869ef8c2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessFilter.php @@ -0,0 +1,26 @@ +valueOptions)) { + $this->valueTitle = $this->t('Domains'); + $this->valueOptions = \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(); + } + return $this->valueOptions; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_access.yml b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_access.yml new file mode 100644 index 000000000..a37af529c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_access.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - domain + - taxonomy +id: taxonomy_term.field_domain_access +field_name: field_domain_access +entity_type: taxonomy_term +type: entity_reference +settings: + target_type: domain +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_all_affiliates.yml b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_all_affiliates.yml new file mode 100644 index 000000000..407d58ca3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_all_affiliates.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - domain + - taxonomy +id: taxonomy_term.field_domain_all_affiliates +field_name: field_domain_all_affiliates +entity_type: taxonomy_term +type: boolean +settings: { } +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml new file mode 100644 index 000000000..5e85d249f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml @@ -0,0 +1,18 @@ +name: "Domain Access module tests" +description: "Support module for domain access testing." +type: module +package: Testing +# version: VERSION +# core: 8.x +hidden: TRUE + +dependencies: + - domain + - domain_access + - taxonomy + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessAllAffiliatesTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessAllAffiliatesTest.php new file mode 100644 index 000000000..0264efd30 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessAllAffiliatesTest.php @@ -0,0 +1,103 @@ +admin_user = $this->drupalCreateUser(array( + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($this->admin_user); + + // Visit the article field administration page. + $this->drupalGet('admin/structure/types/manage/article/fields'); + $this->assertResponse(200, 'Manage fields page accessed.'); + + // Check for the field. + $this->assertText($label, 'Domain form field found.'); + + // Visit the article field display administration page. + $this->drupalGet('admin/structure/types/manage/article/display'); + $this->assertResponse(200, 'Manage field display page accessed.'); + + // Check for the field. + $this->assertText($label, 'Domain form field found.'); + } + + /** + * Tests the storage of the domain access field. + */ + public function testDomainAccessAllFieldStorage() { + $label = 'Send to all affiliates'; + $this->admin_user = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + )); + $this->drupalLogin($this->admin_user); + + // Create 5 domains. + $this->domainCreateTestDomains(5); + + // Visit the article field display administration page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200); + + // Check the new field exists on the page. + $this->assertText($label, 'Found the domain field instance.'); + + // We expect to find 5 domain options. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $string = 'value="' . $domain->id() . '"'; + $this->assertRaw($string, 'Found the domain option.'); + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + + // Try to post a node, assigned to the first two domains. + $edit['title[0][value]'] = 'Test node'; + $edit["field_domain_access[{$one}]"] = TRUE; + $edit["field_domain_access[{$two}]"] = TRUE; + $edit["field_domain_all_affiliates[value]"] = 1; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + $this->assertResponse(200); + $node = \Drupal::entityTypeManager()->getStorage('node')->load(1); + // Check that two values are set. + $values = \Drupal::service('domain_access.manager')->getAccessValues($node); + $this->assertTrue(count($values) == 2, 'Node saved with two domain records.'); + // Check that all affiliates is set. + $this->assertTrue(!empty($node->get(DOMAIN_ACCESS_ALL_FIELD)->value), 'Node assigned to all affiliates.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessDefaultValueTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessDefaultValueTest.php new file mode 100644 index 000000000..cc56b9926 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessDefaultValueTest.php @@ -0,0 +1,99 @@ +admin_user = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + )); + $this->drupalLogin($this->admin_user); + + // Create 5 domains. + $this->domainCreateTestDomains(5); + + // Visit the article field display administration page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200); + + // Check the new field exists on the page. + $this->assertText('Domain Access', 'Found the domain field instance.'); + $this->assertRaw('name="field_domain_access[example_com]" value="example_com" checked="checked"', 'Default domain selected.'); + // Check the all affiliates field. + $this->assertText('Send to all affiliates', 'Found the all affiliates field instance.'); + $this->assertNoRaw('name="field_domain_all_affiliates[value]" value="1" checked="checked"', 'All affiliates not selected.'); + + // Now save the node with the values set. + $edit = [ + 'title[0][value]' => 'Test node', + 'field_domain_access[example_com]' => 'example_com', + ]; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + + // Load the node. + $node = \Drupal::entityTypeManager()->getStorage('node')->load(1); + $this->assertTrue($node, 'Article node created.'); + // Check that the values are set. + $values = \Drupal::service('domain_access.manager')->getAccessValues($node); + $this->assertTrue(count($values) == 1, 'Node saved with one domain record.'); + $allValue = \Drupal::service('domain_access.manager')->getAllValue($node); + $this->assertTrue(empty($allValue), 'Not sent to all affiliates.'); + + // Logout the admin user. + $this->drupalLogout(); + + // Create a limited value user. + $this->test_user = $this->drupalCreateUser(array( + 'create article content', + 'edit any article content', + )); + + // Login and try to edit the created node. + $this->drupalLogin($this->test_user); + + $this->drupalGet('node/1/edit'); + $this->assertResponse(200); + + // Now save the node with the values set. + $edit = [ + 'title[0][value]' => 'Test node update', + ]; + $this->drupalPostForm('node/1/edit', $edit, 'Save'); + + // Load the node. + $node = \Drupal::entityTypeManager()->getStorage('node')->load(1); + $this->assertTrue($node, 'Article node created.'); + // Check that the values are set. + $values = \Drupal::service('domain_access.manager')->getAccessValues($node); + $this->assertTrue(count($values) == 1, 'Node saved with one domain record.'); + $allValue = \Drupal::service('domain_access.manager')->getAllValue($node); + $this->assertTrue(empty($allValue), 'Not sent to all affiliates.'); + + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessElementTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessElementTest.php new file mode 100644 index 000000000..ea511c3fa --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessElementTest.php @@ -0,0 +1,130 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainAccessElement() { + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + )); + $this->drupalLogin($admin); + + $this->drupalGet('node/add/article'); + $this->assertSession()->statusCodeEquals(200); + + // Set the title, so the node can be saved. + $this->fillField('title[0][value]', 'Test node'); + + // We expect to find 5 domain options. We set two as selected. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $count = 0; + $ids = ['example_com', 'one_example_com', 'two_example_com']; + foreach ($domains as $domain) { + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if (in_array($domain->id(), $ids)) { + $this->checkField($locator); + } + } + // Find the all affiliates field. + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->findField($locator); + + // Set all affiliates to TRUE. + $this->checkField($locator); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + $storage = \Drupal::entityTypeManager()->getStorage('node'); + $node = $storage->load(1); + // Check that two values are set. + $manager = \Drupal::service('domain_access.manager'); + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 3, 'Node saved with three domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 1, 'Node saved to all affiliates.'); + + // Now login as a user with limited rights. + $account = $this->drupalCreateUser(array('create article content', 'edit any article content', 'publish to any assigned domain')); + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account->id(), $ids, DOMAIN_ACCESS_FIELD); + $user_storage = \Drupal::entityTypeManager()->getStorage('user'); + $user = $user_storage->load($account->id()); + $values = $manager->getAccessValues($user); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + $value = $manager->getAllValue($user); + $this->assert($value == 0, 'User not saved to all affiliates.'); + + $this->drupalLogin($account); + + $this->drupalGet('node/' . $node->id() . '/edit'); + $this->assertSession()->statusCodeEquals(200); + + foreach ($domains as $domain) { + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if ($domain->id() == 'example_com') { + $this->checkField($locator); + } + elseif ($domain->id() == 'one_example_com') { + $this->uncheckField($locator); + } + + else { + $this->assertSession()->fieldNotExists($locator); + } + } + + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->assertSession()->fieldNotExists($locator); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Now, check the node. + $storage->resetCache(array($node->id())); + $node = $storage->load(1); + // Check that two values are set. + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 2, 'Node saved with two domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 1, 'Node saved to all affiliates.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityFieldTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityFieldTest.php new file mode 100644 index 000000000..d98c2ca1a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityFieldTest.php @@ -0,0 +1,71 @@ +domainCreateTestDomains(5); + } + + /** + * Tests that the fields are accessed properly. + */ + public function testDomainAccessEntityFields() { + $label = 'Send to all affiliates'; + // Create a vocabulary. + $vocabulary = entity_create('taxonomy_vocabulary', array( + 'name' => 'Domain vocabulary', + 'description' => 'Test taxonomy for Domain Access', + 'vid' => 'domain_access', + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + 'weight' => 100, + )); + $vocabulary->save(); + $text['taxonomy_term'] = [ + 'name' => 'term', + 'label' => 'Send to all affiliates', + 'description' => 'Make this term available on all domains.', + ]; + domain_access_confirm_fields('taxonomy_term', 'domain_access', $text); + $this->admin_user = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + 'administer taxonomy', + 'administer taxonomy_term fields', + 'administer taxonomy_term form display', + )); + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/structure/taxonomy/manage/domain_access/overview/fields'); + $this->assertResponse(200, 'Manage fields page accessed.'); + + // Check for a domain field. + $this->assertText('Domain Access', 'Domain form field found.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityReferenceTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityReferenceTest.php new file mode 100644 index 000000000..8e9a277dd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityReferenceTest.php @@ -0,0 +1,98 @@ +admin_user = $this->drupalCreateUser(array( + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($this->admin_user); + + // Visit the article field administration page. + $this->drupalGet('admin/structure/types/manage/article/fields'); + $this->assertResponse(200, 'Manage fields page accessed.'); + + // Check for a domain field. + $this->assertText('Domain Access', 'Domain form field found.'); + + // Visit the article field display administration page. + $this->drupalGet('admin/structure/types/manage/article/display'); + $this->assertResponse(200, 'Manage field display page accessed.'); + + // Check for a domain field. + $this->assertText('Domain Access', 'Domain form field found.'); + } + + /** + * Tests the storage of the domain access field. + */ + public function testDomainAccessFieldStorage() { + $this->admin_user = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + )); + $this->drupalLogin($this->admin_user); + + // Create 5 domains. + $this->domainCreateTestDomains(5); + + // Visit the article field display administration page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200); + + // Check the new field exists on the page. + $this->assertText('Domain Access', 'Found the domain field instance.'); + + // We expect to find 5 domain options. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $string = 'value="' . $domain->id() . '"'; + $this->assertRaw($string, 'Found the domain option.'); + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + + // Try to post a node, assigned to the first two domains. + $edit['title[0][value]'] = 'Test node'; + $edit["field_domain_access[{$one}]"] = TRUE; + $edit["field_domain_access[{$two}]"] = TRUE; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + $this->assertResponse(200); + $node = \Drupal::entityTypeManager()->getStorage('node')->load(1); + // Check that two values are set. + $values = \Drupal::service('domain_access.manager')->getAccessValues($node); + $this->assertTrue(count($values) == 2, 'Node saved with two domain records.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessFieldTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessFieldTest.php new file mode 100644 index 000000000..339942937 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessFieldTest.php @@ -0,0 +1,190 @@ +domainCreateTestDomains(5); + } + + /** + * Tests that the fields are accessed properly. + */ + public function testDomainAccessFields() { + $label = 'Send to all affiliates'; + + // Test a user who can access all domain settings. + $user1 = $this->drupalCreateUser(['create article content', 'publish to any domain']); + $this->drupalLogin($user1); + + // Visit the article creation page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200, 'Article creation found.'); + + // Check for the form options. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $this->assertText($domain->label(), 'Domain form item found.'); + } + $this->assertText($label, 'All affiliates field found.'); + + // Test a user who can access some domain settings. + $user2 = $this->drupalCreateUser(['create article content', 'publish to any assigned domain']); + $active_domain = array_rand($domains, 1); + $this->addDomainsToEntity('user', $user2->id(), $active_domain, DOMAIN_ACCESS_FIELD); + $this->drupalLogin($user2); + + // Visit the article creation page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200, 'Article creation found.'); + + // Check for the form options. + foreach ($domains as $domain) { + if ($domain->id() == $active_domain) { + $this->assertRaw('>' . $domain->label() . '', 'Domain form item found.'); + } + else { + $this->assertNoRaw('>' . $domain->label() . '', 'Domain form item not found.'); + } + } + $this->assertNoText($label, 'All affiliates field not found.'); + + // Test a user who can access no domain settings. + $user3 = $this->drupalCreateUser(array('create article content')); + $this->drupalLogin($user3); + + // Visit the article creation page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200, 'Article creation found.'); + + // Check for the form options. + foreach ($domains as $domain) { + $this->assertNoText($domain->label(), 'Domain form item not found.'); + } + $this->assertNoText($label, 'All affiliates field not found.'); + + // Attempt saving the node. + // The domain/domain affiliates fields are not accessible to this user. + // The save will fail with an EntityStorageException until + // https://www.drupal.org/node/2609252 is fixed. + $edit = array(); + $edit['title[0][value]'] = $this->randomMachineName(8); + $edit['body[0][value]'] = $this->randomMachineName(16); + $this->drupalPostForm('node/add/article', $edit, t('Save')); + + // Check that the node exists in the database. + $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); + $this->assertTrue($node, 'Node found in database.'); + + // Test a user who can assign users to domains. + $user4 = $this->drupalCreateUser(array('administer users', 'assign editors to any domain')); + $this->drupalLogin($user4); + + // Visit the account creation page. + $this->drupalGet('admin/people/create'); + $this->assertResponse(200, 'User creation found.'); + + // Check for the form options. + foreach ($domains as $domain) { + $this->assertText($domain->label(), 'Domain form item found.'); + } + + // Test a user who can assign users to some domains. + $user5 = $this->drupalCreateUser(array('administer users', 'assign domain editors')); + $active_domain = array_rand($domains, 1); + $this->addDomainsToEntity('user', $user5->id(), $active_domain, DOMAIN_ACCESS_FIELD); + $this->drupalLogin($user5); + + // Visit the account creation page. + $this->drupalGet('admin/people/create'); + $this->assertResponse(200, 'User creation found.'); + + // Check for the form options. + foreach ($domains as $domain) { + if ($domain->id() == $active_domain) { + $this->assertRaw('>' . $domain->label() . '', 'Domain form item found.'); + } + else { + $this->assertNoRaw('>' . $domain->label() . '', 'Domain form item not found.'); + } + } + + // Test a user who can access no domain settings. + $user6 = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($user6); + + // Visit the account creation page. + $this->drupalGet('admin/people/create'); + $this->assertResponse(200, 'User creation found.'); + + // Check for the form options. + foreach ($domains as $domain) { + $this->assertNoText($domain->label(), 'Domain form item not found.'); + } + + // Test a user who can access all domain settings. + $user7 = $this->drupalCreateUser(array('bypass node access', 'publish to any domain')); + $this->drupalLogin($user7); + + // Create a new content type and test that the fields are created. + // Create a content type programmatically. + $type = $this->drupalCreateContentType(); + + $type_exists = (bool) NodeType::load($type->id()); + $this->assertTrue($type_exists, 'The new content type has been created in the database.'); + + // Visit the article creation page. + $this->drupalGet('node/add/' . $type->id()); + $this->assertResponse(200, $type->id() . ' creation found.'); + + // Check for the form options. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $this->assertText($domain->label(), 'Domain form item found.'); + } + $this->assertText($label, 'All affiliates field found.'); + + // Test user without access to affiliates field editing their user page. + $user8 = $this->drupalCreateUser(array('change own username')); + $this->drupalLogin($user8); + + $user_edit_page = 'user/' . $user8->id() . '/edit'; + $this->drupalGet($user_edit_page); + // Check for the form options. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $this->assertNoText($domain->label(), 'Domain form item not found.'); + } + + $this->assertNoText($label, 'All affiliates field not found.'); + + // Change own username. + $edit = array(); + $edit['name'] = $this->randomMachineName(); + + $this->drupalPostForm($user_edit_page, $edit, t('Save')); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessGrantsTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessGrantsTest.php new file mode 100644 index 000000000..e669f080e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessGrantsTest.php @@ -0,0 +1,88 @@ +delete('node_access')->execute(); + } + + /** + * Creates a node and tests the creation of node access rules. + */ + public function testDomainAccessGrants() { + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); + + // Create 5 domains. + $this->domainCreateTestDomains(5); + // Assign a node to a random domain. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $active_domain = array_rand($domains, 1); + $domain = $domains[$active_domain]; + // Create an article node. + $node1 = $this->drupalCreateNode(array( + 'type' => 'article', + DOMAIN_ACCESS_FIELD => array($domain->id()), + )); + $this->assertTrue($node_storage->load($node1->id()), 'Article node created.'); + + // Test the response of the node on each site. Should allow access only to + // the selected site. + foreach ($domains as $domain) { + $path = $domain->getPath() . 'node/' . $node1->id(); + $this->drupalGet($path); + if ($domain->id() == $active_domain) { + $this->assertResponse(200); + $this->assertRaw($node1->getTitle(), 'Article found on domain.'); + } + else { + $this->assertResponse(403); + } + } + + // Create an article node. + $node2 = $this->drupalCreateNode(array( + 'type' => 'article', + DOMAIN_ACCESS_FIELD => array($domain->id()), + DOMAIN_ACCESS_ALL_FIELD => 1, + )); + $this->assertTrue($node_storage->load($node2->id()), 'Article node created.'); + // Test the response of the node on each site. Should allow access on all. + foreach ($domains as $domain) { + $path = $domain->getPath() . 'node/' . $node2->id(); + $this->drupalGet($path); + $this->assertResponse(200); + $this->assertRaw($node2->getTitle(), 'Article found on domain.'); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessLanguageSaveTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessLanguageSaveTest.php new file mode 100644 index 000000000..0896d30d2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessLanguageSaveTest.php @@ -0,0 +1,82 @@ +domainCreateTestDomains(5); + + // Add Hungarian and Afrikaans. + ConfigurableLanguage::createFromLangcode('hu')->save(); + ConfigurableLanguage::createFromLangcode('af')->save(); + + // Enable content translation for the current entity type. + \Drupal::service('content_translation.manager')->setEnabled('node', 'page', TRUE); + } + + /** + * Basic test setup. + */ + public function testDomainAccessSave() { + $storage = \Drupal::entityTypeManager()->getStorage('node'); + // Save a node programatically. + $node = $storage->create([ + 'type' => 'article', + 'title' => 'Test node', + 'uid' => '1', + 'status' => 1, + DOMAIN_ACCESS_FIELD => ['example_com'], + DOMAIN_ACCESS_ALL_FIELD => 1, + ]); + $node->save(); + + // Load the node. + $node = $storage->load(1); + + // Check that two values are set properly. + $manager = \Drupal::service('domain_access.manager'); + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 1, 'Node saved with one domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 1, 'Node saved to all affiliates.'); + + // Create an Afrikaans translation assigned to domain 2. + $translation = $node->addTranslation('af'); + $translation->title->value = $this->randomString(); + $translation->{DOMAIN_ACCESS_FIELD} = ['example_com', 'one_example_com']; + $translation->{DOMAIN_ACCESS_ALL_FIELD} = 0; + $translation->status = 1; + $node->save(); + + // Load and check the translated node. + $parent_node = $storage->load(1); + $node = $parent_node->getTranslation('af'); + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 2, 'Node saved with two domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 0, 'Node not saved to all affiliates.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessPermissionsTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessPermissionsTest.php new file mode 100644 index 000000000..67cdd3440 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessPermissionsTest.php @@ -0,0 +1,295 @@ +config('user.role.' . RoleInterface::AUTHENTICATED_ID)->set('permissions', array())->save(); + // Create Basic page node type. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array( + 'type' => 'page', + 'name' => 'Basic page', + 'display_submitted' => FALSE, + )); + $this->drupalCreateContentType(array( + 'type' => 'article', + 'name' => 'Article', + 'display_submitted' => FALSE, + )); + } + $this->accessHandler = \Drupal::entityTypeManager()->getAccessControlHandler('node'); + $this->manager = \Drupal::service('domain_access.manager'); + $this->userStorage = \Drupal::entityTypeManager()->getStorage('user'); + // Create 5 domains. + $this->domainCreateTestDomains(5); + $this->domains = $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + } + + /** + * Runs basic tests for node_access function. + */ + public function testDomainAccessPermissions() { + // Note that these are hook_node_access() rules. Node Access system tests + // are in DomainAccessRecordsTest. + // We expect to find 5 domain options. Set two for later use. + foreach ($this->domains as $domain) { + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + + // Assign our user to domain $two. Test on $one and $two. + $domain_user1 = $this->drupalCreateUser(array( + 'access content', + 'edit domain content', + 'delete domain content', + )); + $this->addDomainsToEntity('user', $domain_user1->id(), $two, DOMAIN_ACCESS_FIELD); + $domain_user1 = $this->userStorage->load($domain_user1->id()); + $assigned = $this->manager->getAccessValues($domain_user1); + $this->assertTrue(count($assigned) == 1, 'User assigned to one domain.'); + $this->assertTrue(isset($assigned[$two]), 'User assigned to proper test domain.'); + // Assign one node to default domain, and one to our test domain. + $domain_node1 = $this->drupalCreateNode(array('type' => 'page', DOMAIN_ACCESS_FIELD => [$one])); + $domain_node2 = $this->drupalCreateNode(array('type' => 'page', DOMAIN_ACCESS_FIELD => [$two])); + $assigned = $this->manager->getAccessValues($domain_node1); + $this->assertTrue(isset($assigned[$one]), 'Node1 assigned to proper test domain.'); + $assigned = $this->manager->getAccessValues($domain_node2); + $this->assertTrue(isset($assigned[$two]), 'Node2 assigned to proper test domain.'); + + // Tests 'edit domain content' to edit content assigned to their domains. + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => FALSE, + 'delete' => FALSE, + ), $domain_node1, $domain_user1); + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + ), $domain_node2, $domain_user1); + + // Tests 'edit domain TYPE content'. + // Assign our user to domain $two. Test on $one and $two. + $domain_user3 = $this->drupalCreateUser(array( + 'access content', + 'update page content on assigned domains', + 'delete page content on assigned domains', + )); + $this->addDomainsToEntity('user', $domain_user3->id(), $two, DOMAIN_ACCESS_FIELD); + $domain_user3 = $this->userStorage->load($domain_user3->id()); + $assigned = $this->manager->getAccessValues($domain_user3); + $this->assertTrue(count($assigned) == 1, 'User assigned to one domain.'); + $this->assertTrue(isset($assigned[$two]), 'User assigned to proper test domain.'); + + // Assign two different node types to our test domain. + $domain_node3 = $this->drupalCreateNode(array('type' => 'article', DOMAIN_ACCESS_FIELD => [$two])); + $domain_node4 = $this->drupalCreateNode(array('type' => 'page', DOMAIN_ACCESS_FIELD => [$two])); + $assigned = $this->manager->getAccessValues($domain_node3); + $this->assertTrue(isset($assigned[$two]), 'Node3 assigned to proper test domain.'); + $assigned = $this->manager->getAccessValues($domain_node4); + $this->assertTrue(isset($assigned[$two]), 'Node4 assigned to proper test domain.'); + + // Tests 'edit TYPE content on assigned domains'. + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => FALSE, + 'delete' => FALSE, + ), $domain_node3, $domain_user3); + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + ), $domain_node4, $domain_user3); + + // @TODO: Test edit and delete for user with 'all affiliates' permission. + // Tests 'edit domain TYPE content'. + // Assign our user to domain $two. Test on $one and $two. + $domain_user4 = $this->drupalCreateUser(array( + 'access content', + 'update page content on assigned domains', + 'delete page content on assigned domains', + )); + $this->addDomainsToEntity('user', $domain_user4->id(), $two, DOMAIN_ACCESS_FIELD); + $this->addDomainsToEntity('user', $domain_user4->id(), 1, DOMAIN_ACCESS_ALL_FIELD); + $domain_user4 = $this->userStorage->load($domain_user4->id()); + $assigned = $this->manager->getAccessValues($domain_user4); + $this->assertTrue(count($assigned) == 1, 'User assigned to one domain.'); + $this->assertTrue(isset($assigned[$two]), 'User assigned to proper test domain.'); + $this->assertTrue(!empty($domain_user4->get(DOMAIN_ACCESS_ALL_FIELD)->value), 'User assign to all affiliates.'); + + // Assign two different node types to our test domain. + $domain_node5 = $this->drupalCreateNode(array('type' => 'article', DOMAIN_ACCESS_FIELD => [$one])); + $domain_node6 = $this->drupalCreateNode(array('type' => 'page', DOMAIN_ACCESS_FIELD => [$one])); + $assigned = $this->manager->getAccessValues($domain_node5); + $this->assertTrue(isset($assigned[$one]), 'Node5 assigned to proper test domain.'); + $assigned = $this->manager->getAccessValues($domain_node6); + $this->assertTrue(isset($assigned[$one]), 'Node6 assigned to proper test domain.'); + + // Tests 'edit TYPE content on assigned domains'. + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => FALSE, + 'delete' => FALSE, + ), $domain_node5, $domain_user4); + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + ), $domain_node6, $domain_user4); + + } + + public function testDomainAccessCreatePermissions() { + foreach ($this->domains as $domain) { + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + // Tests create permissions. Any content on assigned domains. + $domain_account5 = $this->drupalCreateUser(array('access content', 'create domain content')); + $this->addDomainsToEntity('user', $domain_account5->id(), $two, DOMAIN_ACCESS_FIELD); + $domain_user5 = $this->userStorage->load($domain_account5->id()); + $assigned = $this->manager->getAccessValues($domain_user5); + $this->assertTrue(count($assigned) == 1, 'User assigned to one domain.'); + $this->assertTrue(isset($assigned[$two]), 'User assigned to proper test domain.'); + // This test is domain sensitive. + foreach ($this->domains as $domain) { + $this->domainLogin($domain, $domain_account5); + $url = $domain->getPath() . 'node/add/page'; + $this->drupalGet($url); + if ($domain->id() == $two) { + $this->assertResponse(200); + } + else { + $this->assertResponse(403); + } + // The user should be allowed to create articles. + $url = $domain->getPath() . 'node/add/article'; + $this->drupalGet($url); + if ($domain->id() == $two) { + $this->assertResponse(200); + } + else { + $this->assertResponse(403); + } + } + + } + + public function testDomainAccessLimitedCreatePermissions() { + foreach ($this->domains as $domain) { + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + // Tests create permissions. Any content on assigned domains. + $domain_account6 = $this->drupalCreateUser(array('access content', 'create page content on assigned domains')); + $this->addDomainsToEntity('user', $domain_account6->id(), $two, DOMAIN_ACCESS_FIELD); + $domain_user6 = $this->userStorage->load($domain_account6->id()); + $assigned = $this->manager->getAccessValues($domain_user6); + $this->assertTrue(count($assigned) == 1, 'User assigned to one domain.'); + $this->assertTrue(isset($assigned[$two]), 'User assigned to proper test domain.'); + // This test is domain sensitive. + foreach ($this->domains as $domain) { + $this->domainLogin($domain, $domain_account6); + $url = $domain->getPath() . 'node/add/page'; + $this->drupalGet($url); + if ($domain->id() == $two) { + $this->assertResponse(200); + } + else { + $this->assertResponse(403); + } + // The user should not be allowed to create articles. + $url = $domain->getPath() . 'node/add/article'; + $this->drupalGet($url); + $this->assertResponse(403); + } + } + + + /** + * Asserts that node access correctly grants or denies access. + * + * @param array $ops + * An associative array of the expected node access grants for the node + * and account, with each key as the name of an operation (e.g. 'view', + * 'delete') and each value a Boolean indicating whether access to that + * operation should be granted. + * @param \Drupal\node\Entity\NodeInterface $node + * The node object to check. + * @param \Drupal\Core\Session\AccountInterface $account + * The user account for which to check access. + */ + public function assertNodeAccess(array $ops, NodeInterface $node, AccountInterface $account) { + foreach ($ops as $op => $result) { + $this->assertEqual($result, $this->accessHandler->access($node, $op, $account), 'Expected result returned.'); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessRecordsTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessRecordsTest.php new file mode 100644 index 000000000..77950315f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessRecordsTest.php @@ -0,0 +1,80 @@ +domainCreateTestDomains(5); + // Assign a node to a random domain. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $active_domain = array_rand($domains, 1); + $domain = $domains[$active_domain]; + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); + + // Create an article node. + $node1 = $this->drupalCreateNode(array( + 'type' => 'article', + DOMAIN_ACCESS_FIELD => array($domain->id()), + DOMAIN_ACCESS_ALL_FIELD => 0, + )); + $this->assertTrue($node_storage->load($node1->id()), 'Article node created.'); + + // Check to see if grants added by domain_node_access_records made it in. + $query = 'SELECT realm, gid, grant_view, grant_update, grant_delete FROM {node_access} WHERE nid = :nid'; + $records = Database::getConnection() + ->query($query, array(':nid' => $node1->id())) + ->fetchAll(); + + $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); + $this->assertEqual($records[0]->realm, 'domain_id', 'Grant with domain_id acquired for node.'); + $this->assertEqual($records[0]->gid, $domain->getDomainId(), 'Grant with proper id acquired for node.'); + $this->assertEqual($records[0]->grant_view, 1, 'Grant view stored.'); + $this->assertEqual($records[0]->grant_update, 1, 'Grant update stored.'); + $this->assertEqual($records[0]->grant_delete, 1, 'Grant delete stored.'); + + // Create another article node. + $node2 = $this->drupalCreateNode(array( + 'type' => 'article', + DOMAIN_ACCESS_FIELD => array($domain->id()), + DOMAIN_ACCESS_ALL_FIELD => 1, + )); + $this->assertTrue($node_storage->load($node2->id()), 'Article node created.'); + // Check to see if grants added by domain_node_access_records made it in. + $query = 'SELECT realm, gid, grant_view, grant_update, grant_delete FROM {node_access} WHERE nid = :nid ORDER BY realm'; + $records = Database::getConnection() + ->query($query, array(':nid' => $node2->id())) + ->fetchAll(); + $this->assertEqual(count($records), 2, 'Returned the correct number of rows.'); + $this->assertEqual($records[0]->realm, 'domain_id', 'Grant with domain_id acquired for node.'); + $this->assertEqual($records[0]->gid, $domain->getDomainId(), 'Grant with proper id acquired for node.'); + $this->assertEqual($records[0]->grant_view, 1, 'Grant view stored.'); + $this->assertEqual($records[0]->grant_update, 1, 'Grant update stored.'); + $this->assertEqual($records[0]->grant_delete, 1, 'Grant delete stored.'); + $this->assertEqual($records[1]->realm, 'domain_site', 'Grant with domain_site acquired for node.'); + $this->assertEqual($records[1]->gid, 0, 'Grant with proper id acquired for node.'); + $this->assertEqual($records[1]->grant_view, 1, 'Grant view stored.'); + $this->assertEqual($records[1]->grant_update, 0, 'Grant update stored.'); + $this->assertEqual($records[1]->grant_delete, 0, 'Grant delete stored.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessSaveTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessSaveTest.php new file mode 100644 index 000000000..fd06f9eb8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessSaveTest.php @@ -0,0 +1,76 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainAccessSave() { + $storage = \Drupal::entityTypeManager()->getStorage('node'); + // Save a node programatically. + $node = $storage->create([ + 'type' => 'article', + 'title' => 'Test node', + 'uid' => '1', + 'status' => 1, + DOMAIN_ACCESS_FIELD => ['example_com'], + DOMAIN_ACCESS_ALL_FIELD => 1, + ]); + $node->save(); + + // Load the node. + $node = $storage->load(1); + + // Check that two values are set properly. + $manager = \Drupal::service('domain_access.manager'); + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 1, 'Node saved with one domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 1, 'Node saved to all affiliates.'); + + // Save a node with different values. + $node = $storage->create([ + 'type' => 'article', + 'title' => 'Test node', + 'uid' => '1', + 'status' => 1, + DOMAIN_ACCESS_FIELD => ['example_com', 'one_example_com'], + DOMAIN_ACCESS_ALL_FIELD => 0, + ]); + $node->save(); + + // Load and check the node. + $node = $storage->load(2); + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 2, 'Node saved with two domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 0, 'Node not saved to all affiliates.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/README.md b/sites/all/modules/contrib/admin/domain/domain_alias/README.md new file mode 100644 index 000000000..eea1623b8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/README.md @@ -0,0 +1,182 @@ +About aliases +==== + +Aliases define unique domains or patterns that map to existing domain records. +For example, you may want to have the following setup: + +* example.com (primary domain) +* my.example.com (active domain record) +* myexample.com (alias of the my.example.com record) + +In effect, myexample.com and my.example.com behave the same way from a Drupal +perspective. Aliases may also be made to redirect to their parent domain, if +preferred. + +Alias Patterns +==== + +The Drupal 8 version of Domain Alias supports multi-character wildcards as part +of the hostname. You can register an alias to a domain record in any of the +following patterns. +``` + *.example.com + example.*.com + example.com.* + *.*.example.com + example.*.*.com + example.* + example.*.* + example.*.*.* + *.com + *.example.* + example.com:8080 + example.com:* + example.*:* + *.com:* +``` +A maximum of three wildcards are supported. At least one element must not be a +wildcard. Ports may be wildcards as well, but are optional. + +Alias Matching +==== + +When a request is sent to Drupal, the domain negotiation system will look for a +matching record. The order of precedence is as follows. + +Example request: `one.example.com` + +1. Exact domain record match (`one.example.com`) +1. Exact alias match (`one.example.com`) +1. Wildcard match +``` + one.example.com + one.example.* + *.example.com + one.*.com + *.example.* + *.*.com + one.*.*, +``` +Note that wildcard matching happens _in the listed order_. The number of +wildcards is equal to the number of hostname parts minus 1. That is, you cannot register +an alias that is all wildcards. + +Port Matching +=== + +Port matching (e.g. example.com:8080) works exactly as hostname matching, with +one significant change. If the inbound request is on port 80, matches to the +base hostname are permitted, since port 80 is the default port for HTTP. + +For example, a request to example.com:80 will match the following aliases: +``` + example.com:80 + example.com + example.com:* + example.* + example.*:80 + example.*:* + *.com + *.com:80 + *.com:* +``` +Whereas a request to example.com:8080 will not match the hostname without a port +specified. +``` + example.com:8080 + example.com:* + example.*:8080 + example.*:* + *.com:8080 + *.com:* +``` + +Development Workflow +==== + +Aliases can be used to support development across different environments, with unique +URLs. To support this feature, there is now an `environment` field for each alias. The +default environment list is: + +* default +* local +* development +* staging +* testing + +This list may be overridden by setting the `domain_alias.environments` configuration in +settings.php. + +The operation of these environments is as follows: + +* If alias matching the environment is `default`, no changes occur. +* Else, matching aliases are loaded for all domains, so that links are rewritten to be +specific to the specified environment. (See `domain_alias_domain_load()` for the logic.) + +For instance, consider the following configuration. Your site's canonical domains are: + +* example.com +* foo.example.com +* bar.example.com + +When developing locally, developers use `.local` instead of `.com`. These should be +aliased to each domain as set as the `local` environment. + +* example.local > alias to example.com +* foo.example.local > alias to foo.example.com +* bar.example.local > alias to bar.example.com + +When pushing changes to the cloud, we use a development server. These are tied to a +specific cloud host (dev.mycloud.com). You can alias these to the `development` +environment. + +* example.dev.mycloud.com > alias to example.com +* foo.example.dev.mycloud.com > alias to foo.example.com +* bar.example.dev.mycloud.com > alias to bar.example.com + +The pattern can repeat for each of the environments listed above. The intended use of the +default set of environments is: + +* default -- indicates a canonical URL. No changes will be made. +* local -- for local development environments. +* development -- for a development integration server (such as those provided by Acquia, +Pantheon, and Platform.sh) +* staging -- for a pre-deployment server (such as those provided by Acquia, Panthon, and +Platform.sh) +* testing -- for continuous integration services (such as TravisCI or CircleCI). + +None of these environments are required. You may safely set all aliases to default if +your workflow does not span multiple server environments. + +How does it work? +---- + +This feature works by mapping each alias to an environment. If the active request matches +an alias that is set as an environment other than `default`, then matching environment +aliases are loaded for each domain. If a match is found, the `hostname` value for each +domain is overwritten. + +This overwrite affects the base path and request url that Domain module (and Domain Source) +use for writing links. + +Because the environments are specific to hostnames, this feature will only work if the +site's cache recognizes `url.site` as a required cache context. Without that, the render +system will cache the output of a request incorrectly. + +Configuration +---- + +To use this feature, the following steps must be followed: + +* `url.site` must be added as a required_cache_context to your `services.yml` file. +* Aliases must be mapped to a server environment. Default value is `default`. +* All aliases should be listed as part of `trusted_host_settings` in `settings.php`. + +Technical Notes +==== + +The matching follows an explicit sort order shown in DomainAliasSortTest. + +The code will attempt to match domains of different lengths when doing wildcard +matching in an environment. That is, an alias to `example.*` assigned to `local` should +return `example.local` if the active domain is `one.example.local`, assigned to `local`. diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/config/install/domain_alias.settings.yml b/sites/all/modules/contrib/admin/domain/domain_alias/config/install/domain_alias.settings.yml new file mode 100644 index 000000000..41b02b54b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/config/install/domain_alias.settings.yml @@ -0,0 +1,6 @@ +environments: + - default + - local + - development + - staging + - testing diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/config/schema/domain_alias.schema.yml b/sites/all/modules/contrib/admin/domain/domain_alias/config/schema/domain_alias.schema.yml new file mode 100644 index 000000000..8b0320581 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/config/schema/domain_alias.schema.yml @@ -0,0 +1,34 @@ +# Schema for the configuration files of the Domain Alias module. +domain_alias.settings: + type: config_object + label: 'Domain Alias settings' + mapping: + environments: + type: sequence + label: 'Development environments' + sequence: + type: string + label: 'Environment' + +domain_alias.alias.*: + type: config_entity + label: 'Domain alias' + mapping: + id: + type: string + label: 'ID' + uuid: + type: string + label: 'UUID' + domain_id: + type: string + label: 'Domain ID' + pattern: + type: string + label: 'Pattern' + redirect: + type: integer + label: 'Redirect' + environment: + type: string + label: 'Environment' diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml new file mode 100644 index 000000000..d4d57d043 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml @@ -0,0 +1,14 @@ +name: Domain Alias +description: 'Maps multiple host requests to a single domain record.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.install b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.install new file mode 100644 index 000000000..adb2f66a3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.install @@ -0,0 +1,32 @@ +getEditable('domain_alias.settings'); + // Set and save new message value. + $config->set('environments', ['default', 'local', 'development', 'staging', 'testing'])->save(); +} + +/** + * Updates domain_alias schema. + */ +function domain_alias_update_8001() { + $manager = \Drupal::entityDefinitionUpdateManager(); + // Regenerate entity type indexes. + $manager->updateEntityType($manager->getEntityType('domain_alias')); +} + +/** + * Adds domain_alias environment settings. + */ +function domain_alias_update_8002() { + // Set the default environments variable. + domain_alias_set_environments(); +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.action.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.action.yml new file mode 100644 index 000000000..e8ad0ebce --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.action.yml @@ -0,0 +1,5 @@ +domain_alias_add: + route_name: domain_alias.add + title: 'Add domain alias' + appears_on: + - domain_alias.admin diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.task.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.task.yml new file mode 100644 index 000000000..842ea90c2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.task.yml @@ -0,0 +1,4 @@ +domain_alias.list: + title: 'Aliases' + route_name: domain_alias.admin + tab_root_id: entity.domain.edit_form diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.module b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.module new file mode 100644 index 000000000..569c4ae87 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.module @@ -0,0 +1,168 @@ +getMatchType() == DomainNegotiator::DOMAIN_MATCH_EXACT) { + return; + } + // If no exact match, then run the alias load routine. + $hostname = $domain->getHostname(); + $alias_storage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + /** @var \Drupal\domain_alias\Entity\DomainAlias $alias */ + if ($alias = $alias_storage->loadByHostname($hostname)) { + /** @var \Drupal\domain\Entity\Domain $domain */ + if ($domain = $domain_storage->load($alias->getDomainId())) { + $domain->addProperty('alias', $alias); + $domain->setMatchType(DomainNegotiator::DOMAIN_MATCH_ALIAS); + $redirect = $alias->getRedirect(); + if (!empty($redirect)) { + $domain->setRedirect($redirect); + } + } + else { + // If the domain did not load, report an error. + \Drupal::logger('domain_alias')->error('Found matching alias %alias for host request %hostname, but failed to load matching domain with id %id.', + ['%alias' => $alias->getPattern(), + '%hostname' => $hostname, + '%id' => $alias->getDomainId(), + ]); + } + } +} + +/** + * Implements hook_domain_operations(). + */ +function domain_alias_domain_operations(DomainInterface $domain, AccountInterface $account) { + $operations = []; + // Check permissions. The user must be a super-admin or assigned to the domain. + $is_domain_admin = $domain->access('update', $account); + if ($account->hasPermission('administer domain aliases') || ($is_domain_admin && $account->hasPermission('view domain aliases'))) { + // Add aliases to the list of operations. + $operations['domain_alias'] = array( + 'title' => t('Aliases'), + 'url' => Url::fromRoute('domain_alias.admin', array('domain' => $domain->id())), + 'weight' => 60, + ); + } + return $operations; +} + +/** + * Implements hook_ENTITY_TYPE_load(). + */ +function domain_alias_domain_load($entities) { + static $enabled; + // We can only perform meaningful actions if url.site is a cache context. Otherwise, + // the render process ignores our changes. + if (!isset($enabled)) { + $required_cache_contexts = \Drupal::getContainer()->getParameter('renderer.config')['required_cache_contexts']; + if (!in_array('url.site', $required_cache_contexts) && !in_array('url', $required_cache_contexts)) { + $enabled = FALSE; + return; + } + $enabled = TRUE; + } + + // We cannot run before the negotiator service has fired. + $negotiator = \Drupal::service('domain.negotiator'); + $active = $negotiator->getActiveDomain(); + + // Do nothing if no domain is active. + if (is_null($active)) { + return; + } + + // Load and rewrite environement-specific aliases. + $alias_storage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + if (isset($active->alias) && $active->alias->getEnvironment() != 'default') { + foreach ($entities as $id => $domain) { + if ($environment_aliases = $alias_storage->loadByEnvironmentMatch($domain, $active->alias->getEnvironment())) { + foreach($environment_aliases as $environment_alias) { + $pattern = $environment_alias->getPattern(); + // Add a canonical property. + $domain->setCanonical(); + // Override the domain hostname and path. We always prefer a string match. + if (substr_count($pattern, '*') < 1) { + $domain->setHostname($pattern); + $domain->setPath(); + $domain->setUrl(); + break; + } + else { + // Do a wildcard replacement based on the current request. + $request = $negotiator->negotiateActiveHostname(); + // First, check for a wildcard port. + if (substr_count($pattern, ':*') > 0) { + // Do not replace ports unless they are nonstandard. See + // \Symfony\Component\HttpFoundation\Request\getHttpHost(). + if (substr_count($request, ':') > 0) { + $search = explode(':', $pattern); + $replace = explode(':', $request); + if (!empty($search[1]) && !empty($replace[1])) { + $pattern = str_replace(':' . $search[1], ':' . $replace[1], $pattern); + } + } + // If no port wildcard, then remove the port entirely. + else { + $pattern = str_replace(':*', '', $pattern); + } + } + + $replacements = ['.' => '\.', '*' => '(.+?)']; + $regex = '/^' . strtr($active->alias->getPattern(), $replacements) . '$/'; + if (preg_match($regex, $request, $matches) && isset($matches[1])) { + $pattern = str_replace('*', $matches[1], $pattern); + } + + // Do not let the domain loop back on itself. + if ($pattern != $domain->getCanonical()) { + $domain->setHostname($pattern); + $domain->setPath(); + $domain->setUrl(); + } + } + } + } + } + } +} + +/** + * Implements hook_ENTITY_TYPE_delete(). + */ +function domain_alias_domain_delete(EntityInterface $entity) { + $alias_storage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $properties = ['domain_id' => $entity->id()]; + foreach ($alias_storage->loadByProperties($properties) as $alias) { + $alias->delete(); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.permissions.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.permissions.yml new file mode 100644 index 000000000..88fd57655 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.permissions.yml @@ -0,0 +1,14 @@ +administer domain aliases: + title: 'Administer all domain aliases' + restrict access: true +create domain aliases: + title: 'Create domain aliases for assigned domains' + restrict access: true +edit domain aliases: + title: 'Edit domain aliases for assigned domains' + restrict access: true +delete domain aliases: + title: 'Delete domain aliases for assigned domains' + restrict access: true +view domain aliases: + title: 'View aliases for assigned domains' diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.routing.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.routing.yml new file mode 100644 index 000000000..8ac59e418 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.routing.yml @@ -0,0 +1,34 @@ +domain_alias.admin: + path: '/admin/config/domain/alias/{domain}' + defaults: + _controller: '\Drupal\domain_alias\Controller\DomainAliasController::listing' + _title: 'Domain aliases' + _title_context: 'With modules' + requirements: + _entity_access: domain.update + _permission: 'view domain aliases' + +entity.domain_alias.edit_form: + path: '/admin/config/domain/alias/edit/{domain_alias}' + defaults: + _entity_form: domain_alias.edit + _title: 'Edit domain alias' + requirements: + _entity_access: domain_alias.update + +entity.domain_alias.delete_form: + path: '/admin/config/domain/alias/delete/{domain_alias}' + defaults: + _entity_form: domain_alias.delete + _title: 'Delete domain alias' + requirements: + _entity_access: domain_alias.delete + +domain_alias.add: + path: '/admin/config/domain/alias/{domain}/add' + defaults: + _controller: '\Drupal\domain_alias\Controller\DomainAliasController::addAlias' + _title: 'Add domain alias' + requirements: + _entity_access: domain.update + _permission: 'create domain aliases' diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.services.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.services.yml new file mode 100644 index 000000000..104563a94 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.services.yml @@ -0,0 +1,8 @@ +services: + domain_alias.validator: + class: Drupal\domain_alias\DomainAliasValidator + arguments: ['@config.factory', '@entity_type.manager'] + # @deprecated and will be removed for 8.x-1.0 release. + domain_alias.loader: + class: Drupal\domain_alias\DomainAliasLoader + arguments: ['@config.typed'] diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/Controller/DomainAliasController.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/Controller/DomainAliasController.php new file mode 100644 index 000000000..93d1693a1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/Controller/DomainAliasController.php @@ -0,0 +1,48 @@ +id(); + + // Create the stub alias with reference to the parent domain. + $alias = $this->entityTypeManager()->getStorage('domain_alias')->create($values); + + return $this->entityFormBuilder()->getForm($alias); + } + + /** + * Provides the listing page for aliases. + * + * @param \Drupal\domain\DomainInterface $domain + * An domain record entity. + * + * @return array + * A render array as expected by drupal_render(). + */ + public function listing(DomainInterface $domain) { + $list = $this->entityTypeManager()->getListBuilder('domain_alias'); + $list->setDomain($domain); + return $list->render(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasAccessControlHandler.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasAccessControlHandler.php new file mode 100644 index 000000000..512b3ec2e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasAccessControlHandler.php @@ -0,0 +1,45 @@ +prepareUser($account); + // Check the global permission. + if ($account->hasPermission('administer domain aliases')) { + return AccessResult::allowed(); + } + // For other actions we allow admin if they can administer the parent domains. + $domain = $entity->getDomain(); + // If this account can administer the domain, allow access to actions based on + // permission. + if ($is_admin = $this->isDomainAdmin($domain, $account)) { + if ($operation == 'view' && $account->hasPermission('view domain aliases')) { + return AccessResult::allowed(); + } + if ($operation == 'create' && $account->hasPermission('create domain aliases')) { + return AccessResult::allowed(); + } + if ($operation == 'update' && $account->hasPermission('edit domain aliases')) { + return AccessResult::allowed(); + } + if ($operation == 'delete' && $account->hasPermission('delete domain aliases')) { + return AccessResult::allowed(); + } + } + return AccessResult::forbidden(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasForm.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasForm.php new file mode 100644 index 000000000..b046ab998 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasForm.php @@ -0,0 +1,235 @@ +validator = $validator; + $this->config = $config; + $this->entityTypeManager = $entity_type_manager; + $this->aliasStorage = $alias_storage; + $this->domainStorage = $domain_storage; + // Not loaded directly since it is not an interface. + $this->accessHandler = $this->entityTypeManager->getAccessControlHandler('domain'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('domain_alias.validator'), + $container->get('config.factory'), + $container->get('entity_type.manager'), + $container->get('entity_type.manager')->getStorage('domain_alias'), + $container->get('entity_type.manager')->getStorage('domain') + ); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + /** @var \Drupal\domain_alias\DomainAliasInterface $alias */ + $alias = $this->entity; + + $form['domain_id'] = array( + '#type' => 'value', + '#value' => $alias->getDomainId(), + ); + $form['pattern'] = array( + '#type' => 'textfield', + '#title' => $this->t('Pattern'), + '#size' => 40, + '#maxlength' => 80, + '#default_value' => $alias->getPattern(), + '#description' => $this->t('The matching pattern for this alias.'), + '#required' => TRUE, + ); + $form['id'] = array( + '#type' => 'machine_name', + '#default_value' => $alias->id(), + '#machine_name' => array( + 'source' => array('pattern'), + 'exists' => '\Drupal\domain_alias\Entity\DomainAlias::load', + ), + ); + $form['redirect'] = array( + '#type' => 'select', + '#options' => $this->redirectOptions(), + '#default_value' => $alias->getRedirect(), + '#description' => $this->t('Set an optional redirect directive when this alias is invoked.'), + ); + $environments = $this->environmentOptions(); + $form['environment'] = array( + '#type' => 'select', + '#options' => $environments, + '#default_value' => $alias->getEnvironment(), + '#description' => $this->t('Map the alias to a development environment.'), + ); + $form['environment_help'] = [ + '#type' => 'details', + '#open' => FALSE, + '#collapsed' => TRUE, + '#title' => $this->t('Environment list'), + '#description' => $this->t('The table below shows the registered aliases for each environment.'), + ]; + + $domains = $this->domainStorage->loadMultipleSorted(); + $rows = []; + foreach ($domains as $domain) { + // If the user cannot edit the domain, then don't show in the list. + $access = $this->accessHandler->checkAccess($domain, 'update'); + if ($access->isForbidden()) { + continue; + } + $row = []; + $row[] = $domain->label(); + foreach ($environments as $environment) { + $match_output = []; + if ($environment == 'default') { + $match_output[] = $domain->getCanonical(); + } + $matches = $this->aliasStorage->loadByEnvironmentMatch($domain, $environment); + foreach ($matches as $match) { + $match_output[] = $match->getPattern(); + } + $output = [ + '#items' => $match_output, + '#theme' => 'item_list', + ]; + $row[] = \Drupal::service('renderer')->render($output); + } + $rows[] = $row; + } + + $form['environment_help']['table'] = [ + '#type' => 'table', + '#header' => array_merge([$this->t('Domain')], $environments), + '#rows' => $rows, + ]; + + return parent::form($form, $form_state); + } + + /** + * Returns a list of valid redirect options for the form. + * + * @return array + * A list of valid redirect options. + */ + public function redirectOptions() { + return array( + 0 => $this->t('Do not redirect'), + 301 => $this->t('301 redirect: Moved Permanently'), + 302 => $this->t('302 redirect: Found'), + ); + } + + /** + * Returns a list of valid environement options for the form. + * + * @return array + * A list of valid environment options. + */ + public function environmentOptions() { + $list = $this->config->get('domain_alias.settings')->get('environments'); + foreach ($list as $item) { + $environments[$item] = $item; + } + return $environments; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $errors = $this->validator->validate($this->entity); + if (!empty($errors)) { + $form_state->setErrorByName('pattern', $errors); + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /** @var \Drupal\domain_alias\DomainAliasInterface $alias */ + $alias = $this->entity; + $edit_link = $alias->toLink($this->t('Edit'), 'edit-form')->toString(); + if ($alias->save() == SAVED_NEW) { + drupal_set_message($this->t('Created new domain alias.')); + $this->logger('domain_alias')->notice('Created new domain alias %name.', array('%name' => $alias->label(), 'link' => $edit_link)); + } + else { + drupal_set_message($this->t('Updated domain alias.')); + $this->logger('domain_alias')->notice('Updated domain alias %name.', array('%name' => $alias->label(), 'link' => $edit_link)); + } + $form_state->setRedirect('domain_alias.admin', array('domain' => $alias->getDomainId())); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasInterface.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasInterface.php new file mode 100644 index 000000000..b4f341ab8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasInterface.php @@ -0,0 +1,44 @@ +t('Pattern'); + $header['redirect'] = $this->t('Redirect'); + $header['environment'] = $this->t('Environment'); + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + $row = array(); + + $row['label'] = $entity->label(); + $redirect = $entity->getRedirect(); + $row['redirect'] = empty($redirect) ? $this->t('None') : $redirect; + $row['environment'] = $entity->getEnvironment(); + $row += parent::buildRow($entity); + + return $row; + } + + /** + * {@inheritdoc} + */ + public function render() { + $build = array( + '#theme' => 'table', + '#header' => $this->buildHeader(), + '#rows' => array(), + '#empty' => $this->t('No aliases have been created for this domain.'), + ); + foreach ($this->load() as $entity) { + if ($row = $this->buildRow($entity)) { + $build['#rows'][$entity->id()] = $row; + } + } + return $build; + } + + /** + * Loads entity IDs using a pager sorted by the entity id. + * + * @return array + * An array of entity IDs. + */ + protected function getEntityIds() { + $query = $this->getStorage()->getQuery() + ->condition('domain_id', $this->getDomainId()) + ->sort($this->entityType->getKey('id')); + + // Only add the pager if a limit is specified. + if ($this->limit) { + $query->pager($this->limit); + } + return $query->execute(); + } + + /** + * Sets the domain context for this list. + * + * @param \Drupal\domain\DomainInterface $domain + * The domain to set as context for the list. + */ + public function setDomain(DomainInterface $domain) { + $this->domain = $domain; + } + + /** + * Gets the domain context for this list. + * + * @return \Drupal\domain\DomainInterface $domain + * The domain that is context for this list. + */ + public function getDomainId() { + // @TODO: check for a use-case where we might need to derive the id? + return !empty($this->domain) ? $this->domain->id() : NULL; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoader.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoader.php new file mode 100644 index 000000000..c9e3e0d78 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoader.php @@ -0,0 +1,258 @@ +typedConfig = $typed_config; + } + + /** + * {@inheritdoc} + */ + public function loadSchema() { + $fields = $this->typedConfig->getDefinition('domain_alias.alias.*'); + return isset($fields['mapping']) ? $fields['mapping'] : array(); + } + + /** + * {@inheritdoc} + */ + public function load($id, $reset = FALSE) { + $controller = $this->getStorage(); + if ($reset) { + $controller->resetCache(array($id)); + } + return $controller->load($id); + } + + /** + * {@inheritdoc} + */ + public function loadMultiple($ids = NULL, $reset = FALSE) { + $controller = $this->getStorage(); + if ($reset) { + $controller->resetCache($ids); + } + return $controller->loadMultiple($ids); + } + + /** + * {@inheritdoc} + */ + public function loadByHostname($hostname) { + $patterns = $this->getPatterns($hostname); + foreach ($patterns as $pattern) { + if ($alias = $this->loadByPattern($pattern)) { + return $alias; + } + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function loadByPattern($pattern) { + $result = $this->getStorage()->loadByProperties(['pattern' => $pattern]); + if (empty($result)) { + return NULL; + } + return current($result); + } + + /** + * {@inheritdoc} + */ + public function loadByEnvironment($environment) { + $result = $this->getStorage()->loadByProperties(['environment' => $environment]); + if (empty($result)) { + return NULL; + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function loadByEnvironmentMatch(DomainInterface $domain, $environment) { + $result = $this->getStorage()->loadByProperties(['domain_id' => $domain->id(), 'environment' => $environment]); + if (empty($result)) { + return []; + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function sort($a, $b) { + // Fewer wildcards is a closer match. + // A longer string indicates a closer match. + if ((substr_count($a, '*') > substr_count($b, '*')) || (strlen($a) < strlen($b))) { + return 1; + } + return 0; + } + + /** + * Returns an array of eligible matching patterns. + * + * @param string $hostname + * A hostname string, in the format example.com. + * + * @return array + */ + public function getPatterns($hostname) { + $parts = explode('.', $hostname); + $count = count($parts); + // Account for ports. + if (substr_count($hostname, ':') > 0) { + $ports = explode(':', $parts[$count - 1]); + $parts[$count - 1] = preg_replace('/:(\d+)/', '', $parts[$count - 1]); + $parts[] = $ports[1]; + } + // Build the list of possible matching patterns. + $patterns = $this->buildPatterns($parts); + // Pattern lists are sorted based on the fewest wildcards. That gives us + // more precise matches first. + uasort($patterns, array($this, 'sort')); + array_unshift($patterns, $hostname); + + // Account for ports. + if (isset($ports)) { + $patterns = $this->buildPortPatterns($patterns, $hostname); + } + + // Return unique patters. + return array_unique($patterns); + } + + /** + * Builds a list of matching patterns. + * + * @param array $parts + * The hostname of the request, as an array split by dots. + * + * @return array $patterns + * An array of eligible matching patterns. + */ + private function buildPatterns(array $parts) { + $count = count($parts); + for ($i = 0; $i < $count; $i++) { + // Basic replacement of each value. + $temp = $parts; + $temp[$i] = '*'; + $patterns[] = implode('.', $temp); + // Advanced multi-value wildcards. + // Pattern *.* + if (count($temp) > 2 && $i < ($count - 1)) { + $temp[$i + 1] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern foo.bar.* + if ($count > 3 && $i < ($count - 2)) { + $temp[$i + 2] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern *.foo.*. + if ($count > 3 && $i < 2) { + $temp = $parts; + $temp[$i] = '*'; + $temp[$i + 2] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern *.foo.*.* + if ($count > 2) { + $temp = array_fill(0, $count, '*'); + $temp[$i] = $parts[$i]; + $patterns[] = implode('.', $temp); + } + } + return $patterns; + } + + /** + * Builds a list of matching patterns, including ports. + * + * @param array $patterns + * An array of eligible matching patterns. + * @param string $hostname + * A hostname string, in the format example.com. + * + * @return array $patterns + * An array of eligible matching patterns, modified by port. + */ + private function buildPortPatterns(array $patterns, $hostname) { + foreach ($patterns as $index => $pattern) { + // Make a pattern for port wildcards. + if (substr_count($pattern, ':') < 1) { + $new = explode('.', $pattern); + $port = (int) array_pop($new); + $allow = FALSE; + // Do not allow *.* or *:*. + foreach ($new as $item) { + if ($item != '*') { + $allow = TRUE; + } + } + if ($allow) { + // For port 80, allow bare hostname matches. + if ($port == 80) { + // Base hostname with port. + $patterns[] = str_replace(':' . $port, '', $hostname); + // Base hostname is allowed. + $patterns[] = implode('.', $new); + } + // Base hostname with wildcard port. + $patterns[] = str_replace(':' . $port, ':*', $hostname); + // Pattern with exact port. + $patterns[] = implode('.', $new) . ':' . $port; + // Pattern with wildcard port. + $patterns[] = implode('.', $new) . ':*'; + } + unset($patterns[$index]); + } + } + return $patterns; + } + + /** + * Loads the storage controller. + * + * We use the loader very early in the request cycle. As a result, if we try + * to inject the storage container, we hit a circular dependency. Using this + * method at least keeps our code easier to update. + */ + protected function getStorage() { + $storage = \Drupal::entityTypeManager()->getStorage('domain_alias'); + return $storage; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoaderInterface.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoaderInterface.php new file mode 100644 index 000000000..42ebb2c6e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoaderInterface.php @@ -0,0 +1,104 @@ +configFactory = $config_factory; + $this->uuidService = $uuid_service; + $this->languageManager = $language_manager; + $this->typedConfig = $typed_config; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('config.factory'), + $container->get('uuid'), + $container->get('language_manager'), + $container->get('config.typed') + ); + } + + /** + * {@inheritdoc} + */ + public function loadSchema() { + $fields = $this->typedConfig->getDefinition('domain_alias.alias.*'); + return isset($fields['mapping']) ? $fields['mapping'] : array(); + } + + /** + * {@inheritdoc} + */ + public function loadByHostname($hostname) { + $patterns = $this->getPatterns($hostname); + foreach ($patterns as $pattern) { + if ($alias = $this->loadByPattern($pattern)) { + return $alias; + } + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function loadByPattern($pattern) { + $result = $this->loadByProperties(['pattern' => $pattern]); + if (empty($result)) { + return NULL; + } + return current($result); + } + + /** + * {@inheritdoc} + */ + public function loadByEnvironment($environment) { + $result = $this->loadByProperties(['environment' => $environment]); + if (empty($result)) { + return NULL; + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function loadByEnvironmentMatch(DomainInterface $domain, $environment) { + $result = $this->loadByProperties(['domain_id' => $domain->id(), 'environment' => $environment]); + if (empty($result)) { + return []; + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function sort($a, $b) { + // Fewer wildcards is a closer match. + // A longer string indicates a closer match. + if ((substr_count($a, '*') > substr_count($b, '*')) || (strlen($a) < strlen($b))) { + return 1; + } + return 0; + } + + /** + * Returns an array of eligible matching patterns. + * + * @param string $hostname + * A hostname string, in the format example.com. + * + * @return array + */ + public function getPatterns($hostname) { + $parts = explode('.', $hostname); + $count = count($parts); + // Account for ports. + if (substr_count($hostname, ':') > 0) { + $ports = explode(':', $parts[$count - 1]); + $parts[$count - 1] = preg_replace('/:(\d+)/', '', $parts[$count - 1]); + $parts[] = $ports[1]; + } + // Build the list of possible matching patterns. + $patterns = $this->buildPatterns($parts); + // Pattern lists are sorted based on the fewest wildcards. That gives us + // more precise matches first. + uasort($patterns, array($this, 'sort')); + array_unshift($patterns, $hostname); + + // Account for ports. + if (isset($ports)) { + $patterns = $this->buildPortPatterns($patterns, $hostname); + } + + // Return unique patters. + return array_unique($patterns); + } + + /** + * Builds a list of matching patterns. + * + * @param array $parts + * The hostname of the request, as an array split by dots. + * + * @return array $patterns + * An array of eligible matching patterns. + */ + private function buildPatterns(array $parts) { + $count = count($parts); + for ($i = 0; $i < $count; $i++) { + // Basic replacement of each value. + $temp = $parts; + $temp[$i] = '*'; + $patterns[] = implode('.', $temp); + // Advanced multi-value wildcards. + // Pattern *.* + if (count($temp) > 2 && $i < ($count - 1)) { + $temp[$i + 1] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern foo.bar.* + if ($count > 3 && $i < ($count - 2)) { + $temp[$i + 2] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern *.foo.*. + if ($count > 3 && $i < 2) { + $temp = $parts; + $temp[$i] = '*'; + $temp[$i + 2] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern *.foo.*.* + if ($count > 2) { + $temp = array_fill(0, $count, '*'); + $temp[$i] = $parts[$i]; + $patterns[] = implode('.', $temp); + } + } + return $patterns; + } + + /** + * Builds a list of matching patterns, including ports. + * + * @param array $patterns + * An array of eligible matching patterns. + * @param string $hostname + * A hostname string, in the format example.com. + * + * @return array $patterns + * An array of eligible matching patterns, modified by port. + */ + private function buildPortPatterns(array $patterns, $hostname) { + foreach ($patterns as $index => $pattern) { + // Make a pattern for port wildcards. + if (substr_count($pattern, ':') < 1) { + $new = explode('.', $pattern); + $port = (int) array_pop($new); + $allow = FALSE; + // Do not allow *.* or *:*. + foreach ($new as $item) { + if ($item != '*') { + $allow = TRUE; + } + } + if ($allow) { + // For port 80, allow bare hostname matches. + if ($port == 80) { + // Base hostname with port. + $patterns[] = str_replace(':' . $port, '', $hostname); + // Base hostname is allowed. + $patterns[] = implode('.', $new); + } + // Base hostname with wildcard port. + $patterns[] = str_replace(':' . $port, ':*', $hostname); + // Pattern with exact port. + $patterns[] = implode('.', $new) . ':' . $port; + // Pattern with wildcard port. + $patterns[] = implode('.', $new) . ':*'; + } + unset($patterns[$index]); + } + } + return $patterns; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasStorageInterface.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasStorageInterface.php new file mode 100644 index 000000000..8d6c69330 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasStorageInterface.php @@ -0,0 +1,76 @@ +configFactory = $config_factory; + $this->entityTypeManager = $entity_type_manager; + $this->aliasStorage = $this->entityTypeManager->getStorage('domain_alias'); + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + } + + /** + * Validates the rules for a domain alias. + * + * @param \Drupal\domain_alias\DomainAliasInterface $alias + * The Domain Alias to validate. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup | NULL + * A validation error message, if any. + */ + public function validate(DomainAliasInterface $alias) { + $pattern = $alias->getPattern(); + + // 1) Check for at least one dot or the use of 'localhost'. + // Note that localhost can specify a port. + $localhost_check = explode(':', $pattern); + if (substr_count($pattern, '.') == 0 && $localhost_check[0] != 'localhost') { + return $this->t('At least one dot (.) is required, except when using localhost.'); + } + + // 2) Check that the alias only has one wildcard. + $count = substr_count($pattern, '*') + substr_count($pattern, '?'); + if ($count > 1) { + return $this->t('You may only have one wildcard character in each alias.'); + } + // 3) Only one colon allowed, and it must be followed by an integer. + $count = substr_count($pattern, ':'); + if ($count > 1) { + return $this->t('You may only have one colon ":" character in each alias.'); + } + elseif ($count == 1) { + $int = substr($pattern, strpos($pattern, ':') + 1); + if (!is_numeric($int)) { + return $this->t('A colon may only be followed by an integer indicating the proper port.'); + } + } + // 4) Check that the alias doesn't contain any invalid characters. + // Check for valid characters, unless using non-ASCII domains. + $non_ascii = $this->configFactory->get('domain.settings')->get('allow_non_ascii'); + if (!$non_ascii) { + $check = preg_match('/^[a-z0-9\.\+\-\*\?:]*$/', $pattern); + if ($check == 0) { + return $this->t('The pattern contains invalid characters.'); + } + } + // 5) The alias cannot begin or end with a period. + if (substr($pattern, 0, 1) == '.') { + return $this->t('The pattern cannot begin with a dot.'); + } + if (substr($pattern, -1) == '.') { + return $this->t('The pattern cannot end with a dot.'); + } + + // 6) Check that the alias is not a direct match for a registered domain. + $check = preg_match('/[a-z0-9\.\+\-:]*$/', $pattern); + if ($check == 1 && $this->domainStorage->loadByHostname($pattern)) { + return $this->t('The pattern matches an existing domain record.'); + } + // 7) Check that the alias is unique across all records. + if ($alias_check = $this->aliasStorage->loadByPattern($pattern)) { + /** @var \Drupal\domain_alias\DomainAliasInterface $alias_check */ + if ($alias_check->id() != $alias->id()) { + return $this->t('The pattern already exists.'); + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasValidatorInterface.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasValidatorInterface.php new file mode 100644 index 000000000..2fbfcd1f4 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasValidatorInterface.php @@ -0,0 +1,21 @@ +pattern; + } + + /** + * {@inheritdoc} + */ + public function getEnvironment() { + return !empty($this->environment) ? $this->environment : 'default'; + } + + /** + * {@inheritdoc} + */ + public function getDomainId() { + return $this->domain_id; + } + + /** + * {@inheritdoc} + */ + public function getDomain() { + $storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $domains = $storage->loadByProperties(['domain_id' => $this->domain_id]); + return $domains ? current($domains) : NULL; + } + + /** + * {@inheritdoc} + */ + public function getRedirect() { + return $this->redirect; + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + // Invalidate cache tags relevant to domains. + \Drupal::service('cache_tags.invalidator')->invalidateTags(['rendered', 'url.site']); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/Form/DomainAliasDeleteForm.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/Form/DomainAliasDeleteForm.php new file mode 100644 index 000000000..cd8cc6220 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/Form/DomainAliasDeleteForm.php @@ -0,0 +1,22 @@ + $this->entity->getDomainId(), + ]); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/Tests/DomainAliasTestBase.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/Tests/DomainAliasTestBase.php new file mode 100644 index 000000000..c957cc4d0 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/Tests/DomainAliasTestBase.php @@ -0,0 +1,33 @@ +admin_user = $this->drupalCreateUser(array('administer domains', 'access administration pages')); + $this->drupalLogin($this->admin_user); + + // Create test domains. + $this->domainCreateTestDomains(3); + + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $alias_loader = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $domains = $domain_storage->loadMultiple(); + + // Save these for later testing. + $original_domains = $domains; + + $base = $this->base_hostname; + $hostnames = [$base, 'one.' . $base, 'two.'. $base]; + + // Our patterns should map to example.com, one.example.com, two.example.com. + $patterns = ['*.' . $base, 'four.' . $base, 'five.' . $base]; + $i = 0; + foreach ($domains as $domain) { + $this->assert($domain->getHostname() == $hostnames[$i], 'Hostnames set correctly'); + $this->assert($domain->getCanonical() == $hostnames[$i], 'Canonical domains set correctly'); + $values = [ + 'domain_id' => $domain->id(), + 'pattern' => array_shift($patterns), + 'redirect' => 0, + 'environment' => 'local', + ]; + $this->createDomainAlias($values); + $i++; + } + + $path = $domain->getScheme() . 'five.' . $base . '/admin/config/domain'; + + // Visit the domain overview administration page. + $this->drupalGet($path); + $this->assertResponse(200); + + // Test the domains. + $domains = $domain_storage->loadMultiple(); + $this->assertTrue(count($domains) == 3, 'Three domain records found.'); + + // Check the default domain. + $default = $domain_storage->loadDefaultId(); + $key = 'example_com'; + $this->assertTrue($default == $key, 'Default domain set correctly.'); + + // Test some text on the page. + foreach ($domains as $domain) { + $name = $domain->label(); + $this->assertText($name, 'Name found properly.'); + } + // Test the list of actions. + $actions = array('delete', 'disable', 'default'); + foreach ($actions as $action) { + $this->assertRaw("/domain/{$action}/", 'Actions found properly.'); + } + // Check that all domains are active. + $this->assertNoRaw('Inactive', 'Inactive domain not found.'); + + // Disable a domain and test the enable link. + $this->clickLink('Disable', 0); + $this->assertRaw('Inactive', 'Inactive domain found.'); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + foreach ($domain_storage->loadMultiple() as $domain) { + if ($domain->id() == 'one_example_com') { + $this->assertEmpty($domain->status(), 'One domain inactive.'); + } + else { + $this->assertNotEmpty($domain->status(), 'Other domains active.'); + } + } + + // Test the list of actions. + $actions = array('enable', 'delete', 'disable', 'default'); + foreach ($actions as $action) { + $this->assertRaw("/domain/{$action}/", 'Actions found properly.'); + } + // Re-enable the domain. + $this->clickLink('Enable', 0); + $this->assertNoRaw('Inactive', 'Inactive domain not found.'); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + foreach ($domain_storage->loadMultiple() as $domain) { + $this->assertNotEmpty($domain->status(), 'All domains active.'); + } + + // Set a new default domain. + $this->clickLink('Make default', 0); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + // Check the default domain. + $domain_storage->resetCache(); + $default = $domain_storage->loadDefaultId(); + $key = 'one_example_com'; + $this->assertTrue($default == $key, 'Default domain set correctly.'); + + // Did the hostnames change accidentally? + foreach ($domain_storage->loadMultiple() as $id => $domain) { + $this->assertTrue($domain->getHostname() == $original_domains[$id]->getHostname(), 'Hostnames match.'); + } + + } + +} + diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasEnvironmentTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasEnvironmentTest.php new file mode 100644 index 000000000..bf25f66f8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasEnvironmentTest.php @@ -0,0 +1,95 @@ +domainCreateTestDomains(3); + } + + /** + * Test for environment matching. + */ + public function testDomainAliasEnvironments() { + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $alias_loader = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $domains = $domain_storage->loadMultipleSorted(NULL, TRUE); + // Our patterns should map to example.com, one.example.com, two.example.com. + $patterns = ['*.example.com', 'four.example.com', 'five.example.com']; + foreach ($domains as $domain) { + $values = [ + 'domain_id' => $domain->id(), + 'pattern' => array_shift($patterns), + 'redirect' => 0, + 'environment' => 'local', + ]; + $this->createDomainAlias($values); + } + // Test the environment loader. + $local = $alias_loader->loadByEnvironment('local'); + $this->assert(count($local) == 3, 'Three aliases set to local'); + // Test the environment matcher. $domain here is two.example.com + $match = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($match) == 1, 'One environment match loaded'); + $alias = current($match); + $this->assert($alias->getPattern() == 'five.example.com', 'Proper pattern match loaded.'); + + // Set one alias to a different environment + $alias->set('environment', 'testing')->save(); + $local = $alias_loader->loadByEnvironment('local'); + $this->assert(count($local) == 2, 'Two aliases set to local'); + // Test the environment matcher. $domain here is two.example.com + $matches = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($matches) == 0, 'No environment matches loaded'); + + // Test the environment matcher. $domain here is one.example.com + $domain = $domain_storage->load('one_example_com'); + $matches = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($matches) == 1, 'One environment match loaded'); + $alias = current($matches); + $this->assert($alias->getPattern() == 'four.example.com', 'Proper pattern match loaded.'); + + // Now load a page and check things. + // Since we cannot read the service request, we place a block + // which shows links to all domains. + $this->drupalPlaceBlock('domain_switcher_block'); + + // To get around block access, let the anon user view the block. + user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('administer domains')); + // For a non-aliased request, the url list should be normal. + $this->drupalGet($domain->getPath()); + foreach ($domains as $domain) { + $this->assertSession()->assertEscaped($domain->getHostname()); + $this->assertSession()->linkByHrefExists($domain->getPath(), 0, 'Link found: ' . $domain->getPath()); + } + // For an aliased request (four.example.com), the list should be aliased. + $url = $domain->getScheme() . $alias->getPattern() . $domain->getPort(); + $this->drupalGet($url); + foreach ($matches as $match) { + $this->assertSession()->assertEscaped($match->getPattern()); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListBuilderTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListBuilderTest.php new file mode 100644 index 000000000..c75929296 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListBuilderTest.php @@ -0,0 +1,103 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainListBuilder() { + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'administer domain aliases', + 'view domain aliases', + )); + $this->drupalLogin($admin); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Check that links are printed. + foreach ($this->getDomains() as $domain) { + $href = 'admin/config/domain/alias/' . $domain->id(); + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + } + + // Now login as a user with limited rights. + $account = $this->drupalCreateUser(array( + 'create article content', + 'edit any article content', + 'edit assigned domains', + 'view domain list', + 'view domain aliases', + 'edit domain aliases', + )); + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account->id(), $ids, DOMAIN_ADMIN_FIELD); + $user_storage = \Drupal::entityTypeManager()->getStorage('user'); + $user = $user_storage->load($account->id()); + $manager = \Drupal::service('domain.element_manager'); + $values = $manager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + + $this->drupalLogin($account); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Check that links are printed. + $path = 'admin/config/domain/'; + $this->drupalGet($path); + foreach ($this->getDomains() as $domain) { + $href = 'admin/config/domain/alias/' . $domain->id(); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + } + else { + $this->assertSession()->linkByHrefNotExists($href, 'Link not found'); + } + } + + // Check access to the pages/routes. + foreach ($this->getDomains() as $domain) { + $path = 'admin/config/domain/alias/' . $domain->id(); + $this->drupalGet($path); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->statusCodeEquals(200); + } + else { + $this->assertSession()->statusCodeEquals(403); + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListHostnameTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListHostnameTest.php new file mode 100644 index 000000000..e1582ff54 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListHostnameTest.php @@ -0,0 +1,85 @@ +domainCreateTestDomains(3); + } + + /** + * Test for environment matching. + */ + public function testDomainAliasEnvironments() { + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $alias_loader = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $domains = $domain_storage->loadMultiple(); + + $base = $this->base_hostname; + $hostnames = [$base, 'one.' . $base, 'two.'. $base]; + + // Our patterns should map to example.com, one.example.com, two.example.com. + $patterns = ['*.' . $base, 'four.' . $base, 'five.' . $base]; + $i = 0; + foreach ($domains as $domain) { + $this->assert($domain->getHostname() == $hostnames[$i], 'Hostnames set correctly'); + $this->assert($domain->getCanonical() == $hostnames[$i], 'Canonical domains set correctly'); + $values = [ + 'domain_id' => $domain->id(), + 'pattern' => array_shift($patterns), + 'redirect' => 0, + 'environment' => 'local', + ]; + $this->createDomainAlias($values); + $i++; + } + // Test the environment loader. + $local = $alias_loader->loadByEnvironment('local'); + $this->assert(count($local) == 3, 'Three aliases set to local'); + // Test the environment matcher. $domain here is two.example.com + $match = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($match) == 1, 'One environment match loaded'); + $alias = current($match); + $this->assert($alias->getPattern() == 'five.' . $base, 'Proper pattern match loaded.'); + + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($admin); + + // Load an aliased domain. + $this->drupalGet($domain->getScheme() . 'five.' . $base . '/admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Save the form. + $this->pressButton('edit-submit'); + // Ensure the values haven't changed. + $i = 0; + $domains = $domain_storage->loadMultiple(); + foreach ($domains as $domain) { + $this->assert($domain->getHostname() == $hostnames[$i], 'Hostnames set correctly'); + $this->assert($domain->getCanonical() == $hostnames[$i], 'Canonical domains set correctly'); + $i++; + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasNegotiatorTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasNegotiatorTest.php new file mode 100644 index 000000000..1b3e924c2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasNegotiatorTest.php @@ -0,0 +1,111 @@ +domainTableIsEmpty(); + + // Create two new domains programmatically. + $this->domainCreateTestDomains(2); + + // Since we cannot read the service request, we place a block + // which shows the current domain information. + $this->drupalPlaceBlock('domain_server_block'); + + // To get around block access, let the anon user view the block. + user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('administer domains')); + + // Set the storage handles. + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $alias_storage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + + // Set known prefixes that work with our tests. This will give us domains + // 'example.com' and 'one.example.com' aliased to 'two.example.com' and + // 'three.example.com'. + $prefixes = ['two', 'three']; + // Test the response of each home page. + /** @var \Drupal\domain\Entity\Domain $domain */ + foreach ($domain_storage->loadMultiple() as $domain) { + $alias_domains[] = $domain; + $this->drupalGet($domain->getPath()); + $this->assertRaw($domain->label(), 'Loaded the proper domain.'); + $this->assertRaw('Exact match', 'Direct domain match.'); + } + + // Now, test an alias for each domain. + foreach ($alias_domains as $index => $alias_domain) { + $prefix = $prefixes[$index]; + // Set a known pattern. + $pattern = $prefix . '.' . $this->base_hostname; + $this->domainAliasCreateTestAlias($alias_domain, $pattern); + $alias = $alias_storage->loadByPattern($pattern); + // Set the URL for the request. Note that this is not saved, it is just + // URL generation. + $alias_domain->set('hostname', $pattern); + $alias_domain->setPath(); + $url = $alias_domain->getPath(); + $this->drupalGet($url); + $this->assertRaw($alias_domain->label(), 'Loaded the proper domain.'); + $this->assertRaw('ALIAS:', 'No direct domain match.'); + $this->assertRaw($alias->getPattern(), 'Alias match.'); + + // Test redirections. + // @TODO: This could be much more elegant: the redirects break assertRaw() + $alias->set('redirect', 301); + $alias->save(); + $this->drupalGet($url); + $alias->set('redirect', 302); + $alias->save(); + $this->drupalGet($url); + } + // Test a wildcard alias. + // @TODO: Refactor this test to merge with the above. + $alias_domain = $domain_storage->loadDefaultDomain(); + $pattern = '*.' . $this->base_hostname; + $this->domainAliasCreateTestAlias($alias_domain, $pattern); + $alias = $alias_storage->loadByPattern($pattern); + // Set the URL for the request. Note that this is not saved, it is just + // URL generation. + $alias_domain->set('hostname', 'four.' . $this->base_hostname); + $alias_domain->setPath(); + $url = $alias_domain->getPath(); + $this->drupalGet($url); + $this->assertRaw($alias_domain->label(), 'Loaded the proper domain.'); + $this->assertRaw('ALIAS:', 'No direct domain match.'); + $this->assertRaw($alias->getPattern(), 'Alias match.'); + + // Test redirections. + // @TODO: This could be much more elegant: the redirects break assertRaw() + $alias->set('redirect', 301); + $alias->save(); + $this->drupalGet($url); + $alias->set('redirect', 302); + $alias->save(); + $this->drupalGet($url); + + // Revoke the permission change. + user_role_revoke_permissions(RoleInterface::ANONYMOUS_ID, array('administer domains')); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasSortTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasSortTest.php new file mode 100644 index 000000000..f517fdd73 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasSortTest.php @@ -0,0 +1,86 @@ +sortList(); + + $storage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + foreach ($list as $key => $values) { + $patterns = $storage->getPatterns($key); + $this->assertTrue(empty(array_diff($values, $patterns)), 'Pattern matched as expected for ' . $key); + } + + } + + /** + * An array of expected matches to specific domains. + */ + private function sortList() { + return array( + 'example.com' => [ + 'example.com', + 'example.*', + '*.com', + ], + 'one.example.com' => [ + 'one.example.com', + 'one.example.*', + '*.example.com', + 'one.*.com', + '*.example.*', + '*.*.com', + 'one.*.*', + ], + 'one.two.example.com' => [ + 'one.two.example.com', + '*.two.example.com', + 'one.two.example.*', + 'one.*.example.com', + '*.*.example.com', + 'one.two.*.com', + 'one.*.example.*', + 'one.*.*.com', + '*.two.*.com', + 'one.two.*.*', + '*.*.example.com', + '*.*.*.com', + 'one.*.*.*', + '*.two.*.*', + ], + 'example.com:80' => [ + 'example.com:80', + 'example.com', + 'example.*', + 'example.com:*', + 'example.*:80', + 'example.*:*', + '*.com', + '*.com:80', + '*.com:*', + ], + 'example.com:8080' => [ + 'example.com:8080', + 'example.com:*', + 'example.*:8080', + 'example.*:*', + '*.com:8080', + '*.com:*', + ], + ); + } + +} + diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasTestBase.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasTestBase.php new file mode 100644 index 000000000..2057cc88e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasTestBase.php @@ -0,0 +1,29 @@ +domainTableIsEmpty(); + $validator = \Drupal::service('domain_alias.validator'); + + // Create a domain. + $this->domainCreateTestDomains(1, 'foo.com'); + // Check the created domain based on it's known id value. + $key = 'foo.com'; + /** @var \Drupal\domain\Entity\Domain $domain */ + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->loadByHostname($key); + $this->assertTrue(!empty($domain), 'Test domain created.'); + + // Valid patterns to test. Valid is the boolean value. + $patterns = [ + 'localhost' => 1, + 'example.com' => 1, + 'www.example.com' => 1, // see www-prefix test, below. + '*.example.com' => 1, + 'one.example.com' => 1, + 'example.com:8080' => 1, + 'foobar' => 0, // must have a dot or be localhost + '*.*.example.com' => 0, // only one wildcard. + 'example.com::8080' => 0, // only one colon. + 'example.com:abc' => 0, // no letters after a colon. + '.example.com' => 0, // cannot begin with a dot. + 'example.com.' => 0, // cannot end with a dot. + 'EXAMPLE.com' => 0, // lowercase only. + 'éxample.com' => 0, // ascii-only. + 'foo.com' => 0, // duplicate. + ]; + foreach ($patterns as $pattern => $valid) { + $alias = $this->domainAliasCreateTestAlias($domain, $pattern, 0, 'default', FALSE); + $errors = $validator->validate($alias); + if ($valid) { + $this->assertTrue(empty($errors), 'Validation test success.'); + } + else { + $this->assertTrue(!empty($errors), 'Validation test success.'); + } + } + // Test the configurable option. + $config = $this->config('domain.settings'); + $config->set('allow_non_ascii', true)->save(); + // Valid hostnames to test. Valid is the boolean value. + $patterns = [ + 'éxample.com' => 1, // ascii-only allowed. + ]; + foreach ($patterns as $pattern => $valid) { + $alias = $this->domainAliasCreateTestAlias($domain, $pattern, 0, 'default', FALSE); + $errors = $validator->validate($alias); + if ($valid) { + $this->assertTrue(empty($errors), 'Validation test success.'); + } + else { + $this->assertTrue(!empty($errors), 'Validation test success.'); + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasWildcardTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasWildcardTest.php new file mode 100644 index 000000000..12dc5e88b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasWildcardTest.php @@ -0,0 +1,87 @@ +domainCreateTestDomains(3); + } + + /** + * Test for environment matching. + */ + public function testDomainAliasWildcards() { + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $alias_loader = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $domains = $domain_storage->loadMultipleSorted(NULL, TRUE); + // Our patterns should map to example.com, one.example.com, two.example.com. + $patterns = ['example.*', 'four.example.*', 'five.example.*']; + foreach ($domains as $domain) { + $values = [ + 'domain_id' => $domain->id(), + 'pattern' => array_shift($patterns), + 'redirect' => 0, + 'environment' => 'local', + ]; + $this->createDomainAlias($values); + } + // Test the environment loader. + $local = $alias_loader->loadByEnvironment('local'); + $this->assert(count($local) == 3, 'Three aliases set to local'); + // Test the environment matcher. $domain here is two.example.com + $match = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($match) == 1, 'One environment match loaded'); + $alias = current($match); + $this->assert($alias->getPattern() == 'five.example.*', 'Proper pattern match loaded.'); + + // Test the environment matcher. $domain here is one.example.com + $domain = $domain_storage->load('example_com'); + $matches = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($matches) == 1, 'One environment match loaded'); + $alias = current($matches); + $this->assert($alias->getPattern() == 'example.*', 'Proper pattern match loaded.'); + + // Now load a page and check things. + // Since we cannot read the service request, we place a block + // which shows links to all domains. + $this->drupalPlaceBlock('domain_switcher_block'); + + // To get around block access, let the anon user view the block. + user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('administer domains')); + // For a non-aliased request, the url list should be normal. + $this->drupalGet($domain->getPath()); + foreach ($domains as $domain) { + $this->assertSession()->assertEscaped($domain->getHostname()); + $this->assertSession()->linkByHrefExists($domain->getPath(), 0, 'Link found: ' . $domain->getPath()); + } + // For an aliased request (four.example.com), the list should be aliased. + $url = $domain->getScheme() . str_replace('*', 'com', $alias->getPattern()) . $domain->getPort(); + $this->drupalGet($url); + foreach ($matches as $match) { + $this->assertSession()->assertEscaped(str_replace('*', 'com', $match->getPattern())); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Kernel/DomainAliasDomainDeleteTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Kernel/DomainAliasDomainDeleteTest.php new file mode 100644 index 000000000..1c68f76d6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Kernel/DomainAliasDomainDeleteTest.php @@ -0,0 +1,83 @@ +domainCreateTestDomains(2); + + // Get the services. + $this->domainStorage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $this->aliasStorage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + } + + /** + * Tests alias deletion on domain deletion. + */ + public function testDomainDelete() { + $domains = $this->domainStorage->loadMultiple(); + $patterns = [ + 'example_com' => '*.example.com', + 'one_example_com' => 'foo.example.com' + ]; + + // Create an alias. + foreach ($domains as $id => $domain) { + $values = [ + 'domain_id' => $domain->id(), + 'pattern' => $patterns[$id], + 'redirect' => 0, + 'environment' => 'local', + ]; + $this->createDomainAlias($values); + $alias = $this->aliasStorage->loadByPattern($patterns[$id]); + $this->assertTrue(!empty($alias), 'Alias saved properly'); + } + + // Delete one domain. + $domain->delete(); + $alias = $this->aliasStorage->loadByPattern($patterns[$id]); + $this->assertTrue(empty($alias), 'Alias deleted properly'); + + // Check the remaining domain, which should still have an alias. + $domain = $this->domainStorage->load('example_com'); + $alias = $this->aliasStorage->loadByPattern($patterns[$domain->id()]); + $this->assertTrue(!empty($alias), 'Alias retained properly'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Traits/DomainAliasTestTrait.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Traits/DomainAliasTestTrait.php new file mode 100644 index 000000000..0631a04bd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Traits/DomainAliasTestTrait.php @@ -0,0 +1,67 @@ +getStorage('domain_alias')->create($values); + if ($save) { + $alias->save(); + } + + return $alias; + } + + /** + * Creates an alias for testing without passing values. + * + * @param \Drupal\domain\DomainInterface $domain + * A domain entity. + * @param string $pattern + * An optional alias pattern. + * @param int $redirect + * An optional redirect (301 or 302). + * @param int $environment + * An optional environment string. + * @param boolean $save + * Whether to save the alias or return for validation. + * + * @return \Drupal\domain_alias\Entity\DomainAlias + * A domain alias entity. + */ + public function domainAliasCreateTestAlias(DomainInterface $domain, $pattern = NULL, $redirect = 0, $environment = 'default', $save = TRUE) { + if (empty($pattern)) { + $pattern = '*.' . $domain->getHostname(); + } + $values = array( + 'domain_id' => $domain->id(), + 'pattern' => $pattern, + 'redirect' => $redirect, + 'environment' => $environment, + ); + + return $this->createDomainAlias($values, $save); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.info.yml b/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.info.yml new file mode 100644 index 000000000..201f1540a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.info.yml @@ -0,0 +1,14 @@ +name: Domain Alpha Updates +description: 'Provides per-release updates for critical changes.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.install b/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.install new file mode 100644 index 000000000..e51238168 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.install @@ -0,0 +1,92 @@ +getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + /** @var $domain \Drupal\domain\Entity\Domain */ + // Existing id. + $id = $domain->getDomainId(); + // New id. + $domain->createDomainId(); + $new_id = $domain->getDomainId(); + // Check to see if this update is needed. + if ($id != $new_id) { + $domain->save(); + $rebuild = TRUE; + } + } + if ($rebuild) { + // Trigger permissions rebuild action. + node_access_needs_rebuild(TRUE); + } +} + +/** + * Provide a new update for 8001, for users who never ran 8001. + * + * See https://github.com/agentrickard/domain/issues/310. + */ +function domain_alpha_update_8002(&$sandbox) { + domain_alpha_update_8001($sandbox); +} + +/** + * Set the Domain Admin field to use the proper plugin. + */ +function domain_alpha_update_8003(&$sandbox) { + $id = 'user.user.field_domain_admin'; + $storage = \Drupal::entityTypeManager()->getStorage('field_config'); + if ($field = $storage->load($id)) { + $new_field = $field->toArray(); + if ($new_field['settings']['handler'] != 'domain:domain') { + $new_field['settings']['handler'] = 'domain:domain'; + $field_config = $storage->create($new_field); + $field_config->original = $field; + $field_config->enforceIsNew(FALSE); + $field_config->save(); + } + } +} + +/** + * Make all affiliates default value configurable. + */ +function domain_alpha_update_8004() { + $config = \Drupal::configFactory(); + $field_name = '.' . DOMAIN_ACCESS_ALL_FIELD; + // Iterate over all node & user fields. + foreach (['node', 'user'] as $type) { + foreach ($config->listAll('field.field.' . $type . '.') as $field_id) { + if (substr($field_id, -strlen($field_name)) === $field_name) { + $field = $config->getEditable($field_id); + if ($field->get('default_value_callback') === 'Drupal\domain_access\DomainAccessManager::getDefaultAllValue') { + $field + ->clear('default_value_callback') + ->save(TRUE); + } + } + } + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/README.md b/sites/all/modules/contrib/admin/domain/domain_config/README.md new file mode 100644 index 000000000..5e9b48060 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/README.md @@ -0,0 +1,111 @@ +Domain Config +============= + +This module provides a per-domain configuration option so that, for each +domain, you can change configuration items of any type, such as settings like +the site name. + +By default, these overrides are also language-aware. An override may exist per +domain, or per-language per domain, based on the prefixes used in the config +file. + +Usage +===== + +Currently, there is no user interface for changing settings, but you can load +in new files that contain configuration per domain. + +The pattern for override files is +`domain.config.DOMAIN_MACHINE_NAME.LANGCODE.item.name`, with the backup for +language-insensitive files as `domain.config.DOMAIN_MACHINE_NAME.item.name`, +where `item.name` is the name of the configuration item being overridden. + +To override the site name, for instance, you begin by exporting the +corresponding configuration item at the Configuration Synchronization screen +for a single export: +`admin/config/development/configuration/single/export` + +In this case, the type of configuration to be exported is "Simple +configuration" and the particular item is named `system.site`. + +You should have a file like the following: + +```YAML +uuid: 536db351-7aec-407e-a746-70ba9cc9f190 +name: Example +mail: admin@example.com +slogan: '' +page: + 403: '' + 404: '' + front: /user +admin_compact_mode: false +weight_select_max: 100 +langcode: en +default_langcode: en +``` + +An override file should contain only the specific data that you want to override. +To override the name, the only line needed is the one beginning ```name:```. +The resulting override looks like this: + +```YAML +name: Three +``` + +To override both the name and the slogan: + +```YAML +name: Three +slogan: 'My site slogan' +``` + +*Warning: do not copy over the `uuid` value into the new file. Doing so may +cause the configuration import to fail.* + +We want this to apply when the domain `three.example.com` is active and English +is the active language. Therefore, this file would be named +`domain.config.three_example_com.en.system.site`. If we wanted this file to +apply whenever the domain `three.example.com` is active, we would leave off the +language prefix: `domain.config.three_example_com.system.site`. + +For further examples, see the files in +`\domain\domain_config\tests\modules\domain_config_test\config\install`. + +Import that file's contents at the Configuration Synchronization screen for a +single import: +`admin/config/development/configuration/single/import` + +Note that, unlike when you exported the item, here you always select "Simple +configuration" as the configuration type to import, independent of the type of +configuration you're overriding. + +settings.php overrides +---------------------- + +For environment-specific or sensitive overrides, use the settings.php method. +In the above case, add +`$conf['domain.config.three_example_com.en.system.site']['name'] = "My special site";` +to your local settings.php file. This will ensure that the `three.example.com` +domain gets the correct value regardless of other module overrides. + +Read more about the "Configuration override system" +at https://www.drupal.org/node/1928898. + +Installation +============ + +If some variable changes are not picked up when the page renders, you may need +add domain-sensitivity to the site's cache. + +To do so, clone `default.services.yml` to `services.yml` and change the +`required_cache_contexts` value to: + +```YAML + required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions', 'url.site'] +``` + +Dependencies +============ + +- Domain diff --git a/sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml new file mode 100644 index 000000000..8271c474c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml @@ -0,0 +1,14 @@ +name: Domain Configuration +description: 'Allows domain specific configuration.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/domain_config.module b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.module new file mode 100644 index 000000000..bb26c0250 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.module @@ -0,0 +1,6 @@ +storage = $storage; + } + + /** + * {@inheritdoc} + */ + public function loadOverrides($names) { + $overrides = array(); + // loadOverrides() runs on config entities, which means that if we try + // to run this routine on our own data, then we end up in an infinite loop. + // So ensure that we are _not_ looking up a domain.record.*. + $check = current($names); + $list = explode('.', $check); + if (isset($list[0]) && isset($list[1]) && $list[0] == 'domain' && $list[1] == 'record') { + return $overrides; + } + if (empty($this->contextSet)) { + $this->initiateContext(); + } + if (!empty($this->domain)) { + foreach ($names as $name) { + $config_name = $this->getDomainConfigName($name, $this->domain); + // Check to see if the config storage has an appropriately named file + // containing override data. + if ($override = $this->storage->read($config_name['langcode'])) { + $overrides[$name] = $override; + } + // Check to see if we have a file without a specific language. + elseif ($override = $this->storage->read($config_name['domain'])) { + $overrides[$name] = $override; + } + + // Apply any settings.php overrides. + if (isset($GLOBALS['config'][$config_name['langcode']])) { + $overrides[$name] = $GLOBALS['config'][$config_name['langcode']]; + } + elseif (isset($GLOBALS['config'][$config_name['domain']])) { + $overrides[$name] = $GLOBALS['config'][$config_name['domain']]; + } + } + } + return $overrides; + } + + /** + * Get configuration name for this hostname. + * + * It will be the same name with a prefix depending on domain and language: + * @code domain.config.DOMAIN_ID.LANGCODE @endcode + * + * @param string $name + * The name of the config object. + * @param \Drupal\domain\DomainInterface $domain + * The domain object. + * + * @return array + * The domain-language, and domain-specific config names. + */ + protected function getDomainConfigName($name, DomainInterface $domain) { + return [ + 'langcode' => 'domain.config.' . $domain->id() . '.' . $this->language->getId() . '.' . $name, + 'domain' => 'domain.config.' . $domain->id() . '.' . $name, + ]; + } + + /** + * {@inheritdoc} + */ + public function getCacheSuffix() { + $suffix = $this->domain ? $this->domain->id() : ''; + $suffix .= $this->language ? $this->language->getId() : ''; + return ($suffix) ? $suffix : NULL; + } + + /** + * {@inheritdoc} + */ + public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) { + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($name) { + if (empty($this->contextSet)) { + $this->initiateContext(); + } + $metadata = new CacheableMetadata(); + if (!empty($this->domain)) { + $metadata->addCacheContexts(['url.site', 'languages:language_interface']); + } + return $metadata; + } + + /** + * Sets domain and language contexts for the request. + * + * We wait to do this in order to avoid circular dependencies + * with the locale module. + */ + protected function initiateContext() { + // Prevent infinite lookups by caching the request. Since the _construct() + // is called for each lookup, this is more efficient. + $this->contextSet = TRUE; + + // Get the language context. Note that injecting the language manager + // into the service created a circular dependency error, so we load from + // the core service manager. + $this->languageManager = \Drupal::languageManager(); + $this->language = $this->languageManager->getCurrentLanguage(); + + // The same issue is true for the domainNegotiator. + $this->domainNegotiator = \Drupal::service('domain.negotiator'); + // Get the domain context. + $this->domain = $this->domainNegotiator->getActiveDomain(); + // If we have fired too early in the bootstrap, we must force the routine to run. + if (empty($this->domain)) { + $this->domain = $this->domainNegotiator->getActiveDomain(TRUE); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigServiceProvider.php b/sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigServiceProvider.php new file mode 100644 index 000000000..a7841188e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigServiceProvider.php @@ -0,0 +1,38 @@ +getDefinition('router.route_provider'); + $definition->setClass(DomainRouteProvider::class); + + if ($container->hasParameter('renderer.config')) { + $renderer_config = $container->getParameter('renderer.config'); + + if (!in_array('url.site', $renderer_config['required_cache_contexts'])) { + $renderer_config['required_cache_contexts'][] = 'url.site'; + } + + $container->setParameter('renderer.config', $renderer_config); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/src/Routing/DomainRouteProvider.php b/sites/all/modules/contrib/admin/domain/domain_config/src/Routing/DomainRouteProvider.php new file mode 100644 index 000000000..e0c623a66 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/src/Routing/DomainRouteProvider.php @@ -0,0 +1,51 @@ +getHost() . ':' . $request->getPathInfo() . ':' . $request->getQueryString(); + if ($cached = $this->cache->get($cid)) { + $this->currentPath->setPath($cached->data['path'], $request); + $request->query->replace($cached->data['query']); + return $cached->data['routes']; + } + else { + // Just trim on the right side. + $path = $request->getPathInfo(); + $path = $path === '/' ? $path : rtrim($request->getPathInfo(), '/'); + $path = $this->pathProcessor->processInbound($path, $request); + $this->currentPath->setPath($path, $request); + // Incoming path processors may also set query parameters. + $query_parameters = $request->query->all(); + $routes = $this->getRoutesByPath(rtrim($path, '/')); + $cache_value = [ + 'path' => $path, + 'query' => $query_parameters, + 'routes' => $routes, + ]; + $this->cache->set($cid, $cache_value, CacheBackendInterface::CACHE_PERMANENT, ['route_match']); + return $routes; + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/src/Tests/DomainConfigTestBase.php b/sites/all/modules/contrib/admin/domain/domain_config/src/Tests/DomainConfigTestBase.php new file mode 100644 index 000000000..784662b88 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/src/Tests/DomainConfigTestBase.php @@ -0,0 +1,71 @@ + 'Spanish'); + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('domain', 'language', 'domain_config_test', 'domain_config'); + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); + $this->drupalLogin($admin_user); + + // Add language. + $edit = array( + 'predefined_langcode' => 'es', + ); + $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language')); + + // Enable URL language detection and selection. + $edit = array('language_interface[enabled][language-url]' => '1'); + $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings')); + + $this->drupalLogout(); + + // In order to reflect the changes for a multilingual site in the container + // we have to rebuild it. + $this->rebuildContainer(); + + $es = \Drupal::entityTypeManager()->getStorage('configurable_language')->load('es'); + $this->assertTrue(!empty($es), 'Created test language.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/install/domain_config_middleware_test.settings.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/install/domain_config_middleware_test.settings.yml new file mode 100644 index 000000000..03c0bfdd1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/install/domain_config_middleware_test.settings.yml @@ -0,0 +1 @@ +test_setting: false diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/schema/domain_config_middleware_test.schema.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/schema/domain_config_middleware_test.schema.yml new file mode 100644 index 000000000..7fddd2369 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/schema/domain_config_middleware_test.schema.yml @@ -0,0 +1,8 @@ +# Schema for the configuration files of the Domain module. +domain_config_middleware_test.settings: + type: config_object + label: 'Domain config middleware settings' + mapping: + test_setting: + type: boolean + label: 'A test setting' diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml new file mode 100644 index 000000000..09be4495d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml @@ -0,0 +1,17 @@ +name: "Domain config middleware module tests" +description: "Support module for domain config middleware response testing." +type: module +package: Testing +# version: VERSION +# core: 8.x +hidden: TRUE + +dependencies: + - domain + - domain_config + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.services.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.services.yml new file mode 100644 index 000000000..ecaa375c2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.services.yml @@ -0,0 +1,8 @@ +services: + domain_config_test.middleware: + class: Drupal\domain_config_middleware_test\Middleware + arguments: ['@config.factory'] + tags: + # Ensure to come before page caching, so you don't serve cached pages to + # banned users. + - { name: http_middleware, priority: 250 } diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/src/Middleware.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/src/Middleware.php new file mode 100644 index 000000000..08066aee1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/src/Middleware.php @@ -0,0 +1,50 @@ +httpKernel = $http_kernel; + $this->configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { + // This line should break hooks in our code. See https://www.drupal.org/node/2896434. + $config = $this->configFactory->get('domain_config_middleware_test.settings'); + return $this->httpKernel->handle($request, $type, $catch); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.four_example_com.system.site.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.four_example_com.system.site.yml new file mode 100644 index 000000000..320f9b0fe --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.four_example_com.system.site.yml @@ -0,0 +1,3 @@ +name: 'Four' +page: + front: /node/2 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.one_example_com.en.system.site.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.one_example_com.en.system.site.yml new file mode 100644 index 000000000..479549188 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.one_example_com.en.system.site.yml @@ -0,0 +1,10 @@ +name: 'One' +mail: admin@example.com +slogan: '' +page: + 403: '' + 404: '' + front: /node/1 +admin_compact_mode: false +weight_select_max: 100 +langcode: en diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.three_example_com.en.system.site.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.three_example_com.en.system.site.yml new file mode 100644 index 000000000..4c7e58f3b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.three_example_com.en.system.site.yml @@ -0,0 +1,9 @@ +mail: admin@example.com +slogan: '' +page: + 403: '' + 404: '' + front: /node +admin_compact_mode: false +weight_select_max: 100 +langcode: en diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.en.system.site.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.en.system.site.yml new file mode 100644 index 000000000..03791fb87 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.en.system.site.yml @@ -0,0 +1,10 @@ +name: 'Two' +mail: admin@example.com +slogan: 'I have it' +page: + 403: '' + 404: '' + front: /node +admin_compact_mode: false +weight_select_max: 100 +langcode: en diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.es.system.site.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.es.system.site.yml new file mode 100644 index 000000000..33f2ed30c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.es.system.site.yml @@ -0,0 +1,10 @@ +name: 'Dos' +mail: admin@example.com +slogan: 'Yo la tengo' +page: + 403: '' + 404: '' + front: /node +admin_compact_mode: false +weight_select_max: 100 +langcode: en diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml new file mode 100644 index 000000000..7438443d0 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml @@ -0,0 +1,17 @@ +name: "Domain config module tests" +description: "Support module for domain config testing." +type: module +package: Testing +# version: VERSION +# core: 8.x +hidden: TRUE + +dependencies: + - domain + - domain_config + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.module b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.module new file mode 100644 index 000000000..a80ab50a7 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.module @@ -0,0 +1,25 @@ +addProperty('config_test', 'aye'); +} + +/** + * Implements hook_page_attachments_alter(). + */ +function domain_config_test_page_attachments_alter(array &$attachments) { + /** @var \Drupal\domain\DomainNegotiatorInterface $Negotiator */ + $negotiator = \Drupal::service('domain.negotiator'); + $domain = $negotiator->getActiveDomain(); + if (!empty($domain) && $domain->get('config_test') == 'aye') { + $attachments['#attached']['http_header'][] = [ + 'X-Domain-Config-Test-page-attachments-hook', + 'invoked' + ]; + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigAlterHookTest.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigAlterHookTest.php new file mode 100644 index 000000000..a3594a6db --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigAlterHookTest.php @@ -0,0 +1,63 @@ +domainCreateTestDomains(); + + // Get the services. + $this->negotiator = \Drupal::service('domain.negotiator'); + $this->moduleHandler = \Drupal::service('module_handler'); + } + + /** + * Tests domain request alteration. + */ + public function testHookDomainRequestAlter() { + // Check for the count of hook implementations. + $hooks = $this->moduleHandler->getImplementations('domain_request_alter'); + $this->assertCount(1, $hooks, 'One hook implementation found.'); + + // Assert that the hook is also called on a request with a HTTP Middleware + // that requests config thus triggering an early hook invocation (before + // modules are loaded by the kernel). + $this->drupalGet(''); + $this->assertEquals('invoked', $this->drupalGetHeader('X-Domain-Config-Test-page-attachments-hook')); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigHomepageTest.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigHomepageTest.php new file mode 100644 index 000000000..9f58f2a33 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigHomepageTest.php @@ -0,0 +1,71 @@ +config('system.site'); + $site_config->set('page.front', '/node')->save(); + + // No domains should exist. + $this->domainTableIsEmpty(); + // Create four new domains programmatically. + $this->domainCreateTestDomains(5); + // Get the domain list. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $this->drupalCreateNode(array( + 'type' => 'article', + 'title' => 'Node 1', + 'promoted' => TRUE, + )); + $this->drupalCreateNode(array( + 'type' => 'article', + 'title' => 'Node 2', + 'promoted' => TRUE, + )); + $homepages = $this->getHomepages(); + foreach ($domains as $domain) { + $home = $this->drupalGet($domain->getPath()); + + // Check if this setting is picked up. + $expected = $domain->getPath() . $homepages[$domain->id()]; + $expected_home = $this->drupalGet($expected); + + $this->assertTrue($home == $expected_home, 'Proper home page loaded (' . $domain->id() . ').'); + } + } + + /** + * Returns the expected homepage paths for each domain. + */ + private function getHomepages() { + $homepages = array( + 'example_com' => 'node', + 'one_example_com' => 'node/1', + 'two_example_com' => 'node', + 'three_example_com' => 'node', + 'four_example_com' => 'node/2', + ); + return $homepages; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigOverriderTest.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigOverriderTest.php new file mode 100644 index 000000000..3537e11f8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigOverriderTest.php @@ -0,0 +1,111 @@ +domainTableIsEmpty(); + // Create five new domains programmatically. + $this->domainCreateTestDomains(5); + // Get the domain list. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + // Except for the default domain, the page title element should match what + // is in the override files. + // With a language context, based on how we have our files setup, we + // expect the following outcomes: + // - example.com name = 'Drupal' for English, 'Drupal' for Spanish. + // - one.example.com name = 'One' for English, 'Drupal' for Spanish. + // - two.example.com name = 'Two' for English, 'Dos' for Spanish. + // - three.example.com name = 'Drupal' for English, 'Drupal' for Spanish. + // - four.example.com name = 'Four' for English, 'Four' for Spanish. + foreach ($domains as $domain) { + // Test the login page, because our default homepages do not exist. + foreach ($this->langcodes as $langcode => $language) { + $path = $domain->getPath() . $langcode . '/user/login'; + $this->drupalGet($path); + if ($domain->isDefault()) { + $this->assertRaw('Log in | Drupal', 'Loaded the proper site name.'); + } + else { + $this->assertRaw('Log in | ' . $this->expectedName($domain, $langcode) . '', 'Loaded the proper site name.' . 'Log in | ' . $this->expectedName($domain, $langcode) . ''); + } + } + } + } + + /** + * Tests that domain-specific variable overrides in settings.php works. + */ + public function testDomainConfigOverriderFromSettings() { + // Set up overrides. + $settings = []; + $settings['config']['domain.config.one_example_com.en.system.site']['name'] = (object) [ + 'value' => 'First', + 'required' => TRUE, + ]; + $settings['config']['domain.config.four_example_com.system.site']['name'] = (object) [ + 'value' => 'Four overridden in settings', + 'required' => TRUE, + ]; + $this->writeSettings($settings); + + // Create five new domains programmatically. + $this->domainCreateTestDomains(5); + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(['one_example_com', 'four_example_com']); + + $domain_one = $domains['one_example_com']; + $this->drupalGet($domain_one->getPath() . 'user/login'); + $this->assertRaw('Log in | First', 'Found overridden slogan for one.example.com.'); + + $domain_four = $domains['four_example_com']; + $this->drupalGet($domain_four->getPath() . 'user/login'); + $this->assertRaw('Log in | Four overridden in settings', 'Found overridden slogan for four.example.com.'); + } + /** + * Returns the expected site name value from our test configuration. + * + * @param DomainInterface $domain + * The Domain object. + * @param $langcode + * A two-digit language code. + * + * @return string + * The expected name. + */ + private function expectedName(DomainInterface $domain, $langcode = NULL) { + $name = ''; + + switch ($domain->id()) { + case 'one_example_com': + $name = ($langcode == 'es') ? 'Drupal' : 'One'; + break; + + case 'two_example_com': + $name = ($langcode == 'es') ? 'Dos' : 'Two'; + break; + + case 'three_example_com': + $name = 'Drupal'; + break; + + case 'four_example_com': + $name = 'Four'; + break; + } + + return $name; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigTestBase.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigTestBase.php new file mode 100644 index 000000000..5c2f045df --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigTestBase.php @@ -0,0 +1,66 @@ + 'Spanish'); + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('domain', 'language', 'domain_config_test', 'domain_config'); + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); + $this->drupalLogin($admin_user); + + // Add language. + $edit = array( + 'predefined_langcode' => 'es', + ); + $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language')); + + // Enable URL language detection and selection. + $edit = array('language_interface[enabled][language-url]' => '1'); + $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings')); + + $this->drupalLogout(); + + // In order to reflect the changes for a multilingual site in the container + // we have to rebuild it. + $this->rebuildContainer(); + + $es = \Drupal::entityTypeManager()->getStorage('configurable_language')->load('es'); + $this->assertTrue(!empty($es), 'Created test language.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content.yml b/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content.yml new file mode 100644 index 000000000..4eff610de --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content.yml @@ -0,0 +1,1301 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_domain_access + - field.storage.node.field_domain_all_affiliates + module: + - domain_access + - node + - user +id: affiliated_content +label: 'Affiliated content' +module: node +description: 'Find and manage content.' +tag: domain +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_options: + access: + type: domain_content_editor + options: { } + cache: + type: tag + query: + type: views_query + options: + disable_sql_rewrite: true + distinct: true + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: true + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 50 + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: true + caption: '' + summary: '' + description: '' + columns: + node_bulk_form: node_bulk_form + title: title + type: type + name: name + status: status + changed: changed + operations: operations + field_domain_access: field_domain_access + field_domain_all_affiliates: field_domain_all_affiliates + info: + node_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + title: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + type: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + name: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + status: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + changed: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + operations: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + field_domain_access: + align: '' + separator: '' + empty_column: false + responsive: '' + field_domain_all_affiliates: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: changed + empty_table: true + row: + type: fields + fields: + node_bulk_form: + id: node_bulk_form + table: node + field: node_bulk_form + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + action_title: 'With selection' + include_exclude: exclude + selected_actions: { } + plugin_id: node_bulk_form + entity_type: node + title: + id: title + table: node_field_data + field: title + label: Title + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: node + entity_field: title + type: string + settings: + link_to_entity: true + plugin_id: field + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + label: 'Content Type' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: false + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: type + plugin_id: field + field_domain_access: + id: field_domain_access + table: node__field_domain_access + field: field_domain_access + relationship: none + group_type: group + admin_label: '' + label: Domain(s) + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: 1 + group_column: target_id + group_columns: { } + group_rows: 1 + delta_limit: '0' + delta_offset: '0' + delta_reversed: 0 + delta_first_last: 0 + multi_type: separator + separator: ', ' + field_api_classes: 0 + plugin_id: domain_access_field + field_domain_all_affiliates: + id: field_domain_all_affiliates + table: node__field_domain_all_affiliates + field: field_domain_all_affiliates + relationship: none + group_type: group + admin_label: '' + label: 'All affiliates' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + name: + id: name + table: users_field_data + field: name + relationship: uid + label: Author + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: field + type: user_name + entity_type: user + entity_field: name + status: + id: status + table: node_field_data + field: status + label: Status + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: boolean + settings: + format: custom + format_custom_true: Published + format_custom_false: Unpublished + plugin_id: field + entity_type: node + entity_field: status + changed: + id: changed + table: node_field_data + field: changed + label: Updated + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: timestamp + settings: + date_format: short + custom_date_format: '' + timezone: '' + plugin_id: field + entity_type: node + entity_field: changed + operations: + id: operations + table: node + field: operations + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + destination: true + plugin_id: entity_operations + filters: + status: + id: status + table: node_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: Status + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: true + group_info: + label: 'Published status' + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + plugin_id: boolean + entity_type: node + entity_field: status + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: Type + description: '' + use_operator: false + operator: type_op + identifier: type + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: bundle + entity_type: node + entity_field: type + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + identifier: title + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: string + entity_type: node + entity_field: title + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: langcode_op + label: Language + description: '' + use_operator: false + operator: langcode_op + identifier: langcode + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: language + entity_type: node + entity_field: langcode + field_domain_all_affiliates_value: + id: field_domain_all_affiliates_value + table: node__field_domain_all_affiliates + field: field_domain_all_affiliates_value + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: + min: '' + max: '' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + sorts: { } + title: 'Content assigned to All affiliates' + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + empty: true + content: 'No content available.' + plugin_id: text_custom + arguments: + field_domain_access_target_id: + id: field_domain_access_target_id + table: node__field_domain_access + field: field_domain_access_target_id + relationship: none + group_type: group + admin_label: '' + default_action: 'not found' + exception: + value: all + title_enable: false + title: All + title_enable: true + title: '{{ arguments.field_domain_access_target_id }} content' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: true + validate: + type: 'entity:domain' + fail: 'not found' + validate_options: + operation: view + multiple: 0 + access: false + bundles: null + glossary: false + limit: 0 + case: none + path_case: none + transform_dash: false + break_phrase: false + plugin_id: string + relationships: + uid: + id: uid + table: node_field_data + field: uid + admin_label: author + required: true + plugin_id: standard + show_admin_links: false + filter_groups: + operator: AND + groups: + 1: AND + display_extenders: { } + display_plugin: default + display_title: Master + id: default + position: 0 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + max-age: 0 + tags: + - 'config:field.storage.node.field_domain_access' + - 'config:field.storage.node.field_domain_all_affiliates' + page_1: + display_options: + path: admin/content/domain-content/% + menu: + type: none + title: Content + description: '' + expanded: false + parent: '' + weight: -10 + context: '0' + menu_name: admin + tab_options: + type: normal + title: Content + description: 'Find and manage content' + menu_name: admin + weight: -10 + display_extenders: { } + display_description: 'Default page for content per domain' + filters: + status: + id: status + table: node_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: Status + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: true + group_info: + label: 'Published status' + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + plugin_id: boolean + entity_type: node + entity_field: status + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: Type + description: '' + use_operator: false + operator: type_op + identifier: type + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: bundle + entity_type: node + entity_field: type + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + identifier: title + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: string + entity_type: node + entity_field: title + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: langcode_op + label: Language + description: '' + use_operator: false + operator: langcode_op + identifier: langcode + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: language + entity_type: node + entity_field: langcode + defaults: + filters: false + filter_groups: false + title: false + filter_groups: + operator: AND + groups: + 1: AND + title: Content + display_plugin: page + display_title: 'Domain content page' + id: page_1 + position: 1 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + max-age: 0 + tags: + - 'config:field.storage.node.field_domain_access' + - 'config:field.storage.node.field_domain_all_affiliates' + page_2: + display_plugin: page + id: page_2 + display_title: 'All affiliates page' + position: 2 + display_options: + display_extenders: { } + display_description: 'Lists content assigned to all affiliates' + arguments: { } + defaults: + arguments: false + filters: false + filter_groups: false + path: admin/content/domain-content/all_affiliates + filters: + status: + id: status + table: node_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: Status + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: true + group_info: + label: 'Published status' + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + plugin_id: boolean + entity_type: node + entity_field: status + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: Type + description: '' + use_operator: false + operator: type_op + identifier: type + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: bundle + entity_type: node + entity_field: type + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + identifier: title + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: string + entity_type: node + entity_field: title + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: langcode_op + label: Language + description: '' + use_operator: false + operator: langcode_op + identifier: langcode + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: language + entity_type: node + entity_field: langcode + field_domain_all_affiliates_value: + id: field_domain_all_affiliates_value + table: node__field_domain_all_affiliates + field: field_domain_all_affiliates_value + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: + min: '' + max: '' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + field_domain_access_target_id: + id: field_domain_access_target_id + table: node__field_domain_access + field: field_domain_access_target_id + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: field_domain_access_target_id_op + label: Domain + description: '' + use_operator: false + operator: field_domain_access_target_id_op + identifier: field_domain_access_target_id + required: false + remember: true + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: 0 + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: domain_access_filter + filter_groups: + operator: AND + groups: + 1: AND + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + tags: + - 'config:field.storage.node.field_domain_access' + - 'config:field.storage.node.field_domain_all_affiliates' diff --git a/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content_editors.yml b/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content_editors.yml new file mode 100644 index 000000000..0dbc4c89c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content_editors.yml @@ -0,0 +1,1454 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_domain_access + - field.storage.user.field_domain_all_affiliates + module: + - domain_access + - user +id: affiliated_editors +label: 'Affiliated editors' +module: user +description: 'Find and manage people interacting with your site.' +tag: domain +base_table: users_field_data +base_field: uid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: domain_content_admin + options: { } + cache: + type: tag + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: true + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: true + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 50 + offset: 0 + id: 0 + total_pages: 0 + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + quantity: 9 + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + summary: '' + columns: + user_bulk_form: user_bulk_form + name: name + status: status + rid: rid + created: created + access: access + edit_node: edit_node + dropbutton: dropbutton + info: + user_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + name: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + status: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + rid: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + created: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + access: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + edit_node: + align: '' + separator: '' + empty_column: false + responsive: priority-low + dropbutton: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: created + empty_table: true + row: + type: fields + fields: + user_bulk_form: + id: user_bulk_form + table: users + field: user_bulk_form + relationship: none + group_type: group + admin_label: '' + label: 'Bulk update' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: user_bulk_form + entity_type: user + name: + id: name + table: users_field_data + field: name + relationship: none + group_type: group + admin_label: '' + label: Username + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: field + type: user_name + entity_type: user + entity_field: name + status: + id: status + table: users_field_data + field: status + relationship: none + group_type: group + admin_label: '' + label: Status + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: field + type: boolean + settings: + format: custom + format_custom_true: Active + format_custom_false: Blocked + entity_type: user + entity_field: status + roles_target_id: + id: roles_target_id + table: user__roles + field: roles_target_id + relationship: none + group_type: group + admin_label: '' + label: Roles + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: ul + separator: ', ' + plugin_id: user_roles + field_domain_access: + id: field_domain_access + table: user__field_domain_access + field: field_domain_access + relationship: none + group_type: group + admin_label: '' + label: Domain(s) + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: 0 + group_column: target_id + group_columns: { } + group_rows: 1 + delta_limit: '0' + delta_offset: '0' + delta_reversed: 0 + delta_first_last: 0 + multi_type: separator + separator: ', ' + field_api_classes: 0 + plugin_id: domain_access_field + field_domain_all_affiliates: + id: field_domain_all_affiliates + table: user__field_domain_all_affiliates + field: field_domain_all_affiliates + relationship: none + group_type: group + admin_label: '' + label: 'All affiliates' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + created: + id: created + table: users_field_data + field: created + relationship: none + group_type: group + admin_label: '' + label: 'Member for' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: timestamp_ago + settings: + future_format: '@interval' + past_format: '@interval' + granularity: 2 + plugin_id: field + entity_type: user + entity_field: created + access: + id: access + table: users_field_data + field: access + relationship: none + group_type: group + admin_label: '' + label: 'Last access' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: timestamp_ago + settings: + future_format: '@interval hence' + past_format: '@interval ago' + granularity: 2 + plugin_id: field + entity_type: user + entity_field: access + operations: + id: operations + table: users + field: operations + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + destination: true + entity_type: user + plugin_id: entity_operations + mail: + id: mail + table: users_field_data + field: mail + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: true + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: basic_string + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + entity_type: user + entity_field: mail + filters: + combine: + id: combine + table: views + field: combine + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: combine_op + label: 'Name or email contains' + description: '' + use_operator: false + operator: combine_op + identifier: user + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + fields: + name: name + mail: mail + plugin_id: combine + roles_target_id: + id: roles_target_id + table: user__roles + field: roles_target_id + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: roles_target_id_op + label: Role + description: '' + use_operator: false + operator: roles_target_id_op + identifier: role + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + reduce_duplicates: false + plugin_id: user_roles + permission: + id: permission + table: user__roles + field: permission + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: permission_op + label: Permission + description: '' + use_operator: false + operator: permission_op + identifier: permission + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + reduce_duplicates: false + plugin_id: user_permissions + status: + id: status + table: users_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: true + group_info: + label: Status + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Active + operator: '=' + value: '1' + 2: + title: Blocked + operator: '=' + value: '0' + plugin_id: boolean + entity_type: user + entity_field: status + default_langcode: + id: default_langcode + table: users_field_data + field: default_langcode + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: user + entity_field: default_langcode + plugin_id: boolean + uid_raw: + id: uid_raw + table: users_field_data + field: uid_raw + relationship: none + group_type: group + admin_label: '' + operator: '!=' + value: + min: '' + max: '' + value: '0' + group: 1 + exposed: false + expose: + operator_id: '0' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + entity_type: user + sorts: + created: + id: created + table: users_field_data + field: created + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + expose: + label: '' + granularity: second + plugin_id: date + entity_type: user + entity_field: created + title: People + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: 'No people available.' + plugin_id: text_custom + use_more: false + use_more_always: false + use_more_text: more + display_comment: '' + use_ajax: false + hide_attachment_summary: false + show_admin_links: true + group_by: false + link_url: '' + link_display: page_1 + css_class: '' + filter_groups: + operator: AND + groups: + 1: AND + display_extenders: { } + arguments: + field_domain_access_target_id: + id: field_domain_access_target_id + table: user__field_domain_access + field: field_domain_access_target_id + plugin_id: string + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + max-age: 0 + tags: + - 'config:field.storage.user.field_domain_access' + - 'config:field.storage.user.field_domain_all_affiliates' + page_1: + display_plugin: page + id: page_1 + display_title: 'Domain content editors' + position: 1 + display_options: + path: admin/content/domain-editors/% + show_admin_links: false + menu: + type: none + title: List + description: 'Find and manage people interacting with your site.' + expanded: false + parent: '' + weight: -10 + context: '0' + menu_name: admin + tab_options: + type: normal + title: People + description: 'Manage user accounts, roles, and permissions.' + menu_name: admin + weight: 0 + defaults: + show_admin_links: false + arguments: false + display_extenders: { } + display_description: 'Default page for domain content editor lists.' + arguments: + field_domain_access_target_id: + id: field_domain_access_target_id + table: user__field_domain_access + field: field_domain_access_target_id + relationship: none + group_type: group + admin_label: '' + default_action: 'not found' + exception: + value: all + title_enable: false + title: All + title_enable: true + title: '{{ arguments.field_domain_access_target_id }} editors' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: true + validate: + type: 'entity:domain' + fail: 'not found' + validate_options: + operation: view + multiple: 0 + access: false + bundles: null + glossary: false + limit: 0 + case: none + path_case: none + transform_dash: false + break_phrase: false + plugin_id: string + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + max-age: 0 + tags: + - 'config:field.storage.user.field_domain_access' + - 'config:field.storage.user.field_domain_all_affiliates' + page_2: + display_plugin: page + id: page_2 + display_title: 'All affiliates editors' + position: 2 + display_options: + display_extenders: { } + path: admin/content/domain-editors/all_affiliates + display_description: 'Lists editors with the All affiliates permission.' + filters: + combine: + id: combine + table: views + field: combine + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: combine_op + label: 'Name or email contains' + description: '' + use_operator: false + operator: combine_op + identifier: user + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + fields: + name: name + mail: mail + plugin_id: combine + roles_target_id: + id: roles_target_id + table: user__roles + field: roles_target_id + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: roles_target_id_op + label: Role + description: '' + use_operator: false + operator: roles_target_id_op + identifier: role + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + reduce_duplicates: false + plugin_id: user_roles + permission: + id: permission + table: user__roles + field: permission + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: permission_op + label: Permission + description: '' + use_operator: false + operator: permission_op + identifier: permission + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + reduce_duplicates: false + plugin_id: user_permissions + status: + id: status + table: users_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: true + group_info: + label: Status + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Active + operator: '=' + value: '1' + 2: + title: Blocked + operator: '=' + value: '0' + plugin_id: boolean + entity_type: user + entity_field: status + default_langcode: + id: default_langcode + table: users_field_data + field: default_langcode + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: user + entity_field: default_langcode + plugin_id: boolean + uid_raw: + id: uid_raw + table: users_field_data + field: uid_raw + relationship: none + group_type: group + admin_label: '' + operator: '!=' + value: + min: '' + max: '' + value: '0' + group: 1 + exposed: false + expose: + operator_id: '0' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + entity_type: user + field_domain_access_target_id: + id: field_domain_access_target_id + table: user__field_domain_access + field: field_domain_access_target_id + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: field_domain_access_target_id_op + label: Domain + description: '' + use_operator: false + operator: field_domain_access_target_id_op + identifier: field_domain_access_target_id + required: false + remember: true + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: 0 + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: domain_access_filter + field_domain_all_affiliates_value: + id: field_domain_all_affiliates_value + table: user__field_domain_all_affiliates + field: field_domain_all_affiliates_value + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: + min: '' + max: '' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + defaults: + filters: false + filter_groups: false + filter_groups: + operator: AND + groups: + 1: AND + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + tags: + - 'config:field.storage.user.field_domain_access' + - 'config:field.storage.user.field_domain_all_affiliates' diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml new file mode 100644 index 000000000..50c86ce06 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml @@ -0,0 +1,18 @@ +name: Domain Content +description: 'Review content by assigned domain.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + - domain_access + - node + - user + - views + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.install b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.install new file mode 100644 index 000000000..cc15028ed --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.install @@ -0,0 +1,17 @@ +getStorage('view'); + $entities = []; + foreach (['affiliated_content', 'affiliated_editors'] as $id) { + if ($view = $storage->load($id)) { + $entities[$id] = $view; + } + } + if (!empty($entities)) { + $storage->delete($entities); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.links.menu.yml b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.links.menu.yml new file mode 100644 index 000000000..5a4a025b6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.links.menu.yml @@ -0,0 +1,12 @@ +domain_content.content_list: + title: Affiliated content + route_name: domain_content.content_list + parent: system.admin_content + description: 'Lists content by domain assignment.' + weight: 10 +domain_content.editors_list: + title: Affiliated editors + route_name: domain_content.editors_list + parent: system.admin_content + description: 'Lists editors by domain assignment.' + weight: 10 diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.module b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.module new file mode 100644 index 000000000..727f39147 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.module @@ -0,0 +1,71 @@ +getStorage('user')->load($account->id()); + $allowed = \Drupal::service('domain_access.manager')->getAccessValues($user); + $id = $domain->id(); + if ($account->hasPermission('publish to any domain') || ($account->hasPermission('publish to any assigned domain') && isset($allowed[$domain->id()]))) { + $operations['domain_content'] = array( + 'title' => t('Content'), + 'url' => Url::fromUri("internal:/admin/content/domain-content/$id"), + // Core operations start at 0 and increment by 10. + 'weight' => 120, + ); + } + if ($account->hasPermission('assign editors to any domain') || ($account->hasPermission('assign domain editors') && isset($allowed[$domain->id()]))) { + $operations['domain_users'] = array( + 'title' => t('Editors'), + 'url' => Url::fromUri("internal:/admin/content/domain-editors/$id"), + // Core operations start at 0 and increment by 10. + 'weight' => 120, + ); + } + + return $operations; +} + +/** + * Implements hook_requirements. + */ +function domain_content_requirements($phase) { + $requirements = []; + $allow = TRUE; + if ($phase == 'install') { + $list['user'] = 'user'; + $node_types = \Drupal::entityTypeManager()->getStorage('node_type')->loadMultiple(); + foreach ($node_types as $type => $info) { + $list[$type] = 'node'; + } + // Check for required fields. + foreach ($list as $bundle => $entity_type) { + $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_FIELD; + if (!$field = \Drupal::entityTypeManager()->getStorage('field_config')->load($id)) { + $allow = FALSE; + break; + } + $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_ALL_FIELD; + if (!$field = \Drupal::entityTypeManager()->getStorage('field_config')->load($id)) { + $allow = FALSE; + break; + } + } + } + if (!$allow) { + $requirements['domain_content'] = [ + 'title' => t('Domain content'), + 'description' => t('Domain content cannot be enabled until Domain access has installed its required fields.'), + 'severity' => REQUIREMENT_ERROR, + ]; + } + return $requirements; +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.permissions.yml b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.permissions.yml new file mode 100644 index 000000000..629da9e91 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.permissions.yml @@ -0,0 +1,4 @@ +access domain content: + title: 'Access domain content pages' +access domain content editors: + title: 'Access domain editors pages' diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.routing.yml b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.routing.yml new file mode 100644 index 000000000..e352d0590 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.routing.yml @@ -0,0 +1,14 @@ +domain_content.content_list: + path: '/admin/content/domain-content' + defaults: + _controller: 'Drupal\domain_content\Controller\DomainContentController::contentList' + _title: 'Affiliated content' + requirements: + _permission: 'access domain content' +domain_content.editors_list: + path: '/admin/content/domain-editors' + defaults: + _controller: 'Drupal\domain_content\Controller\DomainContentController::editorsList' + _title: 'Affiliated editors' + requirements: + _permission: 'access domain content editors' diff --git a/sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php b/sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php new file mode 100644 index 000000000..ead35f356 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php @@ -0,0 +1,125 @@ +getUser(); + $build = [ + '#theme' => 'table', + '#header' => [$this->t('Domain'), $options['column_header']], + ]; + if ($account->hasPermission($options['all_permission'])) { + $build['#rows'][] = [ + Link::fromTextAndUrl($this->t('All affiliates'), Url::fromUri('internal:/admin/content/' . $options['path'] . '/' . '/all_affiliates')), + $this->getCount($options['type']), + ]; + } + // Loop through domains. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultipleSorted(); + $manager = \Drupal::service('domain_access.manager'); + /** @var \Drupal\domain\DomainInterface $domain */ + foreach ($domains as $domain) { + if ($account->hasPermission($options['all_permission']) || $manager->hasDomainPermissions($account, $domain, [$options['permission']])) { + $row = [ + Link::fromTextAndUrl($domain->label(), Url::fromUri('internal:/admin/content/' . $options['path'] . '/' . $domain->id())), + $this->getCount($options['type'], $domain), + ]; + $build['#rows'][] = $row; + } + } + return $build; + } + + /** + * Generates a list of content by domain. + */ + public function contentList() { + $options = [ + 'type' => 'node', + 'column_header' => $this->t('Content count'), + 'permission' => 'publish to any assigned domain', + 'all_permission' => 'publish to any domain', + 'path' => 'domain-content', + ]; + + return $this->buildList($options); + } + + /** + * Generates a list of editors by domain. + */ + public function editorsList() { + $options = [ + 'type' => 'user', + 'column_header' => $this->t('Editor count'), + 'permission' => 'assign domain editors', + 'all_permission' => 'assign editors to any domain', + 'path' => 'domain-editors', + ]; + + return $this->buildList($options); + } + + /** + * Counts the content for a domain. + * + * @param string $entity_type + * The entity type. + * @param DomainInterface $domain + * The domain to query. If passed NULL, checks status for all affiliates. + * + * @return int + * The content count for the given domain. + */ + protected function getCount($entity_type = 'node', DomainInterface $domain = NULL) { + if (is_null($domain)) { + $field = DOMAIN_ACCESS_ALL_FIELD; + $value = 1; + } + else { + $field = DOMAIN_ACCESS_FIELD; + $value = $domain->id(); + } + // Note that we ignore node access so these queries work on any domain. + $query = \Drupal::entityQuery($entity_type) + ->condition($field, $value) + ->accessCheck(FALSE); + + return count($query->execute()); + } + + /** + * Returns a fully loaded user object for the current request. + * + * @return AccountInterface + * The current user object. + */ + protected function getUser() { + $account = $this->currentUser(); + // Advanced grants for edit/delete require permissions. + return \Drupal::entityTypeManager()->getStorage('user')->load($account->id()); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainContentAccess.php b/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainContentAccess.php new file mode 100644 index 000000000..10b1ff40f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainContentAccess.php @@ -0,0 +1,30 @@ +setRequirement('_permission', 'access domain content'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainEditorAccess.php b/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainEditorAccess.php new file mode 100644 index 000000000..eed22abb1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainEditorAccess.php @@ -0,0 +1,30 @@ +setRequirement('_permission', 'access domain content editors'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentActionsTest.php b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentActionsTest.php new file mode 100644 index 000000000..50e40180b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentActionsTest.php @@ -0,0 +1,79 @@ +admin_user = $this->drupalCreateUser([ + 'administer domains', + 'access administration pages', + 'access domain content', + 'access domain content editors', + 'publish to any domain', + 'assign editors to any domain', + 'bypass node access' // Edit access is required. This is fastest. + ]); + $this->drupalLogin($this->admin_user); + + // Create users and content. + $this->createDomainContent(); + + $url = 'admin/content/domain-content/all_affiliates'; + + $this->drupalGet($url); + + // All the content should be on domain one. + $old_domain = $this->domains['one_example_com']; + $new_domain = $this->domains['two_example_com']; + + // Domains are linked in the output. + $this->assertRaw($old_domain->label() . ''); + $this->assertNoRaw($new_domain->label() . ''); + + // Add some content to domain two. + $edit = [ + 'node_bulk_form[0]' => TRUE, + 'node_bulk_form[1]' => TRUE, + 'action' => 'domain_access_add_action.two_example_com', + ]; + $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); + + // Both domains should be present. + $this->assertRaw($old_domain->label() . ''); + $this->assertRaw($new_domain->label() . ''); + + // Remove some content from domain two. + $edit = [ + 'node_bulk_form[0]' => TRUE, + 'node_bulk_form[1]' => TRUE, + 'action' => 'domain_access_remove_action.two_example_com', + ]; + $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); + + // Domains are linked properly in the output. + $this->assertRaw($old_domain->label() . ''); + $this->assertNoRaw($new_domain->label() . ''); + + // There should be five elements. + $this->assertRaw('node_bulk_form[4]'); + + // Remove one from all affiliates. + $edit = [ + 'node_bulk_form[0]' => TRUE, + 'action' => 'domain_access_none_action', + ]; + $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); + + // There should be four elements. + $this->assertRaw('node_bulk_form[3]'); + $this->assertNoRaw('node_bulk_form[4]'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentCountTest.php b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentCountTest.php new file mode 100644 index 000000000..9cf54a6fd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentCountTest.php @@ -0,0 +1,49 @@ +admin_user = $this->drupalCreateUser([ + 'administer domains', + 'access administration pages', + 'access domain content', + 'access domain content editors', + 'publish to any domain', + 'assign editors to any domain', + ]); + $this->drupalLogin($this->admin_user); + + // Create users and content. + $this->createDomainContent(); + $this->createDomainUsers(); + + // Base Urls for our views. + $urls = [ + 'admin/content/domain-content', + 'admin/content/domain-editors', + ]; + // Test the overview pages. + foreach ($urls as $url) { + $content = $this->drupalGet($url); + $this->assertResponse(200); + // Find the links. + $this->findLink('All affiliates'); + foreach ($this->domains as $id => $domain) { + $this->findLink($domain->label()); + $string = $domain->label() . "5"; + $this->checkContent($content, $string); + } + $string = 'All affiliates5'; + $this->checkContent($content, $string); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentPermissionsTest.php b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentPermissionsTest.php new file mode 100644 index 000000000..1ed8b40a8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentPermissionsTest.php @@ -0,0 +1,173 @@ +admin_user = $this->drupalCreateUser([ + 'administer domains', + 'access administration pages', + 'access domain content', + 'access domain content editors', + 'publish to any domain', + 'assign editors to any domain', + ]); + $this->drupalLogin($this->admin_user); + + // Create users and content. + $this->createDomainContent(); + $this->createDomainUsers(); + + // Base Urls for our views. + $urls = [ + 'admin/content/domain-content', + 'admin/content/domain-editors', + ]; + // Test the overview and domain-specific pages. + foreach ($urls as $url) { + $this->drupalGet($url); + $this->assertResponse(200); + // Find the links. + $this->findLink('All affiliates'); + foreach ($this->domains as $id => $domain) { + $this->findLink($domain->label()); + } + + // All affiliates link. + $this->drupalGet($url . '/all_affiliates'); + $this->assertResponse(200); + + // Individual domain pages. + foreach ($this->domains as $id => $domain) { + $this->drupalGet($url . '/' . $id); + $this->assertResponse(200); + } + } + // This user should be able to see everything but all affiliates + $this->limited_user = $this->drupalCreateUser([ + 'administer domains', + 'access administration pages', + 'access domain content', + 'access domain content editors', + 'publish to any assigned domain', + 'assign domain editors', + ]); + $this->addDomainsToEntity('user', $this->limited_user->id(), array_keys($this->domains), DOMAIN_ACCESS_FIELD); + + $this->drupalLogin($this->limited_user); + // Test the overview and domain-specific pages. + foreach ($urls as $url) { + $this->drupalGet($url); + $this->assertResponse(200); + // Find the links. + $this->assertNoRaw('All affiliates'); + foreach ($this->domains as $id => $domain) { + $this->findLink($domain->label()); + } + + // All affiliates link. + $this->drupalGet($url . '/all_affiliates'); + $this->assertResponse(403); + + // Individual domain pages. + foreach ($this->domains as $id => $domain) { + $this->drupalGet($url . '/' . $id); + $this->assertResponse(200); + } + } + + // This user should be able to see everything but all affiliates and nothing + // for editor assignments. + $this->editor_user = $this->drupalCreateUser([ + 'access administration pages', + 'access domain content', + 'publish to any assigned domain', + ]); + $this->addDomainsToEntity('user', $this->editor_user->id(), array_keys($this->domains), DOMAIN_ACCESS_FIELD); + + $this->drupalLogin($this->editor_user); + // Test the overview and domain-specific pages. + foreach ($urls as $url) { + $expected = 200; + if ($url == 'admin/content/domain-editors') { + $expected = 403; + } + $this->drupalGet($url); + $this->assertResponse($expected); + // Find the links. + $this->assertNoRaw('All affiliates'); + foreach ($this->domains as $id => $domain) { + if ($expected == 200) { + $this->findLink($domain->label()); + } + else { + $this->findNoLink($domain->label()); + } + } + + // All affiliates link will fail for both paths. + $this->drupalGet($url . '/all_affiliates'); + $this->assertResponse(403); + + // Individual domain pages. + foreach ($this->domains as $id => $domain) { + $this->drupalGet($url . '/' . $id); + $this->assertResponse($expected); + } + } + + // This user should be able to see one domain for editor assignments. + $this->assign_user = $this->drupalCreateUser([ + 'access administration pages', + 'access domain content editors', + 'assign domain editors', + ]); + $ids = array_keys($this->domains); + $assigned_id = end($ids); + $this->addDomainsToEntity('user', $this->assign_user->id(), [$assigned_id], DOMAIN_ACCESS_FIELD); + + $this->drupalLogin($this->assign_user); + // Test the overview and domain-specific pages. + foreach ($urls as $url) { + $expected = 200; + if ($url == 'admin/content/domain-content') { + $expected = 403; + } + $this->drupalGet($url); + $this->assertResponse($expected); + // Find the links. + $this->assertNoRaw('All affiliates'); + foreach ($this->domains as $id => $domain) { + if ($expected == 200 && $id == $assigned_id) { + $this->findLink($domain->label()); + } + else { + $this->findNoLink($domain->label()); + } + } + + // All affiliates link will fail for both paths. + $this->drupalGet($url . '/all_affiliates'); + $this->assertResponse(403); + + // Individual domain pages. + foreach ($this->domains as $id => $domain) { + $this->drupalGet($url . '/' . $id); + if ($expected == 200 && $id == $assigned_id) { + $this->assertResponse(200); + } + else { + $this->assertResponse(403); + } + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentTestBase.php b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentTestBase.php new file mode 100644 index 000000000..44786cfb8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentTestBase.php @@ -0,0 +1,109 @@ +domainCreateTestDomains(5); + + $this->domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + } + + /** + * Creates dummy content for testing. + * + * 25 nodes, 5 per domain and 5 to all affiliates. + */ + public function createDomainContent() { + foreach ($this->domains as $id => $domain) { + for ($i = 0; $i < 5; $i++) { + $this->drupalCreateNode([ + 'type' => 'article', + DOMAIN_ACCESS_FIELD => [$id], + DOMAIN_ACCESS_ALL_FIELD => ($id == 'one_example_com') ? 1 : 0, + ]); + } + } + // Rebuild node access rules. + node_access_rebuild(); + } + + /** + * Creates dummy content for testing. + * + * 25 users, 5 per domain and 5 to all affiliates. + */ + public function createDomainUsers() { + foreach ($this->domains as $id => $domain) { + for ($i = 0; $i < 5; $i++) { + $account[$id] = $this->drupalCreateUser([ + 'access administration pages', + 'access domain content', + 'access domain content editors', + 'publish to any domain', + 'assign editors to any domain', + ]); + $this->addDomainsToEntity('user', $account[$id]->id(), $id, DOMAIN_ACCESS_FIELD); + if ($id == 'one_example_com') { + $this->addDomainsToEntity('user', $account[$id]->id(), 1, DOMAIN_ACCESS_ALL_FIELD); + } + } + } + } + + /** + * Strips whitespace from a page response and runs assertRaw() equivalent. + * + * In tests, we were having difficulty with spacing in tables. This method + * takes some concepts from Mink and rearranges them to work for our tests. + * Notably, we don't pull page content from the session request. + * + * @param $content + * The generated HTML, such as from drupalGet(). + * @param $text + * The text string to search for. + */ + public function checkContent($content, $text) { + // Convert all whitespace to spaces. + $content = preg_replace('/\s+/u', ' ', $content); + // Strip all whitespace between tags. + $content = preg_replace('@>\\s+<@', '><', $content); + $regex = '/' . preg_quote($text, '/') . '/ui'; + $message = sprintf('The text "%s" was found in the text of the current page.', $text); + $this->assert((bool) preg_match($regex, $content), $message); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/README.md b/sites/all/modules/contrib/admin/domain/domain_source/README.md new file mode 100644 index 000000000..7508eeba6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/README.md @@ -0,0 +1,58 @@ +Domain Source +====== + +Allows content to be assigned a canonical domain when writing URLs. Domain Source will +ensure that content that appears on multiple domains always links to one URL. + +When links are written to content from another domain, the links +will go to the "source" domain specified for the node. + +Domain Source does not issue redirects. It rewrites links. The only time redirects may +be involved is when content is saved. + +Domain Source Fields +----- + +Domain Source adds a field to each content type that works with or without Domain Access. +The field is optional. If Domain Access is present, only a selected domain may be +assigned as the source domain. + +Domain Source configuration +----- + +The module has a configuration option that allows administrators to disable link rewrites +for specific Drupal paths. These paths are defined by Drupal's routing system. The default' +set of routes are: + +* `canonical` -- the View page of a node +* `delete_form` -- the delete form +* `edit_form` -- the edit form +* `version_history` -- the revisions page +* `revision` -- the revision edit form +* `create` -- not enforced + +Additional routes may be defined by other modules. Most notable Content Translation, +which adds: + +* `content_translation_overview` -- the overview page +* `content_translation_add` -- the add translation link +* `content_translation_edit` -- the edit translation form +* `content_translation_delete` -- the delete translation form + +Additional Entities +----- + +Domain Source is designed to work for content (nodes) but should work with other content +entities. You will need to configure the field for each entity type manually. + +Developer Notes +----- + +Domain Source changes core's `redirect_response_subscriber` service to the +`DomainSourceRedirectResponseSubscriber` class. This allows us to issue redirects to +registered domains and aliases that would otherwise not be recognizes as internal Drupal +links. These redirects typically occur on entity save when the source domain varies from +the current domain. + +Domain Source also implements `OutboundPathProcessorInterface` to rewrite links to an +entity assigned to a source domain. diff --git a/sites/all/modules/contrib/admin/domain/domain_source/config/install/domain_source.settings.yml b/sites/all/modules/contrib/admin/domain/domain_source/config/install/domain_source.settings.yml new file mode 100644 index 000000000..d63da0eb3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/config/install/domain_source.settings.yml @@ -0,0 +1 @@ +exclude_routes: {} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/config/install/field.storage.node.field_domain_source.yml b/sites/all/modules/contrib/admin/domain/domain_source/config/install/field.storage.node.field_domain_source.yml new file mode 100644 index 000000000..9ae9937de --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/config/install/field.storage.node.field_domain_source.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - domain + - node + enforced: + module: + - domain_source +id: node.field_domain_source +field_name: field_domain_source +entity_type: node +type: entity_reference +settings: + target_type: domain +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_source/config/schema/domain_source.schema.yml b/sites/all/modules/contrib/admin/domain/domain_source/config/schema/domain_source.schema.yml new file mode 100644 index 000000000..bbde9a45a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/config/schema/domain_source.schema.yml @@ -0,0 +1,10 @@ +domain_source.settings: + type: config_object + label: 'Domain Source settings' + mapping: + exclude_routes: + type: sequence + label: 'Disable link rewrites for specific entity routes.' + sequence: + type: string + label: 'Route' diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.api.php b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.api.php new file mode 100644 index 000000000..a1cf72dcb --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.api.php @@ -0,0 +1,64 @@ +getStorage('domain')->loadDefaultDomain(); +} + +/** + * Allows modules to specify the target link for a Drupal path. + * + * Note: This hook is not meant to be used for node or entity paths, which + * are handled by hook_domain_source_alter(). This hook is split + * from hook_domain_source_alter() for better performance. + * + * Note that hook_domain_source_alter() only paths that are not content entities. + * + * Currently, no modules in the package implement this hook. + * + * There is no return value for this hook. Modify $source by reference by + * loading a valid domain record or set $source = NULL to discard an existing + * $source value and not rewrite the path. + * + * @param array &$source + * The domain array from domain_get_node_match(), passed by reference. + * @param string $path + * The outbound path request. + * @param array $options + * The options for the url, as defined by + * \Drupal\Core\PathProcessor\OutboundPathProcessorInterface. + */ +function hook_domain_source_path_alter(&$source, $path, $options) { + // Always make admin links go to the primary domain. + $parts = explode('/', $path); + if (isset($parts[0]) && $parts[0] == 'admin') { + $source = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultDomain(); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml new file mode 100644 index 000000000..24ae2905b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml @@ -0,0 +1,15 @@ +name: Domain Source +description: 'Domain-based path rewrites for content.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + - node + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.install b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.install new file mode 100644 index 000000000..654216932 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.install @@ -0,0 +1,30 @@ +getStorage('node_type')->loadMultiple(); + foreach ($node_types as $type => $info) { + $list[$type] = 'node'; + } + // Install our fields. + foreach ($list as $bundle => $entity_type) { + domain_source_confirm_fields($entity_type, $bundle); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.libraries.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.libraries.yml new file mode 100644 index 000000000..a61d45d8e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.libraries.yml @@ -0,0 +1,7 @@ +drupal.domain_source: + version: VERSION + js: + js/domain_source.js: {} + dependencies: + - core/jquery + - core/drupal diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.menu.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.menu.yml new file mode 100644 index 000000000..e40865771 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.menu.yml @@ -0,0 +1,6 @@ +domain_source.settings: + title: Domain Source settings + route_name: domain_source.settings + parent: domain.admin + description: 'Domain Source settings' + weight: 15 diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.task.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.task.yml new file mode 100644 index 000000000..a636149e3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.task.yml @@ -0,0 +1,4 @@ +domain_source.settings: + title: Domain Source settings + route_name: domain_source.settings + base_route: domain.admin diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.module b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.module new file mode 100644 index 000000000..d87e2360e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.module @@ -0,0 +1,265 @@ +getStorage('field_config'); + + if (!$field = $field_config_storage->load($id)) { + $field = array( + 'field_name' => DOMAIN_SOURCE_FIELD, + 'entity_type' => $entity_type, + 'label' => 'Domain Source', + 'bundle' => $bundle, + 'required' => FALSE, + 'description' => 'Select the canonical domain for this content.', + 'settings' => array( + 'handler' => 'default:domain', + // Handler_settings are deprecated but seem to be necessary here. + 'handler_settings' => [ + 'target_bundles' => NULL, + 'sort' => ['field' => 'weight', 'direction' => 'ASC'], + ], + 'target_bundles' => NULL, + 'sort' => ['field' => 'weight', 'direction' => 'ASC'], + ), + ); + $field_config = $field_config_storage->create($field); + $field_config->save(); + } + + // Tell the form system how to behave. Default to radio buttons. + // @TODO: This function is deprecated, but using the OO syntax is causing + // test fails. + entity_get_form_display($entity_type, $bundle, 'default') + ->setComponent(DOMAIN_SOURCE_FIELD, array( + 'type' => 'options_select', + 'weight' => 42, + )) + ->save(); +} + +/** + * Implements hook_ENTITY_TYPE_insert(). + * + * Creates our fields when new node types are created. + * + * @TODO: Make this possible for all entity types. + */ +function domain_source_node_type_insert(EntityInterface $entity) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ + if (!$entity->isSyncing()) { + // Do not fire hook when config sync in progress. + domain_source_confirm_fields('node', $entity->id()); + } +} + +/** + * Returns the source domain associated to an entity. + * + * @param Drupal\Core\Entity\EntityInterface $entity + * The entity to check. + * + * @return string|NULL + * The value assigned to the entity, either a domain id string or NULL. + */ +function domain_source_get(EntityInterface $entity) { + $source = NULL; + + if (!isset($entity->{DOMAIN_SOURCE_FIELD})) { + return $source; + } + + $value = $entity->get(DOMAIN_SOURCE_FIELD)->offsetGet(0); + if (!empty($value)) { + $target_id = $value->target_id; + if ($domain = \Drupal::service('entity_type.manager')->getStorage('domain')->load($target_id)) { + $source = $domain->id(); + } + } + return $source; +} + +/** + * Implements hook_form_alter(). + * + * Find forms that contain the domain source field and allow those to handle + * redirects properly. + */ +function domain_source_form_alter(&$form, &$form_state, $form_id) { + $object = $form_state->getFormObject(); + // Set up our TrustedRedirect handler for form saves. + if (isset($form[DOMAIN_SOURCE_FIELD]) && !empty($object) && is_callable([$object, 'getEntity']) && $entity = $object->getEntity()) { + foreach ($form['actions'] as $key => $element) { + // Redirect submit handlers, but not the preview button. + if ($key != 'preview' && isset($element['#type']) && $element['#type'] == 'submit') { + $form['actions'][$key]['#submit'][] = 'domain_source_form_submit'; + } + } + } +} + +/** + * Validate form submissions. + */ +function domain_source_form_validate($element, \Drupal\Core\Form\FormStateInterface $form_state) { + $values = $form_state->getValues(); + // This is only run if Domain Access is present. + $access_values = $values[DOMAIN_ACCESS_FIELD]; + $source_value = current($values[DOMAIN_SOURCE_FIELD]); + // If no value is selected, that's acceptable. Else run through a check. + $source_set = empty($source_value); + foreach ($access_values as $value) { + if ($value == $source_value) { + $source_set = TRUE; + } + } + if (!$source_set) { + $form_state->setError($element, t('The source domain must be selected as a publishing option.')); + } +} + +/** + * Redirect form submissions to other domains. + */ +function domain_source_form_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) { + // Ensure that we have saved an entity. + if ($object = $form_state->getFormObject()) { + $url = $object->getEntity()->url(); + } + // Validate that the URL will be considered "external" by Drupal, which means + // that a scheme value will be present. + if (!empty($url)) { + $uri_parts = parse_url($url); + // If necessary and secure, issue a TrustedRedirectResponse to the new URL. + if (!empty($uri_parts['host'])) { + // Pass a redirect if necessary. + if (DomainRedirectResponse::checkTrustedHost($uri_parts['host'])) { + $response = new TrustedRedirectResponse($url); + $form_state->setResponse($response); + } + } + } +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm. + */ +function domain_source_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) { + // Add the options hidden from the user silently to the form. + $manager = \Drupal::service('domain_source.element_manager'); + $hide = TRUE; + $form = $manager->setFormOptions($form, $form_state, DOMAIN_SOURCE_FIELD, $hide); + // Add validation if Domain Access is installed. + if (defined('DOMAIN_ACCESS_FIELD') && isset($form[DOMAIN_ACCESS_FIELD])) { + $form[DOMAIN_SOURCE_FIELD]['#element_validate'] = array('domain_source_form_validate'); + // If using a select field, load the JS to show/hide options. + if ($form[DOMAIN_SOURCE_FIELD]['widget']['#type'] == 'select') { + $form['#attached']['library'][] = 'domain_source/drupal.domain_source'; + } + } +} + +/** + * Implements hook_views_data_alter. + */ +function domain_source_views_data_alter(array &$data) { + $table = 'node__' . DOMAIN_SOURCE_FIELD; + $data[$table][DOMAIN_SOURCE_FIELD]['field']['id'] = 'domain_source'; + $data[$table][DOMAIN_SOURCE_FIELD . '_target_id']['filter']['id'] = 'domain_source'; + // Since domains are not stored in the database, relationships cannot be used. + unset($data[$table][DOMAIN_SOURCE_FIELD]['relationship']); +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Add options for domain source when using Devel Generate. + */ +function domain_source_form_devel_generate_form_content_alter(&$form, &$form_state, $form_id) { + // Add our element to the Devel generate form. + $list = ['_derive' => t('Derive from domain selection')]; + $list += \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(); + $form['domain_source'] = array( + '#title' => t('Domain source'), + '#type' => 'checkboxes', + '#options' => $list, + '#weight' => 4, + '#multiple' => TRUE, + '#size' => count($list) > 5 ? 5 : count($list), + '#default_value' => ['_derive'], + '#description' => t('Sets the source domain for created nodes.'), + ); +} + +/** + * Implements hook_ENTITY_TYPE_presave(). + * + * Fires only if Devel Generate module is present, to assign test nodes to + * domains. + */ +function domain_source_node_presave(EntityInterface $node) { + domain_source_presave_generate($node); +} + +/** + * Handles presave operations for devel generate. + */ +function domain_source_presave_generate(EntityInterface $entity) { + // Handle devel module settings. + $exists = \Drupal::moduleHandler()->moduleExists('devel_generate'); + $values = []; + $selections = []; + if ($exists && isset($entity->devel_generate)) { + // If set by the form. + if (isset($entity->devel_generate['domain_access'])) { + $selection = array_filter($entity->devel_generate['domain_access']); + if (isset($selection['random-selection'])) { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $selections = array_rand($domains, ceil(rand(1, count($domains)))); + } + else { + $selections = array_keys($selection); + } + } + if (isset($entity->devel_generate['domain_source'])) { + $selection = $entity->devel_generate['domain_source']; + if ($selection == '_derive') { + if (!empty($selections)) { + $values[DOMAIN_SOURCE_FIELD] = current($selections); + } + else { + $values[DOMAIN_SOURCE_FIELD] = NULL; + } + } + foreach ($values as $name => $value) { + $entity->set($name, $value); + } + } + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.routing.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.routing.yml new file mode 100644 index 000000000..551e37400 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.routing.yml @@ -0,0 +1,7 @@ +domain_source.settings: + path: '/admin/config/domain/domain_source' + defaults: + _title: 'Domain Source settings' + _form: '\Drupal\domain_source\Form\DomainSourceSettingsForm' + requirements: + _permission: 'administer domains' diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.services.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.services.yml new file mode 100644 index 000000000..127264fd4 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.services.yml @@ -0,0 +1,9 @@ +services: + domain_source.element_manager: + class: Drupal\domain_source\DomainSourceElementManager + arguments: ['@entity_type.manager'] + domain_source.path_processor: + class: Drupal\domain_source\HttpKernel\DomainSourcePathProcessor + arguments: ['@domain.negotiator', '@module_handler', '@entity_type.manager', '@path.alias_manager', '@config.factory'] + tags: + - { name: path_processor_outbound, priority: 200 } diff --git a/sites/all/modules/contrib/admin/domain/domain_source/js/domain_source.js b/sites/all/modules/contrib/admin/domain/domain_source/js/domain_source.js new file mode 100644 index 000000000..6e549663a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/js/domain_source.js @@ -0,0 +1,59 @@ +/** + * @file + * Attaches behaviors for the Domain Source module. + * + * If Domain Access is present, we show/hide selected publishing domains. This approach + * currently only works with a select field. + */ +(function ($) { + + "use strict"; + + /** + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.domainSourceAllowed = { + attach: function () { + + // Get the initial setting so that it can be reset. + var initialOption = $("#edit-field-domain-source").val(); + + // Onload, fire initial show/hide. + getDomains(); + + // Get the domains selected by the domain access field. + function getDomains() { + var domains = new Array(); + $("#edit-field-domain-access :checked").each(function(index, obj) { + domains.push(obj.value); + }); + setOptions(domains); + } + + // Based on selected domains, show/hide the selection options. + function setOptions(domains) { + $("#edit-field-domain-source option").each(function(index, obj) { + if (jQuery.inArray(obj.value, domains) == -1 && obj.value != '_none') { + // If the current selection is removed, reset the selection to _none. + if ($("#edit-field-domain-source").val() == obj.value) { + $("#edit-field-domain-source").val('_none'); + } + $("#edit-field-domain-source option[value=" + obj.value + "]").hide(); + } + else { + $("#edit-field-domain-source option[value=" + obj.value + "]").show(); + // If we reselected the initial value, reset the select option. + if (obj.value == initialOption) { + $("#edit-field-domain-source").val(obj.value); + } + } + }); + } + + // When the selections change, recalculate the select options. + $( "#edit-field-domain-access" ).on( "change", getDomains ); + } + }; + +})(jQuery); diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceElementManager.php b/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceElementManager.php new file mode 100644 index 000000000..b70d448b9 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceElementManager.php @@ -0,0 +1,27 @@ +getBuildInfo(); + $entity = $form_state->getFormObject()->getEntity(); + $entity_values = $entity->get(DOMAIN_SOURCE_FIELD)->offsetGet(0); + if (isset($field['widget']['#options']) && !empty($entity_values)) { + $value = $entity_values->getValue('target_id'); + $options = array_diff_key(array_flip($value), $field['widget']['#options']); + } + return array_keys($options); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceServiceProvider.php b/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceServiceProvider.php new file mode 100644 index 000000000..7baea2ba5 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceServiceProvider.php @@ -0,0 +1,20 @@ +getDefinition('redirect_response_subscriber'); + $definition->setClass('Drupal\domain_source\EventSubscriber\DomainSourceRedirectResponseSubscriber'); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/EventSubscriber/DomainSourceRedirectResponseSubscriber.php b/sites/all/modules/contrib/admin/domain/domain_source/src/EventSubscriber/DomainSourceRedirectResponseSubscriber.php new file mode 100644 index 000000000..7570b5582 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/EventSubscriber/DomainSourceRedirectResponseSubscriber.php @@ -0,0 +1,69 @@ +getResponse(); + if ($response instanceof RedirectResponse) { + $request = $event->getRequest(); + + // Let the 'destination' query parameter override the redirect target. + // If $response is already a SecuredRedirectResponse, it might reject the + // new target as invalid, in which case proceed with the old target. + $destination = $request->query->get('destination'); + if ($destination) { + // The 'Location' HTTP header must always be absolute. + $destination = $this->getDestinationAsAbsoluteUrl($destination, $request->getSchemeAndHttpHost()); + try { + $response->setTargetUrl($destination); + } + catch (\InvalidArgumentException $e) { + } + } + + // Regardless of whether the target is the original one or the overridden + // destination, ensure that all redirects are safe. + if (!($response instanceof SecuredRedirectResponse)) { + try { + // SecuredRedirectResponse is an abstract class that requires a + // concrete implementation. Default to DomainRedirectResponse, which + // considers only redirects to sites registered via Domain. + $safe_response = DomainRedirectResponse::createFromRedirectResponse($response); + $safe_response->setRequestContext($this->requestContext); + } + catch (\InvalidArgumentException $e) { + // If the above failed, it's because the redirect target wasn't + // local. Do not follow that redirect. Display an error message + // instead. We're already catching one exception, so trigger_error() + // rather than throw another one. + // We don't throw an exception, because this is a client error rather than a + // server error. + $message = 'Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.'; + trigger_error($message, E_USER_ERROR); + $safe_response = new Response($message, 400); + } + $event->setResponse($safe_response); + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/Form/DomainSourceSettingsForm.php b/sites/all/modules/contrib/admin/domain/domain_source/src/Form/DomainSourceSettingsForm.php new file mode 100644 index 000000000..7b69811ff --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/Form/DomainSourceSettingsForm.php @@ -0,0 +1,60 @@ +getDefinition('node')->getLinkTemplates(); + + foreach ($routes as $route => $path) { + // Some parts of the system prepend drupal:, which the routing + // system doesn't use. The routing system also uses underscores instead + // of dashes. Because Drupal. + $route = str_replace(['-', 'drupal:'], ['_', ''], $route); + $options[$route] = $route; + } + $config = $this->config('domain_source.settings'); + $form['exclude_routes'] = array( + '#type' => 'checkboxes', + '#title' => $this->t('Disable link rewrites for the selected routes.'), + '#default_value' => $config->get('exclude_routes', []), + '#options' => $options, + '#description' => $this->t('Check the routes to disable. Any entity URL with a Domain Source field will be rewritten unless its corresponding route is disabled.'), + ); + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->config('domain_source.settings') + ->set('exclude_routes', $form_state->getValue('exclude_routes')) + ->save(); + + parent::submitForm($form, $form_state); + } + + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php b/sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php new file mode 100644 index 000000000..c5ed83679 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php @@ -0,0 +1,262 @@ +negotiator = $negotiator; + $this->moduleHandler = $module_handler; + $this->entityTypeManager = $entity_type_manager; + $this->aliasManager = $alias_manager; + $this->configFactory = $config_factory; + $this->domainStorage = $entity_type_manager->getStorage('domain'); + } + + /** + * {@inheritdoc} + */ + public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) { + // Load the active domain. + $active_domain = $this->getActiveDomain(); + + // Only act on valid internal paths and when a domain loads. + if (empty($active_domain) || empty($path) || !empty($options['external'])) { + return $path; + } + + // Set the default source information. + $source = NULL; + $options['active_domain'] = $active_domain; + + // Get the current language. + $langcode = NULL; + if (!empty($options['language'])) { + $langcode = $options['language']->getId(); + } + // Get the URL object for this request. + $alias = $this->aliasManager->getPathByAlias($path, $langcode); + $url = Url::fromUserInput($alias, $options); + + // Check the route, if available. Entities can be configured to + // only rewrite specific routes. + if ($url->isRouted() && $this->allowedRoute($url->getRouteName())) { + // Load the entity to check + if (!empty($options['entity'])) { + $entity = $options['entity']; + } + else { + $parameters = $url->getRouteParameters(); + if (!empty($parameters)) { + $entity = $this->getEntity($parameters); + } + } + } + // One hook for entities. + if (!empty($entity)) { + // Enmsure we send the right translation. + if (!empty($langcode) && $entity->hasTranslation($langcode) && $translation = $entity->getTranslation($langcode)) { + $entity = $translation; + } + if ($target_id = domain_source_get($entity)) { + $source = $this->domainStorage->load($target_id); + } + $options['entity'] = $entity; + $options['entity_type'] = $entity->getEntityTypeId(); + $this->moduleHandler->alter('domain_source', $source, $path, $options); + } + // One for other, because the latter is resource-intensive. + else { + $this->moduleHandler->alter('domain_source_path', $source, $path, $options); + } + // If a source domain is specified, rewrite the link. + if (!empty($source)) { + // Note that url rewrites add a leading /, which getPath() also adds. + $options['base_url'] = trim($source->getPath(), '/'); + $options['absolute'] = TRUE; + } + return $path; + } + + /** + * Derive entity data from a given route's parameters. + * + * @param $parameters + * An array of route parameters. + * + * @return $entity|NULL + */ + public function getEntity($parameters) { + $entity = NULL; + $entity_type = key($parameters); + $entity_types = $this->getEntityTypes(); + foreach ($parameters as $entity_type => $value) { + if (!empty($entity_type) && isset($entity_types[$entity_type])) { + $entity = $this->entityTypeManager->getStorage($entity_type)->load($value); + } + } + return $entity; + } + + /** + * Checks that a route's common name is not disallowed. + * + * Looks at the name (e.g. canonical) of the route without regard for + * the entity type. + * + * @parameter $name + * The route name being checked. + * + * @return boolean + */ + public function allowedRoute($name) { + $excluded = $this->getExcludedRoutes(); + $parts = explode('.', $name); + $route_name = end($parts); + // Config is stored as an array. Empty items are not excluded. + return !isset($excluded[$route_name]); + } + + /** + * Gets an array of content entity types, keyed by type. + * + * @return array + */ + public function getEntityTypes() { + if (!isset($this->entityTypes)) { + foreach ($this->entityTypeManager->getDefinitions() as $type => $definition) { + if ($definition->getGroup() == 'content') { + $this->entityTypes[$type] = $type; + } + } + } + return $this->entityTypes; + } + + /** + * Gets the settings for domain source path rewrites. + * + * @return array + */ + public function getExcludedRoutes() { + if (!isset($this->excludedRoutes)) { + $config = $this->configFactory->get('domain_source.settings'); + $routes = $config->get('exclude_routes'); + if (is_array($routes)) { + $this->excludedRoutes = array_flip($routes); + } + else { + $this->excludedRoutes = []; + } + } + return $this->excludedRoutes; + } + + /** + * Gets the settings for domain source path rewrites. + * + * @return array + */ + public function getActiveDomain() { + if (!isset($this->activeDomain)) { + // Ensure that the loader has run. + // In some tests, the kernel event has not. + $active = $this->negotiator->getActiveDomain(); + if (empty($active)) { + $active = $this->negotiator->getActiveDomain(TRUE); + } + $this->activeDomain = $active; + } + return $this->activeDomain; + } +} + diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/field/DomainSource.php b/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/field/DomainSource.php new file mode 100644 index 000000000..756acbe4f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/field/DomainSource.php @@ -0,0 +1,50 @@ +options['settings']['link'])) { + foreach ($items as &$item) { + $object = $item['raw']; + $entity = $object->getEntity(); + $url = $entity->toUrl()->toString(); + $domain = $item['rendered']['#options']['entity']; + $item['rendered']['#type'] = 'markup'; + $item['rendered']['#markup'] = '' . $domain->label() . ''; + } + uasort($items, array($this, 'sort')); + } + + return $items; + } + + /** + * Sort the domain list, if possible. + */ + private function sort($a, $b) { + $domainA = isset($a['rendered']['#options']['entity']) ? $a['rendered']['#options']['entity'] : 0; + $domainB = isset($b['rendered']['#options']['entity']) ? $b['rendered']['#options']['entity'] : 0; + if ($domainA !== 0) { + return $domainA->getWeight() > $domainB->getWeight(); + } + // We don't have a domain object so sort as best we can. + return $a['rendered']['#plain_text'] > $b['rendered']['#plain_text']; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/filter/DomainSource.php b/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/filter/DomainSource.php new file mode 100644 index 000000000..a449ae495 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/filter/DomainSource.php @@ -0,0 +1,41 @@ +valueOptions)) { + $this->valueTitle = $this->t('Domains'); + $this->valueOptions = [ + '_active' => $this->t('Active domain'), + ] + \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(); + } + return $this->valueOptions; + } + + /** + * {@inheritdoc} + */ + public function query() { + $active_index = array_search('_active', (array)$this->value); + if ($active_index !== FALSE) { + $active_id = \Drupal::service('domain.negotiator')->getActiveId(); + $this->value[$active_index] = $active_id; + } + + parent::query(); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php new file mode 100644 index 000000000..2dac3aba0 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php @@ -0,0 +1,94 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainSourceElement() { + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + )); + $this->drupalLogin($admin); + + $this->drupalGet('node/add/article'); + $this->assertSession()->statusCodeEquals(200); + + // Set the title, so the node can be saved. + $this->fillField('title[0][value]', 'Test node'); + + // We expect to find 5 domain options. We set two as selected. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $count = 0; + $ids = ['example_com', 'one_example_com', 'two_example_com']; + foreach ($domains as $domain) { + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if (in_array($domain->id(), $ids)) { + $this->checkField($locator); + } + } + // Find the all affiliates field. + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->findField($locator); + + // Set all affiliates to TRUE. + $this->checkField($locator); + + // Find the Domain Source field. + $locator = DOMAIN_SOURCE_FIELD; + $this->findField($locator); + // Set it to one_example_com. + $this->selectFieldOption($locator, 'one_example_com'); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Edit the node. + $this->drupalGet('node/1/edit'); + $this->assertSession()->statusCodeEquals(200); + + // Set the domain source field to an unselected domain. + $this->selectFieldOption($locator, 'three_example_com'); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->assertEscaped('The source domain must be selected as a publishing option.'); + + // Check the URL. + $url = $this->geturl(); + $this->assert(strpos($url, 'node/1/edit') > 0, 'Form not submitted.'); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceEntityReferenceTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceEntityReferenceTest.php new file mode 100644 index 000000000..63cd4b649 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceEntityReferenceTest.php @@ -0,0 +1,167 @@ +admin_user = $this->drupalCreateUser(array( + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($this->admin_user); + + // Visit the article field administration page. + $this->drupalGet('admin/structure/types/manage/article/fields'); + $this->assertResponse(200, 'Manage fields page sourceed.'); + + // Check for a domain field. + $this->assertText('Domain Source', 'Domain form field found.'); + + // Visit the article field display administration page. + $this->drupalGet('admin/structure/types/manage/article/display'); + $this->assertResponse(200, 'Manage field display page sourceed.'); + + // Check for a domain field. + $this->assertText('Domain Source', 'Domain form field found.'); + } + + /** + * Tests the storage of the domain source field. + */ + public function testDomainSourceFieldStorage() { + $this->admin_user = $this->drupalCreateUser(array( + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'administer menu', + )); + $this->drupalLogin($this->admin_user); + + // Create 5 domains. + $this->domainCreateTestDomains(5); + + // Visit the article field display administration page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200); + + // Check the new field exists on the page. + $this->assertText('Domain Source', 'Found the domain field instance.'); + + // We expect to find 5 domain options + none. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $string = 'value="' . $domain->id() . '"'; + $this->assertRaw($string, 'Found the domain option.'); + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + $two_path = $domain->getPath(); + } + } + $this->assertRaw('value="_none"', 'Found the _none_ option.'); + + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); + + // Try to post a node, assigned to the second domain. + $edit['title[0][value]'] = 'Test node'; + $edit['field_domain_source'] = $two; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + $this->assertResponse(200); + $node = $node_storage->load(1); + // Check that the value is set. + $value = domain_source_get($node); + $this->assertTrue($value == $two, 'Node saved with proper source record.'); + + // Test the URL. + $url = $node->toUrl()->toString(); + $expected_url = $two_path . 'node/1'; + $this->assertTrue($expected_url == $url, 'URL rewritten correctly.'); + + // Try to post a node, assigned to no domain. + $edit['title[0][value]'] = 'Test node'; + $edit["field_domain_source"] = '_none'; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + $this->assertResponse(200); + $node = $node_storage->load(2); + // Check that the value is set. + $value = domain_source_get($node); + $this->assertNull($value, 'Node saved with proper source record.'); + + // Test the url. + $url = $node->toUrl()->toString(); + $expected_url = base_path() . 'node/2'; + $this->assertTrue($expected_url == $url, 'URL rewritten correctly.'); + + // Place the menu block. + $this->drupalPlaceBlock('system_menu_block:main'); + + // Enable main menu as available menu. + $edit = array( + 'menu_options[main]' => 1, + 'menu_parent' => 'main:', + ); + $this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type')); + + // Create a third node that is assigned to a menu. + $edit = array( + 'title[0][value]' => 'Node 3', + 'menu[enabled]' => 1, + 'menu[title]' => 'Test preview', + 'field_domain_source' => $two, + ); + $this->drupalPostForm('node/add/article', $edit, 'Save'); + // Test the URL against expectations, and the rendered menu link. + $node = $node_storage->load(3); + $url = $node->toUrl()->toString(); + $expected_url = $two_path . 'node/3'; + $this->assertTrue($expected_url == $url, 'URL rewritten correctly.'); + // Load the page with a menu and check that link. + $this->drupalGet('node/3'); + $this->assertRaw('href="' . $url, 'Menu link rewritten correctly.'); + + // Remove the field from the node type and make sure nothing breaks. + // See https://www.drupal.org/node/2892612 + $id = 'node.article.field_domain_source'; + if ($field = \Drupal::entityManager()->getStorage('field_config')->load($id)) { + $field->delete(); + field_purge_batch(10, $field->uuid()); + drupal_flush_all_caches(); + } + // Visit the article field display administration page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200); + // Try to post a node, assigned to no domain. + $edit2['title[0][value]'] = 'Test node'; + $this->drupalPostForm('node/add/article', $edit2, 'Save'); + // Test the URL against expectations, and the rendered menu link. + $node = $node_storage->load(4); + $url = $node->toUrl()->toString(); + $expected_url = '/node/4'; + $this->assertTrue($expected_url == $url, 'No URL rewrite performed.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceExcludeTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceExcludeTest.php new file mode 100644 index 000000000..33ec0d7e8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceExcludeTest.php @@ -0,0 +1,90 @@ +createNode(['type' => 'page', 'title' => 'foo', DOMAIN_SOURCE_FIELD => $id]); + + // Variables for our tests. + $path = 'node/1'; + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $source = $domains[$id]; + $expected = $source->getPath() . $path; + $route_name = 'entity.node.canonical'; + $route_parameters = ['node' => 1]; + $uri = 'entity:' . $path; + $uri_path = '/' . $path; + $options = []; + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute'); + + // Get the link using Url::fromUserInput() + $url = URL::fromUserInput($uri_path, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUserInput'); + + // Get the link using Url::fromUri() + $url = URL::fromUri($uri, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUri'); + + // Exclude the edit path from rewrites. + $config = $this->config('domain_source.settings'); + $config->set('exclude_routes', ['edit_form' => 'edit_form'])->save(); + + // Variables for our tests. + $path = 'node/1/edit'; + $expected = '/' . $path; + $route_name = 'entity.node.edit_form'; + $route_parameters = ['node' => 1]; + $uri = 'internal:/' . $path; + $uri_path = '/' . $path; + $options = []; + + // Because of path cache, we have to flush here. + drupal_flush_all_caches(); + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute' . $url); + + // Get the link using Url::fromUserInput() + $url = URL::fromUserInput($uri_path, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUserInput'); + + // Get the link using Url::fromUri() + $url = URL::fromUri($uri, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUri'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceLanguageTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceLanguageTest.php new file mode 100644 index 000000000..1adf48bcd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceLanguageTest.php @@ -0,0 +1,117 @@ +save(); + ConfigurableLanguage::createFromLangcode('af')->save(); + + // Enable content translation for the current entity type. + \Drupal::service('content_translation.manager')->setEnabled('node', 'page', TRUE); + + } + + public function testDomainSourceLanguage() { + // Create a node, assigned to a source domain. + $id = 'one_example_com'; + // Create one node with no language. + $node = $this->drupalCreateNode([ + 'body' => [[]], + 'status' => 1, + DOMAIN_SOURCE_FIELD => $id, + ]); + + // Programmatically create a translation. + $storage = \Drupal::entityTypeManager()->getStorage('node'); + // Reload the node. + $node = $storage->load(1); + // Create an Afrikaans translation assigned to domain 2. + $id2 = 'two_example_com'; + $translation = $node->addTranslation('af'); + $translation->title->value = $this->randomString(); + $translation->{DOMAIN_SOURCE_FIELD} = $id2; + $translation->status = 1; + $node->save(); + + // Variables for our tests. + $path = 'node/1'; + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $source = $domains[$id]; + $expected = $source->getPath() . $path; + $route_name = 'entity.node.canonical'; + $route_parameters = ['node' => 1]; + $uri = 'entity:' . $path; + $uri_path = '/' . $path; + $options = []; + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute'); + + // Get the link using Url::fromUserInput() + $url = URL::fromUserInput($uri_path, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUserInput'); + + // Get the link using Url::fromUri() + $url = URL::fromUri($uri, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUri'); + + // Now test the same for the Arfrikaans translation. + $path = 'node/1'; + $source = $domains[$id2]; + $expected = $source->getPath() . 'af/' . $path; + $route_name = 'entity.node.canonical'; + $route_parameters = ['node' => 1]; + $uri = 'entity:' . $path; + $uri_path = '/' . $path; + $language = \Drupal::entityTypeManager()->getStorage('configurable_language')->load('af'); + $options = ['language' => $language]; + + $translation = $node->getTranslation('af'); + $this->assertTrue(domain_source_get($translation) == $id2, domain_source_get($translation)); + + // Because of path cache, we have to flush here. + drupal_flush_all_caches(); + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute'); + + // Get the link using Url::fromUserInput() + $url = URL::fromUserInput($uri_path, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUserInput'); + + // Get the link using Url::fromUri() + $url = URL::fromUri($uri, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUri'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceTrustedHostTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceTrustedHostTest.php new file mode 100644 index 000000000..ccc22b93a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceTrustedHostTest.php @@ -0,0 +1,76 @@ +createNode(['type' => 'page', 'title' => 'foo', DOMAIN_SOURCE_FIELD => $id]); + + // Variables for our tests. + $path = 'node/1'; + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $source = $domains[$id]; + $expected = $source->getPath() . $path; + $route_name = 'entity.node.canonical'; + $route_parameters = ['node' => 1]; + $options = []; + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute'); + + // Set up two additional domains. + $domain2 = $domains['two_example_com']; + + // Check against trusted host patterns. + $settings['settings']['trusted_host_patterns'] = (object) [ + 'value' => ['^' . $this->prepareTrustedHostname($domain2->getHostname()) . '$'], + 'required' => TRUE, + ]; + $this->writeSettings($settings); + // This URL should fail due to trusted host omission. + $this->drupalGet($url); + $this->assertRaw('The provided host name is not valid for this server.'); + + // Now switch the node to a domain that is trusted. + $node->{DOMAIN_SOURCE_FIELD} = $domain2->id(); + $node->save(); + // Get the link using Url::fromRoute(). + $expected = $domain2->getPath() . $path; + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + // Assert that the URL is what we expect. + $this->assertTrue($url == $expected, 'fromRoute'); + $this->drupalGet($url); + $this->assertResponse(200, 'Url is validated by trusted host settings.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceUrlTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceUrlTest.php new file mode 100644 index 000000000..302f2745d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceUrlTest.php @@ -0,0 +1,62 @@ +createNode(['type' => 'page', 'title' => 'foo', DOMAIN_SOURCE_FIELD => $id]); + + // Variables for our tests. + $path = 'node/1'; + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $source = $domains[$id]; + $expected = $source->getPath() . $path; + $route_name = 'entity.node.canonical'; + $route_parameters = ['node' => 1]; + $uri = 'entity:' . $path; + $uri_path = '/' . $path; + $options = []; + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute'); + + // Get the link using Url::fromUserInput() + $url = URL::fromUserInput($uri_path, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUserInput'); + + // Get the link using Url::fromUri() + $url = URL::fromUri($uri, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUri'); + } + +} diff --git a/sites/all/modules/figli/edlp_corpus/assets/dist/scripts/corpus.min.js b/sites/all/modules/figli/edlp_corpus/assets/dist/scripts/corpus.min.js index 15a1140e3..07b8ba093 100644 --- a/sites/all/modules/figli/edlp_corpus/assets/dist/scripts/corpus.min.js +++ b/sites/all/modules/figli/edlp_corpus/assets/dist/scripts/corpus.min.js @@ -24,17 +24,21 @@ var _canvas = _$canvas[0]; var _ctx = _canvas.getContext('2d'); var _canvas_props = { - 'margin-top':100, - 'margin-bottom':100 + 'margin_top':100, + 'margin_right':0, + 'margin_bottom':100, + 'margin_left':0 }; var _physics = new Physics(); // var _stage = new createjs.Stage('edlp-map'); var _nodes = []; // var _particules = []; - var _p_radius = 6; + var _p_radius = 3; // nodes radius (real radius, not diametre) var _p_velocity_factor = 0.5; var _m_pos = {x:0, y:0}; var _node_hover_id = -1; + var _$entrees_block = $('#block-edlpentreesblock'); + var _$entrees_block_termlinks = $('a.term-link', _$entrees_block); var _entrees_colors = { "130":"rgb(75, 143, 126)", "121":"rgb(134, 142, 36)", @@ -69,15 +73,14 @@ console.log("EdlpCorpus init()"); // console.log("document", document); initMap(); + }; function initMap(){ console.log("EdlpCorpus initMap()"); // console.log('_physics',_physics); initCanvas(); - initEvents(); loadCorpus(); - }; // ______ @@ -93,38 +96,13 @@ function onResizeCanvas() { _canvas.width = window.innerWidth; - _canvas.height = window.innerHeight - _canvas_props['margin-top'] - _canvas_props['margin-bottom']; - _canvas.style['margin-top'] = _canvas_props['margin-top']+"px"; - _canvas.style['margin-bottom'] = _canvas_props['margin-bottom']+"px"; + _canvas.height = window.innerHeight; + // - _canvas_props['margin-top'] - _canvas_props['margin-bottom']; + // _canvas.style['margin-top'] = _canvas_props['margin-top']+"px"; + // _canvas.style['margin-bottom'] = _canvas_props['margin-bottom']+"px"; }; - // ______ __ - // / ____/ _____ ____ / /______ - // / __/ | | / / _ \/ __ \/ __/ ___/ - // / /___ | |/ / __/ / / / /_(__ ) - // /_____/ |___/\___/_/ /_/\__/____/ - function initEvents(){ - _$container - .on('mousemove', function(event) { - event.preventDefault(); - // console.log("onMouseMove", event); - _m_pos.x = event.originalEvent.clientX; - _m_pos.y = event.originalEvent.clientY; - // console.log("Node pos: ", {x:_nodes[0].x, y:_nodes[0].y}); - // console.log("Mouse pos: ", {x:_m_pos.x, y:_m_pos.y}); - }) - .on('click', function(event) { - if(event.target.tagName != "A"){ - console.log("Corpus : click", event); - event.preventDefault(); - if(_node_hover_id != -1){ - console.log("click on node", _nodes[_node_hover_id]); - } - } - }); - }; - // ___ _ // / | (_)___ __ __ // / /| | / / __ `/ |/_/ @@ -135,13 +113,16 @@ // console.log('drupalSettings',drupalSettings); // console.log('window.location', window.location); var path = window.location.origin + drupalSettings.basepath + "edlp/corpus"; - $.getJSON(path, {}, function(data){ - console.log('corpus loaded : data', data); - // console.log('first node', data.nodes[0]); - // buildParticles(data.nodes); - buildNodes(data.nodes); - startAnime(); - }); + $.getJSON(path, {}, onCorpusLoaded); + }; + + function onCorpusLoaded(data){ + console.log('corpus loaded : data', data); + // console.log('first node', data.nodes[0]); + // buildParticles(data.nodes); + buildNodes(data.nodes); + initEvents(); + startAnime(); }; // _ __ __ @@ -155,29 +136,35 @@ var x,y,d; for (var i in nodes) { // console.log('i',i); - x = _p_radius*2 + Math.random()*(_canvas.width - _p_radius*4); - y = _p_radius*2 + Math.random()*(_canvas.height - _p_radius*4); d = i < 1 ? true : false; - _nodes.push(new Node(i,nodes[i],x,y,d)); - // if(i==4) + _nodes.push(new Node(i,nodes[i],d)); + // if(i==0) // break; } }; - function Node(i,n,x,y,d){ + function Node(i,node,d){ this.id = i; - for(key in n) - this[key] = n[key]; + for(key in node) + this[key] = node[key]; this.debug = d; - this.radius = _p_radius; this.mass = 8; + this.base_radius = _p_radius; this.g = { l : 1, // drawing line width s : 1 // drawing space between squares + }; + this.real_radius = this.base_radius + (this.g.l+this.g.s)*this.entrees.length; + this.wall_limits = { + top: _canvas_props.margin_top +this.real_radius, + right: _canvas.width -_canvas_props.margin_right -this.real_radius, + bottom: _canvas.height -_canvas_props.margin_bottom -this.real_radius, + left: _canvas_props.margin_left +this.real_radius } - this.x = x; - this.y = y; + this.x = this.wall_limits.left + Math.random()*(this.wall_limits.right - this.wall_limits.left); + this.y = this.wall_limits.top + Math.random()*(this.wall_limits.bottom - this.wall_limits.top); + this.hover = false; // physics this.p = _physics.makeParticle(this.mass, this.x, this.y); @@ -202,17 +189,16 @@ }; Node.prototype.checkWallBouncing = function(){ - // TODO: correct waal bouncing with centered squares if( - (this.x < 0 && this.p.velocity.x < 0) - || (this.x > _canvas.width-this.radius && this.p.velocity.x > 0) + (this.x < this.wall_limits.left && this.p.velocity.x < 0) + || (this.x > this.wall_limits.right && this.p.velocity.x > 0) ){ // console.log("bounce x"); this.p.velocity.multiplyScalarXY(-1, 1).multiplyScalar(0.75); } if( - (this.y < 0 && this.p.velocity.y < 0) - || (this.y > _canvas.height-this.radius && this.p.velocity.y > 0) + (this.y < this.wall_limits.top && this.p.velocity.y < 0) + || (this.y > this.wall_limits.bottom && this.p.velocity.y > 0) ){ // console.log("bounce y"); this.p.velocity.multiplyScalarXY(1,-1).multiplyScalar(0.75); @@ -225,36 +211,51 @@ }; Node.prototype.checkMouse = function(){ - // TODO: get the real w and h of node (with entrees squares) - if( _m_pos.x > this.x - && _m_pos.x < this.x+this.radius - && _m_pos.y > this.y - && _m_pos.y < this.y+this.radius){ + + if( _m_pos.x > this.x - this.real_radius + && _m_pos.x < this.x + this.real_radius + && _m_pos.y > this.y - this.real_radius + && _m_pos.y < this.y + this.real_radius){ if(_node_hover_id == -1 || _node_hover_id == this.id){ console.log("Node hover", this.id); this.hover = true; + this.highlightEntries(); _node_hover_id = this.id; } }else{ this.hover = false; if (_node_hover_id == this.id) { _node_hover_id = -1; + _$entrees_block_termlinks.removeClass('highlighted'); } } }; - Node.prototype.draw = function(){ - // TODO: center the squares around the pos - _ctx.beginPath(); - _ctx.lineWidth = this.g.l; + Node.prototype.highlightEntries = function(){ + _$entrees_block_termlinks.removeClass('highlighted'); + var entree; for (var i = 0; i < this.entrees.length; i++) { - _ctx.strokeStyle = this.hover ? "red" : _entrees_colors[this.entrees[i]]; - _ctx.strokeRect(this.x-4*i, this.y-4*i, this.radius+8*i, this.radius+8*i); + entree = this.entrees[i]; + _$entrees_block_termlinks.filter(function(){ + return $(this).attr('tid') == entree; + }).addClass('highlighted'); + } + } + + Node.prototype.draw = function(){ + _ctx.beginPath(); + _ctx.lineWidth = this.hover ? this.g.l*2 : this.g.l; + var r,d; + for (var i = 0; i < this.entrees.length; i++) { + _ctx.strokeStyle = _entrees_colors[this.entrees[i]]; + r = this.base_radius + (this.g.l+this.g.s)*i; // radius + d = r*2; // diametre + _ctx.strokeRect(this.x - r,this.y - r,d,d); } _ctx.closePath(); // _ctx.beginPath(); // _ctx.fillStyle = this.hover ? "red" : this.debug ? "blue" : "black"; - // _ctx.fillRect(this.x, this.y, this.radius, this.radius); + // _ctx.fillRect(this.x, this.y, this.base_radius, this.base_radius); // _ctx.closePath(); }; @@ -321,6 +322,42 @@ } }; + // ______ __ + // / ____/ _____ ____ / /______ + // / __/ | | / / _ \/ __ \/ __/ ___/ + // / /___ | |/ / __/ / / / /_(__ ) + // /_____/ |___/\___/_/ /_/\__/____/ + function initEvents(){ + _$container + .on('mousemove', function(event) { + event.preventDefault(); + // console.log("onMouseMove", event); + _m_pos.x = event.originalEvent.clientX; + _m_pos.y = event.originalEvent.clientY; + // console.log("/ _ / - / _ /"); + // console.log("Node pos: ", {x:_nodes[0].x, y:_nodes[0].y}); + // console.log("Mouse pos: ", {x:_m_pos.x, y:_m_pos.y}); + }) + .on('click', function(event) { + if(event.target.tagName != "A"){ + console.log("Corpus : click", event); + event.preventDefault(); + if(_node_hover_id != -1){ + console.log("click on node", _nodes[_node_hover_id]); + } + } + }); + + _$entrees_block_termlinks.on('click', function(event) { + event.preventDefault(); + + _$entrees_block_termlinks.parents('li').removeClass('opened'); + $(this).parents('li').addClass('opened'); + return false; + }); + }; + + // ____ __ // / __ \___ ____ ____/ /__ _____ // / /_/ / _ \/ __ \/ __ / _ \/ ___/ @@ -332,6 +369,11 @@ for (var i = 0; i < _nodes.length; i++) { _nodes[i].onUpdate(); } + if(_node_hover_id != -1){ + document.body.style.cursor = 'pointer'; + }else{ + document.body.style.cursor = 'auto'; + } }; function startAnime(){ diff --git a/sites/all/modules/figli/edlp_corpus/assets/scripts/corpus.js b/sites/all/modules/figli/edlp_corpus/assets/scripts/corpus.js index 15a1140e3..07b8ba093 100644 --- a/sites/all/modules/figli/edlp_corpus/assets/scripts/corpus.js +++ b/sites/all/modules/figli/edlp_corpus/assets/scripts/corpus.js @@ -24,17 +24,21 @@ var _canvas = _$canvas[0]; var _ctx = _canvas.getContext('2d'); var _canvas_props = { - 'margin-top':100, - 'margin-bottom':100 + 'margin_top':100, + 'margin_right':0, + 'margin_bottom':100, + 'margin_left':0 }; var _physics = new Physics(); // var _stage = new createjs.Stage('edlp-map'); var _nodes = []; // var _particules = []; - var _p_radius = 6; + var _p_radius = 3; // nodes radius (real radius, not diametre) var _p_velocity_factor = 0.5; var _m_pos = {x:0, y:0}; var _node_hover_id = -1; + var _$entrees_block = $('#block-edlpentreesblock'); + var _$entrees_block_termlinks = $('a.term-link', _$entrees_block); var _entrees_colors = { "130":"rgb(75, 143, 126)", "121":"rgb(134, 142, 36)", @@ -69,15 +73,14 @@ console.log("EdlpCorpus init()"); // console.log("document", document); initMap(); + }; function initMap(){ console.log("EdlpCorpus initMap()"); // console.log('_physics',_physics); initCanvas(); - initEvents(); loadCorpus(); - }; // ______ @@ -93,38 +96,13 @@ function onResizeCanvas() { _canvas.width = window.innerWidth; - _canvas.height = window.innerHeight - _canvas_props['margin-top'] - _canvas_props['margin-bottom']; - _canvas.style['margin-top'] = _canvas_props['margin-top']+"px"; - _canvas.style['margin-bottom'] = _canvas_props['margin-bottom']+"px"; + _canvas.height = window.innerHeight; + // - _canvas_props['margin-top'] - _canvas_props['margin-bottom']; + // _canvas.style['margin-top'] = _canvas_props['margin-top']+"px"; + // _canvas.style['margin-bottom'] = _canvas_props['margin-bottom']+"px"; }; - // ______ __ - // / ____/ _____ ____ / /______ - // / __/ | | / / _ \/ __ \/ __/ ___/ - // / /___ | |/ / __/ / / / /_(__ ) - // /_____/ |___/\___/_/ /_/\__/____/ - function initEvents(){ - _$container - .on('mousemove', function(event) { - event.preventDefault(); - // console.log("onMouseMove", event); - _m_pos.x = event.originalEvent.clientX; - _m_pos.y = event.originalEvent.clientY; - // console.log("Node pos: ", {x:_nodes[0].x, y:_nodes[0].y}); - // console.log("Mouse pos: ", {x:_m_pos.x, y:_m_pos.y}); - }) - .on('click', function(event) { - if(event.target.tagName != "A"){ - console.log("Corpus : click", event); - event.preventDefault(); - if(_node_hover_id != -1){ - console.log("click on node", _nodes[_node_hover_id]); - } - } - }); - }; - // ___ _ // / | (_)___ __ __ // / /| | / / __ `/ |/_/ @@ -135,13 +113,16 @@ // console.log('drupalSettings',drupalSettings); // console.log('window.location', window.location); var path = window.location.origin + drupalSettings.basepath + "edlp/corpus"; - $.getJSON(path, {}, function(data){ - console.log('corpus loaded : data', data); - // console.log('first node', data.nodes[0]); - // buildParticles(data.nodes); - buildNodes(data.nodes); - startAnime(); - }); + $.getJSON(path, {}, onCorpusLoaded); + }; + + function onCorpusLoaded(data){ + console.log('corpus loaded : data', data); + // console.log('first node', data.nodes[0]); + // buildParticles(data.nodes); + buildNodes(data.nodes); + initEvents(); + startAnime(); }; // _ __ __ @@ -155,29 +136,35 @@ var x,y,d; for (var i in nodes) { // console.log('i',i); - x = _p_radius*2 + Math.random()*(_canvas.width - _p_radius*4); - y = _p_radius*2 + Math.random()*(_canvas.height - _p_radius*4); d = i < 1 ? true : false; - _nodes.push(new Node(i,nodes[i],x,y,d)); - // if(i==4) + _nodes.push(new Node(i,nodes[i],d)); + // if(i==0) // break; } }; - function Node(i,n,x,y,d){ + function Node(i,node,d){ this.id = i; - for(key in n) - this[key] = n[key]; + for(key in node) + this[key] = node[key]; this.debug = d; - this.radius = _p_radius; this.mass = 8; + this.base_radius = _p_radius; this.g = { l : 1, // drawing line width s : 1 // drawing space between squares + }; + this.real_radius = this.base_radius + (this.g.l+this.g.s)*this.entrees.length; + this.wall_limits = { + top: _canvas_props.margin_top +this.real_radius, + right: _canvas.width -_canvas_props.margin_right -this.real_radius, + bottom: _canvas.height -_canvas_props.margin_bottom -this.real_radius, + left: _canvas_props.margin_left +this.real_radius } - this.x = x; - this.y = y; + this.x = this.wall_limits.left + Math.random()*(this.wall_limits.right - this.wall_limits.left); + this.y = this.wall_limits.top + Math.random()*(this.wall_limits.bottom - this.wall_limits.top); + this.hover = false; // physics this.p = _physics.makeParticle(this.mass, this.x, this.y); @@ -202,17 +189,16 @@ }; Node.prototype.checkWallBouncing = function(){ - // TODO: correct waal bouncing with centered squares if( - (this.x < 0 && this.p.velocity.x < 0) - || (this.x > _canvas.width-this.radius && this.p.velocity.x > 0) + (this.x < this.wall_limits.left && this.p.velocity.x < 0) + || (this.x > this.wall_limits.right && this.p.velocity.x > 0) ){ // console.log("bounce x"); this.p.velocity.multiplyScalarXY(-1, 1).multiplyScalar(0.75); } if( - (this.y < 0 && this.p.velocity.y < 0) - || (this.y > _canvas.height-this.radius && this.p.velocity.y > 0) + (this.y < this.wall_limits.top && this.p.velocity.y < 0) + || (this.y > this.wall_limits.bottom && this.p.velocity.y > 0) ){ // console.log("bounce y"); this.p.velocity.multiplyScalarXY(1,-1).multiplyScalar(0.75); @@ -225,36 +211,51 @@ }; Node.prototype.checkMouse = function(){ - // TODO: get the real w and h of node (with entrees squares) - if( _m_pos.x > this.x - && _m_pos.x < this.x+this.radius - && _m_pos.y > this.y - && _m_pos.y < this.y+this.radius){ + + if( _m_pos.x > this.x - this.real_radius + && _m_pos.x < this.x + this.real_radius + && _m_pos.y > this.y - this.real_radius + && _m_pos.y < this.y + this.real_radius){ if(_node_hover_id == -1 || _node_hover_id == this.id){ console.log("Node hover", this.id); this.hover = true; + this.highlightEntries(); _node_hover_id = this.id; } }else{ this.hover = false; if (_node_hover_id == this.id) { _node_hover_id = -1; + _$entrees_block_termlinks.removeClass('highlighted'); } } }; - Node.prototype.draw = function(){ - // TODO: center the squares around the pos - _ctx.beginPath(); - _ctx.lineWidth = this.g.l; + Node.prototype.highlightEntries = function(){ + _$entrees_block_termlinks.removeClass('highlighted'); + var entree; for (var i = 0; i < this.entrees.length; i++) { - _ctx.strokeStyle = this.hover ? "red" : _entrees_colors[this.entrees[i]]; - _ctx.strokeRect(this.x-4*i, this.y-4*i, this.radius+8*i, this.radius+8*i); + entree = this.entrees[i]; + _$entrees_block_termlinks.filter(function(){ + return $(this).attr('tid') == entree; + }).addClass('highlighted'); + } + } + + Node.prototype.draw = function(){ + _ctx.beginPath(); + _ctx.lineWidth = this.hover ? this.g.l*2 : this.g.l; + var r,d; + for (var i = 0; i < this.entrees.length; i++) { + _ctx.strokeStyle = _entrees_colors[this.entrees[i]]; + r = this.base_radius + (this.g.l+this.g.s)*i; // radius + d = r*2; // diametre + _ctx.strokeRect(this.x - r,this.y - r,d,d); } _ctx.closePath(); // _ctx.beginPath(); // _ctx.fillStyle = this.hover ? "red" : this.debug ? "blue" : "black"; - // _ctx.fillRect(this.x, this.y, this.radius, this.radius); + // _ctx.fillRect(this.x, this.y, this.base_radius, this.base_radius); // _ctx.closePath(); }; @@ -321,6 +322,42 @@ } }; + // ______ __ + // / ____/ _____ ____ / /______ + // / __/ | | / / _ \/ __ \/ __/ ___/ + // / /___ | |/ / __/ / / / /_(__ ) + // /_____/ |___/\___/_/ /_/\__/____/ + function initEvents(){ + _$container + .on('mousemove', function(event) { + event.preventDefault(); + // console.log("onMouseMove", event); + _m_pos.x = event.originalEvent.clientX; + _m_pos.y = event.originalEvent.clientY; + // console.log("/ _ / - / _ /"); + // console.log("Node pos: ", {x:_nodes[0].x, y:_nodes[0].y}); + // console.log("Mouse pos: ", {x:_m_pos.x, y:_m_pos.y}); + }) + .on('click', function(event) { + if(event.target.tagName != "A"){ + console.log("Corpus : click", event); + event.preventDefault(); + if(_node_hover_id != -1){ + console.log("click on node", _nodes[_node_hover_id]); + } + } + }); + + _$entrees_block_termlinks.on('click', function(event) { + event.preventDefault(); + + _$entrees_block_termlinks.parents('li').removeClass('opened'); + $(this).parents('li').addClass('opened'); + return false; + }); + }; + + // ____ __ // / __ \___ ____ ____/ /__ _____ // / /_/ / _ \/ __ \/ __ / _ \/ ___/ @@ -332,6 +369,11 @@ for (var i = 0; i < _nodes.length; i++) { _nodes[i].onUpdate(); } + if(_node_hover_id != -1){ + document.body.style.cursor = 'pointer'; + }else{ + document.body.style.cursor = 'auto'; + } }; function startAnime(){ diff --git a/sites/all/modules/figli/edlp_corpus/src/Plugin/Block/BlockEntrees.php b/sites/all/modules/figli/edlp_corpus/src/Plugin/Block/BlockEntrees.php index 8e28d21f2..daa2db5b0 100644 --- a/sites/all/modules/figli/edlp_corpus/src/Plugin/Block/BlockEntrees.php +++ b/sites/all/modules/figli/edlp_corpus/src/Plugin/Block/BlockEntrees.php @@ -30,13 +30,66 @@ class BlockEntrees extends BlockBase { $terms = entity_load_multiple('taxonomy_term', $tids); // dsm($terms); foreach ($terms as $term) { + // dpm($term->toArray()); + $tid = $term->id(); $name = $term->getName(); - $url = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $tid]); - $link = Link::fromTextAndUrl($name, $url); - $link = $link->toRenderable(); - $link["#attributes"]["class"] = 'term-'.$term->id(); - $entrees[] = $link; + + $term_url = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $tid]); + $term_link = Link::fromTextAndUrl($name, $term_url)->toRenderable(); + $term_link['#prefix'] = ''; + $term_link['#suffix'] = ""; + $term_link["#attributes"] = array( + "class" => array('term-'.$term->id(), 'term-link'), + "tid" => $term->id() + ); + + $index_url = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $tid]); + $index_link = Link::fromTextAndUrl('index', $index_url)->toRenderable(); + $index_link['#prefix'] = ''; + $index_link['#suffix'] = ""; + $index_link['#attributes'] = array( + 'class' => array('index-link'), + "tid" => $term->id() + ); + + + $notice_url = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $tid]); + $notice_link = Link::fromTextAndUrl('notice', $notice_url)->toRenderable(); + $notice_link['#prefix'] = ''; + $notice_link['#suffix'] = ""; + $notice_link['#attributes'] = array( + 'class' => array('notice-link'), + "tid" => $term->id() + ); + + $description = $term->get('description')->value; + // dpm($description); + + $entree = array( + '#wrapper_attributes' => array( + 'class' => array('term-'.$term->id(), 'entree'), + 'tid' => $term->id() + ), + 'term_link' => $term_link, + 'entree_content' => array( + '#type' => 'container', + '#attributes' => array( + 'class' => 'entree-content' + ), + 'index_link' => $index_link, + 'notice_link' => $notice_link, + 'description' => array( + '#type' => 'container', + '#markup' => $description, + '#attributes' => array('class'=>'term-description') + ) + ) + ); +// + // dpm($entree); + + $entrees[] = $entree; } return array ( diff --git a/sites/all/themes/custom/edlptheme/assets/dist/styles/app.min.css b/sites/all/themes/custom/edlptheme/assets/dist/styles/app.min.css index 322cf5caf..4ef79124f 100644 --- a/sites/all/themes/custom/edlptheme/assets/dist/styles/app.min.css +++ b/sites/all/themes/custom/edlptheme/assets/dist/styles/app.min.css @@ -32,29 +32,22 @@ main[role="main"] { background-color: rgba(255, 255, 255, 0.9); } footer[role="contentinfo"] { - outline: 1px solid pink; position: fixed; bottom: 0; width: 100%; } -canvas#edlp-map { - outline: 1px solid grey; } - footer { - text-align: center; } + display: table; + padding: 0 0 0.5em 0; } footer > .region { - outline: 1px dotted pink; } + display: table-cell; } footer .region-footer-left { - float: left; text-align: left; } footer .region-footer-center { - display: inline-block; - margin: 0 auto; text-align: center; } footer .region-footer-right { - float: right; text-align: right; - padding-right: 5em; } + min-width: 30px; } footer nav.block-menu { display: inline-block; } footer nav.block-menu ul { @@ -86,7 +79,8 @@ footer { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotateZ(-45deg); - transform: rotateZ(-45deg); } + transform: rotateZ(-45deg); + white-space: nowrap; } footer .block-language ul li { margin: 0; padding: 0; @@ -106,7 +100,10 @@ footer { position: relative; list-style: none; margin: 0 1.5em 0 0; - padding: 0; } + padding: 0; + margin: 0; + white-space: nowrap; + pointer-events: none; } footer .block-block-edlp-entrees ul li a { position: absolute; -webkit-transform-origin: left bottom; @@ -118,48 +115,148 @@ footer { text-transform: uppercase; font-size: 0.756em; white-space: nowrap; } - footer .block-block-edlp-entrees ul li a:before { - content: ""; + footer .block-block-edlp-entrees ul li span.oblique-wrapper { display: inline-block; - width: 7px; - height: 7px; - border: 1px solid #000; - margin-right: 0.5em; } - footer .block-block-edlp-entrees ul li a.term-134:before { - border-color: #2b8f2f; } - footer .block-block-edlp-entrees ul li a.term-121:before { - border-color: #3a33b6; } - footer .block-block-edlp-entrees ul li a.term-125:before { - border-color: #2c9f57; } - footer .block-block-edlp-entrees ul li a.term-119:before { - border-color: #c48978; } - footer .block-block-edlp-entrees ul li a.term-132:before { - border-color: #5270bb; } - footer .block-block-edlp-entrees ul li a.term-122:before { - border-color: #fb54d3; } - footer .block-block-edlp-entrees ul li a.term-129:before { - border-color: #e07483; } - footer .block-block-edlp-entrees ul li a.term-120:before { - border-color: #655845; } - footer .block-block-edlp-entrees ul li a.term-130:before { - border-color: #7e0868; } - footer .block-block-edlp-entrees ul li a.term-118:before { - border-color: #0e7121; } - footer .block-block-edlp-entrees ul li a.term-127:before { - border-color: #dabd42; } - footer .block-block-edlp-entrees ul li a.term-133:before { - border-color: #0399bb; } - footer .block-block-edlp-entrees ul li a.term-128:before { - border-color: #399a1c; } - footer .block-block-edlp-entrees ul li a.term-124:before { - border-color: #708540; } - footer .block-block-edlp-entrees ul li a.term-116:before { - border-color: #191bff; } - footer .block-block-edlp-entrees ul li a.term-117:before { - border-color: #279d84; } - footer .block-block-edlp-entrees ul li a.term-131:before { - border-color: #5219ab; } - footer .block-block-edlp-entrees ul li a.term-126:before { - border-color: #d49cb6; } - footer .block-block-edlp-entrees ul li a.term-123:before { - border-color: #497715; } + position: relative; + width: 1.5em; } + footer .block-block-edlp-entrees ul li span.oblique-wrapper a { + text-transform: none; } + footer .block-block-edlp-entrees ul li a.term-link { + pointer-events: all; } + footer .block-block-edlp-entrees ul li a.term-link:before { + content: ""; + display: inline-block; + width: 7px; + height: 7px; + border: 1px solid #000; + margin-right: 0.5em; } + footer .block-block-edlp-entrees ul li a.term-link[tid='134']:before { + border-color: #2b8f2f; + background-color: #2b8f2f; } + footer .block-block-edlp-entrees ul li a.term-link[tid='121']:before { + border-color: #3a33b6; + background-color: #3a33b6; } + footer .block-block-edlp-entrees ul li a.term-link[tid='125']:before { + border-color: #2c9f57; + background-color: #2c9f57; } + footer .block-block-edlp-entrees ul li a.term-link[tid='119']:before { + border-color: #c48978; + background-color: #c48978; } + footer .block-block-edlp-entrees ul li a.term-link[tid='132']:before { + border-color: #5270bb; + background-color: #5270bb; } + footer .block-block-edlp-entrees ul li a.term-link[tid='122']:before { + border-color: #fb54d3; + background-color: #fb54d3; } + footer .block-block-edlp-entrees ul li a.term-link[tid='129']:before { + border-color: #e07483; + background-color: #e07483; } + footer .block-block-edlp-entrees ul li a.term-link[tid='120']:before { + border-color: #655845; + background-color: #655845; } + footer .block-block-edlp-entrees ul li a.term-link[tid='130']:before { + border-color: #7e0868; + background-color: #7e0868; } + footer .block-block-edlp-entrees ul li a.term-link[tid='118']:before { + border-color: #0e7121; + background-color: #0e7121; } + footer .block-block-edlp-entrees ul li a.term-link[tid='127']:before { + border-color: #dabd42; + background-color: #dabd42; } + footer .block-block-edlp-entrees ul li a.term-link[tid='133']:before { + border-color: #0399bb; + background-color: #0399bb; } + footer .block-block-edlp-entrees ul li a.term-link[tid='128']:before { + border-color: #399a1c; + background-color: #399a1c; } + footer .block-block-edlp-entrees ul li a.term-link[tid='124']:before { + border-color: #708540; + background-color: #708540; } + footer .block-block-edlp-entrees ul li a.term-link[tid='116']:before { + border-color: #191bff; + background-color: #191bff; } + footer .block-block-edlp-entrees ul li a.term-link[tid='117']:before { + border-color: #279d84; + background-color: #279d84; } + footer .block-block-edlp-entrees ul li a.term-link[tid='131']:before { + border-color: #5219ab; + background-color: #5219ab; } + footer .block-block-edlp-entrees ul li a.term-link[tid='126']:before { + border-color: #d49cb6; + background-color: #d49cb6; } + footer .block-block-edlp-entrees ul li a.term-link[tid='123']:before { + border-color: #497715; + background-color: #497715; } + footer .block-block-edlp-entrees ul li .entree-content { + display: inline-block; + width: 0; + overflow: hidden; + opacity: 0; + -webkit-transition: all 300ms ease-in-out; + transition: all 300ms ease-in-out; + -webkit-transition-property: width,opacity; + transition-property: width,opacity; } + footer .block-block-edlp-entrees ul li .entree-content .term-description { + display: inline-block; + margin-left: 2em; + text-align: left; + width: 250px; + white-space: normal; + background-color: rgba(255, 255, 255, 0.9); + padding: 0.5em; } + footer .block-block-edlp-entrees ul li .entree-content .term-description p { + font-size: 0.65em; } + footer .block-block-edlp-entrees ul li:not(.opened) a.term-link:not(:hover):not(.highlighted):before { + background-color: #fff !important; } + footer .block-block-edlp-entrees ul li.opened .entree-content { + width: 350px; + opacity: 1; } + footer #block-userlogin { + position: relative; + width: 20px; + height: 20px; } + footer #block-userlogin h2 { + position: relative; + width: 20px; + height: 20px; + background-image: url(../../img/user.svg); + background-size: contain; + text-indent: 40px; + margin: 0; + overflow: hidden; + z-index: 1; + cursor: pointer; } + footer #block-userlogin .block-content { + z-index: 0; + position: absolute; + right: 0; + bottom: 20px; + padding: 0.5em; + padding-bottom: 20px; + background-color: rgba(255, 255, 255, 0.9); + overflow: hidden; + -webkit-box-sizing: border-box; + box-sizing: border-box; + height: 0px; + opacity: 0; + pointer-events: none; + -webkit-transition: all 0.5s ease-in-out; + transition: all 0.5s ease-in-out; + -webkit-transition-property: height,opacity; + transition-property: height,opacity; } + footer #block-userlogin .block-content form { + font-size: 0.75em; } + footer #block-userlogin .block-content .item-list ul { + margin: 0; } + footer #block-userlogin .block-content .item-list li { + margin: 0; + list-style: none; } + footer #block-userlogin .block-content .item-list li a { + color: inherit; + text-decoration: none; + font-size: 0.75em; + white-space: nowrap; } + footer #block-userlogin:hover .block-content { + height: 200px; + opacity: 1; + pointer-events: auto; } diff --git a/sites/all/themes/custom/edlptheme/assets/img/search.svg b/sites/all/themes/custom/edlptheme/assets/img/search.svg new file mode 100644 index 000000000..00e0d9f68 --- /dev/null +++ b/sites/all/themes/custom/edlptheme/assets/img/search.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/sites/all/themes/custom/edlptheme/assets/img/user.svg b/sites/all/themes/custom/edlptheme/assets/img/user.svg new file mode 100644 index 000000000..cd34e6dee --- /dev/null +++ b/sites/all/themes/custom/edlptheme/assets/img/user.svg @@ -0,0 +1,39 @@ + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/sites/all/themes/custom/edlptheme/assets/styles/app.scss b/sites/all/themes/custom/edlptheme/assets/styles/app.scss index da840f951..9c750b4a2 100644 --- a/sites/all/themes/custom/edlptheme/assets/styles/app.scss +++ b/sites/all/themes/custom/edlptheme/assets/styles/app.scss @@ -24,7 +24,6 @@ body, html{ padding:0; } - div.layout-container{ position: relative; width:100%; height:100%; @@ -40,18 +39,19 @@ header[role="banner"]{ main[role="main"]{ outline:1px solid green; - background-color: rgba(255,255,255, 0.9); + background-color: $transparent-bg; + // display: none; } footer[role="contentinfo"]{ - outline: 1px solid pink; + // outline: 1px solid pink; position: fixed; bottom:0; width:100%; } canvas#edlp-map{ - outline: 1px solid grey; + // outline: 1px solid grey; } @mixin oblique-list { @@ -63,6 +63,7 @@ canvas#edlp-map{ padding: 0; a{ position: absolute; + // display:inline-block; transform-origin: left bottom; transform: rotateZ(-45deg); color: #000; @@ -74,23 +75,23 @@ canvas#edlp-map{ } footer{ - text-align: center; + // text-align: center; + display: table; + padding: 0 0 0.5em 0; >.region{ - outline: 1px dotted pink; + display: table-cell; + // outline: 1px dotted purple; + // position: relative; } .region-footer-left{ - float: left; text-align: left; } .region-footer-center{ - display: inline-block; - margin: 0 auto; text-align: center; } .region-footer-right{ - float: right; text-align: right; - padding-right: 5em; + min-width: 30px; } nav.block-menu{ @@ -112,6 +113,7 @@ footer{ padding:0 0 0 0.5em; transform-origin: left bottom; transform: rotateZ(-45deg); + white-space: nowrap; li{ margin:0; padding:0; list-style: none; @@ -133,10 +135,25 @@ footer{ .block-block-edlp-entrees{ display: inline-block; + // vertical-align: top; ul{ li{ @include oblique-list; - a{ + margin:0; + white-space: nowrap; + pointer-events: none; + span.oblique-wrapper{ + display: inline-block; + position: relative; + width:1.5em; + a{ + text-transform: none; + } + } + + a.term-link{ + pointer-events: all; + // bottom: 0; &:before{ content: ""; display:inline-block; @@ -145,26 +162,132 @@ footer{ border: 1px solid #000; margin-right: 0.5em; } - &.term-134:before{border-color: $entree-color-134;} - &.term-121:before{border-color: $entree-color-121;} - &.term-125:before{border-color: $entree-color-125;} - &.term-119:before{border-color: $entree-color-119;} - &.term-132:before{border-color: $entree-color-132;} - &.term-122:before{border-color: $entree-color-122;} - &.term-129:before{border-color: $entree-color-129;} - &.term-120:before{border-color: $entree-color-120;} - &.term-130:before{border-color: $entree-color-130;} - &.term-118:before{border-color: $entree-color-118;} - &.term-127:before{border-color: $entree-color-127;} - &.term-133:before{border-color: $entree-color-133;} - &.term-128:before{border-color: $entree-color-128;} - &.term-124:before{border-color: $entree-color-124;} - &.term-116:before{border-color: $entree-color-116;} - &.term-117:before{border-color: $entree-color-117;} - &.term-131:before{border-color: $entree-color-131;} - &.term-126:before{border-color: $entree-color-126;} - &.term-123:before{border-color: $entree-color-123;} + &[tid='134']:before{border-color: $e-col-134;background-color: $e-col-134;} + &[tid='121']:before{border-color: $e-col-121;background-color: $e-col-121;} + &[tid='125']:before{border-color: $e-col-125;background-color: $e-col-125;} + &[tid='119']:before{border-color: $e-col-119;background-color: $e-col-119;} + &[tid='132']:before{border-color: $e-col-132;background-color: $e-col-132;} + &[tid='122']:before{border-color: $e-col-122;background-color: $e-col-122;} + &[tid='129']:before{border-color: $e-col-129;background-color: $e-col-129;} + &[tid='120']:before{border-color: $e-col-120;background-color: $e-col-120;} + &[tid='130']:before{border-color: $e-col-130;background-color: $e-col-130;} + &[tid='118']:before{border-color: $e-col-118;background-color: $e-col-118;} + &[tid='127']:before{border-color: $e-col-127;background-color: $e-col-127;} + &[tid='133']:before{border-color: $e-col-133;background-color: $e-col-133;} + &[tid='128']:before{border-color: $e-col-128;background-color: $e-col-128;} + &[tid='124']:before{border-color: $e-col-124;background-color: $e-col-124;} + &[tid='116']:before{border-color: $e-col-116;background-color: $e-col-116;} + &[tid='117']:before{border-color: $e-col-117;background-color: $e-col-117;} + &[tid='131']:before{border-color: $e-col-131;background-color: $e-col-131;} + &[tid='126']:before{border-color: $e-col-126;background-color: $e-col-126;} + &[tid='123']:before{border-color: $e-col-123;background-color: $e-col-123;} + // &.highlighted{ + // color:red; + // } } + + .entree-content{ + display: inline-block; + // outline: 1px solid green; + width:0; + overflow: hidden; + opacity: 0; + transition: all 300ms ease-in-out; + transition-property: width,opacity; + .term-description{ + display: inline-block; + margin-left: 2em; + text-align: left; + width:250px; + white-space: normal; + background-color: $transparent-bg; + padding:0.5em; + p{ + font-size: 0.65em; + } + } + } + + // &.highlighted{ + // a.term_link{ + // color: red; + // } + // } + &:not(.opened){ + a.term-link:not(:hover):not(.highlighted):before{ + background-color: #fff!important; + } + } + &.opened{ + .entree-content{ + width:350px; + opacity: 1; + } + } + // &:not(:first-of-type) .entree-content{display: none;} + } + } + } + + #block-userlogin{ + // outline: 1px solid blue; + $wh:20px; + position: relative; + width:$wh; height: $wh; + // background-color: blue; + h2{ + position: relative; + width:$wh; height:$wh; + background-image: url(../../img/user.svg); + // background-color: red; + background-size: contain; + text-indent: $wh*2; + margin: 0; + overflow: hidden; + z-index: 1; + cursor: pointer; + } + .block-content{ + z-index: 0; + position:absolute; + right:0;bottom:$wh; + padding:0.5em; + padding-bottom: 20px; + background-color: $transparent-bg; + overflow: hidden; + box-sizing:border-box; + height: 0px; + opacity:0; + pointer-events:none; + transition: all 0.5s ease-in-out; + transition-property: height,opacity; + // &:hover{ + // height:200px; + // opacity:1; + // pointer-events: auto; + // } + form{ + font-size: 0.75em; + } + .item-list{ + ul{margin:0;} + li{ + margin:0; + list-style: none; + a{ + color: inherit; + text-decoration: none; + font-size: 0.75em; + white-space:nowrap; + } + } + } + } + &:hover{ + .block-content{ + height:200px; + opacity: 1; + pointer-events:auto; } } } diff --git a/sites/all/themes/custom/edlptheme/assets/styles/base/_colors.scss b/sites/all/themes/custom/edlptheme/assets/styles/base/_colors.scss index 2058cb875..ff137b743 100644 --- a/sites/all/themes/custom/edlptheme/assets/styles/base/_colors.scss +++ b/sites/all/themes/custom/edlptheme/assets/styles/base/_colors.scss @@ -1,21 +1,23 @@ -$entree-color-130: rgb(75, 143, 126); -$entree-color-121: rgb(134, 142, 36); -$entree-color-134: rgb(43, 143, 47); -$entree-color-121: rgb(58, 51, 182); -$entree-color-125: rgb(44, 159, 87); -$entree-color-119: rgb(196, 137, 120); -$entree-color-132: rgb(82, 112, 187); -$entree-color-122: rgb(251, 84, 211); -$entree-color-129: rgb(224, 116, 131); -$entree-color-120: rgb(101, 88, 69); -$entree-color-130: rgb(126, 8, 104); -$entree-color-118: rgb(14, 113, 33); -$entree-color-127: rgb(218, 189, 66); -$entree-color-133: rgb(3, 153, 187); -$entree-color-128: rgb(57, 154, 28); -$entree-color-124: rgb(112, 133, 64); -$entree-color-116: rgb(25, 27, 255); -$entree-color-117: rgb(39, 157, 132); -$entree-color-131: rgb(82, 25, 171); -$entree-color-126: rgb(212, 156, 182); -$entree-color-123: rgb(73, 119, 21); +$transparent-bg: rgba(255,255,255, 0.9); + +$e-col-130: rgb(75, 143, 126); +$e-col-121: rgb(134, 142, 36); +$e-col-134: rgb(43, 143, 47); +$e-col-121: rgb(58, 51, 182); +$e-col-125: rgb(44, 159, 87); +$e-col-119: rgb(196, 137, 120); +$e-col-132: rgb(82, 112, 187); +$e-col-122: rgb(251, 84, 211); +$e-col-129: rgb(224, 116, 131); +$e-col-120: rgb(101, 88, 69); +$e-col-130: rgb(126, 8, 104); +$e-col-118: rgb(14, 113, 33); +$e-col-127: rgb(218, 189, 66); +$e-col-133: rgb(3, 153, 187); +$e-col-128: rgb(57, 154, 28); +$e-col-124: rgb(112, 133, 64); +$e-col-116: rgb(25, 27, 255); +$e-col-117: rgb(39, 157, 132); +$e-col-131: rgb(82, 25, 171); +$e-col-126: rgb(212, 156, 182); +$e-col-123: rgb(73, 119, 21); diff --git a/sites/all/themes/custom/edlptheme/edlptheme.theme b/sites/all/themes/custom/edlptheme/edlptheme.theme new file mode 100644 index 000000000..9b1ab1c04 --- /dev/null +++ b/sites/all/themes/custom/edlptheme/edlptheme.theme @@ -0,0 +1,26 @@ + + {{ title_prefix }} + {% if label %} + {{ label }} + {% endif %} + {{ title_suffix }} +
+ {% block content %} + {{ content }} + {% endblock %} +
+ diff --git a/sites/all/themes/custom/edlptheme/templates/form/form-element.html.twig b/sites/all/themes/custom/edlptheme/templates/form/form-element.html.twig new file mode 100644 index 000000000..3311ebcfa --- /dev/null +++ b/sites/all/themes/custom/edlptheme/templates/form/form-element.html.twig @@ -0,0 +1,96 @@ +{# +/** + * @file + * Default theme implementation for a form element. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - errors: (optional) Any errors for this form element, may not be set. + * - prefix: (optional) The form element prefix, may not be set. + * - suffix: (optional) The form element suffix, may not be set. + * - required: The required marker, or empty if the associated form element is + * not required. + * - type: The type of the element. + * - name: The name of the element. + * - label: A rendered label element. + * - label_display: Label display setting. It can have these values: + * - before: The label is output before the element. This is the default. + * The label includes the #title and the required marker, if #required. + * - after: The label is output after the element. For example, this is used + * for radio and checkbox #type elements. If the #title is empty but the + * field is #required, the label will contain only the required marker. + * - invisible: Labels are critical for screen readers to enable them to + * properly navigate through forms but can be visually distracting. This + * property hides the label for everyone except screen readers. + * - attribute: Set the title attribute on the element to create a tooltip but + * output no label element. This is supported only for checkboxes and radios + * in \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement(). + * It is used where a visual label is not needed, such as a table of + * checkboxes where the row and column provide the context. The tooltip will + * include the title and required marker. + * - description: (optional) A list of description properties containing: + * - content: A description of the form element, may not be set. + * - attributes: (optional) A list of HTML attributes to apply to the + * description content wrapper. Will only be set when description is set. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element. This is the default + * value. + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. + * - disabled: True if the element is disabled. + * - title_display: Title display setting. + * + * @see template_preprocess_form_element() + * + * @ingroup themeable + */ +#} +{% + set classes = [ + 'js-form-item', + 'form-item', + 'js-form-type-' ~ type|clean_class, + 'form-item-' ~ name|clean_class, + 'js-form-item-' ~ name|clean_class, + title_display not in ['after', 'before'] ? 'form-no-label', + disabled == 'disabled' ? 'form-disabled', + errors ? 'form-item--error', + ] +%} +{% + set description_classes = [ + 'description', + description_display == 'invisible' ? 'visually-hidden', + ] +%} + + {% if label_display in ['before', 'invisible'] %} + {{ label }} + {% endif %} + {% if prefix is not empty %} + {{ prefix }} + {% endif %} + {% if description_display == 'before' and description.content %} + + {{ description.content }} + + {% endif %} + {{ children }} + {% if suffix is not empty %} + {{ suffix }} + {% endif %} + {% if label_display == 'after' %} + {{ label }} + {% endif %} + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {% if description_display in ['after', 'invisible'] and description.content %} + + {{ description.content }} + + {% endif %} + diff --git a/sites/all/themes/custom/edlptheme/templates/form/input--textfield.html.twig b/sites/all/themes/custom/edlptheme/templates/form/input--textfield.html.twig new file mode 100644 index 000000000..e8896e422 --- /dev/null +++ b/sites/all/themes/custom/edlptheme/templates/form/input--textfield.html.twig @@ -0,0 +1,16 @@ +{# +/** + * @file + * Default theme implementation for an 'input' #type form element. + * + * Available variables: + * - attributes: A list of HTML attributes for the input element. + * - children: Optional additional rendered elements. + * + * @see template_preprocess_input() + * + * @ingroup themeable + */ +#} +{{ children }} + diff --git a/sites/all/themes/custom/edlptheme/theme.theme b/sites/all/themes/custom/edlptheme/theme.theme deleted file mode 100644 index e45b0e99f..000000000 --- a/sites/all/themes/custom/edlptheme/theme.theme +++ /dev/null @@ -1,6 +0,0 @@ - 'databasename', - * 'username' => 'sqlusername', - * 'password' => 'sqlpassword', - * 'host' => 'localhost', - * 'port' => '3306', - * 'driver' => 'mysql', - * 'prefix' => '', - * 'collation' => 'utf8mb4_general_ci', - * ); - * @endcode - */ - $databases = array(); - -/** - * Customizing database settings. - * - * Many of the values of the $databases array can be customized for your - * particular database system. Refer to the sample in the section above as a - * starting point. - * - * The "driver" property indicates what Drupal database driver the - * connection should use. This is usually the same as the name of the - * database type, such as mysql or sqlite, but not always. The other - * properties will vary depending on the driver. For SQLite, you must - * specify a database file name in a directory that is writable by the - * webserver. For most other drivers, you must specify a - * username, password, host, and database name. - * - * Transaction support is enabled by default for all drivers that support it, - * including MySQL. To explicitly disable it, set the 'transactions' key to - * FALSE. - * Note that some configurations of MySQL, such as the MyISAM engine, don't - * support it and will proceed silently even if enabled. If you experience - * transaction related crashes with such configuration, set the 'transactions' - * key to FALSE. - * - * For each database, you may optionally specify multiple "target" databases. - * A target database allows Drupal to try to send certain queries to a - * different database if it can but fall back to the default connection if not. - * That is useful for primary/replica replication, as Drupal may try to connect - * to a replica server when appropriate and if one is not available will simply - * fall back to the single primary server (The terms primary/replica are - * traditionally referred to as master/slave in database server documentation). - * - * The general format for the $databases array is as follows: - * @code - * $databases['default']['default'] = $info_array; - * $databases['default']['replica'][] = $info_array; - * $databases['default']['replica'][] = $info_array; - * $databases['extra']['default'] = $info_array; - * @endcode - * - * In the above example, $info_array is an array of settings described above. - * The first line sets a "default" database that has one primary database - * (the second level default). The second and third lines create an array - * of potential replica databases. Drupal will select one at random for a given - * request as needed. The fourth line creates a new database with a name of - * "extra". - * - * You can optionally set prefixes for some or all database table names - * by using the 'prefix' setting. If a prefix is specified, the table - * name will be prepended with its value. Be sure to use valid database - * characters only, usually alphanumeric and underscore. If no prefixes - * are desired, leave it as an empty string ''. - * - * To have all database names prefixed, set 'prefix' as a string: - * @code - * 'prefix' => 'main_', - * @endcode - * To provide prefixes for specific tables, set 'prefix' as an array. - * The array's keys are the table names and the values are the prefixes. - * The 'default' element is mandatory and holds the prefix for any tables - * not specified elsewhere in the array. Example: - * @code - * 'prefix' => array( - * 'default' => 'main_', - * 'users' => 'shared_', - * 'sessions' => 'shared_', - * 'role' => 'shared_', - * 'authmap' => 'shared_', - * ), - * @endcode - * You can also use a reference to a schema/database as a prefix. This may be - * useful if your Drupal installation exists in a schema that is not the default - * or you want to access several databases from the same code base at the same - * time. - * Example: - * @code - * 'prefix' => array( - * 'default' => 'main.', - * 'users' => 'shared.', - * 'sessions' => 'shared.', - * 'role' => 'shared.', - * 'authmap' => 'shared.', - * ); - * @endcode - * NOTE: MySQL and SQLite's definition of a schema is a database. - * - * Advanced users can add or override initial commands to execute when - * connecting to the database server, as well as PDO connection settings. For - * example, to enable MySQL SELECT queries to exceed the max_join_size system - * variable, and to reduce the database connection timeout to 5 seconds: - * @code - * $databases['default']['default'] = array( - * 'init_commands' => array( - * 'big_selects' => 'SET SQL_BIG_SELECTS=1', - * ), - * 'pdo' => array( - * PDO::ATTR_TIMEOUT => 5, - * ), - * ); - * @endcode - * - * WARNING: The above defaults are designed for database portability. Changing - * them may cause unexpected behavior, including potential data loss. See - * https://www.drupal.org/developing/api/database/configuration for more - * information on these defaults and the potential issues. - * - * More details can be found in the constructor methods for each driver: - * - \Drupal\Core\Database\Driver\mysql\Connection::__construct() - * - \Drupal\Core\Database\Driver\pgsql\Connection::__construct() - * - \Drupal\Core\Database\Driver\sqlite\Connection::__construct() - * - * Sample Database configuration format for PostgreSQL (pgsql): - * @code - * $databases['default']['default'] = array( - * 'driver' => 'pgsql', - * 'database' => 'databasename', - * 'username' => 'sqlusername', - * 'password' => 'sqlpassword', - * 'host' => 'localhost', - * 'prefix' => '', - * ); - * @endcode - * - * Sample Database configuration format for SQLite (sqlite): - * @code - * $databases['default']['default'] = array( - * 'driver' => 'sqlite', - * 'database' => '/path/to/databasefilename', - * ); - * @endcode - */ - -/** - * Location of the site configuration files. - * - * The $config_directories array specifies the location of file system - * directories used for configuration data. On install, the "sync" directory is - * created. This is used for configuration imports. The "active" directory is - * not created by default since the default storage for active configuration is - * the database rather than the file system. (This can be changed. See "Active - * configuration settings" below). - * - * The default location for the "sync" directory is inside a randomly-named - * directory in the public files path. The setting below allows you to override - * the "sync" location. - * - * If you use files for the "active" configuration, you can tell the - * Configuration system where this directory is located by adding an entry with - * array key CONFIG_ACTIVE_DIRECTORY. - * - * Example: - * @code - * $config_directories = array( - * CONFIG_SYNC_DIRECTORY => '/directory/outside/webroot', - * ); - * @endcode - */ -$config_directories = array(); - -/** - * Settings: - * - * $settings contains environment-specific configuration, such as the files - * directory and reverse proxy address, and temporary configuration, such as - * security overrides. - * - * @see \Drupal\Core\Site\Settings::get() - */ - -/** - * The active installation profile. - * - * Changing this after installation is not recommended as it changes which - * directories are scanned during extension discovery. If this is set prior to - * installation this value will be rewritten according to the profile selected - * by the user. - * - * @see install_select_profile() - */ -# $settings['install_profile'] = ''; - -/** - * Salt for one-time login links, cancel links, form tokens, etc. - * - * This variable will be set to a random value by the installer. All one-time - * login links will be invalidated if the value is changed. Note that if your - * site is deployed on a cluster of web servers, you must ensure that this - * variable has the same value on each server. - * - * For enhanced security, you may set this variable to the contents of a file - * outside your document root; you should also ensure that this file is not - * stored with backups of your database. - * - * Example: - * @code - * $settings['hash_salt'] = file_get_contents('/home/example/salt.txt'); - * @endcode - */ -$settings['hash_salt'] = ''; - -/** - * Deployment identifier. - * - * Drupal's dependency injection container will be automatically invalidated and - * rebuilt when the Drupal core version changes. When updating contributed or - * custom code that changes the container, changing this identifier will also - * allow the container to be invalidated as soon as code is deployed. - */ -# $settings['deployment_identifier'] = \Drupal::VERSION; - -/** - * Access control for update.php script. - * - * If you are updating your Drupal installation using the update.php script but - * are not logged in using either an account with the "Administer software - * updates" permission or the site maintenance account (the account that was - * created during installation), you will need to modify the access check - * statement below. Change the FALSE to a TRUE to disable the access check. - * After finishing the upgrade, be sure to open this file again and change the - * TRUE back to a FALSE! - */ -$settings['update_free_access'] = FALSE; - -/** - * External access proxy settings: - * - * If your site must access the Internet via a web proxy then you can enter the - * proxy settings here. Set the full URL of the proxy, including the port, in - * variables: - * - $settings['http_client_config']['proxy']['http']: The proxy URL for HTTP - * requests. - * - $settings['http_client_config']['proxy']['https']: The proxy URL for HTTPS - * requests. - * You can pass in the user name and password for basic authentication in the - * URLs in these settings. - * - * You can also define an array of host names that can be accessed directly, - * bypassing the proxy, in $settings['http_client_config']['proxy']['no']. - */ -# $settings['http_client_config']['proxy']['http'] = 'http://proxy_user:proxy_pass@example.com:8080'; -# $settings['http_client_config']['proxy']['https'] = 'http://proxy_user:proxy_pass@example.com:8080'; -# $settings['http_client_config']['proxy']['no'] = ['127.0.0.1', 'localhost']; - -/** - * Reverse Proxy Configuration: - * - * Reverse proxy servers are often used to enhance the performance - * of heavily visited sites and may also provide other site caching, - * security, or encryption benefits. In an environment where Drupal - * is behind a reverse proxy, the real IP address of the client should - * be determined such that the correct client IP address is available - * to Drupal's logging, statistics, and access management systems. In - * the most simple scenario, the proxy server will add an - * X-Forwarded-For header to the request that contains the client IP - * address. However, HTTP headers are vulnerable to spoofing, where a - * malicious client could bypass restrictions by setting the - * X-Forwarded-For header directly. Therefore, Drupal's proxy - * configuration requires the IP addresses of all remote proxies to be - * specified in $settings['reverse_proxy_addresses'] to work correctly. - * - * Enable this setting to get Drupal to determine the client IP from - * the X-Forwarded-For header (or $settings['reverse_proxy_header'] if set). - * If you are unsure about this setting, do not have a reverse proxy, - * or Drupal operates in a shared hosting environment, this setting - * should remain commented out. - * - * In order for this setting to be used you must specify every possible - * reverse proxy IP address in $settings['reverse_proxy_addresses']. - * If a complete list of reverse proxies is not available in your - * environment (for example, if you use a CDN) you may set the - * $_SERVER['REMOTE_ADDR'] variable directly in settings.php. - * Be aware, however, that it is likely that this would allow IP - * address spoofing unless more advanced precautions are taken. - */ -# $settings['reverse_proxy'] = TRUE; - -/** - * Specify every reverse proxy IP address in your environment. - * This setting is required if $settings['reverse_proxy'] is TRUE. - */ -# $settings['reverse_proxy_addresses'] = array('a.b.c.d', ...); - -/** - * Set this value if your proxy server sends the client IP in a header - * other than X-Forwarded-For. - */ -# $settings['reverse_proxy_header'] = 'X_CLUSTER_CLIENT_IP'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Proto. - */ -# $settings['reverse_proxy_proto_header'] = 'X_FORWARDED_PROTO'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Host. - */ -# $settings['reverse_proxy_host_header'] = 'X_FORWARDED_HOST'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Port. - */ -# $settings['reverse_proxy_port_header'] = 'X_FORWARDED_PORT'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than Forwarded. - */ -# $settings['reverse_proxy_forwarded_header'] = 'FORWARDED'; - -/** - * Page caching: - * - * By default, Drupal sends a "Vary: Cookie" HTTP header for anonymous page - * views. This tells a HTTP proxy that it may return a page from its local - * cache without contacting the web server, if the user sends the same Cookie - * header as the user who originally requested the cached page. Without "Vary: - * Cookie", authenticated users would also be served the anonymous page from - * the cache. If the site has mostly anonymous users except a few known - * editors/administrators, the Vary header can be omitted. This allows for - * better caching in HTTP proxies (including reverse proxies), i.e. even if - * clients send different cookies, they still get content served from the cache. - * However, authenticated users should access the site directly (i.e. not use an - * HTTP proxy, and bypass the reverse proxy if one is used) in order to avoid - * getting cached pages from the proxy. - */ -# $settings['omit_vary_cookie'] = TRUE; - -/** - * Class Loader. - * - * If the APC extension is detected, the Symfony APC class loader is used for - * performance reasons. Detection can be prevented by setting - * class_loader_auto_detect to false, as in the example below. - */ -# $settings['class_loader_auto_detect'] = FALSE; - -/* - * If the APC extension is not detected, either because APC is missing or - * because auto-detection has been disabled, auto-loading falls back to - * Composer's ClassLoader, which is good for development as it does not break - * when code is moved in the file system. You can also decorate the base class - * loader with another cached solution than the Symfony APC class loader, as - * all production sites should have a cached class loader of some sort enabled. - * - * To do so, you may decorate and replace the local $class_loader variable. For - * example, to use Symfony's APC class loader without automatic detection, - * uncomment the code below. - */ -/* -if ($settings['hash_salt']) { - $prefix = 'drupal.' . hash('sha256', 'drupal.' . $settings['hash_salt']); - $apc_loader = new \Symfony\Component\ClassLoader\ApcClassLoader($prefix, $class_loader); - unset($prefix); - $class_loader->unregister(); - $apc_loader->register(); - $class_loader = $apc_loader; -} -*/ - -/** - * Authorized file system operations: - * - * The Update Manager module included with Drupal provides a mechanism for - * site administrators to securely install missing updates for the site - * directly through the web user interface. On securely-configured servers, - * the Update manager will require the administrator to provide SSH or FTP - * credentials before allowing the installation to proceed; this allows the - * site to update the new files as the user who owns all the Drupal files, - * instead of as the user the webserver is running as. On servers where the - * webserver user is itself the owner of the Drupal files, the administrator - * will not be prompted for SSH or FTP credentials (note that these server - * setups are common on shared hosting, but are inherently insecure). - * - * Some sites might wish to disable the above functionality, and only update - * the code directly via SSH or FTP themselves. This setting completely - * disables all functionality related to these authorized file operations. - * - * @see https://www.drupal.org/node/244924 - * - * Remove the leading hash signs to disable. - */ -# $settings['allow_authorize_operations'] = FALSE; - -/** - * Default mode for directories and files written by Drupal. - * - * Value should be in PHP Octal Notation, with leading zero. - */ -# $settings['file_chmod_directory'] = 0775; -# $settings['file_chmod_file'] = 0664; - -/** - * Public file base URL: - * - * An alternative base URL to be used for serving public files. This must - * include any leading directory path. - * - * A different value from the domain used by Drupal to be used for accessing - * public files. This can be used for a simple CDN integration, or to improve - * security by serving user-uploaded files from a different domain or subdomain - * pointing to the same server. Do not include a trailing slash. - */ -# $settings['file_public_base_url'] = 'http://downloads.example.com/files'; - -/** - * Public file path: - * - * A local file system path where public files will be stored. This directory - * must exist and be writable by Drupal. This directory must be relative to - * the Drupal installation directory and be accessible over the web. - */ -# $settings['file_public_path'] = 'sites/default/files'; - -/** - * Private file path: - * - * A local file system path where private files will be stored. This directory - * must be absolute, outside of the Drupal installation directory and not - * accessible over the web. - * - * Note: Caches need to be cleared when this value is changed to make the - * private:// stream wrapper available to the system. - * - * See https://www.drupal.org/documentation/modules/file for more information - * about securing private files. - */ -# $settings['file_private_path'] = ''; - -/** - * Session write interval: - * - * Set the minimum interval between each session write to database. - * For performance reasons it defaults to 180. - */ -# $settings['session_write_interval'] = 180; - -/** - * String overrides: - * - * To override specific strings on your site with or without enabling the Locale - * module, add an entry to this list. This functionality allows you to change - * a small number of your site's default English language interface strings. - * - * Remove the leading hash signs to enable. - * - * The "en" part of the variable name, is dynamic and can be any langcode of - * any added language. (eg locale_custom_strings_de for german). - */ -# $settings['locale_custom_strings_en'][''] = array( -# 'forum' => 'Discussion board', -# '@count min' => '@count minutes', -# ); - -/** - * A custom theme for the offline page: - * - * This applies when the site is explicitly set to maintenance mode through the - * administration page or when the database is inactive due to an error. - * The template file should also be copied into the theme. It is located inside - * 'core/modules/system/templates/maintenance-page.html.twig'. - * - * Note: This setting does not apply to installation and update pages. - */ -# $settings['maintenance_theme'] = 'bartik'; - -/** - * PHP settings: - * - * To see what PHP settings are possible, including whether they can be set at - * runtime (by using ini_set()), read the PHP documentation: - * http://php.net/manual/ini.list.php - * See \Drupal\Core\DrupalKernel::bootEnvironment() for required runtime - * settings and the .htaccess file for non-runtime settings. - * Settings defined there should not be duplicated here so as to avoid conflict - * issues. - */ - -/** - * If you encounter a situation where users post a large amount of text, and - * the result is stripped out upon viewing but can still be edited, Drupal's - * output filter may not have sufficient memory to process it. If you - * experience this issue, you may wish to uncomment the following two lines - * and increase the limits of these variables. For more information, see - * http://php.net/manual/pcre.configuration.php. - */ -# ini_set('pcre.backtrack_limit', 200000); -# ini_set('pcre.recursion_limit', 200000); - -/** - * Active configuration settings. - * - * By default, the active configuration is stored in the database in the - * {config} table. To use a different storage mechanism for the active - * configuration, do the following prior to installing: - * - Create an "active" directory and declare its path in $config_directories - * as explained under the 'Location of the site configuration files' section - * above in this file. To enhance security, you can declare a path that is - * outside your document root. - * - Override the 'bootstrap_config_storage' setting here. It must be set to a - * callable that returns an object that implements - * \Drupal\Core\Config\StorageInterface. - * - Override the service definition 'config.storage.active'. Put this - * override in a services.yml file in the same directory as settings.php - * (definitions in this file will override service definition defaults). - */ -# $settings['bootstrap_config_storage'] = array('Drupal\Core\Config\BootstrapConfigStorageFactory', 'getFileStorage'); - -/** - * Configuration overrides. - * - * To globally override specific configuration values for this site, - * set them here. You usually don't need to use this feature. This is - * useful in a configuration file for a vhost or directory, rather than - * the default settings.php. - * - * Note that any values you provide in these variable overrides will not be - * viewable from the Drupal administration interface. The administration - * interface displays the values stored in configuration so that you can stage - * changes to other environments that don't have the overrides. - * - * There are particular configuration values that are risky to override. For - * example, overriding the list of installed modules in 'core.extension' is not - * supported as module install or uninstall has not occurred. Other examples - * include field storage configuration, because it has effects on database - * structure, and 'core.menu.static_menu_link_overrides' since this is cached in - * a way that is not config override aware. Also, note that changing - * configuration values in settings.php will not fire any of the configuration - * change events. - */ -# $config['system.site']['name'] = 'My Drupal site'; -# $config['system.theme']['default'] = 'stark'; -# $config['user.settings']['anonymous'] = 'Visitor'; - -/** - * Fast 404 pages: - * - * Drupal can generate fully themed 404 pages. However, some of these responses - * are for images or other resource files that are not displayed to the user. - * This can waste bandwidth, and also generate server load. - * - * The options below return a simple, fast 404 page for URLs matching a - * specific pattern: - * - $config['system.performance']['fast_404']['exclude_paths']: A regular - * expression to match paths to exclude, such as images generated by image - * styles, or dynamically-resized images. The default pattern provided below - * also excludes the private file system. If you need to add more paths, you - * can add '|path' to the expression. - * - $config['system.performance']['fast_404']['paths']: A regular expression to - * match paths that should return a simple 404 page, rather than the fully - * themed 404 page. If you don't have any aliases ending in htm or html you - * can add '|s?html?' to the expression. - * - $config['system.performance']['fast_404']['html']: The html to return for - * simple 404 pages. - * - * Remove the leading hash signs if you would like to alter this functionality. - */ -# $config['system.performance']['fast_404']['exclude_paths'] = '/\/(?:styles)|(?:system\/files)\//'; -# $config['system.performance']['fast_404']['paths'] = '/\.(?:txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i'; -# $config['system.performance']['fast_404']['html'] = '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'; - -/** - * Load services definition file. - */ -$settings['container_yamls'][] = __DIR__ . '/services.yml'; - -/** - * Override the default service container class. - * - * This is useful for example to trace the service container for performance - * tracking purposes, for testing a service container with an error condition or - * to test a service container that throws an exception. - */ -# $settings['container_base_class'] = '\Drupal\Core\DependencyInjection\Container'; - -/** - * Trusted host configuration. - * - * Drupal core can use the Symfony trusted host mechanism to prevent HTTP Host - * header spoofing. - * - * To enable the trusted host mechanism, you enable your allowable hosts - * in $settings['trusted_host_patterns']. This should be an array of regular - * expression patterns, without delimiters, representing the hosts you would - * like to allow. - * - * For example: - * @code - * $settings['trusted_host_patterns'] = array( - * '^www\.example\.com$', - * ); - * @endcode - * will allow the site to only run from www.example.com. - * - * If you are running multisite, or if you are running your site from - * different domain names (eg, you don't redirect http://www.example.com to - * http://example.com), you should specify all of the host patterns that are - * allowed by your site. - * - * For example: - * @code - * $settings['trusted_host_patterns'] = array( - * '^example\.com$', - * '^.+\.example\.com$', - * '^example\.org$', - * '^.+\.example\.org$', - * ); - * @endcode - * will allow the site to run off of all variants of example.com and - * example.org, with all subdomains included. - */ - -/** - * Load local development override configuration, if available. - * - * Use settings.local.php to override variables on secondary (staging, - * development, etc) installations of this site. Typically used to disable - * caching, JavaScript/CSS compression, re-routing of outgoing emails, and - * other things that should not happen on development and testing sites. - * - * Keep this code block at the end of this file to take full effect. - */ -# if (file_exists(__DIR__ . '/settings.local.php')) { -# include __DIR__ . '/settings.local.php'; -# } diff --git a/sites/development.services.yml b/sites/development.services.yml index 73cc99825..bcdcb682a 100644 --- a/sites/development.services.yml +++ b/sites/development.services.yml @@ -5,3 +5,9 @@ services: cache.backend.null: class: Drupal\Core\Cache\NullBackendFactory + +parameters: + twig.config: + debug: true + auto_reload: true + cache: false