diff --git a/composer.json b/composer.json index 440480482e0e8c7acdffeddf6e503dd53b647fb8..baf02ead48f18acab60a5de7e848f2a96826d459 100644 --- a/composer.json +++ b/composer.json @@ -95,6 +95,7 @@ "drupal/embed": "1.0", "drupal/entity": "1.0-beta1", "drupal/entity_browser": "1.4", + "drupal/entity_clone": "^1.0@beta", "drupal/entity_embed": "1.0-beta2", "drupal/entity_reference_revisions": "1.3", "drupal/externalauth": "1.1", diff --git a/composer.lock b/composer.lock index 81b26543f40b5b589a04f99083a4d8be8a8d7e0c..4ee33676772693ef5723f6fb42e8c9b0cd9eeccf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "69a66388caf4a15e8bfb8680c7774d79", + "content-hash": "354da2ce0fea7aab24e06b2dd23b17c9", "packages": [ { "name": "alchemy/zippy", @@ -3402,6 +3402,53 @@ "irc": "irc://irc.freenode.org/drupal-contribute" } }, + { + "name": "drupal/entity_clone", + "version": "1.0.0-beta1", + "source": { + "type": "git", + "url": "https://git.drupal.org/project/entity_clone", + "reference": "8.x-1.0-beta1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/entity_clone-8.x-1.0-beta1.zip", + "reference": "8.x-1.0-beta1", + "shasum": "d0ace20bbe1672fbe6d0cdd6da8f5b80cd156643" + }, + "require": { + "drupal/core": "~8.0" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "drupal": { + "version": "8.x-1.0-beta1", + "datestamp": "1546583284", + "security-coverage": { + "status": "not-covered", + "message": "Beta releases are not covered by Drupal security advisories." + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "vpeltot", + "homepage": "https://www.drupal.org/user/1361586" + } + ], + "description": "Add a clone action for all entities", + "homepage": "https://www.drupal.org/project/entity_clone", + "support": { + "source": "http://cgit.drupalcode.org/entity_clone" + } + }, { "name": "drupal/entity_embed", "version": "1.0.0-beta2", @@ -10614,6 +10661,7 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { + "drupal/entity_clone": 10, "drupal/migrate_devel": 20, "drupal/roleassign": 15, "drupal/view_unpublished": 15 diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 60c5c44d629d92a74dc9e204d320455cdadc3a71..95ecc75af79b6b0035e0f23f6e296965cdfe0cd7 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -3503,6 +3503,55 @@ "irc": "irc://irc.freenode.org/drupal-contribute" } }, + { + "name": "drupal/entity_clone", + "version": "1.0.0-beta1", + "version_normalized": "1.0.0.0-beta1", + "source": { + "type": "git", + "url": "https://git.drupal.org/project/entity_clone", + "reference": "8.x-1.0-beta1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/entity_clone-8.x-1.0-beta1.zip", + "reference": "8.x-1.0-beta1", + "shasum": "d0ace20bbe1672fbe6d0cdd6da8f5b80cd156643" + }, + "require": { + "drupal/core": "~8.0" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "drupal": { + "version": "8.x-1.0-beta1", + "datestamp": "1546583284", + "security-coverage": { + "status": "not-covered", + "message": "Beta releases are not covered by Drupal security advisories." + } + } + }, + "installation-source": "dist", + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "vpeltot", + "homepage": "https://www.drupal.org/user/1361586" + } + ], + "description": "Add a clone action for all entities", + "homepage": "https://www.drupal.org/project/entity_clone", + "support": { + "source": "http://cgit.drupalcode.org/entity_clone" + } + }, { "name": "drupal/entity_embed", "version": "1.0.0-beta2", diff --git a/web/modules/entity_clone/LICENSE.txt b/web/modules/entity_clone/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/web/modules/entity_clone/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/web/modules/entity_clone/config/schema/entity_clone.settings.schema.yml b/web/modules/entity_clone/config/schema/entity_clone.settings.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..602453c34951a139657c0b58169b375a6d6ab93c --- /dev/null +++ b/web/modules/entity_clone/config/schema/entity_clone.settings.schema.yml @@ -0,0 +1,88 @@ +entity_clone.settings: + type: config_object + label: 'Module entity_clone settings' + mapping: + form_settings: + type: mapping + mapping: + taxonomy_term: + type: mapping + mapping: + default_value: + type: boolean + disable: + type: boolean + hidden: + type: boolean + block_content: + type: mapping + mapping: + default_value: + type: boolean + disable: + type: boolean + hidden: + type: boolean + comment: + type: mapping + mapping: + default_value: + type: boolean + disable: + type: boolean + hidden: + type: boolean + contact_message: + type: mapping + mapping: + default_value: + type: boolean + disable: + type: boolean + hidden: + type: boolean + file: + type: mapping + mapping: + default_value: + type: boolean + disable: + type: boolean + hidden: + type: boolean + node: + type: mapping + mapping: + default_value: + type: boolean + disable: + type: boolean + hidden: + type: boolean + shortcut: + type: mapping + mapping: + default_value: + type: boolean + disable: + type: boolean + hidden: + type: boolean + user: + type: mapping + mapping: + default_value: + type: boolean + disable: + type: boolean + hidden: + type: boolean + menu_link_content: + type: mapping + mapping: + default_value: + type: boolean + disable: + type: boolean + hidden: + type: boolean diff --git a/web/modules/entity_clone/entity_clone.info.yml b/web/modules/entity_clone/entity_clone.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..4f70fcb5ee80bce7d4600dd58d029cd100ef0c70 --- /dev/null +++ b/web/modules/entity_clone/entity_clone.info.yml @@ -0,0 +1,11 @@ +name: Entity Clone +description: Add a clone action for all entities +# core: "8.x" +type: module +configure: entity_clone.settings + +# Information added by Drupal.org packaging script on 2019-01-04 +version: '8.x-1.0-beta1' +core: '8.x' +project: 'entity_clone' +datestamp: 1546583287 diff --git a/web/modules/entity_clone/entity_clone.links.menu.yml b/web/modules/entity_clone/entity_clone.links.menu.yml new file mode 100644 index 0000000000000000000000000000000000000000..f013a12f9ba486b55461ea6f72f73cfea553749c --- /dev/null +++ b/web/modules/entity_clone/entity_clone.links.menu.yml @@ -0,0 +1,5 @@ +entity_clone.settings: + title: 'Entity clone settings' + description: 'Entity clone settings.' + route_name: entity_clone.settings + parent: 'system.admin_config_content' diff --git a/web/modules/entity_clone/entity_clone.links.task.yml b/web/modules/entity_clone/entity_clone.links.task.yml new file mode 100644 index 0000000000000000000000000000000000000000..427f5759dce38294a6a0ed1fc695759a08de174f --- /dev/null +++ b/web/modules/entity_clone/entity_clone.links.task.yml @@ -0,0 +1,8 @@ +entity_clone.clone: + deriver: 'Drupal\entity_clone\Plugin\Derivative\DynamicLocalTasks' + weight: 100 +entity_clone.settings: + title: 'Entity clone settings' + route_name: entity_clone.settings + base_route: entity_clone.settings + weight: 0 diff --git a/web/modules/entity_clone/entity_clone.module b/web/modules/entity_clone/entity_clone.module new file mode 100644 index 0000000000000000000000000000000000000000..7b6a3a919abde6c18d4dad747ae684c5aa4066ef --- /dev/null +++ b/web/modules/entity_clone/entity_clone.module @@ -0,0 +1,132 @@ +<?php + +/** + * @file + * Contains entity_clone.module. + */ + +use Drupal\Core\Entity\ContentEntityTypeInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Config\Entity\ConfigEntityTypeInterface; +use Drupal\entity_clone\EntityClone\Config\ConfigEntityCloneBase; +use Drupal\entity_clone\EntityClone\Config\ConfigEntityCloneFormBase; +use Drupal\entity_clone\EntityClone\Config\ConfigWithFieldEntityClone; +use Drupal\entity_clone\EntityClone\Config\FieldConfigEntityClone; +use Drupal\entity_clone\EntityClone\Content\ContentEntityCloneBase; +use Drupal\entity_clone\EntityClone\Content\ContentEntityCloneFormBase; +use Drupal\entity_clone\EntityClone\Content\FileEntityClone; +use Drupal\entity_clone\EntityClone\Content\TaxonomyTermEntityClone; +use Drupal\entity_clone\EntityClone\Content\UserEntityClone; + +/** + * Implements hook_help(). + */ +function entity_clone_help($route_name, RouteMatchInterface $route_match) { + switch ($route_name) { + // Main module help for the entity_clone module. + case 'help.page.entity_clone': + $output = ''; + $output .= '<h3>' . t('About') . '</h3>'; + $output .= '<p>' . t('Provides a new operation to clone all Entities.') . '</p>'; + return $output; + + default: + return; + + } +} + +/** + * Implements hook_entity_type_build(). + */ +function entity_clone_entity_type_build(array &$entity_types) { + $specific_handler = [ + 'file' => [ + 'entity_clone' => FileEntityClone::class, + 'entity_clone_form' => ContentEntityCloneFormBase::class, + ], + 'user' => [ + 'entity_clone' => UserEntityClone::class, + 'entity_clone_form' => ContentEntityCloneFormBase::class, + ], + 'field_config' => [ + 'entity_clone' => FieldConfigEntityClone::class, + 'entity_clone_form' => ConfigEntityCloneFormBase::class, + ], + 'node_type' => [ + 'entity_clone' => ConfigWithFieldEntityClone::class, + 'entity_clone_form' => ConfigEntityCloneFormBase::class, + ], + 'comment_type' => [ + 'entity_clone' => ConfigWithFieldEntityClone::class, + 'entity_clone_form' => ConfigEntityCloneFormBase::class, + ], + 'block_content_type' => [ + 'entity_clone' => ConfigWithFieldEntityClone::class, + 'entity_clone_form' => ConfigEntityCloneFormBase::class, + ], + 'contact_form' => [ + 'entity_clone' => ConfigWithFieldEntityClone::class, + 'entity_clone_form' => ConfigEntityCloneFormBase::class, + ], + 'taxonomy_term' => [ + 'entity_clone' => TaxonomyTermEntityClone::class, + 'entity_clone_form' => ContentEntityCloneFormBase::class, + ], + ]; + + /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */ + foreach ($entity_types as &$entity_type) { + if (isset($specific_handler[$entity_type->id()])) { + $entity_type->setHandlerClass('entity_clone', $specific_handler[$entity_type->id()]['entity_clone']); + if (isset($specific_handler[$entity_type->id()]['entity_clone_form'])) { + $entity_type->setHandlerClass('entity_clone_form', $specific_handler[$entity_type->id()]['entity_clone_form']); + } + } + elseif (!$entity_type->getHandlerClass('entity_clone') && $entity_type instanceof ContentEntityTypeInterface) { + $entity_type->setHandlerClass('entity_clone', ContentEntityCloneBase::class); + $entity_type->setHandlerClass('entity_clone_form', ContentEntityCloneFormBase::class); + } + elseif (!$entity_type->getHandlerClass('entity_clone') && $entity_type instanceof ConfigEntityTypeInterface) { + $entity_type->setHandlerClass('entity_clone', ConfigEntityCloneBase::class); + $entity_type->setHandlerClass('entity_clone_form', ConfigEntityCloneFormBase::class); + } + } +} + +/** + * Declares entity operations. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity on which the linked operations will be performed. + * + * @return array + * An operations array as returned by + * EntityListBuilderInterface::getOperations(). + * + * @see \Drupal\Core\Entity\EntityListBuilderInterface::getOperations() + */ +function entity_clone_entity_operation(EntityInterface $entity) { + if ($entity->hasLinkTemplate('clone-form')) { + return [ + 'clone' => [ + 'title' => t('Clone'), + 'weight' => 50, + 'url' => $entity->toUrl('clone-form'), + ], + ]; + } + + return []; +} + +/** + * Implements hook_entity_type_alter(). + */ +function entity_clone_entity_type_alter(array &$entity_types) { + /** @var \Drupal\Core\Entity\EntityType[] $entity_types */ + foreach ($entity_types as $entity_type_id => $entity_type) { + $entity_type->setLinkTemplate('clone-form', "/entity_clone/$entity_type_id/{{$entity_type_id}}"); + } +} diff --git a/web/modules/entity_clone/entity_clone.permissions.yml b/web/modules/entity_clone/entity_clone.permissions.yml new file mode 100644 index 0000000000000000000000000000000000000000..6e259f5f594a6b1543e836dbc1cb38fc6c964773 --- /dev/null +++ b/web/modules/entity_clone/entity_clone.permissions.yml @@ -0,0 +1,2 @@ +permission_callbacks: + - Drupal\entity_clone\EntityClonePermissions::permissions diff --git a/web/modules/entity_clone/entity_clone.post_update.php b/web/modules/entity_clone/entity_clone.post_update.php new file mode 100644 index 0000000000000000000000000000000000000000..67349bb564ebca59939235e9c9f0f17ca95df699 --- /dev/null +++ b/web/modules/entity_clone/entity_clone.post_update.php @@ -0,0 +1,24 @@ +<?php + +/** + * @file + * Contains entity_clone.post_update.php. + */ + +/** + * Populates new entity_clone form settings. + */ +function entity_clone_post_update_populate_form_settings() { + /** @var \Drupal\entity_clone\EntityCloneSettingsManager $entity_clone_settings_manager */ + $entity_clone_settings_manager = \Drupal::service('entity_clone.settings.manager'); + $form_settings = []; + foreach (array_keys($entity_clone_settings_manager->getContentEntityTypes()) as $entity_type_id) { + $form_settings[$entity_type_id] = [ + 'default_value' => FALSE, + 'disable' => FALSE, + 'hidden' => FALSE, + ]; + } + + \Drupal::configFactory()->getEditable('entity_clone.settings')->set('form_settings', $form_settings)->save(); +} diff --git a/web/modules/entity_clone/entity_clone.routing.yml b/web/modules/entity_clone/entity_clone.routing.yml new file mode 100644 index 0000000000000000000000000000000000000000..b89a94b73d350ae0aafbeafad79dc9a6cf7e8834 --- /dev/null +++ b/web/modules/entity_clone/entity_clone.routing.yml @@ -0,0 +1,9 @@ +entity_clone.settings: + path: '/admin/config/system/entity-clone' + defaults: + _form: 'Drupal\entity_clone\Form\EntityCloneSettingsForm' + _title: 'Entity clone settings' + options: + _admin_route: TRUE + requirements: + _permission: 'administer entity clone' diff --git a/web/modules/entity_clone/entity_clone.services.yml b/web/modules/entity_clone/entity_clone.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..20cf5db105eb6c965ad63eb73bb49622e14b64f5 --- /dev/null +++ b/web/modules/entity_clone/entity_clone.services.yml @@ -0,0 +1,9 @@ +services: + entity_clone.settings.manager: + class: Drupal\entity_clone\EntityCloneSettingsManager + arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@config.factory'] + entity_clone.route_subscriber: + class: Drupal\entity_clone\Routing\RouteSubscriber + arguments: ['@entity_type.manager'] + tags: + - { name: event_subscriber } diff --git a/web/modules/entity_clone/src/EntityClone/Config/ConfigEntityCloneBase.php b/web/modules/entity_clone/src/EntityClone/Config/ConfigEntityCloneBase.php new file mode 100644 index 0000000000000000000000000000000000000000..c0c33490a956af671df83c129fc1c2605a006e69 --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/Config/ConfigEntityCloneBase.php @@ -0,0 +1,86 @@ +<?php + +namespace Drupal\entity_clone\EntityClone\Config; + +use Drupal\Core\Entity\EntityHandlerInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\entity_clone\EntityClone\EntityCloneInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Class ConfigEntityCloneBase. + */ +class ConfigEntityCloneBase implements EntityHandlerInterface, EntityCloneInterface { + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The entity type ID. + * + * @var string + */ + protected $entityTypeId; + + /** + * Constructs a new ConfigEntityCloneBase. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param string $entity_type_id + * The entity type ID. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, $entity_type_id) { + $this->entityTypeManager = $entity_type_manager; + $this->entityTypeId = $entity_type_id; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $container->get('entity_type.manager'), + $entity_type->id() + ); + } + + /** + * {@inheritdoc} + */ + public function cloneEntity(EntityInterface $entity, EntityInterface $cloned_entity, array $properties = []) { + /** @var \Drupal\core\Config\Entity\ConfigEntityInterface $cloned_entity */ + $id_key = $this->entityTypeManager->getDefinition($this->entityTypeId)->getKey('id'); + $label_key = $this->entityTypeManager->getDefinition($this->entityTypeId)->getKey('label'); + + // Set new entity properties. + if (isset($properties['id'])) { + if ($id_key) { + $cloned_entity->set($id_key, $properties['id']); + } + unset($properties['id']); + } + + if (isset($properties['label'])) { + if ($label_key) { + $cloned_entity->set($label_key, $properties['label']); + } + unset($properties['label']); + } + + foreach ($properties as $key => $property) { + $cloned_entity->set($key, $property); + } + + $cloned_entity->save(); + return $cloned_entity; + + } + +} diff --git a/web/modules/entity_clone/src/EntityClone/Config/ConfigEntityCloneFormBase.php b/web/modules/entity_clone/src/EntityClone/Config/ConfigEntityCloneFormBase.php new file mode 100644 index 0000000000000000000000000000000000000000..4f1c4360921397d0dc3825af337350b9afbc4d73 --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/Config/ConfigEntityCloneFormBase.php @@ -0,0 +1,110 @@ +<?php + +namespace Drupal\entity_clone\EntityClone\Config; + +use Drupal\Core\Entity\EntityHandlerInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\TranslationManager; +use Drupal\entity_clone\EntityClone\EntityCloneFormInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Class ConfigEntityCloneFormBase. + */ +class ConfigEntityCloneFormBase implements EntityHandlerInterface, EntityCloneFormInterface { + + /** + * The string translation. + * + * @var \Drupal\Core\StringTranslation\TranslationManager + */ + protected $translationManager; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Constructs a new ConfigEntityCloneFormBase. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\StringTranslation\TranslationManager $translation_manager + * The string translation manager. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationManager $translation_manager) { + $this->entityTypeManager = $entity_type_manager; + $this->translationManager = $translation_manager; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $container->get('entity_type.manager'), + $container->get('string_translation') + ); + } + + /** + * {@inheritdoc} + */ + public function formElement(EntityInterface $entity, $parent = TRUE) { + $form = []; + + if ($this->entityTypeManager->getDefinition($entity->getEntityTypeId())->getKey('label')) { + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->translationManager->translate('New Label'), + '#maxlength' => 255, + '#required' => TRUE, + ]; + } + + $form['id'] = [ + '#type' => 'machine_name', + '#title' => $this->translationManager->translate('New Id'), + '#maxlength' => 255, + '#required' => TRUE, + ]; + + // If entity must have a prefix + // (e.g. entity_form_mode, entity_view_mode, ...). + if (method_exists($entity, 'getTargetType')) { + $form['id']['#field_prefix'] = $entity->getTargetType() . '.'; + } + + if (method_exists($entity, 'load')) { + $form['id']['#machine_name'] = [ + 'exists' => get_class($entity) . '::load', + ]; + } + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getValues(FormStateInterface $form_state) { + // If entity must have a prefix + // (e.g. entity_form_mode, entity_view_mode, ...). + $field_prefix = ''; + if (isset($form_state->getCompleteForm()['id']['#field_prefix'])) { + $field_prefix = $form_state->getCompleteForm()['id']['#field_prefix']; + } + + return [ + 'id' => $field_prefix . $form_state->getValue('id'), + 'label' => $form_state->getValue('label'), + ]; + } + +} diff --git a/web/modules/entity_clone/src/EntityClone/Config/ConfigWithFieldEntityClone.php b/web/modules/entity_clone/src/EntityClone/Config/ConfigWithFieldEntityClone.php new file mode 100644 index 0000000000000000000000000000000000000000..7cdb093ed48d08149ab8433d6184706670646bb5 --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/Config/ConfigWithFieldEntityClone.php @@ -0,0 +1,105 @@ +<?php + +namespace Drupal\entity_clone\EntityClone\Config; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Field\FieldConfigInterface; + +/** + * Class ContentEntityCloneBase. + */ +class ConfigWithFieldEntityClone extends ConfigEntityCloneBase { + + /** + * {@inheritdoc} + */ + public function cloneEntity(EntityInterface $entity, EntityInterface $cloned_entity, array $properties = []) { + $cloned_entity = parent::cloneEntity($entity, $cloned_entity, $properties); + $bundle_of = $cloned_entity->getEntityType()->getBundleOf(); + if ($bundle_of) { + $this->cloneFields($entity->id(), $cloned_entity->id(), $bundle_of); + } + + $view_displays = \Drupal::service('entity_display.repository')->getViewModes($bundle_of); + $view_displays = array_merge($view_displays, ['default' => 'default']); + if (!empty($view_displays)) { + $this->cloneDisplays('view', $entity->id(), $cloned_entity->id(), $view_displays, $bundle_of); + } + + $view_displays = \Drupal::service('entity_display.repository')->getFormModes($bundle_of); + $view_displays = array_merge($view_displays, ['default' => 'default']); + if (!empty($view_displays)) { + $this->cloneDisplays('form', $entity->id(), $cloned_entity->id(), $view_displays, $bundle_of); + } + + return $cloned_entity; + } + + /** + * Clone all fields. Each field re-use existing field storage. + * + * @param string $entity_id + * The base entity ID. + * @param string $cloned_entity_id + * The cloned entity ID. + * @param string $bundle_of + * The bundle of the cloned entity. + */ + protected function cloneFields($entity_id, $cloned_entity_id, $bundle_of) { + /** @var \Drupal\Core\Entity\EntityFieldManager $field_manager */ + $field_manager = \Drupal::service('entity_field.manager'); + $fields = $field_manager->getFieldDefinitions($bundle_of, $entity_id); + foreach ($fields as $field_definition) { + if ($field_definition instanceof FieldConfigInterface) { + if ($this->entityTypeManager->hasHandler($this->entityTypeManager->getDefinition($field_definition->getEntityTypeId()) + ->id(), 'entity_clone') + ) { + /** @var \Drupal\entity_clone\EntityClone\EntityCloneInterface $field_config_clone_handler */ + $field_config_clone_handler = $this->entityTypeManager->getHandler($this->entityTypeManager->getDefinition($field_definition->getEntityTypeId()) + ->id(), 'entity_clone'); + $field_config_properties = [ + 'id' => $field_definition->getName(), + 'label' => $field_definition->label(), + 'skip_storage' => TRUE, + ]; + $cloned_field_definition = $field_definition->createDuplicate(); + $cloned_field_definition->set('bundle', $cloned_entity_id); + $field_config_clone_handler->cloneEntity($field_definition, $cloned_field_definition, $field_config_properties); + } + } + } + } + + /** + * Clone all fields. Each field re-use existing field storage. + * + * @param string $type + * The type of display (view or form). + * @param string $entity_id + * The base entity ID. + * @param string $cloned_entity_id + * The cloned entity ID. + * @param array $view_displays + * All view available display for this type. + * @param string $bundle_of + * The bundle of the cloned entity. + */ + protected function cloneDisplays($type, $entity_id, $cloned_entity_id, array $view_displays, $bundle_of) { + foreach ($view_displays as $view_display_id => $view_display) { + /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface $display */ + $display = $this->entityTypeManager->getStorage('entity_' . $type . '_display')->load($bundle_of . '.' . $entity_id . '.' . $view_display_id); + if ($display) { + /** @var \Drupal\entity_clone\EntityClone\EntityCloneInterface $view_display_clone_handler */ + $view_display_clone_handler = $this->entityTypeManager->getHandler($this->entityTypeManager->getDefinition($display->getEntityTypeId()) + ->id(), 'entity_clone'); + $view_display_properties = [ + 'id' => $bundle_of . '.' . $cloned_entity_id . '.' . $view_display_id, + ]; + $cloned_view_display = $display->createDuplicate(); + $cloned_view_display->set('bundle', $cloned_entity_id); + $view_display_clone_handler->cloneEntity($display, $cloned_view_display, $view_display_properties); + } + } + } + +} diff --git a/web/modules/entity_clone/src/EntityClone/Config/FieldConfigEntityClone.php b/web/modules/entity_clone/src/EntityClone/Config/FieldConfigEntityClone.php new file mode 100644 index 0000000000000000000000000000000000000000..b16d1edaf9613632637bd6dbd89f1f4788f4100f --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/Config/FieldConfigEntityClone.php @@ -0,0 +1,33 @@ +<?php + +namespace Drupal\entity_clone\EntityClone\Config; + +use Drupal\Core\Entity\EntityInterface; + +/** + * Class FieldConfigEntityClone. + */ +class FieldConfigEntityClone extends ConfigEntityCloneBase { + + /** + * {@inheritdoc} + */ + public function cloneEntity(EntityInterface $field_config, EntityInterface $cloned_field_config, array $properties = []) { + /** @var \Drupal\field\Entity\FieldConfig $field_config */ + /** @var \Drupal\field\Entity\FieldConfig $cloned_field_config */ + /** @var \Drupal\field\Entity\FieldStorageConfig $cloned_field_storage */ + + if ((!isset($properties['skip_storage']) || !$properties['skip_storage'])) { + $cloned_field_storage = $field_config->getFieldStorageDefinition()->createDuplicate(); + $cloned_field_storage->set('field_name', $properties['id']); + $cloned_field_storage->set('id', $properties['id'] . '.' . $cloned_field_storage->getTargetEntityTypeId()); + $cloned_field_storage->save(); + } + unset($properties['skip_storage']); + + $properties['field_name'] = $properties['id']; + $properties['id'] = $cloned_field_config->getTargetEntityTypeId() . '.' . $cloned_field_config->getTargetBundle() . '.' . $properties['id']; + return parent::cloneEntity($field_config, $cloned_field_config, $properties); + } + +} diff --git a/web/modules/entity_clone/src/EntityClone/Content/ContentEntityCloneBase.php b/web/modules/entity_clone/src/EntityClone/Content/ContentEntityCloneBase.php new file mode 100644 index 0000000000000000000000000000000000000000..b3b7de1faef6ec638597f47fa7e6cb42600da4f7 --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/Content/ContentEntityCloneBase.php @@ -0,0 +1,177 @@ +<?php + +namespace Drupal\entity_clone\EntityClone\Content; + +use Drupal\Core\Entity\EntityHandlerInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldConfigInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\entity_clone\EntityClone\EntityCloneInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Class ContentEntityCloneBase. + */ +class ContentEntityCloneBase implements EntityHandlerInterface, EntityCloneInterface { + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The entity type ID. + * + * @var string + */ + protected $entityTypeId; + + /** + * Constructs a new ContentEntityCloneBase. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param string $entity_type_id + * The entity type ID. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, $entity_type_id) { + $this->entityTypeManager = $entity_type_manager; + $this->entityTypeId = $entity_type_id; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $container->get('entity_type.manager'), + $entity_type->id() + ); + } + + /** + * {@inheritdoc} + */ + public function cloneEntity(EntityInterface $entity, EntityInterface $cloned_entity, array $properties = []) { + // Clone referenced entities. + if ($cloned_entity instanceof FieldableEntityInterface && $entity instanceof FieldableEntityInterface) { + foreach ($cloned_entity->getFieldDefinitions() as $field_id => $field_definition) { + if ($this->fieldIsClonable($field_definition)) { + $field = $entity->get($field_id); + /** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $value */ + if ($field->count() > 0) { + $cloned_entity->set($field_id, $this->cloneReferencedEntities($field, $field_definition, $properties)); + } + } + } + } + + $this->setClonedEntityLabel($entity, $cloned_entity); + $cloned_entity->save(); + return $cloned_entity; + } + + /** + * Determines if a field is clonable. + * + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition. + * + * @return bool + * TRUE if th field is clonable; FALSE otherwise. + */ + protected function fieldIsClonable(FieldDefinitionInterface $field_definition) { + $clonable_field_types = [ + 'entity_reference', + 'entity_reference_revisions', + ]; + + $type_is_clonable = in_array($field_definition->getType(), $clonable_field_types, TRUE); + if (($field_definition instanceof FieldConfigInterface) && $type_is_clonable) { + return TRUE; + } + return FALSE; + } + + /** + * Sets the cloned entity's label. + * + * @param \Drupal\Core\Entity\EntityInterface $original_entity + * The original entity. + * @param \Drupal\Core\Entity\EntityInterface $cloned_entity + * The entity cloned from the original. + */ + protected function setClonedEntityLabel(EntityInterface $original_entity, EntityInterface $cloned_entity) { + $label_key = $this->entityTypeManager->getDefinition($this->entityTypeId)->getKey('label'); + if ($label_key) { + $cloned_entity->set($label_key, $original_entity->label() . ' - Cloned'); + } + } + + /** + * Clone referenced entities. + * + * @param \Drupal\Core\Field\FieldItemListInterface $field + * The field item. + * @param \Drupal\Core\Field\FieldConfigInterface $field_definition + * The field definition. + * @param array $properties + * All new properties to replace old. + * + * @return array + * Referenced entities. + */ + protected function cloneReferencedEntities(FieldItemListInterface $field, FieldConfigInterface $field_definition, array $properties) { + $referenced_entities = []; + foreach ($field as $value) { + // Check if we're not dealing with an entity that has been deleted in the meantime + if (!$referenced_entity = $value->get('entity')->getTarget()) { + continue; + } + /** @var \Drupal\Core\Entity\ContentEntityInterface $referenced_entity */ + $referenced_entity = $value->get('entity')->getTarget()->getValue(); + if (!empty($properties['recursive'][$field_definition->id()]['references'][$referenced_entity->id()]['clone'])) { + + $cloned_reference = $referenced_entity->createDuplicate(); + /** @var \Drupal\entity_clone\EntityClone\EntityCloneInterface $entity_clone_handler */ + $entity_clone_handler = $this->entityTypeManager->getHandler($referenced_entity->getEntityTypeId(), 'entity_clone'); + $child_properties = $this->getChildProperties($properties, $field_definition, $referenced_entity); + $entity_clone_handler->cloneEntity($referenced_entity, $cloned_reference, $child_properties); + + $referenced_entities[] = $cloned_reference; + } + else { + $referenced_entities[] = $referenced_entity; + } + } + return $referenced_entities; + } + + /** + * Fetches the properties of a child entity. + * + * @param array $properties + * Properties of the clone operation. + * @param \Drupal\Core\Field\FieldConfigInterface $field_definition + * The field definition. + * @param \Drupal\Core\Entity\EntityInterface $referenced_entity + * The field's target entity. + * + * @return array + * Child properties. + */ + protected function getChildProperties(array $properties, FieldConfigInterface $field_definition, EntityInterface $referenced_entity) { + $child_properties = []; + if (isset($properties['recursive'][$field_definition->id()]['references'][$referenced_entity->id()]['children'])) { + $child_properties = $properties['recursive'][$field_definition->id()]['references'][$referenced_entity->id()]['children']; + } + return $child_properties; + } + +} diff --git a/web/modules/entity_clone/src/EntityClone/Content/ContentEntityCloneFormBase.php b/web/modules/entity_clone/src/EntityClone/Content/ContentEntityCloneFormBase.php new file mode 100644 index 0000000000000000000000000000000000000000..f4e5be8353dc4931c3a7dc174b33728faf9f9b55 --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/Content/ContentEntityCloneFormBase.php @@ -0,0 +1,269 @@ +<?php + +namespace Drupal\entity_clone\EntityClone\Content; + +use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityHandlerInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Field\FieldConfigInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\StringTranslation\TranslationManager; +use Drupal\entity_clone\EntityClone\EntityCloneFormInterface; +use Drupal\entity_clone\EntityCloneSettingsManager; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Class ContentEntityCloneFormBase. + */ +class ContentEntityCloneFormBase implements EntityHandlerInterface, EntityCloneFormInterface { + + use StringTranslationTrait; + + /** + * The entity type manager. + * + * @var \Drupal\Core\StringTranslation\TranslationManager + */ + protected $translationManager; + + /** + * The entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManager + */ + protected $entityTypeManager; + + /** + * The entity clone settings manager service. + * + * @var \Drupal\entity_clone\EntityCloneSettingsManager + */ + protected $entityCloneSettingsManager; + + /** + * Entities we've found while cloning. + * + * @var \Drupal\Core\Entity\ContentEntityInterface[] + */ + protected $discovered_entities = []; + + /** + * Constructs a new ContentEntityCloneFormBase. + * + * @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\StringTranslation\TranslationManager $translation_manager + * The string translation manager. + * @param \Drupal\entity_clone\EntityCloneSettingsManager $entity_clone_settings_manager + * The entity clone settings manager. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationManager $translation_manager, EntityCloneSettingsManager $entity_clone_settings_manager) { + $this->entityTypeManager = $entity_type_manager; + $this->translationManager = $translation_manager; + $this->entityCloneSettingsManager = $entity_clone_settings_manager; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $container->get('entity_type.manager'), + $container->get('string_translation'), + $container->get('entity_clone.settings.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function formElement(EntityInterface $entity, $parent = TRUE) { + $form = [ + 'recursive' => [] + ]; + + if ($entity instanceof FieldableEntityInterface) { + foreach ($entity->getFieldDefinitions() as $field_id => $field_definition) { + if ($field_definition instanceof FieldConfigInterface && in_array($field_definition->getType(), ['entity_reference', 'entity_reference_revisions'], TRUE)) { + $field = $entity->get($field_id); + /** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $value */ + if ($field->count() > 0) { + $form['recursive'] = array_merge($form['recursive'], $this->getRecursiveFormElement($field_definition, $field_id, $field)); + } + } + } + + if ($parent) { + $form = array_merge([ + 'description' => [ + '#markup' => $this->t(" + <p>Specify the child entities (the entities referenced by this entity) that should also be cloned as part of + the cloning process. If they're not included, these fields' referenced entities will be the same as in the + original. In other words, fields in both the original entity and the cloned entity will refer to the same + referenced entity. Examples:</p> + + <p>If you have a Paragraph field in your entity, and you choose not to clone it here, deleting the original + or cloned entity will also delete the Paragraph field from the other one. So you probably want to clone + Paragraph fields.</p> + + <p>However, if you have a User reference field, you probably don't want to clone it here because a new User + will be created for referencing by the clone.</p> + + <p>Some options may be disabled here, preventing you from changing them, as set by your administrator. Some + options may also be missing, hidden by your administrator, forcing you to clone with the default settings. + It's possible that there are no options here for you at all, or none need to be set, in which case you may + simply hit the <em>Clone</em> button.</p> + "), + '#access' => $this->descriptionShouldBeShown($form), + ], + ], $form); + } + } + + return $form; + } + + /** + * Get the recursive form element. + * + * @param \Drupal\Core\Field\FieldConfigInterface $field_definition + * The field definition. + * @param string $field_id + * The field ID. + * @param \Drupal\Core\Field\FieldItemListInterface $field + * The field item. + * + * @return array + * The form element for a recursive clone. + */ + protected function getRecursiveFormElement(FieldConfigInterface $field_definition, $field_id, FieldItemListInterface $field) { + $form_element = [ + '#tree' => TRUE, + ]; + + $fieldset_access = !$this->entityCloneSettingsManager->getHiddenValue($field_definition->getFieldStorageDefinition()->getSetting('target_type')); + $form_element[$field_definition->id()] = [ + '#type' => 'fieldset', + '#title' => $this->t('Entities referenced by field <em>@label (@field_id)</em>.', [ + '@label' => $field_definition->label(), + '@field_id' => $field_id, + ]), + '#access' => $fieldset_access, + '#description_should_be_shown' => $fieldset_access, + ]; + + foreach ($field as $value) { + // Check if we're not dealing with an entity that has been deleted in the meantime + if (!$referenced_entity = $value->get('entity')->getTarget()) { + continue; + } + /** @var \Drupal\Core\Entity\ContentEntityInterface $referenced_entity */ + $referenced_entity = $value->get('entity')->getTarget()->getValue(); + + $form_element[$field_definition->id()]['references'][$referenced_entity->id()]['clone'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Clone entity <strong>ID:</strong> <em>@entity_id</em>, <strong>Type:</strong> <em>@entity_type - @bundle</em>, <strong>Label:</strong> <em>@entity_label</em>', [ + '@entity_id' => $referenced_entity->id(), + '@entity_type' => $referenced_entity->getEntityTypeId(), + '@bundle' => $referenced_entity->bundle(), + '@entity_label' => $referenced_entity->label(), + ]), + '#default_value' => $this->entityCloneSettingsManager->getDefaultValue($referenced_entity->getEntityTypeId()), + '#access' => $referenced_entity->access('view label'), + ]; + + if ($this->entityCloneSettingsManager->getDisableValue($referenced_entity->getEntityTypeId())) { + $form_element[$field_definition->id()]['references'][$referenced_entity->id()]['clone']['#attributes'] = [ + 'disabled' => TRUE, + ]; + $form_element[$field_definition->id()]['references'][$referenced_entity->id()]['clone']['#value'] = $form_element[$field_definition->id()]['references'][$referenced_entity->id()]['clone']['#default_value']; + } + + $form_element[$field_definition->id()]['references'][$referenced_entity->id()]['target_entity_type_id'] = [ + '#type' => 'hidden', + '#value' => $referenced_entity->getEntityTypeId(), + ]; + + $form_element[$field_definition->id()]['references'][$referenced_entity->id()]['target_bundle'] = [ + '#type' => 'hidden', + '#value' => $referenced_entity->bundle(), + ]; + if ($referenced_entity instanceOf ContentEntityInterface) { + $form_element[$field_definition->id()]['references'][$referenced_entity->id()]['children'] = $this->getChildren($referenced_entity); + } + } + + return $form_element; + } + + /** + * Fetches clonable children from a field. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $referenced_entity + * The field item list. + * + * @return array + * The list of children. + */ + protected function getChildren(ContentEntityInterface $referenced_entity) { + // Use memoization to prevent circular references. + if (array_key_exists($referenced_entity->id(), $this->discovered_entities)) { + return; + } + + /** @var \Drupal\entity_clone\EntityClone\EntityCloneFormInterface $entity_clone_handler */ + if ($this->entityTypeManager->hasHandler($referenced_entity->getEntityTypeId(), 'entity_clone_form')) { + // Record that we've found this entity. + $this->discovered_entities[$referenced_entity->id()] = $referenced_entity; + + $entity_clone_form_handler = $this->entityTypeManager->getHandler($referenced_entity->getEntityTypeId(), 'entity_clone_form'); + return $entity_clone_form_handler->formElement($referenced_entity, FALSE); + } + + return; + } + + /** + * {@inheritdoc} + */ + public function getValues(FormStateInterface $form_state) { + return $form_state->getValues(); + } + + /** + * Checks if description should be shown. + * + * If there are no recursive elements visible, the description should be + * hidden. + * + * @param array $form + * Form. + * + * @return bool + * TRUE if description should be shown. + */ + protected function descriptionShouldBeShown(array $form) { + $show_description = TRUE; + if (!isset($form['recursive'])) { + $show_description = FALSE; + } + + $visible = FALSE; + array_walk_recursive($form['recursive'], function ($item, $key) use (&$visible) { + if ($key === '#description_should_be_shown') { + $visible = ($visible || $item); + } + }); + + if (!$visible) { + $show_description = FALSE; + } + return $show_description; + } + +} diff --git a/web/modules/entity_clone/src/EntityClone/Content/FileEntityClone.php b/web/modules/entity_clone/src/EntityClone/Content/FileEntityClone.php new file mode 100644 index 0000000000000000000000000000000000000000..fb552c46312f8a8facc47fe6f857bd068d7710fc --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/Content/FileEntityClone.php @@ -0,0 +1,21 @@ +<?php + +namespace Drupal\entity_clone\EntityClone\Content; + +use Drupal\Core\Entity\EntityInterface; + +/** + * Class ContentEntityCloneBase. + */ +class FileEntityClone extends ContentEntityCloneBase { + + /** + * {@inheritdoc} + */ + public function cloneEntity(EntityInterface $entity, EntityInterface $cloned_entity, array $properties = []) { + /** @var \Drupal\file\FileInterface $cloned_entity */ + $cloned_file = file_copy($cloned_entity, $cloned_entity->getFileUri(), FILE_EXISTS_RENAME); + return parent::cloneEntity($entity, $cloned_file, $properties); + } + +} diff --git a/web/modules/entity_clone/src/EntityClone/Content/TaxonomyTermEntityClone.php b/web/modules/entity_clone/src/EntityClone/Content/TaxonomyTermEntityClone.php new file mode 100644 index 0000000000000000000000000000000000000000..15388c666fe74bc02bdea30e7e11c81014abeab8 --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/Content/TaxonomyTermEntityClone.php @@ -0,0 +1,26 @@ +<?php + +namespace Drupal\entity_clone\EntityClone\Content; + +use Drupal\Core\Entity\EntityInterface; + +/** + * Class TaxonomyTermEntityClone. + */ +class TaxonomyTermEntityClone extends ContentEntityCloneBase { + + /** + * {@inheritdoc} + */ + public function cloneEntity(EntityInterface $entity, EntityInterface $cloned_entity, array $properties = []) { + /** @var \Drupal\core\Entity\ContentEntityInterface $cloned_entity */ + + // Enforce a parent if the cloned term doesn't have a parent. + // (First level of a taxonomy tree). + if (!isset($cloned_entity->parent->target_id)) { + $cloned_entity->set('parent', 0); + } + return parent::cloneEntity($entity, $cloned_entity, $properties); + } + +} diff --git a/web/modules/entity_clone/src/EntityClone/Content/UserEntityClone.php b/web/modules/entity_clone/src/EntityClone/Content/UserEntityClone.php new file mode 100644 index 0000000000000000000000000000000000000000..68e64b49b3598f75450d89753e5204bc33b94676 --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/Content/UserEntityClone.php @@ -0,0 +1,21 @@ +<?php + +namespace Drupal\entity_clone\EntityClone\Content; + +use Drupal\Core\Entity\EntityInterface; + +/** + * Class ContentEntityCloneBase. + */ +class UserEntityClone extends ContentEntityCloneBase { + + /** + * {@inheritdoc} + */ + public function cloneEntity(EntityInterface $entity, EntityInterface $cloned_entity, array $properties = []) { + /** @var \Drupal\user\UserInterface $cloned_entity */ + $cloned_entity->set('name', $cloned_entity->getAccountName() . '_cloned'); + return parent::cloneEntity($entity, $cloned_entity, $properties); + } + +} diff --git a/web/modules/entity_clone/src/EntityClone/EntityCloneFormInterface.php b/web/modules/entity_clone/src/EntityClone/EntityCloneFormInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..55346c1f89b08f56fd077076502f97a9fa30f379 --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/EntityCloneFormInterface.php @@ -0,0 +1,38 @@ +<?php + +namespace Drupal\entity_clone\EntityClone; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Form\FormStateInterface; + +/** + * Defines a common interface for all entity clone form objects. + */ +interface EntityCloneFormInterface { + + /** + * Get all specific form element. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. + * + * @param bool $parent + * Is the parent form element. + * + * @return array + * The form elements. + */ + public function formElement(EntityInterface $entity, $parent = TRUE); + + /** + * Get all new values provided by the specific form element. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return array + * An array containing all new values. + */ + public function getValues(FormStateInterface $form_state); + +} diff --git a/web/modules/entity_clone/src/EntityClone/EntityCloneInterface.php b/web/modules/entity_clone/src/EntityClone/EntityCloneInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ed020233a981e9d67e7c768cad60c9562d9aa003 --- /dev/null +++ b/web/modules/entity_clone/src/EntityClone/EntityCloneInterface.php @@ -0,0 +1,27 @@ +<?php + +namespace Drupal\entity_clone\EntityClone; + +use Drupal\Core\Entity\EntityInterface; + +/** + * Defines a common interface for all entity clone objects. + */ +interface EntityCloneInterface { + + /** + * Clone an entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. + * @param \Drupal\Core\Entity\EntityInterface $cloned_entity + * The cloned entity. + * @param array $properties + * All new properties to replace old. + * + * @return \Drupal\Core\Entity\EntityInterface + * The new saved entity. + */ + public function cloneEntity(EntityInterface $entity, EntityInterface $cloned_entity, array $properties = []); + +} diff --git a/web/modules/entity_clone/src/EntityClonePermissions.php b/web/modules/entity_clone/src/EntityClonePermissions.php new file mode 100644 index 0000000000000000000000000000000000000000..3057d57d9a7f942bff4cfebe88ec20f9a8fd86a1 --- /dev/null +++ b/web/modules/entity_clone/src/EntityClonePermissions.php @@ -0,0 +1,71 @@ +<?php + +namespace Drupal\entity_clone; + +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\StringTranslation\TranslationManager; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides dynamic permissions of the entity_clone module. + */ +class EntityClonePermissions implements ContainerInjectionInterface { + + /** + * The entoty type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The string translation manager. + * + * @var \Drupal\Core\StringTranslation\TranslationManager + */ + protected $translationManager; + + + /** + * Constructs a new EntityClonePermissions instance. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager + * The entity type manager. + * @param \Drupal\Core\StringTranslation\TranslationManager $string_translation + * The string translation manager. + */ + public function __construct(EntityTypeManagerInterface $entity_manager, TranslationManager $string_translation) { + $this->entityTypeManager = $entity_manager; + $this->translationManager = $string_translation; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + $container->get('string_translation') + ); + } + + /** + * Returns an array of entity_clone permissions. + * + * @return array + * The permission list. + */ + public function permissions() { + $permissions = []; + + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { + $permissions['clone ' . $entity_type_id . ' entity'] = $this->translationManager->translate('Clone all <em>@label</em> entities', [ + '@label' => $entity_type->getLabel(), + ]); + } + + return $permissions; + } + +} diff --git a/web/modules/entity_clone/src/EntityCloneSettingsManager.php b/web/modules/entity_clone/src/EntityCloneSettingsManager.php new file mode 100644 index 0000000000000000000000000000000000000000..59899a3fb0199e8d9603a8d20bb1c11b3e5a5673 --- /dev/null +++ b/web/modules/entity_clone/src/EntityCloneSettingsManager.php @@ -0,0 +1,149 @@ +<?php + +namespace Drupal\entity_clone; + +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\ContentEntityTypeInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; + +/** + * Manage entity clone configuration. + */ +class EntityCloneSettingsManager { + + /** + * The entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The entity type bundle info service. + * + * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface + */ + protected $entityTypeBundleInfo; + + /** + * The immutable entity clone settings configuration entity. + * + * @var \Drupal\Core\Config\ImmutableConfig + */ + protected $config; + + /** + * The editable entity clone settings configuration entity. + * + * @var \Drupal\Core\Config\Config + */ + protected $editableConfig; + + /** + * EntityCloneSettingsManager constructor. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager service. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle info service. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory service. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, ConfigFactoryInterface $config_factory) { + $this->entityTypeManager = $entity_type_manager; + $this->entityTypeBundleInfo = $entity_type_bundle_info; + $this->config = $config_factory->get('entity_clone.settings'); + $this->editableConfig = $config_factory->getEditable('entity_clone.settings'); + } + + /** + * Get all content entity types. + * + * @return \Drupal\Core\Entity\ContentEntityTypeInterface[] + * An array containing all content entity types. + */ + public function getContentEntityTypes() { + $definitions = $this->entityTypeManager->getDefinitions(); + $ret = []; + foreach ($definitions as $machine => $type) { + if ($type instanceof ContentEntityTypeInterface) { + $ret[$machine] = $type; + } + } + + return $ret; + } + + /** + * Set the entity clone settings. + * + * @param array $settings + * The settings from the form. + */ + public function setFormSettings(array $settings) { + if (isset($settings['table'])) { + array_walk_recursive($settings['table'], function (&$item) { + if ($item == '1') { + $item = TRUE; + } + else { + $item = FALSE; + } + }); + $this->editableConfig->set('form_settings', $settings['table'])->save(); + } + } + + /** + * Get the checkbox default value for a given entity type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return bool + * The default value. + */ + public function getDefaultValue($entity_type_id) { + $form_settings = $this->config->get('form_settings'); + if (isset($form_settings[$entity_type_id]['default_value'])) { + return $form_settings[$entity_type_id]['default_value']; + } + return FALSE; + } + + /** + * Get the checkbox disable value for a given entity type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return bool + * The disable value. + */ + public function getDisableValue($entity_type_id) { + $form_settings = $this->config->get('form_settings'); + if (isset($form_settings[$entity_type_id]['disable'])) { + return $form_settings[$entity_type_id]['disable']; + } + return FALSE; + } + + /** + * Get the checkbox hidden value for a given entity type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return bool + * The hidden value. + */ + public function getHiddenValue($entity_type_id) { + $form_settings = $this->config->get('form_settings'); + if (isset($form_settings[$entity_type_id]['hidden'])) { + return $form_settings[$entity_type_id]['hidden']; + } + return FALSE; + } + +} diff --git a/web/modules/entity_clone/src/Event/EntityCloneEvent.php b/web/modules/entity_clone/src/Event/EntityCloneEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..8889928858ad02c73e3192f379109cc1970bd79a --- /dev/null +++ b/web/modules/entity_clone/src/Event/EntityCloneEvent.php @@ -0,0 +1,80 @@ +<?php + +namespace Drupal\entity_clone\Event; + +use Drupal\Core\Entity\EntityInterface; +use Symfony\Component\EventDispatcher\Event; + +/** + * Represents entity selection as event. + */ +class EntityCloneEvent extends Event { + + /** + * Entity being cloned. + * + * @var \Drupal\Core\Entity\EntityInterface + */ + protected $entity; + + /** + * New cloned entity. + * + * @var \Drupal\Core\Entity\EntityInterface + */ + protected $clonedEntity; + + /** + * Properties. + * + * @var array + */ + protected $properties; + + /** + * Constructs an EntityCloneEvent object. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The original entity that was cloned. + * @param \Drupal\Core\Entity\EntityInterface $cloned_entity + * The clone of the original entity. + * @param array $properties + * The entity's properties. + */ + public function __construct(EntityInterface $entity, EntityInterface $cloned_entity, array $properties = []) { + $this->entity = $entity; + $this->clonedEntity = $cloned_entity; + $this->properties = $properties; + } + + /** + * Gets entity being cloned. + * + * @return \Drupal\Core\Entity\EntityInterface + * The original entity. + */ + public function getEntity() { + return $this->entity; + } + + /** + * Gets new cloned entity. + * + * @return \Drupal\Core\Entity\EntityInterface + * The cloned entity. + */ + public function getClonedEntity() { + return $this->clonedEntity; + } + + /** + * Gets entity properties. + * + * @return array + * The list of properties. + */ + public function getProperties() { + return $this->properties; + } + +} diff --git a/web/modules/entity_clone/src/Event/EntityCloneEvents.php b/web/modules/entity_clone/src/Event/EntityCloneEvents.php new file mode 100644 index 0000000000000000000000000000000000000000..7faf8e2293c1a78e2f98c5e131aa8b330b5cb949 --- /dev/null +++ b/web/modules/entity_clone/src/Event/EntityCloneEvents.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\entity_clone\Event; + +/** + * Contains all events thrown by entity clone. + */ +final class EntityCloneEvents { + + /** + * The PRE_CLONE event occurs before the entity was cloned. + * + * @var string + */ + const PRE_CLONE = 'entity_clone.pre_clone'; + + /** + * The POST_CLONE event occurs when an entity has been cloned and saved. + * + * @var string + */ + const POST_CLONE = 'entity_clone.post_clone'; + +} diff --git a/web/modules/entity_clone/src/Form/EntityCloneForm.php b/web/modules/entity_clone/src/Form/EntityCloneForm.php new file mode 100644 index 0000000000000000000000000000000000000000..8e7e658ca654d0ff242b7dc8c64d9649d53bebe3 --- /dev/null +++ b/web/modules/entity_clone/src/Form/EntityCloneForm.php @@ -0,0 +1,202 @@ +<?php + +namespace Drupal\entity_clone\Form; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\StringTranslation\TranslationManager; +use Drupal\entity_clone\Event\EntityCloneEvent; +use Drupal\entity_clone\Event\EntityCloneEvents; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Implements an entity Clone form. + */ +class EntityCloneForm extends FormBase { + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The entity ready to clone. + * + * @var \Drupal\Core\Entity\EntityInterface + */ + protected $entity; + + /** + * The entity type définition. + * + * @var \Drupal\Core\Entity\EntityTypeInterface + */ + protected $entityTypeDefinition; + + /** + * The string translation manager. + * + * @var \Drupal\Core\StringTranslation\TranslationManager + */ + protected $stringTranslationManager; + + /** + * Event dispatcher service. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * Constructs a new Entity Clone form. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match service. + * @param \Drupal\Core\StringTranslation\TranslationManager $string_translation + * The string translation manager. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher + * The event dispatcher service. + * + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, RouteMatchInterface $route_match, TranslationManager $string_translation, EventDispatcherInterface $eventDispatcher) { + $this->entityTypeManager = $entity_type_manager; + $this->stringTranslationManager = $string_translation; + $this->eventDispatcher = $eventDispatcher; + + $parameter_name = $route_match->getRouteObject()->getOption('_entity_clone_entity_type_id'); + $this->entity = $route_match->getParameter($parameter_name); + + $this->entityTypeDefinition = $entity_type_manager->getDefinition($this->entity->getEntityTypeId()); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + $container->get('current_route_match'), + $container->get('string_translation'), + $container->get('event_dispatcher') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'entity_clone_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + if ($this->entity && $this->entityTypeDefinition->hasHandlerClass('entity_clone')) { + + /** @var \Drupal\entity_clone\EntityClone\EntityCloneFormInterface $entity_clone_handler */ + if ($this->entityTypeManager->hasHandler($this->entityTypeDefinition->id(), 'entity_clone_form')) { + $entity_clone_form_handler = $this->entityTypeManager->getHandler($this->entityTypeDefinition->id(), 'entity_clone_form'); + $form = array_merge($form, $entity_clone_form_handler->formElement($this->entity)); + } + + $form['clone'] = [ + '#type' => 'submit', + '#value' => 'Clone', + ]; + + $form['abort'] = [ + '#type' => 'submit', + '#value' => 'Abort', + '#submit' => ['::cancelForm'], + ]; + } + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) {} + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\entity_clone\EntityClone\EntityCloneInterface $entity_clone_handler */ + $entity_clone_handler = $this->entityTypeManager->getHandler($this->entityTypeDefinition->id(), 'entity_clone'); + if ($this->entityTypeManager->hasHandler($this->entityTypeDefinition->id(), 'entity_clone_form')) { + $entity_clone_form_handler = $this->entityTypeManager->getHandler($this->entityTypeDefinition->id(), 'entity_clone_form'); + } + + $properties = []; + if (isset($entity_clone_form_handler) && $entity_clone_form_handler) { + $properties = $entity_clone_form_handler->getValues($form_state); + } + + $duplicate = $this->entity->createDuplicate(); + + $this->eventDispatcher->dispatch(EntityCloneEvents::PRE_CLONE, new EntityCloneEvent($this->entity, $duplicate, $properties)); + $cloned_entity = $entity_clone_handler->cloneEntity($this->entity, $duplicate, $properties); + $this->eventDispatcher->dispatch(EntityCloneEvents::POST_CLONE, new EntityCloneEvent($this->entity, $duplicate, $properties)); + + drupal_set_message($this->stringTranslationManager->translate('The entity <em>@entity (@entity_id)</em> of type <em>@type</em> was cloned', [ + '@entity' => $this->entity->label(), + '@entity_id' => $this->entity->id(), + '@type' => $this->entity->getEntityTypeId(), + ])); + + $this->formSetRedirect($form_state, $cloned_entity); + } + + /** + * Cancel form handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function cancelForm(array &$form, FormStateInterface $form_state) { + $this->formSetRedirect($form_state, $this->entity); + } + + /** + * Set a redirect on form state. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The cloned entity. + */ + protected function formSetRedirect(FormStateInterface $form_state, EntityInterface $entity) { + if ($entity && $entity->hasLinkTemplate('canonical')) { + $form_state->setRedirect($entity->toUrl()->getRouteName(), $entity->toUrl()->getRouteParameters()); + } + else { + $form_state->setRedirect('<front>'); + } + } + + /** + * Gets the entity of this form. + * + * @return \Drupal\Core\Entity\EntityInterface + * The entity. + */ + public function getEntity() { + return $this->entity; + } + +} diff --git a/web/modules/entity_clone/src/Form/EntityCloneSettingsForm.php b/web/modules/entity_clone/src/Form/EntityCloneSettingsForm.php new file mode 100644 index 0000000000000000000000000000000000000000..9dde82bf5b60c2eec92c0e1cdf6cbc6f3396ef7a --- /dev/null +++ b/web/modules/entity_clone/src/Form/EntityCloneSettingsForm.php @@ -0,0 +1,125 @@ +<?php + +namespace Drupal\entity_clone\Form; + +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\entity_clone\EntityCloneSettingsManager; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provide the settings form for entity clone. + */ +class EntityCloneSettingsForm extends ConfigFormBase implements ContainerInjectionInterface { + + /** + * The entity clone settings manager. + * + * @var \Drupal\entity_clone\EntityCloneSettingsManager + */ + protected $entityCloneSettingsManager; + + /** + * {@inheritdoc} + * + * @var \Drupal\entity_clone\EntityCloneSettingsManager $entity_clone_settings_manager + */ + public function __construct(ConfigFactoryInterface $config_factory, EntityCloneSettingsManager $entity_clone_settings_manager) { + parent::__construct($config_factory); + $this->entityCloneSettingsManager = $entity_clone_settings_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('config.factory'), + $container->get('entity_clone.settings.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getEditableConfigNames() { + return ['entity_clone.settings']; + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'entity_clone_settings_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['#tree'] = TRUE; + + $form['form_settings'] = [ + '#tree' => TRUE, + '#type' => 'fieldset', + '#title' => $this->t('Clone form settings'), + '#description' => $this->t(" + For each type of child entity (the entity that's referenced by the entity being + cloned), please set your cloning preferences. This will affect the clone form presented to users when they + clone entities. Default behaviour for whether or not the child entities should be cloned is specified in + the left-most column. To prevent users from altering behaviour for each type when they're actually cloning + (but still allowing them to see what will happen), use the middle column. The right-most column can be used + to hide the form options from users completely. This will run the clone operation with the defaults set here + (in the left-most column). See the clone form (by cloning an entity) for more information. + "), + '#open' => TRUE, + '#collapsible' => FALSE, + ]; + + $form['form_settings']['table'] = [ + '#type' => 'table', + '#header' => [ + 'label' => $this->t('Label'), + 'default_value' => $this->t('Checkboxes default value'), + 'disable' => $this->t('Disable checkboxes'), + 'hidden' => $this->t('Hide checkboxes'), + ], + ]; + + foreach ($this->entityCloneSettingsManager->getContentEntityTypes() as $type_id => $type) { + $form['form_settings']['table'][$type_id] = [ + 'label' => [ + '#type' => 'label', + '#title' => $this->t('@type', [ + '@type' => $type->getLabel(), + ]), + ], + 'default_value' => [ + '#type' => 'checkbox', + '#default_value' => $this->entityCloneSettingsManager->getDefaultValue($type_id), + ], + 'disable' => [ + '#type' => 'checkbox', + '#default_value' => $this->entityCloneSettingsManager->getDisableValue($type_id), + ], + 'hidden' => [ + '#type' => 'checkbox', + '#default_value' => $this->entityCloneSettingsManager->getHiddenValue($type_id), + ], + ]; + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->entityCloneSettingsManager->setFormSettings($form_state->getValue('form_settings')); + parent::submitForm($form, $form_state); + } + +} diff --git a/web/modules/entity_clone/src/Plugin/Derivative/DynamicLocalTasks.php b/web/modules/entity_clone/src/Plugin/Derivative/DynamicLocalTasks.php new file mode 100644 index 0000000000000000000000000000000000000000..44e1689a263ef7f31307695207856b3398fc96d6 --- /dev/null +++ b/web/modules/entity_clone/src/Plugin/Derivative/DynamicLocalTasks.php @@ -0,0 +1,77 @@ +<?php + +namespace Drupal\entity_clone\Plugin\Derivative; + +use Drupal\Component\Plugin\Derivative\DeriverBase; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; +use Drupal\Core\StringTranslation\TranslationManager; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Defines dynamic local tasks. + */ +class DynamicLocalTasks extends DeriverBase implements ContainerDeriverInterface { + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The translation manager. + * + * @var \Drupal\Core\StringTranslation\TranslationManager + */ + protected $translationManager; + + /** + * Constructs a new DynamicLocalTasks. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\StringTranslation\TranslationManager $string_translation + * The translation manager. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationManager $string_translation) { + $this->entityTypeManager = $entity_type_manager; + $this->translationManager = $string_translation; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, $base_plugin_id) { + return new static( + $container->get('entity_type.manager'), + $container->get('string_translation') + ); + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions($base_plugin_definition) { + $this->derivatives = []; + + /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { + $has_clone_path = $entity_type->hasLinkTemplate('clone-form'); + $has_canonical_path = $entity_type->hasLinkTemplate('canonical'); + + if ($has_clone_path) { + $this->derivatives["$entity_type_id.clone_tab"] = array( + 'route_name' => "entity.$entity_type_id.clone_form", + 'title' => $this->translationManager->translate('Clone'), + 'base_route' => "entity.$entity_type_id." . ($has_canonical_path ? "canonical" : "edit_form"), + 'weight' => 100, + ); + } + } + + return $this->derivatives; + } + +} diff --git a/web/modules/entity_clone/src/Routing/RouteSubscriber.php b/web/modules/entity_clone/src/Routing/RouteSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..f3bf092f73d2837e765e04055d26f031368b256f --- /dev/null +++ b/web/modules/entity_clone/src/Routing/RouteSubscriber.php @@ -0,0 +1,85 @@ +<?php + +namespace Drupal\entity_clone\Routing; + +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Routing\RouteSubscriberBase; +use Drupal\Core\Routing\RoutingEvents; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * Subscriber for Entity Clone routes. + */ +class RouteSubscriber extends RouteSubscriberBase { + + /** + * The entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Constructs a new RouteSubscriber object. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager + * The entity type manager. + */ + public function __construct(EntityTypeManagerInterface $entity_manager) { + $this->entityTypeManager = $entity_manager; + } + + /** + * {@inheritdoc} + */ + protected function alterRoutes(RouteCollection $collection) { + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { + if ($route = $this->getEntityCloneRoute($entity_type)) { + $collection->add("entity.$entity_type_id.clone_form", $route); + } + } + } + + /** + * Gets the entity_clone route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getEntityCloneRoute(EntityTypeInterface $entity_type) { + if ($clone_form = $entity_type->getLinkTemplate('clone-form')) { + $entity_type_id = $entity_type->id(); + $route = new Route($clone_form); + $route + ->addDefaults([ + '_form' => '\Drupal\entity_clone\Form\EntityCloneForm', + '_title' => 'Clone ' . $entity_type->getLabel(), + ]) + ->addRequirements([ + '_permission' => 'clone ' . $entity_type->id() . ' entity', + ]) + ->setOption('_entity_clone_entity_type_id', $entity_type_id) + ->setOption('_admin_route', TRUE) + ->setOption('parameters', [ + $entity_type_id => ['type' => 'entity:' . $entity_type_id], + ]); + + return $route; + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events = parent::getSubscribedEvents(); + $events[RoutingEvents::ALTER] = 'onAlterRoutes'; + return $events; + } + +} diff --git a/web/modules/entity_clone/src/Tests/EntityCloneActionTest.php b/web/modules/entity_clone/src/Tests/EntityCloneActionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f60728753444b52e2c06f9ab28a8e8a0141e4297 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneActionTest.php @@ -0,0 +1,93 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneActionTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create an action and test a clone. + * + * @group entity_clone + */ +class EntityCloneActionTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'action']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'administer actions', + 'clone action entity' + ]; + + /** + * An administrative user with permission to configure actions settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testActionEntityClone() { + foreach (\Drupal::service('plugin.manager.action')->getDefinitions() as $id => $definition) { + if (is_subclass_of($definition['class'], '\Drupal\Core\Plugin\PluginFormInterface') && $definition['label'] == 'Send email') { + $action_key = $id; + break; + } + } + + $edit = [ + 'label' => 'Test send email action for clone', + 'id' => 'test_send_email_for_clone', + 'recipient' => 'test@recipient.com', + 'subject' => 'test subject', + 'message' => 'test message', + ]; + $this->drupalPostForm("admin/config/system/actions/add/$action_key", $edit, t('Save')); + + $actions = \Drupal::entityTypeManager() + ->getStorage('action') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $action = reset($actions); + + $edit = [ + 'label' => 'Test send email action cloned', + 'id' => 'test_send_email_cloned', + ]; + $this->drupalPostForm('entity_clone/action/' . $action->id(), $edit, t('Clone')); + + $actions = \Drupal::entityTypeManager() + ->getStorage('action') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $action = reset($actions); + $this->assertTrue($action, 'Test action cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneBlockTest.php b/web/modules/entity_clone/src/Tests/EntityCloneBlockTest.php new file mode 100644 index 0000000000000000000000000000000000000000..07295d543147fba4f51f21dcc94e7daf0fba7013 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneBlockTest.php @@ -0,0 +1,82 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneBlockTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\block\Entity\Block; +use Drupal\simpletest\WebTestBase; + +/** + * Create an block and test a clone. + * + * @group entity_clone + */ +class EntityCloneBlockTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'block']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'administer blocks', + 'clone block entity' + ]; + + /** + * An administrative user with permission to configure blocks settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testBlockEntityClone() { + $config = \Drupal::configFactory(); + $block = Block::create([ + 'plugin' => 'test_block', + 'region' => 'sidebar_first', + 'id' => 'test_block', + 'theme' => $config->get('system.theme')->get('default'), + 'label' => $this->randomMachineName(8), + 'visibility' => [], + 'weight' => 0, + ]); + $block->save(); + + $edit = [ + 'id' => 'test_block_cloned', + ]; + $this->drupalPostForm('entity_clone/block/' . $block->id(), $edit, t('Clone')); + + $blocks = \Drupal::entityTypeManager() + ->getStorage('block') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $block = reset($blocks); + $this->assertTrue($block, 'Test block cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneCommentTest.php b/web/modules/entity_clone/src/Tests/EntityCloneCommentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..33ce635a046fce4cce8017ace10876f658cd3c66 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneCommentTest.php @@ -0,0 +1,75 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneCommentTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\comment\Tests\CommentTestBase; +use Drupal\comment\Tests\CommentTestTrait; + +/** + * Create a comment and test a clone. + * + * @group entity_clone + */ +class EntityCloneCommentTest extends CommentTestBase { + + use CommentTestTrait; + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'block', 'comment', 'node', 'history', 'field_ui', 'datetime']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'administer content types', + 'administer comments', + 'administer comment types', + 'administer comment fields', + 'administer comment display', + 'skip comment approval', + 'post comments', + 'access comments', + 'access user profiles', + 'access content', + 'clone comment entity' + ]; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testCommentEntityClone() { + $subject = 'Test comment for clone'; + $body = $this->randomMachineName(); + $comment = $this->postComment($this->node, $body, $subject, TRUE); + + $this->drupalPostForm('entity_clone/comment/' . $comment->id(), [], t('Clone')); + + $comments = \Drupal::entityTypeManager() + ->getStorage('comment') + ->loadByProperties([ + 'subject' => $subject . ' - Cloned', + 'comment_body' => $body, + ]); + $comments = reset($comments); + $this->assertTrue($comments, 'Test comment cloned found in database.'); + } + +} \ No newline at end of file diff --git a/web/modules/entity_clone/src/Tests/EntityCloneContentRecursiveTest.php b/web/modules/entity_clone/src/Tests/EntityCloneContentRecursiveTest.php new file mode 100644 index 0000000000000000000000000000000000000000..589bfcfb8a472a72bcf9cab30a89e94b47d3943e --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneContentRecursiveTest.php @@ -0,0 +1,131 @@ +<?php + +namespace Drupal\entity_clone\Tests; + +use Drupal\node\Entity\Node; +use Drupal\node\Tests\NodeTestBase; +use Drupal\taxonomy\Entity\Term; + +/** + * Create a content and test a clone. + * + * @group entity_clone + */ +class EntityCloneContentRecursiveTest extends NodeTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'block', 'node', 'datetime']; + + /** + * Profile to install. + * + * @var string + */ + protected $profile = 'standard'; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'bypass node access', + 'administer nodes', + 'clone node entity', + ]; + + /** + * A user with permission to bypass content access checks. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + /** + * Test clone a content entity with another entities attached. + */ + public function testContentEntityClone() { + + $term_title = $this->randomMachineName(8); + $term = Term::create([ + 'vid' => 'tags', + 'name' => $term_title, + ]); + $term->save(); + + $node_title = $this->randomMachineName(8); + $node = Node::create([ + 'type' => 'article', + 'title' => $node_title, + 'field_tags' => [ + 'target_id' => $term->id(), + ], + ]); + $node->save(); + + $settings = [ + 'taxonomy_term' => [ + 'default_value' => 1, + 'disable' => 0, + 'hidden' => 0, + ] + ]; + \Drupal::service('config.factory')->getEditable('entity_clone.settings')->set('form_settings', $settings)->save(); + + $this->drupalPostForm('entity_clone/node/' . $node->id(), [ + 'recursive[node.article.field_tags][references][' . $term->id() . '][clone]' => 1, + ], t('Clone')); + + $nodes = \Drupal::entityTypeManager() + ->getStorage('node') + ->loadByProperties([ + 'title' => $node_title . ' - Cloned', + ]); + /** @var \Drupal\node\Entity\Node $node */ + $node = reset($nodes); + $this->assertTrue($node, 'Test node cloned found in database.'); + + $terms = \Drupal::entityTypeManager() + ->getStorage('taxonomy_term') + ->loadByProperties([ + 'name' => $term_title . ' - Cloned', + ]); + /** @var \Drupal\taxonomy\Entity\Term $term */ + $term = reset($terms); + $this->assertTrue($term, 'Test term referenced by node cloned too found in database.'); + + $node->delete(); + $term->delete(); + + $nodes = \Drupal::entityTypeManager() + ->getStorage('node') + ->loadByProperties([ + 'title' => $node_title, + ]); + $node = reset($nodes); + $this->assertTrue($node, 'Test original node found in database.'); + + $terms = \Drupal::entityTypeManager() + ->getStorage('taxonomy_term') + ->loadByProperties([ + 'name' => $term_title, + ]); + $term = reset($terms); + $this->assertTrue($term, 'Test original term found in database.'); + } + +} diff --git a/web/modules/entity_clone/src/Tests/EntityCloneContentTest.php b/web/modules/entity_clone/src/Tests/EntityCloneContentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0e751fe2b52fd96f0936df51f810ecc5f7acbe55 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneContentTest.php @@ -0,0 +1,74 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneContentTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\node\Entity\Node; +use Drupal\node\Tests\NodeTestBase; + +/** + * Create a content and test a clone. + * + * @group entity_clone + */ +class EntityCloneContentTest extends NodeTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'block', 'node', 'datetime']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'bypass node access', + 'administer nodes', + 'clone node entity' + ]; + + /** + * A user with permission to bypass content access checks. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testContentEntityClone() { + $node_title = $this->randomMachineName(8); + $node = Node::create([ + 'type' => 'page', + 'title' => $node_title, + ]); + $node->save(); + + $this->drupalPostForm('entity_clone/node/' . $node->id(), [], t('Clone')); + + $nodes = \Drupal::entityTypeManager() + ->getStorage('node') + ->loadByProperties([ + 'title' => $node_title . ' - Cloned', + ]); + $node = reset($nodes); + $this->assertTrue($node, 'Test node cloned found in database.'); + } + +} \ No newline at end of file diff --git a/web/modules/entity_clone/src/Tests/EntityCloneCustomBlockTest.php b/web/modules/entity_clone/src/Tests/EntityCloneCustomBlockTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6da92651e28bd0327fa3145940b6c15711d77c01 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneCustomBlockTest.php @@ -0,0 +1,71 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneCustomBlockTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\block_content\Tests\BlockContentTestBase; + +/** + * Creat ea block and test a clone. + * + * @group entity_clone + */ +class EntityCloneCustomBlockTest extends BlockContentTestBase { + + /** + * Modules to enable. + * + * Enable dummy module that implements hook_block_insert() for exceptions and + * field_ui to edit display settings. + * + * @var array + */ + public static $modules = ['entity_clone', 'block', 'block_content']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = ['administer blocks', 'clone block_content entity']; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + $this->drupalLogin($this->adminUser); + } + + public function testCustomBlockEntityClone() { + + $edit = array(); + $edit['info[0][value]'] = 'Test block ready to clone'; + $edit['body[0][value]'] = $this->randomMachineName(16); + $this->drupalPostForm('block/add/basic', $edit, t('Save')); + + $blocks = \Drupal::entityTypeManager() + ->getStorage('block_content') + ->loadByProperties([ + 'info' => $edit['info[0][value]'], + ]); + $block = reset($blocks); + $this->assertTrue($block, 'Test Block for clone found in database.'); + + $this->drupalPostForm('entity_clone/block_content/' . $block->id(), [], t('Clone')); + + $blocks = \Drupal::entityTypeManager() + ->getStorage('block_content') + ->loadByProperties([ + 'info' => $edit['info[0][value]'] . ' - Cloned', + 'body' => $edit['body[0][value]'], + ]); + $block = reset($blocks); + $this->assertTrue($block, 'Test Block cloned found in database.'); + } + +} \ No newline at end of file diff --git a/web/modules/entity_clone/src/Tests/EntityCloneDateFormatTest.php b/web/modules/entity_clone/src/Tests/EntityCloneDateFormatTest.php new file mode 100644 index 0000000000000000000000000000000000000000..40a7442128d9be337c44ea97bf7a76c0daca278a --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneDateFormatTest.php @@ -0,0 +1,84 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneDateFormatTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create a date format and test a clone. + * + * @group entity_clone + */ +class EntityCloneDateFormatTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone date_format entity', + 'administer site configuration' + ]; + + /** + * An administrative user with permission to configure date formats settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testDateFormatEntityClone() { + $edit = [ + 'label' => 'Test date format for clone', + 'id' => 'test_date_format_for_clone', + 'date_format_pattern' => 'Y m d', + ]; + $this->drupalPostForm("admin/config/regional/date-time/formats/add", $edit, t('Add format')); + + $date_formats = \Drupal::entityTypeManager() + ->getStorage('date_format') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $date_format = reset($date_formats); + + $edit = [ + 'id' => 'test_date_format_cloned', + 'label' => 'Test date format cloned', + ]; + $this->drupalPostForm('entity_clone/date_format/' . $date_format->id(), $edit, t('Clone')); + + $date_formats = \Drupal::entityTypeManager() + ->getStorage('date_format') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $date_format = reset($date_formats); + $this->assertTrue($date_format, 'Test date format cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneEntityFormModeTest.php b/web/modules/entity_clone/src/Tests/EntityCloneEntityFormModeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..68235fe2acfebb4eef0d251b54c0feb42bc802e9 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneEntityFormModeTest.php @@ -0,0 +1,76 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneEntityFormModeTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test an entity form mode clone. + * + * @group entity_clone + */ +class EntityCloneEntityFormModeTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone entity_form_mode entity' + ]; + + /** + * An administrative user with permission to configure entity form modes settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testEntityFormModeEntityClone() { + $entity_form_modes = \Drupal::entityTypeManager() + ->getStorage('entity_form_mode') + ->loadByProperties([ + 'id' => 'user.register', + ]); + $entity_form_mode = reset($entity_form_modes); + + $edit = [ + 'label' => 'User register cloned form mode', + 'id' => 'register_clone', + ]; + $this->drupalPostForm('entity_clone/entity_form_mode/' . $entity_form_mode->id(), $edit, t('Clone')); + + $entity_form_modes = \Drupal::entityTypeManager() + ->getStorage('entity_form_mode') + ->loadByProperties([ + 'id' => 'user.' . $edit['id'], + ]); + $entity_form_mode = reset($entity_form_modes); + $this->assertTrue($entity_form_mode, 'Test entity form mode cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneEntityViewModeTest.php b/web/modules/entity_clone/src/Tests/EntityCloneEntityViewModeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..78768071a410c20e6d9dc43df4eb886defa139d9 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneEntityViewModeTest.php @@ -0,0 +1,76 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneEntityViewModeTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test an entity view mode clone. + * + * @group entity_clone + */ +class EntityCloneEntityViewModeTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone entity_view_mode entity' + ]; + + /** + * An administrative user with permission to configure entity view modes settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testEntityViewModeEntityClone() { + $entity_view_modes = \Drupal::entityTypeManager() + ->getStorage('entity_view_mode') + ->loadByProperties([ + 'id' => 'user.full', + ]); + $entity_view_mode = reset($entity_view_modes); + + $edit = [ + 'label' => 'User full cloned view mode', + 'id' => 'register_clone', + ]; + $this->drupalPostForm('entity_clone/entity_view_mode/' . $entity_view_mode->id(), $edit, t('Clone')); + + $entity_view_modes = \Drupal::entityTypeManager() + ->getStorage('entity_view_mode') + ->loadByProperties([ + 'id' => 'user.' . $edit['id'], + ]); + $entity_view_mode = reset($entity_view_modes); + $this->assertTrue($entity_view_mode, 'Test entity view mode cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneFileTest.php b/web/modules/entity_clone/src/Tests/EntityCloneFileTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d477aa0f9fa7b16ee4f259a4ec158bc7683f5814 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneFileTest.php @@ -0,0 +1,79 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneFileTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\file\Entity\File; +use Drupal\simpletest\WebTestBase; + +/** + * Create a filer and test a clone. + * + * @group entity_clone + */ +class EntityCloneFileTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'file']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone file entity' + ]; + + /** + * An administrative user with permission to configure files settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testFileEntityClone() { + /** @var \Drupal\file\FileInterface $file */ + $file = File::create(array( + 'uid' => 1, + 'filename' => 'druplicon.txt', + 'uri' => 'public://druplicon.txt', + 'filemime' => 'text/plain', + 'status' => FILE_STATUS_PERMANENT, + )); + file_put_contents($file->getFileUri(), 'hello world'); + $file->save(); + + $this->drupalPostForm('entity_clone/file/' . $file->id(), [], t('Clone')); + + $files = \Drupal::entityTypeManager() + ->getStorage('file') + ->loadByProperties([ + 'filename' => 'druplicon.txt - Cloned', + ]); + $file = reset($files); + $this->assertTrue($file, 'Test file cloned found in database.'); + + $this->assertEqual($file->getFileUri(), 'public://druplicon_0.txt', 'The stored file is also cloned.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneFilterFormatTest.php b/web/modules/entity_clone/src/Tests/EntityCloneFilterFormatTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c458e2334a2160f84957ed096e8acfca716c9a48 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneFilterFormatTest.php @@ -0,0 +1,83 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneFilterFormatTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create a filter format and test a clone. + * + * @group entity_clone + */ +class EntityCloneFilterFormatTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'filter']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone filter_format entity', + 'administer filters' + ]; + + /** + * An administrative user with permission to configure filter formats settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testFilterFormatEntityClone() { + $edit = [ + 'name' => 'Test filter format for clone', + 'format' => 'test_filter_format_for_clone', + ]; + $this->drupalPostForm("admin/config/content/formats/add", $edit, t('Save configuration')); + + $filter_formats = \Drupal::entityTypeManager() + ->getStorage('filter_format') + ->loadByProperties([ + 'format' => $edit['format'], + ]); + $filter_format = reset($filter_formats); + + $edit = [ + 'id' => 'test_filter_format_cloned', + 'label' => 'Test filter format cloned', + ]; + $this->drupalPostForm('entity_clone/filter_format/' . $filter_format->id(), $edit, t('Clone')); + + $filter_formats = \Drupal::entityTypeManager() + ->getStorage('filter_format') + ->loadByProperties([ + 'format' => $edit['id'], + ]); + $filter_format = reset($filter_formats); + $this->assertTrue($filter_format, 'Test filter format cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneImageStyleTest.php b/web/modules/entity_clone/src/Tests/EntityCloneImageStyleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..648e22778470a62641f87c85cbff9a2c73a994db --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneImageStyleTest.php @@ -0,0 +1,83 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneImageStyleTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create an image style and test a clone. + * + * @group entity_clone + */ +class EntityCloneImageStyleTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'image']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone image_style entity', + 'administer image styles' + ]; + + /** + * An administrative user with permission to configure image styles settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testImageStyleEntityClone() { + $edit = [ + 'label' => 'Test image style for clone', + 'name' => 'test_image_style_for_clone', + ]; + $this->drupalPostForm("admin/config/media/image-styles/add", $edit, t('Create new style')); + + $image_styles = \Drupal::entityTypeManager() + ->getStorage('image_style') + ->loadByProperties([ + 'name' => $edit['name'], + ]); + $image_style = reset($image_styles); + + $edit = [ + 'id' => 'test_iamge_style_cloned', + 'label' => 'Test image_style cloned', + ]; + $this->drupalPostForm('entity_clone/image_style/' . $image_style->id(), $edit, t('Clone')); + + $image_styles = \Drupal::entityTypeManager() + ->getStorage('image_style') + ->loadByProperties([ + 'name' => $edit['id'], + ]); + $image_style = reset($image_styles); + $this->assertTrue($image_style, 'Test image style cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneLanguageTest.php b/web/modules/entity_clone/src/Tests/EntityCloneLanguageTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bffba1912764765428f4702b4b6df22ff2cd6935 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneLanguageTest.php @@ -0,0 +1,82 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneLanguageTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create an language and test a clone. + * + * @group entity_clone + */ +class EntityCloneLanguageTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'language']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'administer languages', + 'clone configurable_language entity' + ]; + + /** + * An administrative user with permission to configure languages settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testLanguageEntityClone() { + $edit = [ + 'predefined_langcode' => 'fr' + ]; + $this->drupalPostForm("/admin/config/regional/language/add", $edit, t('Add language')); + + $languages = \Drupal::entityTypeManager() + ->getStorage('configurable_language') + ->loadByProperties([ + 'id' => 'fr', + ]); + $language = reset($languages); + + $edit = [ + 'id' => 'test_language_cloned', + 'label' => 'French language cloned', + ]; + $this->drupalPostForm('entity_clone/configurable_language/' . $language->id(), $edit, t('Clone')); + + $languages = \Drupal::entityTypeManager() + ->getStorage('configurable_language') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $language = reset($languages); + $this->assertTrue($language, 'Test language cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneMenuTest.php b/web/modules/entity_clone/src/Tests/EntityCloneMenuTest.php new file mode 100644 index 0000000000000000000000000000000000000000..81c393df06b58bbf0255ab99e9281674b0f8607c --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneMenuTest.php @@ -0,0 +1,78 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneMenuTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create a menu and test a clone. + * + * @group entity_clone + */ +class EntityCloneMenuTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'menu_ui']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone menu entity', + 'administer menu' + ]; + + /** + * An administrative user with permission to configure menus settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testMenuEntityClone() { + + $menus = \Drupal::entityTypeManager() + ->getStorage('menu') + ->loadByProperties([ + 'id' => 'account', + ]); + $menu = reset($menus); + + $edit = [ + 'label' => 'Test menu cloned', + 'id' => 'test_menu_cloned', + ]; + $this->drupalPostForm('entity_clone/menu/' . $menu->id(), $edit, t('Clone')); + + $menus = \Drupal::entityTypeManager() + ->getStorage('menu') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $menu = reset($menus); + $this->assertTrue($menu, 'Test menu cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneResponsiveImageStyleTest.php b/web/modules/entity_clone/src/Tests/EntityCloneResponsiveImageStyleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d6385fa89d8347375de3a473ce7729ea574401db --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneResponsiveImageStyleTest.php @@ -0,0 +1,85 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneResponsiveImageStyleTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create a responsive image style and test a clone. + * + * @group entity_clone + */ +class EntityCloneResponsiveImageStyleTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'responsive_image']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone responsive_image_style entity', + 'administer responsive images' + ]; + + /** + * An administrative user with permission to configure image styles settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testResponsiveImageStyleEntityClone() { + $edit = [ + 'label' => 'Test responsive image style for clone', + 'id' => 'test_responsive_image_style_for_clone', + 'breakpoint_group' => 'responsive_image', + 'fallback_image_style' => 'large', + ]; + $this->drupalPostForm("admin/config/media/responsive-image-style/add", $edit, t('Save')); + + $responsive_image_styles = \Drupal::entityTypeManager() + ->getStorage('responsive_image_style') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $responsive_image_style = reset($responsive_image_styles); + + $edit = [ + 'id' => 'test_responsive_image_style_cloned', + 'label' => 'Test responsive image style cloned', + ]; + $this->drupalPostForm('entity_clone/responsive_image_style/' . $responsive_image_style->id(), $edit, t('Clone')); + + $responsive_image_styles = \Drupal::entityTypeManager() + ->getStorage('responsive_image_style') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $responsive_image_style = reset($responsive_image_styles); + $this->assertTrue($responsive_image_style, 'Test responsive image style cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneRoleTest.php b/web/modules/entity_clone/src/Tests/EntityCloneRoleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b551305754e669e18424d2f2ab2dacc0e5f92f0c --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneRoleTest.php @@ -0,0 +1,83 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneRoleTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create a role and test a clone. + * + * @group entity_clone + */ +class EntityCloneRoleTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'user']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'administer permissions', + 'clone user_role entity' + ]; + + /** + * An administrative user with permission to configure roles settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testRoleEntityClone() { + $edit = [ + 'label' => 'Test role for clone', + 'id' => 'test_role_for_clone', + ]; + $this->drupalPostForm("/admin/people/roles/add", $edit, t('Save')); + + $roles = \Drupal::entityTypeManager() + ->getStorage('user_role') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $role = reset($roles); + + $edit = [ + 'id' => 'test_role_cloned', + 'label' => 'Test role cloned', + ]; + $this->drupalPostForm('entity_clone/user_role/' . $role->id(), $edit, t('Clone')); + + $roles = \Drupal::entityTypeManager() + ->getStorage('user_role') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $role = reset($roles); + $this->assertTrue($role, 'Test role cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneSearchPageTest.php b/web/modules/entity_clone/src/Tests/EntityCloneSearchPageTest.php new file mode 100644 index 0000000000000000000000000000000000000000..24cee534f17e5e7a2a26fb150e3b8ce5e41ae610 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneSearchPageTest.php @@ -0,0 +1,84 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneSearchPageTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create a search page and test a clone. + * + * @group entity_clone + */ +class EntityCloneSearchPageTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'search', 'node']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'administer search', + 'clone search_page entity' + ]; + + /** + * An administrative user with permission to configure search pages settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testSearchPageEntityClone() { + $edit = [ + 'label' => 'Test search page for clone', + 'id' => 'test_search_page_for_clone', + 'path' => 'test_search_page_for_clone_url', + ]; + $this->drupalPostForm("/admin/config/search/pages/add/node_search", $edit, t('Save')); + + $search_pages = \Drupal::entityTypeManager() + ->getStorage('search_page') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $search_page = reset($search_pages); + + $edit = [ + 'id' => 'test_search_page_cloned', + 'label' => 'Test search page cloned', + ]; + $this->drupalPostForm('entity_clone/search_page/' . $search_page->id(), $edit, t('Clone')); + + $search_pages = \Drupal::entityTypeManager() + ->getStorage('search_page') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $search_page = reset($search_pages); + $this->assertTrue($search_page, 'Test search page cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneShortcutSetTest.php b/web/modules/entity_clone/src/Tests/EntityCloneShortcutSetTest.php new file mode 100644 index 0000000000000000000000000000000000000000..46c8c0e4a2ed23a55dcb5073b217a1b13cc79ccd --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneShortcutSetTest.php @@ -0,0 +1,69 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneShortcutSetTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create a shortcut set and test a clone. + * + * @group entity_clone + */ +class EntityCloneShortcutSetTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'shortcut']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone shortcut_set entity' + ]; + + /** + * An administrative user with permission to configure shortcuts settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testShortcutSetEntityClone() { + $edit = [ + 'id' => 'test_shortcut_set_cloned', + 'label' => 'Test shortcut set cloned', + ]; + $this->drupalPostForm('entity_clone/shortcut_set/default', $edit, t('Clone')); + + $shortcut_sets = \Drupal::entityTypeManager() + ->getStorage('shortcut_set') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $shortcut_set = reset($shortcut_sets); + $this->assertTrue($shortcut_set, 'Test default shortcut set cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneUserTest.php b/web/modules/entity_clone/src/Tests/EntityCloneUserTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1de2d157d32571d5aef9987ebddea8a5361eeef5 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneUserTest.php @@ -0,0 +1,65 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneUserTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create a user and test a clone. + * + * @group entity_clone + */ +class EntityCloneUserTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'user']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone user entity' + ]; + + /** + * An administrative user with permission to configure users settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions, 'test_user'); + $this->drupalLogin($this->adminUser); + } + + public function testUserEntityClone() { + $this->drupalPostForm('entity_clone/user/' . $this->adminUser->id(), [], t('Clone')); + + $users = \Drupal::entityTypeManager() + ->getStorage('user') + ->loadByProperties([ + 'name' => 'test_user_cloned', + ]); + $user = reset($users); + $this->assertTrue($user, 'Test user cloned found in database.'); + } + +} + diff --git a/web/modules/entity_clone/src/Tests/EntityCloneViewTest.php b/web/modules/entity_clone/src/Tests/EntityCloneViewTest.php new file mode 100644 index 0000000000000000000000000000000000000000..900ad05c8882dbc44d338479315b2c830d6ee6a3 --- /dev/null +++ b/web/modules/entity_clone/src/Tests/EntityCloneViewTest.php @@ -0,0 +1,69 @@ +<?php + +/** + * @file + * Definition of Drupal\entity_clone\Tests\EntityCloneViewTest. + */ + +namespace Drupal\entity_clone\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Create a view and test a clone. + * + * @group entity_clone + */ +class EntityCloneViewTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_clone', 'views']; + + /** + * Permissions to grant admin user. + * + * @var array + */ + protected $permissions = [ + 'clone view entity' + ]; + + /** + * An administrative user with permission to configure views settings. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * Sets the test up. + */ + protected function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->adminUser); + } + + public function testViewEntityClone() { + $edit = [ + 'id' => 'test_view_cloned', + 'label' => 'Test view cloned', + ]; + $this->drupalPostForm('entity_clone/view/who_s_new', $edit, t('Clone')); + + $views = \Drupal::entityTypeManager() + ->getStorage('view') + ->loadByProperties([ + 'id' => $edit['id'], + ]); + $view = reset($views); + $this->assertTrue($view, 'Test default view cloned found in database.'); + } + +} +