diff --git a/composer.json b/composer.json index 9044987ea4669a60d677f5b7625a30bdc8c84a2e..5eb72531909e6620f4a608fe11bfd4f607f558ea 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,7 @@ "drupal/media_library_edit": "3.0.4", "drupal/menu_block": "1.13", "drupal/menu_breadcrumb": "^2.0@alpha", - "drupal/metatag": "^2.0", + "drupal/metatag": "^2.1", "drupal/mobile_detect": "^3.0", "drupal/module_filter": "5.0.3", "drupal/msqrole": "^1.0", @@ -89,7 +89,7 @@ "drupal/views_ajax_history": "1.7", "drupal/views_autocomplete_filters": "2.0.2", "drupal/views_bootstrap": "^3.9", - "drupal/views_bulk_operations": "4.3.1", + "drupal/views_bulk_operations": "4.3.2", "drupal/views_fieldsets": "4.0.1", "drupal/views_infinite_scroll": "2.0.3", "drupal/webform": "^6.2", diff --git a/composer.lock b/composer.lock index 8bf689440aded7f410b8eeb747ea0025c16f22dd..bcf220f38f88f72a5f15e0166992d53017d39557 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9cd149040b3174cd97cbb77e65eee592", + "content-hash": "f7079ba4b8cae98cc5815affb284d842", "packages": [ { "name": "algolia/places", @@ -2764,17 +2764,17 @@ }, { "name": "drupal/editoria11y", - "version": "2.1.19", + "version": "2.1.20", "source": { "type": "git", "url": "https://git.drupalcode.org/project/editoria11y.git", - "reference": "2.1.19" + "reference": "2.1.20" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/editoria11y-2.1.19.zip", - "reference": "2.1.19", - "shasum": "0ccf651071837e1ee6fcfb8fbb1c279d2fc1bc27" + "url": "https://ftp.drupal.org/files/projects/editoria11y-2.1.20.zip", + "reference": "2.1.20", + "shasum": "67f8e2eb17ed3d3db6cd93782ed1faa88c206640" }, "require": { "drupal/core": "^9 || ^10 || ^11" @@ -2785,8 +2785,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "2.1.19", - "datestamp": "1726514804", + "version": "2.1.20", + "datestamp": "1731097435", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -3898,27 +3898,27 @@ }, { "name": "drupal/jquery_ui_datepicker", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://git.drupalcode.org/project/jquery_ui_datepicker.git", - "reference": "2.1.0" + "reference": "2.1.1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/jquery_ui_datepicker-2.1.0.zip", - "reference": "2.1.0", - "shasum": "22e1cceeba22522cdd23f08ec345a4dfb2e8550f" + "url": "https://ftp.drupal.org/files/projects/jquery_ui_datepicker-2.1.1.zip", + "reference": "2.1.1", + "shasum": "29e56e8fa351fefd34e80529768ddc69a460149d" }, "require": { - "drupal/core": "^9.2 || ^10 || 11", + "drupal/core": "^9.2 || ^10 || ^11", "drupal/jquery_ui": "^1.7" }, "type": "drupal-module", "extra": { "drupal": { - "version": "2.1.0", - "datestamp": "1717094444", + "version": "2.1.1", + "datestamp": "1730932612", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -4652,17 +4652,17 @@ }, { "name": "drupal/metatag", - "version": "2.0.2", + "version": "2.1.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/metatag.git", - "reference": "2.0.2" + "reference": "2.1.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/metatag-2.0.2.zip", - "reference": "2.0.2", - "shasum": "748013c50a0ed5e10359413bb3481392a0bf0d3f" + "url": "https://ftp.drupal.org/files/projects/metatag-2.1.0.zip", + "reference": "2.1.0", + "shasum": "c28fe2fdac68a9370a6af6cbafff4425dd5148f3" }, "require": { "drupal/core": "^9.4 || ^10 || ^11", @@ -4670,6 +4670,7 @@ "php": ">=8.0" }, "require-dev": { + "drupal/forum": "*", "drupal/hal": "^1 || ^2 || ^9", "drupal/metatag_dc": "*", "drupal/metatag_open_graph": "*", @@ -4681,8 +4682,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "2.0.2", - "datestamp": "1722869772", + "version": "2.1.0", + "datestamp": "1731004042", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -4704,7 +4705,7 @@ "role": "Developer" }, { - "name": "Dave Reid", + "name": "dave reid", "homepage": "https://www.drupal.org/user/53892" } ], @@ -6790,17 +6791,17 @@ }, { "name": "drupal/views_bulk_operations", - "version": "4.3.1", + "version": "4.3.2", "source": { "type": "git", "url": "https://git.drupalcode.org/project/views_bulk_operations.git", - "reference": "4.3.1" + "reference": "4.3.2" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-4.3.1.zip", - "reference": "4.3.1", - "shasum": "1089fe41ddb01313f34d55e19e8f3a5157889430" + "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-4.3.2.zip", + "reference": "4.3.2", + "shasum": "b3d0ee06abb15520595b83324e93c5500d5dcef3" }, "require": { "drupal/core": "^10.3 || ^11" @@ -6817,8 +6818,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "4.3.1", - "datestamp": "1729683242", + "version": "4.3.2", + "datestamp": "1731070018", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 1c6157f2692e4380d9299c839294c77864dfaf03..02d1425f43e6026b858a9a3db6e3d87357db1478 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -2899,18 +2899,18 @@ }, { "name": "drupal/editoria11y", - "version": "2.1.19", - "version_normalized": "2.1.19.0", + "version": "2.1.20", + "version_normalized": "2.1.20.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/editoria11y.git", - "reference": "2.1.19" + "reference": "2.1.20" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/editoria11y-2.1.19.zip", - "reference": "2.1.19", - "shasum": "0ccf651071837e1ee6fcfb8fbb1c279d2fc1bc27" + "url": "https://ftp.drupal.org/files/projects/editoria11y-2.1.20.zip", + "reference": "2.1.20", + "shasum": "67f8e2eb17ed3d3db6cd93782ed1faa88c206640" }, "require": { "drupal/core": "^9 || ^10 || ^11" @@ -2921,8 +2921,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "2.1.19", - "datestamp": "1726514804", + "version": "2.1.20", + "datestamp": "1731097435", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -4091,28 +4091,28 @@ }, { "name": "drupal/jquery_ui_datepicker", - "version": "2.1.0", - "version_normalized": "2.1.0.0", + "version": "2.1.1", + "version_normalized": "2.1.1.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/jquery_ui_datepicker.git", - "reference": "2.1.0" + "reference": "2.1.1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/jquery_ui_datepicker-2.1.0.zip", - "reference": "2.1.0", - "shasum": "22e1cceeba22522cdd23f08ec345a4dfb2e8550f" + "url": "https://ftp.drupal.org/files/projects/jquery_ui_datepicker-2.1.1.zip", + "reference": "2.1.1", + "shasum": "29e56e8fa351fefd34e80529768ddc69a460149d" }, "require": { - "drupal/core": "^9.2 || ^10 || 11", + "drupal/core": "^9.2 || ^10 || ^11", "drupal/jquery_ui": "^1.7" }, "type": "drupal-module", "extra": { "drupal": { - "version": "2.1.0", - "datestamp": "1717094444", + "version": "2.1.1", + "datestamp": "1730932612", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -4887,18 +4887,18 @@ }, { "name": "drupal/metatag", - "version": "2.0.2", - "version_normalized": "2.0.2.0", + "version": "2.1.0", + "version_normalized": "2.1.0.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/metatag.git", - "reference": "2.0.2" + "reference": "2.1.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/metatag-2.0.2.zip", - "reference": "2.0.2", - "shasum": "748013c50a0ed5e10359413bb3481392a0bf0d3f" + "url": "https://ftp.drupal.org/files/projects/metatag-2.1.0.zip", + "reference": "2.1.0", + "shasum": "c28fe2fdac68a9370a6af6cbafff4425dd5148f3" }, "require": { "drupal/core": "^9.4 || ^10 || ^11", @@ -4906,6 +4906,7 @@ "php": ">=8.0" }, "require-dev": { + "drupal/forum": "*", "drupal/hal": "^1 || ^2 || ^9", "drupal/metatag_dc": "*", "drupal/metatag_open_graph": "*", @@ -4917,8 +4918,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "2.0.2", - "datestamp": "1722869772", + "version": "2.1.0", + "datestamp": "1731004042", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -4941,7 +4942,7 @@ "role": "Developer" }, { - "name": "Dave Reid", + "name": "dave reid", "homepage": "https://www.drupal.org/user/53892" } ], @@ -7133,18 +7134,18 @@ }, { "name": "drupal/views_bulk_operations", - "version": "4.3.1", - "version_normalized": "4.3.1.0", + "version": "4.3.2", + "version_normalized": "4.3.2.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/views_bulk_operations.git", - "reference": "4.3.1" + "reference": "4.3.2" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-4.3.1.zip", - "reference": "4.3.1", - "shasum": "1089fe41ddb01313f34d55e19e8f3a5157889430" + "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-4.3.2.zip", + "reference": "4.3.2", + "shasum": "b3d0ee06abb15520595b83324e93c5500d5dcef3" }, "require": { "drupal/core": "^10.3 || ^11" @@ -7161,8 +7162,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "4.3.1", - "datestamp": "1729683242", + "version": "4.3.2", + "datestamp": "1731070018", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 97279b9a8c4c1fef356b9ba9a523a083d9a5691f..0be4cb5c9e8660f3004a114f415e1c746abe3a79 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'asc-web-services/drupal-upstream', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '5d7b11b15b4cd5b6292fbe8cff21c0b27b3af763', + 'reference' => '1a36cfb4ea4c433f69eb007024b0ac2ecee36bdf', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -22,7 +22,7 @@ 'asc-web-services/drupal-upstream' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '5d7b11b15b4cd5b6292fbe8cff21c0b27b3af763', + 'reference' => '1a36cfb4ea4c433f69eb007024b0ac2ecee36bdf', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -560,9 +560,9 @@ 'dev_requirement' => false, ), 'drupal/editoria11y' => array( - 'pretty_version' => '2.1.19', - 'version' => '2.1.19.0', - 'reference' => '2.1.19', + 'pretty_version' => '2.1.20', + 'version' => '2.1.20.0', + 'reference' => '2.1.20', 'type' => 'drupal-module', 'install_path' => __DIR__ . '/../../web/modules/editoria11y', 'aliases' => array(), @@ -722,9 +722,9 @@ 'dev_requirement' => false, ), 'drupal/jquery_ui_datepicker' => array( - 'pretty_version' => '2.1.0', - 'version' => '2.1.0.0', - 'reference' => '2.1.0', + 'pretty_version' => '2.1.1', + 'version' => '2.1.1.0', + 'reference' => '2.1.1', 'type' => 'drupal-module', 'install_path' => __DIR__ . '/../../web/modules/jquery_ui_datepicker', 'aliases' => array(), @@ -839,9 +839,9 @@ 'dev_requirement' => false, ), 'drupal/metatag' => array( - 'pretty_version' => '2.0.2', - 'version' => '2.0.2.0', - 'reference' => '2.0.2', + 'pretty_version' => '2.1.0', + 'version' => '2.1.0.0', + 'reference' => '2.1.0', 'type' => 'drupal-module', 'install_path' => __DIR__ . '/../../web/modules/metatag', 'aliases' => array(), @@ -1145,9 +1145,9 @@ 'dev_requirement' => false, ), 'drupal/views_bulk_operations' => array( - 'pretty_version' => '4.3.1', - 'version' => '4.3.1.0', - 'reference' => '4.3.1', + 'pretty_version' => '4.3.2', + 'version' => '4.3.2.0', + 'reference' => '4.3.2', 'type' => 'drupal-module', 'install_path' => __DIR__ . '/../../web/modules/views_bulk_operations', 'aliases' => array(), diff --git a/web/modules/editoria11y/editoria11y.info.yml b/web/modules/editoria11y/editoria11y.info.yml index e173e7d0d91d80a1760dea48c23c23fbf9732216..718c078d9d36c6eda5e2135d81a09b7739b42c45 100644 --- a/web/modules/editoria11y/editoria11y.info.yml +++ b/web/modules/editoria11y/editoria11y.info.yml @@ -9,7 +9,7 @@ dependencies: - drupal:views package: 'User interface' -# Information added by Drupal.org packaging script on 2024-09-16 -version: '2.1.19' +# Information added by Drupal.org packaging script on 2024-11-08 +version: '2.1.20' project: 'editoria11y' -datestamp: 1726514806 +datestamp: 1731097437 diff --git a/web/modules/editoria11y/editoria11y.module b/web/modules/editoria11y/editoria11y.module index 6a307f556edfeba9ab02fc4774d34cb217e8fedd..5989c0b9a665b87bf7c1c35a97868b0de8d60855 100644 --- a/web/modules/editoria11y/editoria11y.module +++ b/web/modules/editoria11y/editoria11y.module @@ -5,7 +5,6 @@ * Editoria11y module file. */ -use Drupal\Component\Utility\DeprecationHelper; use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityInterface; @@ -198,14 +197,7 @@ function editoria11y_page_attachments(array &$attachments): void { // @todo this fails on user route? if (is_array($page_title)) { /** @var \Drupal\Core\Render\RendererInterface $renderer */ - $renderer = \Drupal::service('renderer'); - $page_title = DeprecationHelper::backwardsCompatibleCall( - currentVersion: \Drupal::VERSION, - deprecatedVersion: '10.3', - currentCallable: fn() => $renderer->renderInIsolation($page_title), - // @phpstan-ignore-next-line (Handled deprecation) - deprecatedCallable: fn() => $renderer->renderPlain($page_title), - ); + $page_title = Drupal::service('renderer')->renderPlain($page_title); } } if (empty($page_title)) { diff --git a/web/modules/jquery_ui_datepicker/jquery_ui_datepicker.info.yml b/web/modules/jquery_ui_datepicker/jquery_ui_datepicker.info.yml index 546905b67f42f387ebf6fdde3ef4fb13c5d4b33b..5e6269a9b54135695ac75a15199c978db97221ce 100644 --- a/web/modules/jquery_ui_datepicker/jquery_ui_datepicker.info.yml +++ b/web/modules/jquery_ui_datepicker/jquery_ui_datepicker.info.yml @@ -2,11 +2,11 @@ name: jQuery UI Datepicker type: module description: 'Provides jQuery UI Datepicker library.' package: jQuery UI -core_version_requirement: ^9.2 || ^10 || 11 +core_version_requirement: ^9.2 || ^10 || ^11 dependencies: - jquery_ui:jquery_ui (>=8.x-1.7) -# Information added by Drupal.org packaging script on 2024-05-30 -version: '2.1.0' +# Information added by Drupal.org packaging script on 2024-11-06 +version: '2.1.1' project: 'jquery_ui_datepicker' -datestamp: 1717094446 +datestamp: 1730932614 diff --git a/web/modules/metatag/.gitlab-ci.yml b/web/modules/metatag/.gitlab-ci.yml index 372f1f85be31e3307fb4b51d4bd9ce012b2c15f0..c508e56228c245c3309553ee4fc677aaa8fb7bb3 100644 --- a/web/modules/metatag/.gitlab-ci.yml +++ b/web/modules/metatag/.gitlab-ci.yml @@ -56,23 +56,31 @@ include: ################ variables: - # Run tests in parallel. - _PHPUNIT_CONCURRENT: 1 - # Run tests against the previous and next core minor releases, to help make + # Extra words for CSpell. + _CSPELL_WORDS: "colonspace" + + # Run tests against the previous and next core major releases, to help make # sure small changes don't break anything too quickly. OPT_IN_TEST_NEXT_MINOR: 1 - OPT_IN_TEST_PREVIOUS_MINOR: 1 + OPT_IN_TEST_PREVIOUS_MAJOR: 1 + + # Run tests in parallel. + _PHPUNIT_CONCURRENT: 1 # Don't allow failures from coding standards tests. cspell: allow_failure: false -phpcs: - allow_failure: false +# @todo Fix all phpcs issues. +# phpcs: +# allow_failure: false # @todo Fix all phpstan issues. # phstan: # allow_failure: false stylelint: allow_failure: false +composer (next major): + variables: + _LENIENT_ALLOW_LIST: page_manager, hal ################################################################################### # diff --git a/web/modules/metatag/CHANGELOG.txt b/web/modules/metatag/CHANGELOG.txt index 9a167e7128c9a658748867c07eef2d89a9eb505e..acb0b295c8f7a271df31e8dba65313e3a1abf9ee 100644 --- a/web/modules/metatag/CHANGELOG.txt +++ b/web/modules/metatag/CHANGELOG.txt @@ -1,3 +1,31 @@ +Metatag 2.1.0, 2024-11-07 +------------------------- +#2563651 by damienmckenna, alison, bob.hinrichs, benjifisher: Migration for + Metatag-D7 default configurations. +#3454473 by damienmckenna: Migrations: Metatag-D7 misc settings. +#3479994 by damienmckenna: Split D7 settings migration into a separate test. +#3485053 by damienmckenna: Fix cspell errors. +#3433376 by damienmckenna, ankitv18, murilohp, project update bot, jrglasgow, + chandu7929: Automated Drupal 11 compatibility fixes. +#3311415 by grevil, damienmckenna, anybody: Allow to trim special characters + from the end. +#3470415 by murilohp, waspper, damienmckenna: Support the commerce_store entity + type. +#3466343 by damienmckenna, eyesyte: Support the comment entity type. +#3377205 by damienmckenna, grevil, gillesbailleux, anybody: Allow OG "See also" + to have multiple values. +#3469049 by damienmckenna, ankitv18, anybody: Minor typo in metatag.install. +#3380911 by naveenvalecha, abhisekmazumdar, eleonel, anybody, damienmckenna, + 2dareis2do, joachim: Add Custom Meta as a submodule. +#2952675 by biancaradu27, loze, alvar0hurtad0, id.conky, damienmckenna, + marysmech, musa.thomas, justskew, smavri, anybody, jason.bell, waldomero, + zaporylie: Add support for contextual path arguments. +#3426426 by damienmckenna, erwangel: Explain that the Administer Metatag + permission does not control access to the per-entity field. +#3422406 by eduardo morales alberti, sascha_meissner, damienmckenna: Generate + tokens values method does not take into account the entity language. + + Metatag 2.0.2, 2024-08-05 ------------------------- #3465991 by podarok, DamienMcKenna: Allow installation on Drupal 11 via diff --git a/web/modules/metatag/composer.json b/web/modules/metatag/composer.json index 5c807d8acd9d99a04c20ce707731cf443b20f9e8..0ecf826566ab21744eda742138bea2391a3943d1 100644 --- a/web/modules/metatag/composer.json +++ b/web/modules/metatag/composer.json @@ -26,6 +26,7 @@ "drupal/token": "^1.0" }, "require-dev": { + "drupal/forum": "*", "drupal/hal": "^1 || ^2 || ^9", "drupal/page_manager": "^4.0", "drupal/redirect": "^1.0", diff --git a/web/modules/metatag/config/install/metatag.settings.yml b/web/modules/metatag/config/install/metatag.settings.yml index 46b3010ce2994275957e13c00c8b7d4ace6f686f..6906bdcc53dc87a32e4d42ed9f25f7ea5672dd0d 100644 --- a/web/modules/metatag/config/install/metatag.settings.yml +++ b/web/modules/metatag/config/install/metatag.settings.yml @@ -4,3 +4,4 @@ tag_trim_method: beforeValue tag_trim_maxlength: { } tag_scroll_max_height: '' use_maxlength: true +tag_trim_end: "|.,-:;/+&([{\"'" diff --git a/web/modules/metatag/config/schema/metatag.settings.schema.yml b/web/modules/metatag/config/schema/metatag.settings.schema.yml index 6412ce18a03c1d3e5c18c1bec4c05ab41695ea35..433d2c2b2fed2282a35a332e16530e079b68077b 100644 --- a/web/modules/metatag/config/schema/metatag.settings.schema.yml +++ b/web/modules/metatag/config/schema/metatag.settings.schema.yml @@ -32,3 +32,6 @@ metatag.settings: tag_scroll_max_height: type: string label: 'Add max height value' + tag_trim_end: + type: string + label: 'End of the word trimming' diff --git a/web/modules/metatag/metatag.info.yml b/web/modules/metatag/metatag.info.yml index 1a6395b8f6567441a040e69b1b1dc5a2cfbd1db0..6984fcbf83fd81be9031e10c1ace049d8a6ebd4a 100644 --- a/web/modules/metatag/metatag.info.yml +++ b/web/modules/metatag/metatag.info.yml @@ -9,7 +9,7 @@ dependencies: - drupal:field - token:token -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag.install b/web/modules/metatag/metatag.install index a814dd80fe073ec249e1ff24123dd2e441f5f586..eb4c0efbda144806ba4ede697f9c2ec3cfbe58f8 100644 --- a/web/modules/metatag/metatag.install +++ b/web/modules/metatag/metatag.install @@ -125,7 +125,8 @@ function metatag_update_8109(&$sandbox) { // Determine the table and "value" field names. // @todo The class path to getTableMapping() seems to be invalid? - $table_mapping = Drupal::entityTypeManager() + /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ + $table_mapping = \Drupal::entityTypeManager() ->getStorage($field->getTargetEntityTypeId()) ->getTableMapping(); $field_table = $table_mapping->getFieldTableName($field_name); @@ -151,7 +152,7 @@ function metatag_update_8109(&$sandbox) { $sandbox['fields'][$field_counter]['field_value_field'] = $field_value_field; $sandbox['fields'][$field_counter]['records'] = $records; - $sandbox['total_records'] += count($sandbox['fields'][$field_counter]['records'] = $records); + $sandbox['total_records'] += count($sandbox['fields'][$field_counter]['records']); $field_counter++; } } @@ -230,3 +231,13 @@ function metatag_update_8109(&$sandbox) { return (string) t("There were no overridden Metatag records."); } } + +/** + * Sets tag_trim_end standard settings for existing installations. + */ +function metatag_update_9401() { + $config_factory = \Drupal::configFactory(); + $config = $config_factory->getEditable('metatag.settings'); + $config->set('tag_trim_end', "|.,-:;/+&([{\"'"); + $config->save(); +} diff --git a/web/modules/metatag/metatag.module b/web/modules/metatag/metatag.module index ba90c386ba28ebf3fa70ef20b548b7d647b965be..29e020d1a2ff3474e3c604d83430b8f2c020ebdf 100644 --- a/web/modules/metatag/metatag.module +++ b/web/modules/metatag/metatag.module @@ -934,7 +934,6 @@ function _metatag_is_migration_plugin_supported(array $definition) { 'entity:block', 'entity:block_content', 'entity:block_content_type', - 'entity:comment', 'entity:comment_type', 'entity:contact_form', 'entity:date_format', @@ -963,7 +962,6 @@ function _metatag_is_migration_plugin_supported(array $definition) { 'entity:commerce_shipment', 'entity:commerce_shipping_method', 'entity:commerce_stock_location', - 'entity:commerce_store', 'entity:linkcheckerlink', 'entity:path_alias', 'entity:redirect', diff --git a/web/modules/metatag/metatag.permissions.yml b/web/modules/metatag/metatag.permissions.yml index bdff82ed4693d1d2b7632528a6310fa4ca0dc887..93318c236bab29151db648f44fab58646a501606 100644 --- a/web/modules/metatag/metatag.permissions.yml +++ b/web/modules/metatag/metatag.permissions.yml @@ -1,4 +1,4 @@ 'administer meta tags': title: Administer meta tags - description: Control the main settings pages and modify per-object meta tags. + description: Control the Metatag settings pages. Does not control access to the Metatag field on content forms, etc, use the Field Permissions module or hook_form_alter(). 'restrict access': TRUE diff --git a/web/modules/metatag/metatag.post_update.php b/web/modules/metatag/metatag.post_update.php index 78bc0f4546cca9ba4addb99ad8adfeb3823d837e..5b5e3571d27dfe28994a3968888e5d8b37a4954c 100644 --- a/web/modules/metatag/metatag.post_update.php +++ b/web/modules/metatag/metatag.post_update.php @@ -63,6 +63,7 @@ function _metatag_list_entity_field_tables(): array { $entity_type = $entity_type_manager->getDefinition($entity_type_id); // Determine the table and "value" field names. + /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ $table_mapping = $entity_type_manager->getStorage($entity_type_id) ->getTableMapping(); $field_table = $table_mapping->getFieldTableName($field_name); diff --git a/web/modules/metatag/metatag_app_links/metatag_app_links.info.yml b/web/modules/metatag/metatag_app_links/metatag_app_links.info.yml index 0902e143e3ff6d67929142abd0290f22f2fda70d..2bd785372e92f9caec240ca3dac20a8db9f41f09 100644 --- a/web/modules/metatag/metatag_app_links/metatag_app_links.info.yml +++ b/web/modules/metatag/metatag_app_links/metatag_app_links.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_custom_tags/config/install/metatag_custom_tags.metatag_custom_tag.sitename.yml b/web/modules/metatag/metatag_custom_tags/config/install/metatag_custom_tags.metatag_custom_tag.sitename.yml new file mode 100644 index 0000000000000000000000000000000000000000..0ea6d5d8044d201b47d6814d08de8f5c9c4c6da7 --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/config/install/metatag_custom_tags.metatag_custom_tag.sitename.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: { } +id: sitename +label: Sitename +description: 'The name of the site.' +htmlElement: meta +htmlNameAttribute: name +htmlValueAttribute: content diff --git a/web/modules/metatag/metatag_custom_tags/config/schema/metatag_custom_tags.metatag_custom_tag.schema.yml b/web/modules/metatag/metatag_custom_tags/config/schema/metatag_custom_tags.metatag_custom_tag.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..95c235be057014488d96534708a6204300cfedd4 --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/config/schema/metatag_custom_tags.metatag_custom_tag.schema.yml @@ -0,0 +1,22 @@ +metatag_custom_tags.metatag_custom_tag.*: + type: config_entity + label: 'Custom tag' + mapping: + id: + type: string + label: 'ID' + label: + type: label + label: 'Label' + description: + type: string + label: 'Description' + htmlElement: + type: string + label: 'Element of the html tag. e.g. meta, link, etc.' + htmlNameAttribute: + type: string + label: 'The attribute this tag uses for the name. e.g. name, property, http-equiv, itemprop, rel, etc.' + htmlValueAttribute: + type: string + label: 'The value of the html name attribute. e.g. content, href, etc.' diff --git a/web/modules/metatag/metatag_custom_tags/config/schema/metatag_custom_tags.metatag_tag.schema.yml b/web/modules/metatag/metatag_custom_tags/config/schema/metatag_custom_tags.metatag_tag.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..c4ac751c62aa1981a45b48bfb41e6a90740c331b --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/config/schema/metatag_custom_tags.metatag_tag.schema.yml @@ -0,0 +1,3 @@ +metatag.metatag_tag.metatag_custom_tag:*: + type: text + label: 'Custom Tag' diff --git a/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.info.yml b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..9b01b61fb582cba51f8374774dab55d8471488ff --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.info.yml @@ -0,0 +1,16 @@ +name: 'Metatag: Custom Tags' +type: module +description: Provides support to define and manage custom meta tags. +core_version_requirement: ^9.5 || ^10 || ^11 +package: SEO +configure: entity.metatag_custom_tag.collection +dependencies: + - metatag:metatag + +test_dependencies: + - metatag:metatag + +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' +project: 'metatag' +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.links.action.yml b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.links.action.yml new file mode 100644 index 0000000000000000000000000000000000000000..071aeff958b6706d40c7884a1e5fbead56f21d4c --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.links.action.yml @@ -0,0 +1,5 @@ +entity.metatag_custom_tag.admin_add: + route_name: entity.metatag_custom_tag.admin_add + title: 'Add Custom tag' + appears_on: + - entity.metatag_custom_tag.collection diff --git a/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.links.menu.yml b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.links.menu.yml new file mode 100644 index 0000000000000000000000000000000000000000..896aa6a248fc5ef7775e47f82adb283867d625ab --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.links.menu.yml @@ -0,0 +1,6 @@ +entity.metatag_custom_tag.collection: + title: 'Custom tags' + description: 'Configure Custom tags.' + route_name: entity.metatag_custom_tag.collection + parent: entity.metatag_defaults.collection + weight: 20 diff --git a/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.links.task.yml b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.links.task.yml new file mode 100644 index 0000000000000000000000000000000000000000..95f5d36e51f96f11efb727c80d14f5b48d060673 --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.links.task.yml @@ -0,0 +1,5 @@ +entity.metatag_custom_tag.collection: + route_name: 'entity.metatag_custom_tag.collection' + base_route: 'entity.metatag_defaults.collection' + title: 'Custom tags' + weight: 20 diff --git a/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.module b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.module new file mode 100644 index 0000000000000000000000000000000000000000..f0fccbd6730afbab4550a77585706d650d303411 --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.module @@ -0,0 +1,35 @@ +<?php + +/** + * @file + * Contains metatag_custom_tags.module. + */ + +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\Url; + +/** + * Implements hook_help(). + */ +function metatag_custom_tags_help($route_name, RouteMatchInterface $route_match) { + switch ($route_name) { + // Main module help for the Metatag: Custom Tags module. + case 'help.page.metatag_custom_tags': + $output = '<h2>' . (string) new TranslatableMarkup('About') . '</h2>'; + $output .= '<p>' . (string) new TranslatableMarkup('This module provides support to define and manage custom meta tags.'); + $output .= '<h3>' . (string) new TranslatableMarkup('Intended workflow') . '</h3>'; + $output .= '<p>' . (string) new TranslatableMarkup('The module provides the option to create different <a href=":metatag_custom_tag">"Custom Tags"</a> to use for different purposes.', [':metatag_custom_tag' => Url::fromRoute('entity.metatag_custom_tag.collection')->toString()]) . '</p>'; + $output .= '<p>' . (string) new TranslatableMarkup('The best way of using Metatag: Custom Tags is as follows:') . '</p>'; + $output .= '<ol>'; + $output .= '<li>' . (string) new TranslatableMarkup('Create the <a href=":metatag_custom_tag_create">"Custom Tag"</a>, fill in the specific values that every page should have.', [':metatag_custom_tag_create' => Url::fromRoute('entity.metatag_custom_tag.admin_add')->toString()]) . '</li>'; + $output .= '<li>' . (string) new TranslatableMarkup('Customize the <a href=":defaults">global defaults</a>, fill in the specific values and tokens that every page should have.', [':defaults' => Url::fromRoute('entity.metatag_defaults.edit_form', ['metatag_defaults' => 'global'])->toString()]) . '</li>'; + $output .= '</ol>'; + return $output; + + // The main configuration page. + case 'entity.metatag_custom_tag.collection': + $output = '<p>' . (string) new TranslatableMarkup('Configure Custom tags below.') . '</p>'; + return $output; + } +} diff --git a/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.permissions.yml b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.permissions.yml new file mode 100644 index 0000000000000000000000000000000000000000..7b09e7fe76d5112d14a6d62d1e7496bcd7789829 --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.permissions.yml @@ -0,0 +1,4 @@ +administer custom meta tags: + title: 'Administer Custom tags' + description: Control the main settings pages and modify custom tags. + 'restrict access': TRUE diff --git a/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.routing.yml b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.routing.yml new file mode 100644 index 0000000000000000000000000000000000000000..aa9510065b4ef8f16aa227c05ea0eb168b0d6b62 --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/metatag_custom_tags.routing.yml @@ -0,0 +1,39 @@ +entity.metatag_custom_tag.collection: + path: '/admin/config/search/metatag/custom-tags' + defaults: + _entity_list: 'metatag_custom_tag' + _title: 'Custom tags' + requirements: + _permission: 'administer custom meta tags' + options: + _admin_route: TRUE + +entity.metatag_custom_tag.admin_add: + path: '/admin/config/search/metatag/custom-tags/add' + defaults: + _entity_form: 'metatag_custom_tag.add' + _title: 'Add Custom tag' + requirements: + _permission: 'administer custom meta tags' + options: + _admin_route: TRUE + +entity.metatag_custom_tag.edit_form: + path: '/admin/config/search/metatag/custom-tags/{metatag_custom_tag}/edit' + defaults: + _entity_form: 'metatag_custom_tag.edit' + _title_callback: '\Drupal\metatag_custom_tags\Form\MetaTagCustomTagForm::getTitle' + requirements: + _permission: 'administer custom meta tags' + options: + _admin_route: TRUE + +entity.metatag_custom_tag.delete_form: + path: '/admin/config/search/metatag/custom-tags/{metatag_custom_tag}/delete' + defaults: + _entity_form: 'metatag_custom_tag.delete' + _title: 'Delete Custom tag' + requirements: + _permission: 'administer custom meta tags' + options: + _admin_route: TRUE diff --git a/web/modules/metatag/metatag_custom_tags/src/Entity/MetaTagCustomTag.php b/web/modules/metatag/metatag_custom_tags/src/Entity/MetaTagCustomTag.php new file mode 100644 index 0000000000000000000000000000000000000000..5e355ea22f5b70c3e0923e0ae05f3979c6f1b475 --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/src/Entity/MetaTagCustomTag.php @@ -0,0 +1,89 @@ +<?php + +namespace Drupal\metatag_custom_tags\Entity; + +use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\metatag_custom_tags\MetaTagCustomTagInterface; + +/** + * Defines the custom meta tag entity type. + * + * @ConfigEntityType( + * id = "metatag_custom_tag", + * label = @Translation("Custom tag"), + * label_collection = @Translation("Custom tags"), + * label_singular = @Translation("Custom tag"), + * label_plural = @Translation("Custom tags"), + * label_count = @PluralTranslation( + * singular = "@count Custom tag", + * plural = "@count Custom tags", + * ), + * handlers = { + * "list_builder" = "Drupal\metatag_custom_tags\MetaTagCustomTagListBuilder", + * "form" = { + * "add" = "Drupal\metatag_custom_tags\Form\MetaTagCustomTagForm", + * "edit" = "Drupal\metatag_custom_tags\Form\MetaTagCustomTagForm", + * "delete" = "Drupal\Core\Entity\EntityDeleteForm", + * }, + * }, + * config_prefix = "metatag_custom_tag", + * admin_permission = "administer custom meta tags", + * links = { + * "collection" = "/admin/config/search/metatag/custom-tags", + * "add-form" = "/admin/config/search/metatag/custom-tags/add", + * "edit-form" = "/admin/config/search/metatag/custom-tags/{metatag_custom_tag}/edit", + * "delete-form" = "/admin/config/search/metatag/custom-tags/{metatag_custom_tag}/delete", + * }, + * entity_keys = { + * "id" = "id", + * "label" = "label", + * }, + * config_export = { + * "id", + * "label", + * "description", + * "htmlElement", + * "htmlNameAttribute", + * "htmlValueAttribute", + * }, + * ) + */ +class MetaTagCustomTag extends ConfigEntityBase implements MetaTagCustomTagInterface { + + /** + * The ID. + */ + protected string $id; + + /** + * The label. + */ + protected string $label; + + /** + * The description. + */ + protected string $description; + + /** + * The string this tag uses for the element itself. + * + * @var string + */ + protected $htmlElement; + + /** + * The attribute this tag uses for the name. + * + * @var string + */ + protected $htmlNameAttribute; + + /** + * The attribute this tag uses for the contents. + * + * @var string + */ + protected $htmlValueAttribute; + +} diff --git a/web/modules/metatag/metatag_custom_tags/src/Form/MetaTagCustomTagForm.php b/web/modules/metatag/metatag_custom_tags/src/Form/MetaTagCustomTagForm.php new file mode 100644 index 0000000000000000000000000000000000000000..650d70dc083e2c4a562175a34055d394e1b248ce --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/src/Form/MetaTagCustomTagForm.php @@ -0,0 +1,137 @@ +<?php + +namespace Drupal\metatag_custom_tags\Form; + +use Drupal\Core\Entity\EntityForm; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\metatag_custom_tags\MetaTagCustomTagInterface; + +/** + * Form handler for the MetaTag Custom Tag entity type. + * + * @package Drupal\metatag_custom_tags\Form + */ +class MetaTagCustomTagForm extends EntityForm { + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state): array { + + $form = parent::form($form, $form_state); + + $entity = $this->entity; + + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Name'), + '#maxlength' => 255, + '#description' => $this->t('Specify the name of the custom tag.'), + '#required' => TRUE, + '#default_value' => $entity->label() ?? '', + ]; + + $form['id'] = [ + '#type' => 'machine_name', + '#default_value' => $entity->id(), + '#machine_name' => [ + 'exists' => [$this, 'exist'], + ], + '#disabled' => !$entity->isNew(), + ]; + + $form['description'] = [ + '#type' => 'textfield', + '#title' => $this->t('Description'), + '#maxlength' => 255, + '#description' => $this->t('Specify the description of the Custom tag.'), + '#required' => TRUE, + '#default_value' => $entity->get('description') ?? '', + ]; + + $form['htmlElement'] = [ + '#type' => 'select', + '#title' => $this->t('HTML element'), + '#options' => [ + 'meta' => $this->t('Meta'), + 'link' => $this->t('Link'), + ], + '#description' => $this->t('Select the HTML element of the Custom tag.'), + '#required' => TRUE, + '#default_value' => $entity->get('htmlElement') ?? 'meta', + ]; + + $form['htmlNameAttribute'] = [ + '#type' => 'select', + '#title' => $this->t('Name attribute'), + '#options' => [ + 'name' => $this->t('Name'), + 'property' => $this->t('Property'), + 'http-equiv' => $this->t('Http Equiv'), + 'itemprop' => $this->t('Item Prop'), + 'rel' => $this->t('Rel'), + ], + '#description' => $this->t('Select the Name attribute of the Custom tag.'), + '#required' => TRUE, + '#default_value' => $entity->get('htmlNameAttribute') ?? 'name', + ]; + + $form['htmlValueAttribute'] = [ + '#type' => 'select', + '#title' => $this->t('Value attribute'), + '#options' => [ + 'content' => $this->t('Content'), + 'href' => $this->t('Href'), + ], + '#description' => $this->t('Select the Value attribute of the Custom tag.'), + '#required' => TRUE, + '#default_value' => $entity->get('htmlValueAttribute') ?? 'content', + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state): int { + $result = parent::save($form, $form_state); + $message_args = ['%label' => $this->entity->label()]; + $this->messenger()->addStatus( + match($result) { + \SAVED_NEW => $this->t('Created %label Custom tag.', $message_args), + \SAVED_UPDATED => $this->t('Updated %label Custom tag.', $message_args), + } + ); + \Drupal::service('plugin.manager.metatag.tag')->clearCachedDefinitions(); + $form_state->setRedirectUrl($this->entity->toUrl('collection')); + return $result; + } + + /** + * Helper function to check whether the configuration entity exists. + */ + public function exist($id) { + $entity = $this->entityTypeManager->getStorage('metatag_custom_tag')->getQuery() + ->condition('id', $id) + ->execute(); + return (bool) $entity; + } + + /** + * Route title callback. + * + * @param \Drupal\metatag_custom_tags\MetaTagCustomTagInterface $metatag_custom_tag + * Custom tag entity. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * Translated route title. + */ + public function getTitle(MetaTagCustomTagInterface $metatag_custom_tag): TranslatableMarkup { + return $this->t('Edit Custom tag @label', [ + '@label' => $metatag_custom_tag->label(), + ]); + } + +} diff --git a/web/modules/metatag/metatag_custom_tags/src/MetaTagCustomTagInterface.php b/web/modules/metatag/metatag_custom_tags/src/MetaTagCustomTagInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..9f4094b61c54a9ae7a3db9c57369889e820c07ef --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/src/MetaTagCustomTagInterface.php @@ -0,0 +1,12 @@ +<?php + +namespace Drupal\metatag_custom_tags; + +use Drupal\Core\Config\Entity\ConfigEntityInterface; + +/** + * Provides an interface for defining Custom Tag entities. + */ +interface MetaTagCustomTagInterface extends ConfigEntityInterface { + // Add get/set methods for your configuration properties here. +} diff --git a/web/modules/metatag/metatag_custom_tags/src/MetaTagCustomTagListBuilder.php b/web/modules/metatag/metatag_custom_tags/src/MetaTagCustomTagListBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..0454bb9bfd87a3bf33a02710984c0bb802acc3a7 --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/src/MetaTagCustomTagListBuilder.php @@ -0,0 +1,32 @@ +<?php + +namespace Drupal\metatag_custom_tags; + +use Drupal\Core\Config\Entity\ConfigEntityListBuilder; +use Drupal\Core\Entity\EntityInterface; + +/** + * Provides a listing of custom tags. + */ +class MetaTagCustomTagListBuilder extends ConfigEntityListBuilder { + + /** + * {@inheritdoc} + */ + public function buildHeader(): array { + $header['label'] = $this->t('Name'); + $header['description'] = $this->t('Description'); + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity): array { + /** @var \Drupal\metatag_custom_tags\MetaTagCustomTagInterface $entity */ + $row['label'] = $entity->label(); + $row['description'] = $entity->get('description'); + return $row + parent::buildRow($entity); + } + +} diff --git a/web/modules/metatag/metatag_custom_tags/src/Plugin/Derivative/MetaTagCustomTagDeriver.php b/web/modules/metatag/metatag_custom_tags/src/Plugin/Derivative/MetaTagCustomTagDeriver.php new file mode 100644 index 0000000000000000000000000000000000000000..c4f717f135a0afeff3d4ebf665679920cef27bae --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/src/Plugin/Derivative/MetaTagCustomTagDeriver.php @@ -0,0 +1,80 @@ +<?php + +namespace Drupal\metatag_custom_tags\Plugin\Derivative; + +use Drupal\Component\Plugin\Derivative\DeriverBase; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\StringTranslation\TranslationInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Create a new metatag_custom_tags tag plugin for custom tags. + */ +class MetaTagCustomTagDeriver extends DeriverBase implements ContainerDeriverInterface { + + use StringTranslationTrait; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Constructs a new MetaTagCustomTagDeriver object. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation + * The string translation service. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) { + $this->entityTypeManager = $entity_type_manager; + $this->stringTranslation = $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) { + // Get a list of all metatag custom tags. + $config_entity = $this->entityTypeManager->getStorage('metatag_custom_tag'); + $metatag_custom_tags = $config_entity->loadMultiple() ?? []; + // Now we loop over them and declare the derivatives. + foreach ($metatag_custom_tags as $id => $metatag_custom_tag) { + // The base definition includes the annotations defined in the plugin. + $derivative = $base_plugin_definition; + + // Here we fill in any missing keys on the MetatagTag annotation. + $derivative['weight']++; + $derivative['id'] = $id; + $derivative['name'] = $id; + // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString + $derivative['label'] = $this->t($metatag_custom_tag->get('label')); + // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString + $derivative['description'] = $this->t($metatag_custom_tag->get('description')); + $derivative['htmlElement'] = $metatag_custom_tag->get('htmlElement'); + $derivative['htmlNameAttribute'] = $metatag_custom_tag->get('htmlNameAttribute'); + $derivative['htmlValueAttribute'] = $metatag_custom_tag->get('htmlValueAttribute'); + + // Reference derivatives based on their UUID instead of the record ID. + $this->derivatives[$derivative['id']] = $derivative; + } + + return $this->derivatives; + } + +} diff --git a/web/modules/metatag/metatag_custom_tags/src/Plugin/metatag/Group/MetaTagCustomTags.php b/web/modules/metatag/metatag_custom_tags/src/Plugin/metatag/Group/MetaTagCustomTags.php new file mode 100644 index 0000000000000000000000000000000000000000..3613b34588e130d91fb718bcbcd4b7fe0270345c --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/src/Plugin/metatag/Group/MetaTagCustomTags.php @@ -0,0 +1,19 @@ +<?php + +namespace Drupal\metatag_custom_tags\Plugin\metatag\Group; + +use Drupal\metatag\Plugin\metatag\Group\GroupBase; + +/** + * The metatag custom tag group. + * + * @MetatagGroup( + * id = "metatag_custom_tags", + * label = @Translation("Custom tags"), + * description = @Translation("These custom tags are designed to use the custom purpose on the website."), + * weight = 3 + * ) + */ +class MetaTagCustomTags extends GroupBase { + // Inherits everything from Base. +} diff --git a/web/modules/metatag/metatag_custom_tags/src/Plugin/metatag/Tag/MetaTagCustomTag.php b/web/modules/metatag/metatag_custom_tags/src/Plugin/metatag/Tag/MetaTagCustomTag.php new file mode 100644 index 0000000000000000000000000000000000000000..b1f414e7d5538efd220d3a626957b9691fce003d --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/src/Plugin/metatag/Tag/MetaTagCustomTag.php @@ -0,0 +1,775 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\metatag_custom_tags\Plugin\metatag\Tag; + +use Drupal\Component\Plugin\PluginBase; +use Drupal\Component\Render\PlainTextOutput; +use Drupal\Component\Utility\Random; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\metatag\MetatagSeparator; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Custom configured meta tags will be available. + * + * The meta tag's values will be based upon this annotation. + * + * @MetatagTag( + * id = "metatag_custom_tag", + * deriver = "Drupal\metatag_custom_tags\Plugin\Derivative\MetaTagCustomTagDeriver", + * label = @Translation("Custom Tag"), + * description = @Translation("This plugin will be cloned from these settings for each custom tag."), + * name = "metatag_custom_tag", + * weight = 1, + * group = "metatag_custom_tags", + * type = "string", + * secure = FALSE, + * multiple = TRUE + * ) + */ +class MetaTagCustomTag extends PluginBase { + use MetatagSeparator; + use StringTranslationTrait; + + /** + * Machine name of the meta tag plugin. + * + * @var string + */ + protected $id; + + /** + * Official metatag name. + * + * @var string + */ + protected $name; + + /** + * The title of the plugin. + * + * @var \Drupal\Core\Annotation\Translation + * + * @ingroup plugin_translatable + */ + protected $label; + + /** + * A longer explanation of what the field is for. + * + * @var \Drupal\Core\Annotation\Translation + * + * @ingroup plugin_translatable + */ + protected $description; + + /** + * The category this meta tag fits in. + * + * @var string + */ + protected $group; + + /** + * Type of the value being stored. + * + * @var string + */ + protected $type; + + /** + * True if URL must use HTTPS. + * + * @var bool + */ + protected $secure; + + /** + * True if more than one is allowed. + * + * @var bool + */ + protected $multiple; + + /** + * True if the tag should use a text area. + * + * @var bool + */ + protected $long; + + /** + * True if the tag should be trimmable. + * + * @var bool + */ + protected $trimmable; + + /** + * True if the URL value(s) must be absolute. + * + * @var bool + */ + protected $absoluteUrl; + + /** + * Retrieves the currently active request object. + * + * @var \Symfony\Component\HttpFoundation\Request + */ + protected $request; + + /** + * The value of the meta tag in this instance. + * + * @var string|array + */ + protected $value; + + /** + * The sort order for this meta tag. + * + * @var int + */ + protected $weight; + + /** + * Config factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The string this tag uses for the element itself. + * + * @var string + */ + protected $htmlElement; + + /** + * The attribute this tag uses for the name. + * + * @var string + */ + protected $htmlNameAttribute; + + /** + * The attribute this tag uses for the contents. + * + * @var string + */ + protected $htmlValueAttribute; + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + // Set the properties from the annotation. + // @todo Should we have setProperty() methods for each of these? + $this->id = $plugin_definition['id']; + $this->name = $plugin_definition['name']; + $this->label = $plugin_definition['label']; + $this->description = $plugin_definition['description'] ?? ''; + $this->htmlElement = $plugin_definition['htmlElement'] ?? 'meta'; + $this->htmlNameAttribute = $plugin_definition['htmlNameAttribute'] ?? 'name'; + $this->htmlValueAttribute = $plugin_definition['htmlValueAttribute'] ?? 'content'; + $this->group = $plugin_definition['group']; + $this->weight = $plugin_definition['weight']; + $this->type = $plugin_definition['type']; + $this->secure = !empty($plugin_definition['secure']); + $this->multiple = !empty($plugin_definition['multiple']); + $this->trimmable = !empty($plugin_definition['trimmable']); + $this->long = !empty($plugin_definition['long']); + $this->absoluteUrl = !empty($plugin_definition['absolute_url']); + $this->request = \Drupal::request(); + + // @todo Is there a DI-friendly way of doing this? + $this->configFactory = \Drupal::service('config.factory'); + + // Set an initial value. + $this->value = ''; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = new static( + $configuration, + $plugin_id, + $plugin_definition + ); + $instance->setConfigFactory($container->get('config.factory')); + return $instance; + } + + /** + * Sets ConfigFactoryInterface service. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory + * The Config Factory service. + */ + public function setConfigFactory(ConfigFactoryInterface $configFactory) { + $this->configFactory = $configFactory; + } + + /** + * Obtain the meta tag's internal ID. + * + * @return string + * This meta tag's internal ID. + */ + public function id(): string { + return $this->id; + } + + /** + * This meta tag's label. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * The label. + */ + public function label(): TranslatableMarkup|string { + return $this->label; + } + + /** + * The meta tag's description. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * This meta tag's description. + */ + public function description(): TranslatableMarkup|string { + return $this->description; + } + + /** + * The meta tag's machine name. + * + * @return string + * This meta tag's machine name. + */ + public function name(): string { + return $this->name; + } + + /** + * The meta tag group this meta tag belongs to. + * + * @return string + * The meta tag's group name. + */ + public function group(): string { + return $this->group; + } + + /** + * This meta tag's form field's weight. + * + * @return int|float + * The form API weight for this. May be any number supported by Form API. + */ + public function weight(): mixed { + return $this->weight; + } + + /** + * Obtain this meta tag's type. + * + * @return string + * This meta tag's type. + */ + public function type(): string { + return $this->type; + } + + /** + * Determine whether this meta tag is an image tag. + * + * @return bool + * Whether this meta tag is an image. + */ + public function isImage(): bool { + return $this->type() == 'image'; + } + + /** + * Whether or not this meta tag must output secure (HTTPS) URLs. + * + * @return bool + * Whether or not this meta tag must output secure (HTTPS) URLs. + */ + public function secure(): bool { + return $this->secure; + } + + /** + * Whether or not this meta tag must output secure (HTTPS) URLs. + * + * @return bool + * Whether or not this meta tag must output secure (HTTPS) URLs. + */ + public function isSecure(): bool { + return (bool) $this->secure; + } + + /** + * Whether or not this meta tag supports multiple values. + * + * @return bool + * Whether or not this meta tag supports multiple values. + */ + public function multiple(): bool { + return $this->multiple; + } + + /** + * Whether or not this meta tag supports multiple values. + * + * @return bool + * Whether or not this meta tag supports multiple values. + */ + public function isMultiple(): bool { + return (bool) $this->multiple; + } + + /** + * Whether or not this meta tag should use a text area. + * + * @return bool + * Whether or not this meta tag should use a text area. + */ + public function isLong(): bool { + return $this->long; + } + + /** + * Whether or not this meta tag stores a URL or URI value. + * + * @return bool + * Whether or not this meta tag should contain a URL or URI value. + */ + public function isUrl(): bool { + // Secure URLs are URLs. + if ($this->isSecure()) { + return TRUE; + } + // Absolute URLs are URLs. + if ($this->requiresAbsoluteUrl()) { + return TRUE; + } + // URIs are URL-adjacent. + if ($this->type == 'uri') { + return TRUE; + } + return FALSE; + } + + /** + * Get the HTML attribute used to store this meta tag's value. + * + * @return string + * The HTML attribute used to store this meta tag's value. + */ + public function getHtmlValueAttribute(): string { + return $this->htmlValueAttribute; + } + + /** + * Whether or not this meta tag must output required absolute URLs. + * + * @return bool + * Whether or not this meta tag must output required absolute URLs. + */ + public function requiresAbsoluteUrl(): bool { + return $this->absoluteUrl; + } + + /** + * Whether or not this meta tag is active. + * + * @return bool + * Whether this meta tag has been enabled. + */ + public function isActive(): bool { + return TRUE; + } + + /** + * Generate a form element for this meta tag. + * + * @param array $element + * The existing form element to attach to. + * + * @return array + * The completed form element. + */ + public function form(array $element = []): array { + $form = [ + '#type' => $this->isLong() ? 'textarea' : 'textfield', + '#title' => $this->label(), + '#default_value' => $this->value(), + '#maxlength' => 1024, + '#required' => $element['#required'] ?? FALSE, + '#description' => $this->description(), + '#element_validate' => [[get_class($this), 'validateTag']], + ]; + + // Optional handling for items that allow multiple values. + $separator = $this->getSeparator(); + if (!empty($this->multiple)) { + $form['#description'] .= ' ' . $this->t('Multiple values may be used, separated by `:delimiter`. Note: Tokens that return multiple values will be handled automatically.', [':delimiter' => $separator]); + } + + // Optional handling for images. + if ((!empty($this->type())) && ($this->type() === 'image')) { + $form['#description'] .= ' ' . $this->t('This will be able to extract the URL from an image field if the field is configured properly.'); + } + + if (!empty($this->absolute_url)) { + $form['#description'] .= ' ' . $this->t('Any relative or protocol-relative URLs will be converted to absolute URLs.'); + } + + // Optional handling for secure paths. + if (!empty($this->secure)) { + $form['#description'] .= ' ' . $this->t('Any URLs which start with "http://" will be converted to "https://".'); + } + + $settings = \Drupal::config('metatag.settings'); + $trimlengths = $settings->get('tag_trim_maxlength') ?? []; + if (!empty($trimlengths['metatag_maxlength_' . $this->id])) { + $maxlength = intval($trimlengths['metatag_maxlength_' . $this->id]); + if (is_numeric($maxlength) && $maxlength > 0) { + $form['#description'] .= ' ' . $this->t('This will be truncated to a maximum of %max characters after any tokens are processed.', ['%max' => $maxlength]); + + // Optional support for the Maxlength module. + if (\Drupal::moduleHandler()->moduleExists('maxlength')) { + if ($settings->get('use_maxlength') ?? TRUE) { + $form['#attributes']['class'][] = 'maxlength'; + $form['#attached']['library'][] = 'maxlength/maxlength'; + $form['#maxlength_js'] = TRUE; + $form['#attributes']['data-maxlength'] = $maxlength; + } + } + } + } + + return $form; + } + + /** + * Obtain the current meta tag's raw value. + * + * @return string|array + * The current raw meta tag value. + */ + public function value(): string|array { + return $this->value; + } + + /** + * Assign the current meta tag a value. + * + * @param mixed $value + * The value to assign this meta tag. + */ + public function setValue($value): void { + // If the argument is an array then store it as-is. If the argument is + // anything else, convert it to a string. + if (is_array($value)) { + $this->value = $value; + } + else { + $this->value = (string) $value; + } + } + + /** + * Make the string presentable. + * + * This removes whitespace from either side of the string, and removes extra + * whitespace inside the string so that it only contains one single space, + * all line breaks and tabs are replaced by spaces. + * + * @param string $value + * The raw string to process. + * + * @return string + * The meta tag value after processing. + */ + protected function tidy($value): string { + if (is_null($value) || $value == '') { + return ''; + } + + $value = str_replace(["\r\n", "\n", "\r", "\t"], ' ', $value); + $value = preg_replace('/\s+/', ' ', $value); + return trim($value); + } + + /** + * Generate the HTML tag output for a meta tag. + * + * @return array + * A render array. + */ + public function output(): array { + // If there is no value, just return either an empty array or empty string. + if (is_null($this->value) || $this->value == '') { + return []; + } + + // Get configuration. + $separator = $this->getSeparator(); + + // If this contains embedded image tags, extract the image URLs. + if ($this->type() === 'image') { + $value = $this->parseImageUrl($this->value); + } + else { + $value = PlainTextOutput::renderFromHtml($this->value); + } + + $values = $this->multiple() ? explode($separator, $value) : [$value]; + $elements = []; + foreach ($values as $value) { + $value = $this->tidy($value); + if ($value != '' && $this->requiresAbsoluteUrl()) { + // Relative URL. + if (parse_url($value, PHP_URL_HOST) == NULL) { + $value = $this->request->getSchemeAndHttpHost() . $value; + } + // Protocol-relative URL. + elseif (substr($value, 0, 2) === '//') { + $value = $this->request->getScheme() . ':' . $value; + } + } + + // If tag must be secure, convert all http:// to https://. + if ($this->secure() && strpos($value, 'http://') !== FALSE) { + $value = str_replace('http://', 'https://', $value); + } + + $value = $this->trimValue($value); + + $elements[] = [ + '#tag' => $this->htmlElement, + '#attributes' => [ + $this->htmlNameAttribute => $this->name, + $this->htmlValueAttribute => $value, + ], + ]; + } + + return $this->multiple() ? $elements : reset($elements); + } + + /** + * Validates the metatag data. + * + * @param array $element + * The form element to process. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + */ + public static function validateTag(array &$element, FormStateInterface $form_state): void { + // @todo If there is some common validation, put it here. Otherwise, make + // it abstract? + } + + /** + * Extract any image URLs that might be found in a meta tag. + * + * @return string + * A comma separated list of any image URLs found in the meta tag's value, + * or the original string if no images were identified. + */ + protected function parseImageUrl($value): string { + global $base_root; + + // Skip all logic if the string is empty. Unlike other scenarios, the logic + // in this method is predicated on the value being a legitimate string, so + // it's ok to skip all possible "empty" values, including the number 0, etc. + if (empty($value)) { + return ''; + } + + // If image tag src is relative (starts with /), convert to an absolute + // link; ignore protocol-relative URLs. + $image_tag = FALSE; + if (strpos($value, '<img src="/') !== FALSE && strpos($value, '<img src="//') === FALSE) { + $value = str_replace('<img src="/', '<img src="' . $base_root . '/', $value); + $image_tag = TRUE; + } + + if ($this->multiple()) { + // Split the string into an array, remove empty items. + if ($image_tag) { + preg_match_all('%\s*(|,\s*)(<\s*img\s+[^>]+>)%m', $value, $matches); + $values = array_filter($matches[2] ?? []); + } + else { + $values = array_filter(explode($this->getSeparator(), $value)); + } + } + else { + $values = [$value]; + } + + // Check through the value(s) to see if there are any image tags. + foreach ($values as $key => $val) { + $matches = []; + preg_match('/src="([^"]*)"/', $val, $matches); + if (!empty($matches[1])) { + $values[$key] = $matches[1]; + } + + // If an image wasn't found then remove any other HTML tags in the string. + else { + $values[$key] = PlainTextOutput::renderFromHtml($val); + } + } + + // Make sure there aren't any blank items in the array. + $values = array_filter($values); + + // Convert the array back into a delimited string before sending it back. + return implode($this->getSeparator(), $values); + } + + /** + * Trims a value if it is trimmable. + * + * This method uses metatag settings and the MetatagTrimmer service. + * + * @param string $value + * The string value to trim. + * + * @return string + * The trimmed string value. + */ + protected function trimValue($value): string { + if (TRUE === $this->trimmable) { + $settings = \Drupal::config('metatag.settings'); + $trimMethod = $settings->get('tag_trim_method'); + $trimMaxlengthArray = $settings->get('tag_trim_maxlength'); + if (empty($trimMethod) || empty($trimMaxlengthArray)) { + return $value; + } + $currentMaxValue = 0; + foreach ($trimMaxlengthArray as $metaTagName => $maxValue) { + if ($metaTagName == 'metatag_maxlength_' . $this->id) { + $currentMaxValue = $maxValue; + } + } + $trimmerService = \Drupal::service('metatag.trimmer'); + $value = $trimmerService->trimByMethod($value, $currentMaxValue, $trimMethod); + } + return $value; + } + + /** + * The xpath string which identifies this meta tag on a form. + * + * To skip testing the form field exists, return an empty array. + * + * @return string + * An xpath-formatted string for matching a field on the form. + */ + public function getTestFormXpath(): array { + // "Long" values use a text area on the form, so handle them automatically. + if ($this->isLong()) { + return [ + // @todo This should work but it results in the following error: + // DOMXPath::query(): Invalid predicate. + // "//textarea[@name='{$this->id}'", + ]; + } + // Default to a single text input field. + else { + return ["//input[@name='{$this->id}' and @type='text']"]; + } + } + + /** + * Generate a random value for testing purposes. + * + * As a reasonable default, this will generating two words of 8 characters + * each with simple machine name -style strings; image meta tags will generate + * an absolute URL for an image. + * + * @return array + * An array containing a normal string. + */ + public function getTestFormData(): array { + $random = new Random(); + + // Provide a default value. + if ($this->isImage()) { + // @todo Add proper validation of image meta values. + return [ + $this->id => 'https://www.example.com/images/' . $random->word(6) . '-' . $random->word(6) . '.png', + ]; + } + // Absolute URLs that are specifically secure. + elseif ($this->isSecure()) { + return [ + $this->id => 'https://www.example.com/' . $random->word(6) . '-' . $random->word(6) . '.html', + ]; + } + // Absolute URLs that are not necessarily secure. + elseif ($this->requiresAbsoluteUrl()) { + return [ + $this->id => 'http://www.example.com/' . $random->word(6) . '-' . $random->word(6) . '.html', + ]; + } + // Relative URLs. + elseif ($this->isUrl()) { + return [ + $this->id => '/' . $random->word(6) . '/' . $random->word(6) . '.html', + ]; + } + else { + return [ + // Use three alphanumeric strings joined with spaces. + $this->id => $random->word(6) . ' ' . $random->word(6) . ' ' . $random->word(6), + ]; + } + } + + /** + * The xpath string which identifies this meta tag presence on the page. + * + * @return array + * A list of xpath-formatted string(s) for matching a field on the page. + */ + public function getTestOutputExistsXpath(): array { + return ["//" . $this->htmlElement . "[@" . $this->htmlNameAttribute . "='{$this->name}']"]; + } + + /** + * The xpath string which identifies this meta tag's output on the page. + * + * @param array $values + * The field names and values that were submitted. + * + * @return array + * A list of xpath-formatted string(s) for matching a field on the page. + */ + public function getTestOutputValuesXpath(array $values): array { + $xpath_strings = []; + foreach ($values as $value) { + $xpath_strings[] = "//" . $this->htmlElement . "[@" . $this->htmlNameAttribute . "='{$this->name}' and @" . $this->htmlValueAttribute . "='{$value}']"; + } + return $xpath_strings; + } + +} diff --git a/web/modules/metatag/metatag_custom_tags/tests/src/Functional/MetatagCustomTagAdministrationTest.php b/web/modules/metatag/metatag_custom_tags/tests/src/Functional/MetatagCustomTagAdministrationTest.php new file mode 100755 index 0000000000000000000000000000000000000000..68b69eeb5e306916c655ac3cb9d9b1b66c5b1170 --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/tests/src/Functional/MetatagCustomTagAdministrationTest.php @@ -0,0 +1,85 @@ +<?php + +namespace Drupal\Tests\metatag_custom_tags\Functional; + +use Drupal\Tests\BrowserTestBase; + +/** + * Tests the Metatag: Custom tags administration. + * + * @group metatag_custom_tags + */ +class MetatagCustomTagAdministrationTest extends BrowserTestBase { + + use MetatagCustomTagHelperTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'block', + 'node', + 'user', + 'metatag', + 'metatag_custom_tags', + ]; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * A user with admin permissions. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * {@inheritdoc} + */ + public function setUp(): void { + parent::setUp(); + $this->drupalPlaceBlock('local_actions_block'); + + $this->adminUser = $this + ->drupalCreateUser([ + 'administer custom meta tags', + ]); + } + + /** + * Tests the Custom tag administration end to end. + */ + public function testMetatagCustomTagAdministration() { + $this->drupalLogin($this->adminUser); + // Remove default custom meta tags. + $this->removeDefaultCustomTags(); + // Check metatag custom tag listing empty text. + $this->metatagCustomTagListingEmptyText(); + // Access metatag custom tag listing page operations for name type. + $this->metatagCustomTagListingPageOperations('meta', 'name', 'content'); + // Access metatag custom tag listing page operations for property type. + $this->metatagCustomTagListingPageOperations('meta', 'name', 'content'); + // Access metatag custom tag listing page operations for http-equiv type. + $this->metatagCustomTagListingPageOperations('meta', 'http-equiv', 'content'); + } + + /** + * Test metatag custom tag listing page operations. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * @throws \Behat\Mink\Exception\ExpectationException + * @throws \Behat\Mink\Exception\ResponseTextException + */ + public function metatagCustomTagListingPageOperations($htmlElement, $htmlNameAttribute, $htmlValueAttribute) { + // Create custom meta tag. + $this->createCustomMetaTag($htmlElement, $htmlNameAttribute, $htmlValueAttribute); + // Update custom meta tag. + $this->updateCustomMetaTag(); + // Delete custom meta tag. + $this->deleteCustomMetaTag(); + } + +} diff --git a/web/modules/metatag/metatag_custom_tags/tests/src/Functional/MetatagCustomTagHelperTrait.php b/web/modules/metatag/metatag_custom_tags/tests/src/Functional/MetatagCustomTagHelperTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..17db928b0b707a8e87f2cecf6f53c6f17d50d8ea --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/tests/src/Functional/MetatagCustomTagHelperTrait.php @@ -0,0 +1,94 @@ +<?php + +namespace Drupal\Tests\metatag_custom_tags\Functional; + +/** + * Custom tag helper functions for the automated tests. + */ +trait MetatagCustomTagHelperTrait { + + /** + * Create Custom tag. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * @throws \Behat\Mink\Exception\ExpectationException + * @throws \Behat\Mink\Exception\ResponseTextException + */ + protected function createCustomMetaTag($htmlElement, $htmlNameAttribute, $htmlValueAttribute) { + // Access custom meta add page. + $this->drupalGet('admin/config/search/metatag/custom-tags/add'); + $this->assertSession()->statusCodeEquals(200); + $edit = []; + $edit['id'] = 'foo'; + $edit['label'] = 'foo label'; + $edit['description'] = 'foo description'; + $edit['htmlElement'] = $htmlElement; + $edit['htmlNameAttribute'] = $htmlNameAttribute; + $edit['htmlValueAttribute'] = $htmlValueAttribute; + $this->submitForm($edit, 'Save'); + $this->assertSession()->addressEquals('/admin/config/search/metatag/custom-tags'); + $this->assertSession()->pageTextContains('Created foo label Custom tag.'); + } + + /** + * Update Custom Metatag. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * @throws \Behat\Mink\Exception\ExpectationException + * @throws \Behat\Mink\Exception\ResponseTextException + */ + protected function updateCustomMetaTag() { + $this->drupalGet('admin/config/search/metatag/custom-tags/foo/edit'); + $this->assertSession()->statusCodeEquals(200); + $this->submitForm(['description' => 'foo description updated'], 'Save'); + $this->assertSession()->addressEquals('/admin/config/search/metatag/custom-tags'); + $this->assertSession()->pageTextContains('Updated foo label Custom tag.'); + } + + /** + * Delete Custom Metatag. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * @throws \Behat\Mink\Exception\ExpectationException + * @throws \Behat\Mink\Exception\ResponseTextException + */ + protected function deleteCustomMetaTag() { + $this->drupalGet('admin/config/search/metatag/custom-tags/foo/delete'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Are you sure you want to delete the Custom tag foo label?'); + $this->submitForm([], 'Delete'); + $this->assertSession()->addressEquals('/admin/config/search/metatag/custom-tags'); + } + + /** + * Remove default custom tags. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * @throws \Behat\Mink\Exception\ExpectationException + * @throws \Behat\Mink\Exception\ResponseTextException + */ + protected function removeDefaultCustomTags() { + $this->drupalGet('admin/config/search/metatag/custom-tags/sitename/delete'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Are you sure you want to delete the Custom tag Sitename?'); + $this->submitForm([], 'Delete'); + $this->assertSession()->addressEquals('/admin/config/search/metatag/custom-tags'); + } + + /** + * Check metatag custom tag listing page empty text. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * @throws \Behat\Mink\Exception\ExpectationException + * @throws \Behat\Mink\Exception\ResponseTextException + */ + protected function metatagCustomTagListingEmptyText() { + $this->drupalGet('admin/config/search/metatag/custom-tags'); + $this->assertSession()->statusCodeEquals(200); + // Check that the Add tag link exists. + $this->assertSession()->linkByHrefExists('admin/config/search/metatag/custom-tags/add'); + // Check that empty message exists. + $this->assertSession()->pageTextContains('There are no Custom tags yet.'); + } + +} diff --git a/web/modules/metatag/metatag_custom_tags/tests/src/Functional/MetatagCustomTagsTest.php b/web/modules/metatag/metatag_custom_tags/tests/src/Functional/MetatagCustomTagsTest.php new file mode 100755 index 0000000000000000000000000000000000000000..c720d235eeb9d9ce6ec442b95fdee1cfb8b0ce6a --- /dev/null +++ b/web/modules/metatag/metatag_custom_tags/tests/src/Functional/MetatagCustomTagsTest.php @@ -0,0 +1,157 @@ +<?php + +namespace Drupal\Tests\metatag_custom_tags\Functional; + +use Drupal\Tests\BrowserTestBase; + +/** + * Tests the Custom tags. + * + * @group metatag_custom_tags + */ +class MetatagCustomTagsTest extends BrowserTestBase { + + use MetatagCustomTagHelperTrait; + + /** + * Profile to use. + * + * @var string + */ + protected $profile = 'testing'; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'block', + 'node', + 'user', + 'metatag', + 'metatag_custom_tags', + 'metatag_test_custom_route', + ]; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * A user with admin permissions. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * {@inheritdoc} + */ + public function setUp(): void { + parent::setUp(); + + $this->adminUser = $this + ->drupalCreateUser([ + 'administer site configuration', + 'administer meta tags', + 'administer custom meta tags', + 'access content', + ]); + } + + /** + * Tests the metatag custom tag http-equiv. + */ + public function testMetatagCustomTagHttpEquiv() { + $this->drupalLogin($this->adminUser); + // Perform metatag custom tag add operation from the listing page. + $this->createCustomMetaTag('meta', 'http-equiv', 'content'); + // Rebuild cache. + $this->rebuildAll(); + // Save the value into the metatag custom tag. + $this->drupalGet('/admin/config/search/metatag/global'); + $this->submitForm(['metatag_custom_tag:foo' => 'foo value'], 'Save'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Saved the Global Metatag defaults.'); + // Load the metatag custom route to verify metatag custom tag exists. + $this->drupalGet('/metatag_test_custom_route'); + $this->assertSession()->elementExists('xpath', '//meta[@http-equiv="foo" and @content="foo value"]'); + } + + /** + * Tests the metatag custom tag name. + */ + public function testMetatagCustomTagName() { + $this->drupalLogin($this->adminUser); + // Perform metatag custom tag add operation from the listing page. + $this->createCustomMetaTag('meta', 'name', 'content'); + // Rebuild cache. + $this->rebuildAll(); + // Save the value into the metatag custom tag. + $this->drupalGet('/admin/config/search/metatag/global'); + $this->submitForm(['metatag_custom_tag:foo' => 'foo value'], 'Save'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Saved the Global Metatag defaults.'); + // Load the metatag custom route to verify metatag custom tag exists. + $this->drupalGet('/metatag_test_custom_route'); + $this->assertSession()->elementExists('xpath', '//meta[@name="foo" and @content="foo value"]'); + } + + /** + * Tests the metatag custom tag property. + */ + public function testMetatagCustomTagProperty() { + $this->drupalLogin($this->adminUser); + // Perform metatag custom tag add operation from the listing page. + $this->createCustomMetaTag('meta', 'property', 'content'); + // Rebuild cache. + $this->rebuildAll(); + // Save the value into the metatag custom tag. + $this->drupalGet('/admin/config/search/metatag/global'); + $this->submitForm(['metatag_custom_tag:foo' => 'foo value'], 'Save'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Saved the Global Metatag defaults.'); + // Load the metatag custom route to verify metatag custom tag exists. + $this->drupalGet('/metatag_test_custom_route'); + $this->assertSession()->elementExists('xpath', '//meta[@property="foo" and @content="foo value"]'); + } + + /** + * Tests the metatag custom tag ItemProp. + */ + public function testMetatagCustomTagItemProp() { + $this->drupalLogin($this->adminUser); + // Perform metatag custom tag add operation from the listing page. + $this->createCustomMetaTag('meta', 'itemprop', 'content'); + // Rebuild cache. + $this->rebuildAll(); + // Save the value into the metatag custom tag. + $this->drupalGet('/admin/config/search/metatag/global'); + $this->submitForm(['metatag_custom_tag:foo' => 'foo value'], 'Save'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Saved the Global Metatag defaults.'); + // Load the metatag custom route to verify metatag custom tag exists. + $this->drupalGet('/metatag_test_custom_route'); + $this->assertSession()->elementExists('xpath', '//meta[@itemprop="foo" and @content="foo value"]'); + } + + /** + * Tests the metatag custom tag property. + */ + public function testMetatagCustomTagLinkRel() { + $this->drupalLogin($this->adminUser); + // Perform metatag custom tag add operation from the listing page. + $this->createCustomMetaTag('link', 'rel', 'href'); + // Rebuild cache. + $this->rebuildAll(); + // Save the value into the metatag custom tag. + $this->drupalGet('/admin/config/search/metatag/global'); + $this->submitForm(['metatag_custom_tag:foo' => 'foo value'], 'Save'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Saved the Global Metatag defaults.'); + // Load the metatag custom route to verify metatag custom tag exists. + $this->drupalGet('/metatag_test_custom_route'); + $this->assertSession()->elementExists('xpath', '//link[@rel="foo" and @href="foo value"]'); + } + +} diff --git a/web/modules/metatag/metatag_dc/metatag_dc.info.yml b/web/modules/metatag/metatag_dc/metatag_dc.info.yml index 4ed2e3b4904b32e4b1d9783321faaa3eec5d3979..876e31b972d79e9515f8cf16abba5d3f2b56eb14 100644 --- a/web/modules/metatag/metatag_dc/metatag_dc.info.yml +++ b/web/modules/metatag/metatag_dc/metatag_dc.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_dc_advanced/metatag_dc_advanced.info.yml b/web/modules/metatag/metatag_dc_advanced/metatag_dc_advanced.info.yml index 5606be4d29e0ebaaee8f6b0551be31dc703a144e..8ad4e3a461372179f5cb34675429c2f04f3740dc 100644 --- a/web/modules/metatag/metatag_dc_advanced/metatag_dc_advanced.info.yml +++ b/web/modules/metatag/metatag_dc_advanced/metatag_dc_advanced.info.yml @@ -7,7 +7,7 @@ dependencies: - metatag:metatag - metatag:metatag_dc -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_extended_perms/metatag_extended_perms.info.yml b/web/modules/metatag/metatag_extended_perms/metatag_extended_perms.info.yml index 3228cf4f06af7d3fd1f2b160573a6a0e9f261746..664b66df130ad0496214eb39c04384f2f00f4b94 100644 --- a/web/modules/metatag/metatag_extended_perms/metatag_extended_perms.info.yml +++ b/web/modules/metatag/metatag_extended_perms/metatag_extended_perms.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_facebook/metatag_facebook.info.yml b/web/modules/metatag/metatag_facebook/metatag_facebook.info.yml index f436eee7b00ce79ddd23e593b393317007dc5118..8f5dbd072cd8fe3b22cbf73019dc01e50ab75a21 100644 --- a/web/modules/metatag/metatag_facebook/metatag_facebook.info.yml +++ b/web/modules/metatag/metatag_facebook/metatag_facebook.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_favicons/metatag_favicons.info.yml b/web/modules/metatag/metatag_favicons/metatag_favicons.info.yml index a2eca8d7dbe13802a774ec6a9dc0cb38189bf921..1d09a11f33b0543e8640fbd014b16b0a5cf28d3e 100644 --- a/web/modules/metatag/metatag_favicons/metatag_favicons.info.yml +++ b/web/modules/metatag/metatag_favicons/metatag_favicons.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_google_cse/metatag_google_cse.info.yml b/web/modules/metatag/metatag_google_cse/metatag_google_cse.info.yml index 637ebe347376043f008b61f6b02ce609ecf18c9c..26aa4abba4165fb1f6f7cdfaa177f276c98a46b4 100644 --- a/web/modules/metatag/metatag_google_cse/metatag_google_cse.info.yml +++ b/web/modules/metatag/metatag_google_cse/metatag_google_cse.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_google_plus/metatag_google_plus.info.yml b/web/modules/metatag/metatag_google_plus/metatag_google_plus.info.yml index eb6fcb681e39e98722c7badce4688de74aa1bbe7..8d81adffbe0aebc41d292bbf0e677cf5855228ec 100644 --- a/web/modules/metatag/metatag_google_plus/metatag_google_plus.info.yml +++ b/web/modules/metatag/metatag_google_plus/metatag_google_plus.info.yml @@ -7,7 +7,7 @@ hidden: true dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_hreflang/metatag_hreflang.info.yml b/web/modules/metatag/metatag_hreflang/metatag_hreflang.info.yml index cecf97713b36386338a22cd4d1330e15fc2bd083..dd4659e8b34e22f846c875cba8f9384c2f6ba0e7 100644 --- a/web/modules/metatag/metatag_hreflang/metatag_hreflang.info.yml +++ b/web/modules/metatag/metatag_hreflang/metatag_hreflang.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_mobile/metatag_mobile.info.yml b/web/modules/metatag/metatag_mobile/metatag_mobile.info.yml index 1254f6fba857563b986d16d27b6a5b93bc838dd9..69d2a642f883af47540c0f595766d1d828e63c9c 100644 --- a/web/modules/metatag/metatag_mobile/metatag_mobile.info.yml +++ b/web/modules/metatag/metatag_mobile/metatag_mobile.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_mobile/tests/src/Functional/TestCoreTagRemoval.php b/web/modules/metatag/metatag_mobile/tests/src/Functional/TestCoreTagRemoval.php index 8eb137cce57a2b73f9a1442635b82c04320c2586..bc8b6159c953013f842d111223592fbcce907a4b 100644 --- a/web/modules/metatag/metatag_mobile/tests/src/Functional/TestCoreTagRemoval.php +++ b/web/modules/metatag/metatag_mobile/tests/src/Functional/TestCoreTagRemoval.php @@ -17,6 +17,11 @@ class TestCoreTagRemoval extends BrowserTestBase { use FieldUiTestTrait; use MetatagHelperTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/web/modules/metatag/metatag_open_graph/metatag_open_graph.info.yml b/web/modules/metatag/metatag_open_graph/metatag_open_graph.info.yml index 889f0015384a6b339f1436d2c3280ad5f757cb46..1358f2351fd3c4cd5f9051bc7d96824347af7977 100644 --- a/web/modules/metatag/metatag_open_graph/metatag_open_graph.info.yml +++ b/web/modules/metatag/metatag_open_graph/metatag_open_graph.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_open_graph/src/Plugin/metatag/Tag/OgSeeAlso.php b/web/modules/metatag/metatag_open_graph/src/Plugin/metatag/Tag/OgSeeAlso.php index 235ea1e57dbc8680b62a8da67f296460db7c03e3..f837938bf68753f18b5eb84163b884995b465fd2 100644 --- a/web/modules/metatag/metatag_open_graph/src/Plugin/metatag/Tag/OgSeeAlso.php +++ b/web/modules/metatag/metatag_open_graph/src/Plugin/metatag/Tag/OgSeeAlso.php @@ -16,7 +16,7 @@ * weight = 16, * type = "uri", * secure = FALSE, - * multiple = FALSE + * multiple = TRUE * ) */ class OgSeeAlso extends MetaPropertyBase { diff --git a/web/modules/metatag/metatag_open_graph_products/metatag_open_graph_products.info.yml b/web/modules/metatag/metatag_open_graph_products/metatag_open_graph_products.info.yml index 13a8ab9cf9b29d3a5201628970db4c7b10d2a922..d8fe5c06f83a60a5a843a522b2135591fe569184 100644 --- a/web/modules/metatag/metatag_open_graph_products/metatag_open_graph_products.info.yml +++ b/web/modules/metatag/metatag_open_graph_products/metatag_open_graph_products.info.yml @@ -7,7 +7,7 @@ dependencies: - metatag:metatag - metatag:metatag_open_graph -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_page_manager/metatag_page_manager.info.yml b/web/modules/metatag/metatag_page_manager/metatag_page_manager.info.yml index d4b402aa581b422397d79efd41ffa89e8dde0f5b..ed3823fc25bb8c182500e7a02e6bfcee6b37f8e0 100644 --- a/web/modules/metatag/metatag_page_manager/metatag_page_manager.info.yml +++ b/web/modules/metatag/metatag_page_manager/metatag_page_manager.info.yml @@ -7,7 +7,7 @@ dependencies: - page_manager:page_manager - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_pinterest/metatag_pinterest.info.yml b/web/modules/metatag/metatag_pinterest/metatag_pinterest.info.yml index c219515e7ea642ca159e700d5a532b24528de9b6..bc0ec5b0a4139ea32464fc513f5ca9c9d4d65482 100644 --- a/web/modules/metatag/metatag_pinterest/metatag_pinterest.info.yml +++ b/web/modules/metatag/metatag_pinterest/metatag_pinterest.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_routes/metatag_routes.info.yml b/web/modules/metatag/metatag_routes/metatag_routes.info.yml index 532dfe5244137e6d32fb349dd661c51a057a6f2e..360937fe28b2e418501753a8da59767c44be9988 100644 --- a/web/modules/metatag/metatag_routes/metatag_routes.info.yml +++ b/web/modules/metatag/metatag_routes/metatag_routes.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_routes/metatag_routes.module b/web/modules/metatag/metatag_routes/metatag_routes.module index 91fb33c6b57e2c65c04e8b9fe7814cdcdea1a750..5c74e537cab734d25100b65c30e55f16f257b213 100644 --- a/web/modules/metatag/metatag_routes/metatag_routes.module +++ b/web/modules/metatag/metatag_routes/metatag_routes.module @@ -30,7 +30,9 @@ function metatag_routes_metatags_alter(array &$metatags, array $context) { // Ignore some system routes that are not appropriate for meta tags. if (metatag_is_current_route_supported()) { // Look to see if a configuration was assigned for this route. - $current_route = \Drupal::routeMatch()->getRouteName(); + /** @var \Drupal\metatag_routes\Helper\MetatagRoutesHelperInterface $metatag_routes_helper */ + $metatag_routes_helper = \Drupal::service('metatag_routes.helper'); + $current_route = $metatag_routes_helper->getCurrentMetatagRouteId(); if (!empty($current_route)) { $defaults = \Drupal::entityTypeManager() ->getStorage('metatag_defaults') diff --git a/web/modules/metatag/metatag_routes/metatag_routes.services.yml b/web/modules/metatag/metatag_routes/metatag_routes.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..339238ddbadf2931302b6256521be110677f1822 --- /dev/null +++ b/web/modules/metatag/metatag_routes/metatag_routes.services.yml @@ -0,0 +1,4 @@ +services: + metatag_routes.helper: + class: Drupal\metatag_routes\Helper\MetatagRoutesHelper + arguments: ['@current_route_match'] diff --git a/web/modules/metatag/metatag_routes/src/Form/MetatagCustomCreateForm.php b/web/modules/metatag/metatag_routes/src/Form/MetatagCustomCreateForm.php index 301f30ba22b3d872d1971e65f39717c67e92a658..647a8ef9c1039dc61b4e3dd46816572b60c131a4 100644 --- a/web/modules/metatag/metatag_routes/src/Form/MetatagCustomCreateForm.php +++ b/web/modules/metatag/metatag_routes/src/Form/MetatagCustomCreateForm.php @@ -5,9 +5,10 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Path\PathValidator; +use Drupal\Core\Path\PathValidatorInterface; use Drupal\Core\Routing\AdminContext; -use Drupal\Core\Routing\RouteProvider; +use Drupal\Core\Routing\RouteProviderInterface; +use Drupal\metatag_routes\Helper\MetatagRoutesHelperInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -45,19 +46,28 @@ class MetatagCustomCreateForm extends FormBase { */ protected $adminContext; + /** + * Drupal\metatag_routes\Helper\MetatagRoutesHelperInterface definition. + * + * @var \Drupal\metatag_routes\Helper\MetatagRoutesHelperInterface + */ + protected $metatagRoutesHelper; + /** * {@inheritdoc} */ public function __construct( EntityTypeManagerInterface $entity_type_manager, - RouteProvider $route_provider, - PathValidator $path_validator, + RouteProviderInterface $route_provider, + PathValidatorInterface $path_validator, AdminContext $admin_context, + MetatagRoutesHelperInterface $metatag_routes_helper, ) { $this->entityTypeManager = $entity_type_manager; $this->routeProvider = $route_provider; $this->pathValidator = $path_validator; $this->adminContext = $admin_context; + $this->metatagRoutesHelper = $metatag_routes_helper; } /** @@ -68,7 +78,8 @@ public static function create(ContainerInterface $container) { $container->get('entity_type.manager'), $container->get('router.route_provider'), $container->get('path.validator'), - $container->get('router.admin_context') + $container->get('router.admin_context'), + $container->get('metatag_routes.helper') ); } @@ -132,22 +143,29 @@ public function validateForm(array &$form, FormStateInterface $form_state) { // Avoid including entity routes. $params = $url_object->getRouteParameters(); - $entity_type = !empty($params) ? key($params) : NULL; - $entity_types = ['node', 'taxonomy_term', 'user']; - if (isset($entity_type) && in_array($entity_type, $entity_types)) { + // Exclude entities routes. + if (preg_match('/^entity\..+\.canonical$/', $url_object->getRouteName())) { $form_state->setErrorByName('metatag_url', - $this->t('The entities routes metatags must be added by fields. @entity_type - @id', [ - '@entity_type' => $entity_type, - '@id' => $params[$entity_type], - ])); + $this->t('The entities routes metatags must be added by fields.') + ); return FALSE; } + if (count($params) > 0) { + $form_state->setValue('params', $params); + } + // Validate that the route doesn't have metatags created already. - $ids = $this->entityTypeManager->getStorage('metatag_defaults')->getQuery() - ->accessCheck(FALSE) - ->condition('id', $route_name)->execute(); - if ($ids) { + if ($route_name) { + $route_with_params = $this->metatagRoutesHelper->createMetatagRouteId($route_name, $params); + $ids = $this->entityTypeManager + ->getStorage('metatag_defaults') + ->getQuery() + ->accessCheck(FALSE) + ->condition('id', $route_with_params) + ->execute(); + } + if (!empty($ids)) { $form_state->setErrorByName('metatag_url', $this->t('There are already metatags created for this route.')); return FALSE; @@ -166,10 +184,12 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // Get values for form submission. $route_name = $form_state->getValue('route_name'); $url = $form_state->getValue('metatag_url'); + $params = $form_state->getValue('params'); + $id = $this->metatagRoutesHelper->createMetatagRouteId($route_name, $params); if ($route_name && $url) { // Create the new metatag entity. $entity = $this->entityTypeManager->getStorage('metatag_defaults')->create([ - 'id' => $route_name, + 'id' => $id, 'label' => $url, ]); $entity->save(); @@ -180,7 +200,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // Redirect to metatag edit page. $form_state->setRedirect('entity.metatag_defaults.edit_form', [ - 'metatag_defaults' => $route_name, + 'metatag_defaults' => $id, ]); } else { diff --git a/web/modules/metatag/metatag_routes/src/Helper/MetatagRoutesHelper.php b/web/modules/metatag/metatag_routes/src/Helper/MetatagRoutesHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..f95dbcbb34cd28403e24c6903c9d779cea23acec --- /dev/null +++ b/web/modules/metatag/metatag_routes/src/Helper/MetatagRoutesHelper.php @@ -0,0 +1,63 @@ +<?php + +namespace Drupal\metatag_routes\Helper; + +use Drupal\Core\Routing\CurrentRouteMatch; + +/** + * Class MetatagRoutesHelper. + * + * @package Drupal\metatag_routes\Helper + */ +class MetatagRoutesHelper implements MetatagRoutesHelperInterface { + + /** + * The route match. + * + * @var \Drupal\Core\Routing\CurrentRouteMatch + */ + protected $currentRouteMatch; + + /** + * Constructor. + * + * @param \Drupal\Core\Routing\CurrentRouteMatch $current_route_match + * The route match. + */ + public function __construct(CurrentRouteMatch $current_route_match) { + $this->currentRouteMatch = $current_route_match; + } + + /** + * @{@inheritdoc} + */ + public function createMetatagRouteId($route_name, $params = NULL) { + if ($params) { + return $route_name . $this->getParamsHash(json_encode($params)); + } + + return $route_name; + } + + /** + * @{@inheritdoc} + */ + public function getCurrentMetatagRouteId() { + $route_name = $this->currentRouteMatch->getRouteName(); + $params = $this->currentRouteMatch->getRawParameters()->all(); + + if ($params) { + return $route_name . $this->getParamsHash(json_encode($params)); + } + + return $route_name; + } + + /** + * Return hash of given parameters. + */ + protected function getParamsHash($params) { + return md5(serialize($params)); + } + +} diff --git a/web/modules/metatag/metatag_routes/src/Helper/MetatagRoutesHelperInterface.php b/web/modules/metatag/metatag_routes/src/Helper/MetatagRoutesHelperInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a3f0ead2677d389c46c3419af4b34953c92832d9 --- /dev/null +++ b/web/modules/metatag/metatag_routes/src/Helper/MetatagRoutesHelperInterface.php @@ -0,0 +1,33 @@ +<?php + +namespace Drupal\metatag_routes\Helper; + +/** + * Interface MetatagRoutesHelperInterface. + * + * @package Drupal\metatag_routes\Helper + */ +interface MetatagRoutesHelperInterface { + + /** + * Create metatag route ID from given route name and parameters. + * + * @param string $route_name + * Route name. + * @param array $params + * Raw route parameters. + * + * @return string + * Created metatag route id. + */ + public function createMetatagRouteId($route_name, $params = NULL); + + /** + * Return current metatag route ID. + * + * @return string + * Created metatag route id. + */ + public function getCurrentMetatagRouteId(); + +} diff --git a/web/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.info.yml b/web/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.info.yml index efbfb7883322f1cdc9d066e4270f796061540277..3817e0fb6f2d0f0a7745cb663fcdd90cdce00735 100644 --- a/web/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.info.yml +++ b/web/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_verification/metatag_verification.info.yml b/web/modules/metatag/metatag_verification/metatag_verification.info.yml index e79857b61a606814c7534e7637690f7aa3945a95..7d7c853d98cfc74fa45fa8af193e7a64e3fdb0a1 100644 --- a/web/modules/metatag/metatag_verification/metatag_verification.info.yml +++ b/web/modules/metatag/metatag_verification/metatag_verification.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_views/metatag_views.info.yml b/web/modules/metatag/metatag_views/metatag_views.info.yml index 585f63363ceede4e8bf45f2f66f50f134e497a56..130a0c93abf1506b8628c9c8d5c55d00f58e56c3 100644 --- a/web/modules/metatag/metatag_views/metatag_views.info.yml +++ b/web/modules/metatag/metatag_views/metatag_views.info.yml @@ -7,7 +7,7 @@ dependencies: - metatag:metatag - drupal:views -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/metatag_views/src/Form/MetatagViewsTranslationForm.php b/web/modules/metatag/metatag_views/src/Form/MetatagViewsTranslationForm.php index f9907f930d2fcdc91951e6368374a5624d59eace..0a300dde670db57144ccb4644310454cc5f4c660 100644 --- a/web/modules/metatag/metatag_views/src/Form/MetatagViewsTranslationForm.php +++ b/web/modules/metatag/metatag_views/src/Form/MetatagViewsTranslationForm.php @@ -3,6 +3,7 @@ namespace Drupal\metatag_views\Form; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -100,15 +101,21 @@ class MetatagViewsTranslationForm extends FormBase { */ protected $baseData = []; + /** + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected ModuleHandlerInterface $moduleHandler; + /** * {@inheritdoc} */ - public function __construct(MetatagManagerInterface $metatag_manager, EntityTypeManagerInterface $entity_type_manager, MetatagToken $token, MetatagTagPluginManager $tagPluginManager, ConfigurableLanguageManagerInterface $language_manager) { + public function __construct(MetatagManagerInterface $metatag_manager, EntityTypeManagerInterface $entity_type_manager, MetatagToken $token, MetatagTagPluginManager $tagPluginManager, ConfigurableLanguageManagerInterface $language_manager, ModuleHandlerInterface $moduleHandler) { $this->metatagManager = $metatag_manager; $this->viewsManager = $entity_type_manager->getStorage('view'); $this->tokenService = $token; $this->tagPluginManager = $tagPluginManager; $this->languageManager = $language_manager; + $this->moduleHandler = $moduleHandler; } /** @@ -120,7 +127,8 @@ public static function create(ContainerInterface $container) { $container->get('entity_type.manager'), $container->get('metatag.token'), $container->get('plugin.manager.metatag.tag'), - $container->get('language_manager') + $container->get('language_manager'), + $container->get('module_handler') ); } @@ -170,7 +178,11 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Get meta tags from the view entity. $form['#tree'] = TRUE; - $form['#attached']['library'][] = 'config_translation/drupal.config_translation.admin'; + if ($this->moduleHandler->moduleExists('config_translation')) { + // Only add the config_translation/drupal.config_translation.admin if the + // module is enabled. + $form['#attached']['library'][] = 'config_translation/drupal.config_translation.admin'; + } $form['#title'] = $this->t('Edit @language translation for %view: %display metatags', [ '%view' => $this->view->label(), diff --git a/web/modules/metatag/metatag_views/src/MetatagViewsCacheWrapper.php b/web/modules/metatag/metatag_views/src/MetatagViewsCacheWrapper.php index f1c11c8f2f8b9e8169a9e0928d89e26adce51c48..66b64bd97d113223ef29938d8d1c69de673a7f1d 100644 --- a/web/modules/metatag/metatag_views/src/MetatagViewsCacheWrapper.php +++ b/web/modules/metatag/metatag_views/src/MetatagViewsCacheWrapper.php @@ -230,7 +230,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public static function trustedCallbacks() { + public static function trustedCallbacks(): array|string { return CachePluginBase::trustedCallbacks(); } @@ -307,14 +307,14 @@ public function globalTokenForm(&$form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public static function preRenderAddFieldsetMarkup(array $form) { + public static function preRenderAddFieldsetMarkup(array $form): array { return CachePluginBase::preRenderAddFieldsetMarkup($form); } /** * {@inheritdoc} */ - public static function preRenderFlattenData($form) { + public static function preRenderFlattenData($form): array { return CachePluginBase::preRenderFlattenData($form); } @@ -335,7 +335,7 @@ public function getProvider() { /** * {@inheritdoc} */ - public static function queryLanguageSubstitutions() { + public static function queryLanguageSubstitutions(): array { return CachePluginBase::queryLanguageSubstitutions(); } @@ -384,7 +384,7 @@ public function setStringTranslation(TranslationInterface $translation) { /** * {@inheritdoc} */ - public function setMessenger(MessengerInterface $messenger) { + public function setMessenger(MessengerInterface $messenger): MessengerInterface { $this->plugin->setMessenger($messenger); } diff --git a/web/modules/metatag/migrations/d7_metatag_defaults.yml b/web/modules/metatag/migrations/d7_metatag_defaults.yml new file mode 100644 index 0000000000000000000000000000000000000000..8b1d49faec576b1799df132ec1b7293c9e9adbcc --- /dev/null +++ b/web/modules/metatag/migrations/d7_metatag_defaults.yml @@ -0,0 +1,44 @@ +# A default migration mapping for Metatag-D7 default configuration. +# +# @see Drupal\metatag\Plugin\migrate\source\d7\MetatagDefault + +id: d7_metatag_defaults +label: Metatag defaults configuration +migration_tags: + - Drupal 7 + +source: + plugin: d7_metatag_defaults + source_module: metatag + ignore_map: true + constants: + status: true + # Not currently used, see below. + # langcode: en + +destination: + # @todo Can this work without specifying config_name here? + plugin: entity:metatag_defaults + +process: + # Custom process plugins are used to simplify creating appropriate values. + id: + plugin: d7_metatag_config_id + source: instance + label: + plugin: d7_metatag_config_label + source: instance + + # @todo Load the status from the config object, because on D7 the items can be + # disabled. + status: 'constants/status' + + # Let the langcode be assigned at runtime. + # langcode: 'constants/langcode' + + # Use a custom process plugin to convert the defaults from D7. + # @see \Drupal\metatag\Plugin\migrate\process\d7\MetatagDefaults + # @see \Drupal\metatag\Plugin\migrate\MigrateMetatagD7Trait + tags: + source: config + plugin: d7_metatag_defaults diff --git a/web/modules/metatag/migrations/d7_metatag_settings.yml b/web/modules/metatag/migrations/d7_metatag_settings.yml new file mode 100644 index 0000000000000000000000000000000000000000..5d9c6361fa8c350e63251f38fa11baf8bc3fa6d6 --- /dev/null +++ b/web/modules/metatag/migrations/d7_metatag_settings.yml @@ -0,0 +1,49 @@ +id: d7_metatag_settings +label: Metatag settings +migration_tags: + - Drupal 7 + - Configuration + +source: + plugin: variable + # Each of the D7 variables need to be listed here. + variables: + - metatag_separator + - metatag_use_maxlength + - metatag_maxlength_title + - metatag_maxlength_description + - metatag_maxlength_abstract + - metatag_maxlength_keywords + source_module: metatag + +process: + # Each of the D7 variables needs to be mapped here to the correct config name + # for D8+. + # CONFIG_NAME: metatag_VARIABLE_NAME + + # Custom separator, defaults to ",". + separator: metatag_separator + + # Whether to use the Maxlength module if it is installed to indicate in the + # UI that the field is approaching its maximum length. + use_maxlength: metatag_use_maxlength + + # An array in the format tag_name:length to control the maximum length that + # each trimmable meta tag can be. In D7 these are stored as a variable named + # metatag_maxlength_TAGNAME, whereas in D8+ they are stored as a single array. + tag_trim_maxlength/title: metatag_maxlength_title + tag_trim_maxlength/description: metatag_maxlength_description + tag_trim_maxlength/abstract: metatag_maxlength_abstract + tag_trim_maxlength/keywords: metatag_maxlength_keywords + + # No equivalent on Drupal 7, so skipping this. + # entity_type_groups: + # tag_scroll_max_height: + # tag_trim_method: + + # Lots of D7 variables did not have an equivalent setting in D8+ or were + # replaced by something else, e.g. the Metatag field. + +destination: + plugin: config + config_name: metatag.settings diff --git a/web/modules/metatag/migrations/state/metatag.migrate_drupal.yml b/web/modules/metatag/migrations/state/metatag.migrate_drupal.yml index 6c1f51d09fbc767d50828d08caccfe5f108102a9..01b4cd3d434502d3800cb68ae0d2ca7d22dc38b6 100644 --- a/web/modules/metatag/migrations/state/metatag.migrate_drupal.yml +++ b/web/modules/metatag/migrations/state/metatag.migrate_drupal.yml @@ -7,6 +7,9 @@ # These changes are finished, excluding bug reports. finished: 7: + # The main configuration and all entity values are fully migrated. + metatag: metatag + # All of the meta tags from these submodules have breen ported and should # work correctly. metatag_app_links: metatag @@ -41,10 +44,6 @@ not_finished: page_title: metatag 7: - # Metatag. - # @todo Configuration: https://www.drupal.org/project/metatag/issues/2563651 - metatag: metatag - # These will need custom work. # @todo https://www.drupal.org/project/metatag/issues/2563653 metatag_context: metatag_context diff --git a/web/modules/metatag/src/Entity/MetatagDefaults.php b/web/modules/metatag/src/Entity/MetatagDefaults.php index 755b1e64730a6fc2cd7737b14a1cf868803fcfcf..4c79a6d3b415784e4ca9b1b1e27744824a95e671 100644 --- a/web/modules/metatag/src/Entity/MetatagDefaults.php +++ b/web/modules/metatag/src/Entity/MetatagDefaults.php @@ -88,7 +88,7 @@ public function hasTag($tag_id): bool { * @return array|null * Array containing the tag values or NULL if not found. */ - public function getTag($tag_id): array|NULL { + public function getTag($tag_id): array|string|NULL { if (!$this->hasTag($tag_id)) { return NULL; } diff --git a/web/modules/metatag/src/Form/MetatagDefaultsForm.php b/web/modules/metatag/src/Form/MetatagDefaultsForm.php index 1806d80cdfe9939a7de27a9e580b954e2ad20cb0..7e93641a432ba6b90f3d4a4c29d28aa4aad261f8 100644 --- a/web/modules/metatag/src/Form/MetatagDefaultsForm.php +++ b/web/modules/metatag/src/Form/MetatagDefaultsForm.php @@ -106,6 +106,7 @@ public static function create(ContainerInterface $container) { */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $metatag_defaults */ $metatag_defaults = $this->entity; $form['#ajax_wrapper_id'] = 'metatag-defaults-form-ajax-wrapper'; @@ -353,8 +354,6 @@ public static function getSupportedEntityTypes(): array { $unsupported_types = [ // Custom blocks. 'block_content', - // Comments. - 'comment', // Contact messages are the messages submitted on individual contact forms // so obviously shouldn't get meta tags. 'contact_message', @@ -374,7 +373,6 @@ public static function getSupportedEntityTypes(): array { 'commerce_shipment', 'commerce_shipping_method', 'commerce_stock_location', - 'commerce_store', // LinkChecker. 'linkcheckerlink', // Redirect. diff --git a/web/modules/metatag/src/Form/MetatagSettingsForm.php b/web/modules/metatag/src/Form/MetatagSettingsForm.php index 07e964df8c6778fc3365492ce45a24ab6d94b026..7495240b0dd44c8992d070fe3fb6fce44b21a907 100644 --- a/web/modules/metatag/src/Form/MetatagSettingsForm.php +++ b/web/modules/metatag/src/Form/MetatagSettingsForm.php @@ -181,7 +181,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { } $form['tag_trim']['tag_trim_method'] = [ - '#title' => $this->t('Meta Tags: Trimming Options'), + '#title' => $this->t('Trimming options'), '#type' => 'select', '#required' => TRUE, '#default_value' => $trimMethod ?? 'beforeValue', @@ -192,6 +192,13 @@ public function buildForm(array $form, FormStateInterface $form_state) { ], ]; + $form['tag_trim']['tag_trim_end'] = [ + '#title' => $this->t('Characters to trim'), + '#type' => 'textfield', + '#default_value' => $this->config('metatag.settings')->get('tag_trim_end'), + '#description' => $this->t('A list of characters to trim at the end of all metatags. Provide a single string without any separators, e.g. "|,." (instead of "| , ."). Note that spaces, tabs, new lines, carriage returns and vertical tabs (" \n\r\t\v") will be trimmed automatically and do not need to be listed in this field. The trimming is applied at the very end after the tag is trimmed for length, and after the trimming option was executed.'), + ]; + $scrollheight = $this->config('metatag.settings')->get('tag_scroll_max_height'); $form['firehose_widget'] = [ @@ -237,6 +244,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $settings->set('tag_trim_method', $trimmingMethod); $trimmingValues = $form_state->getValue(['tag_trim', 'maxlength']); $settings->set('tag_trim_maxlength', $trimmingValues); + $trimEndCharacters = $form_state->getValue(['tag_trim', 'tag_trim_end']); + $settings->set('tag_trim_end', $trimEndCharacters); // Widget settings. $scrollheightvalue = $form_state->getValue([ diff --git a/web/modules/metatag/src/MetatagManager.php b/web/modules/metatag/src/MetatagManager.php index ddb679666cb400d7cbac82cb0550de5a53d1a93b..2d84bcb4b133e8fe0af6f4f417f38ad4caa4024e 100644 --- a/web/modules/metatag/src/MetatagManager.php +++ b/web/modules/metatag/src/MetatagManager.php @@ -586,9 +586,12 @@ public function generateRawElements(array $tags, $entity = NULL, BubbleableMetad return []; } + // Use the entity's language code, if one is defined. + $langcode = NULL; // Prepare any tokens that might exist. $token_replacements = []; if ($entity) { + $langcode = $entity->language()->getId(); // @todo This needs a better way of discovering the context. if ($entity instanceof ViewEntityInterface) { // Views tokens require the ViewExecutable, not the config entity. @@ -600,12 +603,6 @@ public function generateRawElements(array $tags, $entity = NULL, BubbleableMetad } } - // Use the entity's language code, if one is defined. - $langcode = NULL; - if ($entity) { - $langcode = $entity->language()->getId(); - } - $definitions = $this->sortedTags(); // Sort the meta tags so they are rendered in the correct order. @@ -673,9 +670,11 @@ public function generateTokenValues(array $tags, $entity = NULL): array { } $entity_identifier = '_none'; + // Use the entity's language code, if one is defined. + $langcode = NULL; if ($entity) { - $entity_identifier = $entity->getEntityTypeId() . ':' . ($entity->uuid() ?? $entity->id()) . ':' . $entity->language() - ->getId(); + $langcode = $entity->language()->getId(); + $entity_identifier = $entity->getEntityTypeId() . ':' . ($entity->uuid() ?? $entity->id()) . ':' . $langcode; } if (!isset($this->processedTokenCache[$entity_identifier])) { @@ -702,7 +701,7 @@ public function generateTokenValues(array $tags, $entity = NULL): array { $token_replacements = [$entity->getEntityTypeId() => $entity]; } } - $processed_value = $this->processTagValue($tag, $value, $token_replacements, TRUE); + $processed_value = $this->processTagValue($tag, $value, $token_replacements, TRUE, $langcode); $this->processedTokenCache[$entity_identifier][$tag_name] = $tag->multiple() ? explode($tag->getSeparator(), $processed_value) : $processed_value; } } diff --git a/web/modules/metatag/src/MetatagSeparator.php b/web/modules/metatag/src/MetatagSeparator.php index f8f54dbe273f0692db86b686537fbf9319456cac..94d3f6c7e3c142419327e56a2b1fbf72047a9e3a 100644 --- a/web/modules/metatag/src/MetatagSeparator.php +++ b/web/modules/metatag/src/MetatagSeparator.php @@ -2,6 +2,8 @@ namespace Drupal\metatag; +use Drupal\Core\Config\ConfigFactoryInterface; + /** * Separator logic used elsewhere. */ @@ -24,10 +26,10 @@ trait MetatagSeparator { * The correct separator. */ public function getSeparator(): string { - $separator = ''; - // Load the separator saved in configuration. - $config = $this->configFactory->get('metatag.settings'); + /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */ + $config_factory = \Drupal::configFactory(); + $config = $config_factory->get('metatag.settings'); // @todo This extra check shouldn't be needed. if ($config) { diff --git a/web/modules/metatag/src/MetatagTrimmer.php b/web/modules/metatag/src/MetatagTrimmer.php index 64184a9e9252e5c149680256793ce2a0bb939d3b..c8f59e2e005080aaf22aeae60bf08f48f291ee20 100644 --- a/web/modules/metatag/src/MetatagTrimmer.php +++ b/web/modules/metatag/src/MetatagTrimmer.php @@ -67,6 +67,28 @@ public function trimBeforeValue($string, $maxlength): string { return trim($returnedString); } + /** + * Trims characters at the end of a string. + * + * @param string $string + * The string to apply the trimming to. + * @param string $trimEndChars + * The characters to trim at the end of the string. + * + * @return string + * The string with the requested end characters removed. + */ + public function trimEndChars(string $string, string $trimEndChars = ''): string { + if (empty($trimEndChars)) { + return rtrim($string); + } + else { + // Note the use of str_replace() so "\" won't be recognized as a parameter + // for an escape sequence. + return rtrim($string, " \n\r\t\v\x00" . str_replace("\\", "\\\\", $trimEndChars)); + } + } + /** * Trims a value based on the given length and the given method. * @@ -77,25 +99,37 @@ public function trimBeforeValue($string, $maxlength): string { * @param string $method * The trim method to use for the trimming. * Allowed values: 'afterValue', 'onValue' and 'beforeValue'. + * @param string $trimEndChars + * The characters to trim at the end of the string. */ - public function trimByMethod($value, $maxlength, $method): string { + public function trimByMethod($value, $maxlength, $method, $trimEndChars = ''): string { + if ($trimEndChars === NULL) { + $trimEndChars = ''; + } if (empty($value) || empty($maxlength)) { return $value; } + $trimmedValue = $value; switch ($method) { case 'afterValue': - return $this->trimAfterValue($value, $maxlength); + $trimmedValue = $this->trimAfterValue($value, $maxlength); + break; case 'onValue': - return trim(mb_substr($value, 0, $maxlength)); + $trimmedValue = trim(mb_substr($value, 0, $maxlength)); + break; case 'beforeValue': - return $this->trimBeforeValue($value, $maxlength); + $trimmedValue = $this->trimBeforeValue($value, $maxlength); + break; default: throw new Exception('Unknown trimming method: ' . $method); } + + // Do additional cleanup trimming: + return $this->trimEndChars($trimmedValue, $trimEndChars); } } diff --git a/web/modules/metatag/src/Plugin/Field/FieldType/MetatagFieldItem.php b/web/modules/metatag/src/Plugin/Field/FieldType/MetatagFieldItem.php index b3dbdd990dff9fbcb804713112ef27d14fdd9251..4e840c60a8f187a1f4aef8aaa01bc200c93b8498 100644 --- a/web/modules/metatag/src/Plugin/Field/FieldType/MetatagFieldItem.php +++ b/web/modules/metatag/src/Plugin/Field/FieldType/MetatagFieldItem.php @@ -66,7 +66,7 @@ public function preSave() { // Get the value about to be saved. // @todo Does this need to be rewritten to use $this->getValue()? - $current_value = $this->value; + $current_value = $this->getValue()['value'] ?? ''; // Only unserialize if still serialized string. if (is_string($current_value)) { @@ -89,7 +89,7 @@ public function preSave() { ksort($tags_to_save); // Update the value to only save overridden tags. - $this->value = Json::encode($tags_to_save); + $this->setValue(['value' => Json::encode($tags_to_save)]); } } diff --git a/web/modules/metatag/src/Plugin/Field/FieldWidget/MetatagFirehose.php b/web/modules/metatag/src/Plugin/Field/FieldWidget/MetatagFirehose.php index 4a45c3edcd7d8f226ad1074906f3968dad94925e..2f69d93c6041d2900aca904690a9075ef0f9ab2c 100644 --- a/web/modules/metatag/src/Plugin/Field/FieldWidget/MetatagFirehose.php +++ b/web/modules/metatag/src/Plugin/Field/FieldWidget/MetatagFirehose.php @@ -167,8 +167,10 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen $entity_type_groups = $settings->get('entity_type_groups'); // Find the current entity type and bundle. - $entity_type = $item->getEntity()->getentityTypeId(); - $entity_bundle = $item->getEntity()->bundle(); + /** @var \Drupal\Core\Entity\FieldableEntityInterface $get_entity */ + $get_entity = $item->getEntity(); + $entity_type = $get_entity->getentityTypeId(); + $entity_bundle = $get_entity->bundle(); // See if there are requested groups for this entity type and bundle. $groups = []; diff --git a/web/modules/metatag/src/Plugin/metatag/Tag/MetaNameBase.php b/web/modules/metatag/src/Plugin/metatag/Tag/MetaNameBase.php index f0c94c119e9837cd96ea4c74eb17f0cbdb083280..7e9a3e1d182bc629be8b8f4536cbbe9a56450702 100644 --- a/web/modules/metatag/src/Plugin/metatag/Tag/MetaNameBase.php +++ b/web/modules/metatag/src/Plugin/metatag/Tag/MetaNameBase.php @@ -644,6 +644,7 @@ protected function trimValue($value): string { $settings = \Drupal::config('metatag.settings'); $trimMethod = $settings->get('tag_trim_method'); $trimMaxlengthArray = $settings->get('tag_trim_maxlength'); + $trimEndChars = $settings->get('tag_trim_end'); if (empty($trimMethod) || empty($trimMaxlengthArray)) { return $value; } @@ -654,7 +655,8 @@ protected function trimValue($value): string { } } $trimmerService = \Drupal::service('metatag.trimmer'); - $value = $trimmerService->trimByMethod($value, $currentMaxValue, $trimMethod); + // Do the trimming: + $value = $trimmerService->trimByMethod($value, $currentMaxValue, $trimMethod, $trimEndChars); } return $value; } diff --git a/web/modules/metatag/src/Plugin/migrate/MigrateMetatagD7Trait.php b/web/modules/metatag/src/Plugin/migrate/MigrateMetatagD7Trait.php new file mode 100644 index 0000000000000000000000000000000000000000..3ef420223d545448cf243fbdf3bc81bdab15cd38 --- /dev/null +++ b/web/modules/metatag/src/Plugin/migrate/MigrateMetatagD7Trait.php @@ -0,0 +1,468 @@ +<?php + +namespace Drupal\metatag\Plugin\migrate; + +use Drupal\Component\Serialization\Json; +use Drupal\Component\Utility\Unicode; +use Drupal\migrate\MigrateException; + +/** + * Provides some helper logic for migrating data from Metatag-D7. + */ +trait MigrateMetatagD7Trait { + + /** + * Convert meta tags from Metatag-D7 to the new naming convention. + * + * Unsupported meta tags will not be converted, only known meta tags. + * + * @param array $old_tags + * The old data from Metatag-D7, converted into an array. + * + * @return array + * The data converted to the plugin names required by this version of + * Metatag. + */ + public function transformTags(array $old_tags): array { + $tags_map = $this->tagsMap(); + + $metatags = []; + + foreach ($old_tags as $d7_metatag_name => $metatag_value) { + // If there's no data for this tag, ignore everything. + if (empty($metatag_value)) { + continue; + } + + // @todo Skip these values for now, maybe some version supported these? + if (!is_array($metatag_value) || empty($metatag_value['value'])) { + continue; + } + + // Convert the D7 meta tag name to the D8 equivalent. If this meta tag + // is not recognized, skip it. + if (empty($tags_map[$d7_metatag_name])) { + continue; + } + $d8_metatag_name = $tags_map[$d7_metatag_name]; + + // Convert the nested arrays to a flat structure. + if (is_array($metatag_value['value'])) { + // Remove empty values. + $metatag_value['value'] = array_filter($metatag_value['value']); + // Convert the array into a comma-separated list. + $metatag_value = implode(', ', $metatag_value['value']); + } + else { + $metatag_value = $metatag_value['value']; + } + if (!Unicode::validateUtf8($metatag_value)) { + $metatag_value = Unicode::convertToUtf8($metatag_value, 'Windows-1252'); + } + + // Keep the entire data structure. + $metatags[$d8_metatag_name] = $metatag_value; + } + + // Sort the meta tags alphabetically to make testing easier. + ksort($metatags); + + return $metatags; + } + + /** + * Decode the different versions of encoded values supported by Metatag. + * + * Metatag v1 stored data in serialized arrays. Metatag v2 stores data in + * JSON-encoded strings. + * + * @param string $string + * The string to decode. + * + * @return array + * A Metatag values array. + */ + private function decodeValue($string): array { + $data = []; + + // Serialized arrays from Metatag v1. + if (substr($string, 0, 2) === 'a:') { + $data = @unserialize($string, ['allowed_classes' => FALSE]); + } + + // Encoded JSON from Metatag v2. + elseif (substr($string, 0, 2) === '{"') { + // @todo Handle non-array responses. + $data = json_decode($string, TRUE); + } + + // This is expected to be an array, if it isn't then something went wrong. + if (!is_array($data)) { + throw new MigrateException('Data from Metatag-D7 was not a serialized array or a JSON-encoded string.'); + } + + return $data; + } + + /** + * Match Metatag-D7 meta tags with their D8 counterparts. + * + * @return array + * An array of D7 tags to their D8 counterparts. + */ + protected function tagsMap(): array { + $map = [ + // From the main Metatag module. + 'abstract' => 'abstract', + 'cache-control' => 'cache_control', + 'canonical' => 'canonical_url', + 'description' => 'description', + 'expires' => 'expires', + 'generator' => 'generator', + 'geo.placename' => 'geo_placename', + 'geo.position' => 'geo_position', + 'geo.region' => 'geo_region', + 'icbm' => 'icbm', + 'image_src' => 'image_src', + 'keywords' => 'keywords', + 'next' => 'next', + 'original-source' => 'original_source', + 'pragma' => 'pragma', + 'prev' => 'prev', + 'rating' => 'rating', + 'referrer' => 'referrer', + 'refresh' => 'refresh', + 'revisit-after' => 'revisit_after', + 'rights' => 'rights', + 'robots' => 'robots', + 'set_cookie' => 'set_cookie', + 'shortlink' => 'shortlink', + 'title' => 'title', + + // From metatag_app_links.metatag.inc: + 'al:android:app_name' => 'al_android_app_name', + 'al:android:class' => 'al_android_class', + 'al:android:package' => 'al_android_package', + 'al:android:url' => 'al_android_url', + 'al:ios:app_name' => 'al_ios_app_name', + 'al:ios:app_store_id' => 'al_ios_app_store_id', + 'al:ios:url' => 'al_ios_url', + 'al:ipad:app_name' => 'al_ipad_app_name', + 'al:ipad:app_store_id' => 'al_ipad_app_store_id', + 'al:ipad:url' => 'al_ipad_url', + 'al:iphone:app_name' => 'al_iphone_app_name', + 'al:iphone:app_store_id' => 'al_iphone_app_store_id', + 'al:iphone:url' => 'al_iphone_url', + 'al:web:should_fallback' => 'al_web_should_fallback', + 'al:web:url' => 'al_web_url', + 'al:windows:app_id' => 'al_windows_app_id', + 'al:windows:app_name' => 'al_windows_app_name', + 'al:windows:url' => 'al_windows_url', + 'al:windows_phone:app_id' => 'al_windows_phone_app_id', + 'al:windows_phone:app_name' => 'al_windows_phone_app_name', + 'al:windows_phone:url' => 'al_windows_phone_url', + 'al:windows_universal:app_id' => 'al_windows_universal_app_id', + 'al:windows_universal:app_name' => 'al_windows_universal_app_name', + 'al:windows_universal:url' => 'al_windows_universal_url', + + // From metatag_dc.metatag.inc: + 'dcterms.contributor' => 'dcterms_contributor', + 'dcterms.coverage' => 'dcterms_coverage', + 'dcterms.creator' => 'dcterms_creator', + 'dcterms.date' => 'dcterms_date', + 'dcterms.description' => 'dcterms_description', + 'dcterms.format' => 'dcterms_format', + 'dcterms.identifier' => 'dcterms_identifier', + 'dcterms.language' => 'dcterms_language', + 'dcterms.publisher' => 'dcterms_publisher', + 'dcterms.relation' => 'dcterms_relation', + 'dcterms.rights' => 'dcterms_rights', + 'dcterms.source' => 'dcterms_source', + 'dcterms.subject' => 'dcterms_subject', + 'dcterms.title' => 'dcterms_title', + 'dcterms.type' => 'dcterms_type', + + // From metatag_dc_advanced.metatag.inc: + 'dcterms.abstract' => 'dcterms_abstract', + 'dcterms.accessRights' => 'dcterms_access_rights', + 'dcterms.accrualMethod' => 'dcterms_accrual_method', + 'dcterms.accrualPeriodicity' => 'dcterms_accrual_periodicity', + 'dcterms.accrualPolicy' => 'dcterms_accrual_policy', + 'dcterms.alternative' => 'dcterms_alternative', + 'dcterms.audience' => 'dcterms_audience', + 'dcterms.available' => 'dcterms_available', + 'dcterms.bibliographicCitation' => 'dcterms_bibliographic_citation', + 'dcterms.conformsTo' => 'dcterms_conforms_to', + 'dcterms.created' => 'dcterms_created', + 'dcterms.dateAccepted' => 'dcterms_date_accepted', + 'dcterms.dateCopyrighted' => 'dcterms_date_copyrighted', + 'dcterms.dateSubmitted' => 'dcterms_date_submitted', + 'dcterms.educationLevel' => 'dcterms_education_level', + 'dcterms.extent' => 'dcterms_extent', + 'dcterms.hasFormat' => 'dcterms_has_format', + 'dcterms.hasPart' => 'dcterms_has_part', + 'dcterms.hasVersion' => 'dcterms_has_version', + 'dcterms.instructionalMethod' => 'dcterms_instructional_method', + 'dcterms.isFormatOf' => 'dcterms_is_format_of', + 'dcterms.isPartOf' => 'dcterms_is_part_of', + 'dcterms.isReferencedBy' => 'dcterms_is_referenced_by', + 'dcterms.isReplacedBy' => 'dcterms_is_replaced_by', + 'dcterms.isRequiredBy' => 'dcterms_is_required_by', + 'dcterms.issued' => 'dcterms_issued', + 'dcterms.isVersionOf' => 'dcterms_is_version_of', + 'dcterms.license' => 'dcterms_license', + 'dcterms.mediator' => 'dcterms_mediator', + 'dcterms.medium' => 'dcterms_medium', + 'dcterms.modified' => 'dcterms_modified', + 'dcterms.provenance' => 'dcterms_provenance', + 'dcterms.references' => 'dcterms_references', + 'dcterms.replaces' => 'dcterms_replaces', + 'dcterms.requires' => 'dcterms_requires', + 'dcterms.rightsHolder' => 'dcterms_rights_holder', + 'dcterms.spatial' => 'dcterms_spatial', + 'dcterms.tableOfContents' => 'dcterms_table_of_contents', + 'dcterms.temporal' => 'dcterms_temporal', + 'dcterms.valid' => 'dcterms_valid', + + // From metatag_facebook.metatag.inc: + 'fb:admins' => 'fb_admins', + 'fb:app_id' => 'fb_app_id', + 'fb:pages' => 'fb_pages', + + // From metatag_favicons.metatag.inc: + 'apple-touch-icon' => 'apple_touch_icon', + 'apple-touch-icon-precomposed' => 'apple_touch_icon_precomposed', + 'apple-touch-icon-precomposed_114x114' => 'apple_touch_icon_precomposed_114x114', + 'apple-touch-icon-precomposed_120x120' => 'apple_touch_icon_precomposed_120x120', + 'apple-touch-icon-precomposed_144x144' => 'apple_touch_icon_precomposed_144x144', + 'apple-touch-icon-precomposed_152x152' => 'apple_touch_icon_precomposed_152x152', + 'apple-touch-icon-precomposed_180x180' => 'apple_touch_icon_precomposed_180x180', + 'apple-touch-icon-precomposed_72x72' => 'apple_touch_icon_precomposed_72x72', + 'apple-touch-icon-precomposed_76x76' => 'apple_touch_icon_precomposed_76x76', + 'apple-touch-icon_114x114' => 'apple_touch_icon_114x114', + 'apple-touch-icon_120x120' => 'apple_touch_icon_120x120', + 'apple-touch-icon_144x144' => 'apple_touch_icon_144x144', + 'apple-touch-icon_152x152' => 'apple_touch_icon_152x152', + 'apple-touch-icon_180x180' => 'apple_touch_icon_180x180', + 'apple-touch-icon_72x72' => 'apple_touch_icon_72x72', + 'apple-touch-icon_76x76' => 'apple_touch_icon_76x76', + 'icon_16x16' => 'icon_16x16', + 'icon_192x192' => 'icon_192x192', + 'icon_32x32' => 'icon_32x32', + 'icon_96x96' => 'icon_96x96', + 'mask-icon' => 'mask-icon', + 'shortcut icon' => 'shortcut_icon', + + // From metatag_google_cse.metatag.inc: + 'audience' => 'audience', + 'department' => 'department', + 'doc_status' => 'doc_status', + 'google_rating' => 'google_rating', + 'thumbnail' => 'thumbnail', + + // From metatag_google_plus.metatag.inc; not doing these, Google+ closed. + 'itemtype' => '', + 'itemprop:name' => '', + 'itemprop:description' => '', + 'itemprop:image' => '', + 'author' => '', + 'publisher' => '', + + // From metatag_hreflang.metatag.inc: + 'hreflang_xdefault' => 'hreflang_xdefault', + // @todo https://www.drupal.org/project/metatag/issues/3077778 + // 'hreflang_' . $langcode => 'hreflang_per_language', + // From metatag_mobile.metatag.inc: + 'alternate_handheld' => 'alternate_handheld', + 'android-app-link-alternative' => 'android_app_link_alternative', + 'android-manifest' => 'android_manifest', + 'apple-itunes-app' => 'apple_itunes_app', + 'apple-mobile-web-app-capable' => 'apple_mobile_web_app_capable', + 'apple-mobile-web-app-status-bar-style' => 'apple_mobile_web_app_status_bar_style', + 'apple-mobile-web-app-title' => 'apple_mobile_web_app_title', + 'application-name' => 'application_name', + 'cleartype' => 'cleartype', + 'format-detection' => 'format_detection', + 'HandheldFriendly' => 'handheldfriendly', + 'ios-app-link-alternative' => 'ios_app_link_alternative', + 'MobileOptimized' => 'mobileoptimized', + 'msapplication-allowDomainApiCalls' => 'msapplication_allowDomainApiCalls', + 'msapplication-allowDomainMetaTags' => 'msapplication_allowDomainMetaTags', + 'msapplication-badge' => 'msapplication_badge', + 'msapplication-config' => 'msapplication_config', + 'msapplication-navbutton-color' => 'msapplication_navbutton_color', + 'msapplication-notification' => 'msapplication_notification', + 'msapplication-square150x150logo' => 'msapplication_square150x150logo', + 'msapplication-square310x310logo' => 'msapplication_square310x310logo', + 'msapplication-square70x70logo' => 'msapplication_square70x70logo', + 'msapplication-starturl' => 'msapplication_starturl', + 'msapplication-task' => 'msapplication_task', + 'msapplication-task-separator' => 'msapplication_task_separator', + 'msapplication-tilecolor' => 'msapplication_tilecolor', + 'msapplication-tileimage' => 'msapplication_tileimage', + 'msapplication-tooltip' => 'msapplication_tooltip', + 'msapplication-wide310x150logo' => 'msapplication_wide310x150logo', + 'msapplication-window' => 'msapplication_window', + 'theme-color' => 'theme_color', + 'viewport' => 'viewport', + 'x-ua-compatible' => 'x_ua_compatible', + + // From metatag_opengraph.metatag.inc: + // https://www.drupal.org/project/metatag/issues/3077782 + 'article:author' => 'article_author', + 'article:expiration_time' => 'article_expiration_time', + 'article:modified_time' => 'article_modified_time', + 'article:published_time' => 'article_published_time', + 'article:publisher' => 'article_publisher', + 'article:section' => 'article_section', + 'article:tag' => 'article_tag', + 'book:author' => 'book_author', + 'book:isbn' => 'book_isbn', + 'book:release_date' => 'book_release_date', + 'book:tag' => 'book_tag', + 'og:audio' => 'og_audio', + 'og:audio:secure_url' => 'og_audio_secure_url', + 'og:audio:type' => 'og_audio_type', + 'og:country_name' => 'og_country_name', + 'og:description' => 'og_description', + 'og:determiner' => 'og_determiner', + 'og:email' => 'og_email', + 'og:fax_number' => 'og_fax_number', + 'og:image' => 'og_image', + // @todo '' => 'og_image_alt', + 'og:image:height' => 'og_image_height', + 'og:image:secure_url' => 'og_image_secure_url', + 'og:image:type' => 'og_image_type', + 'og:image:url' => 'og_image_url', + 'og:image:width' => 'og_image_width', + 'og:latitude' => 'og_latitude', + 'og:locale' => 'og_locale', + 'og:locale:alternate' => 'og_locale_alternative', + 'og:locality' => 'og_locality', + 'og:longitude' => 'og_longitude', + 'og:phone_number' => 'og_phone_number', + 'og:postal_code' => 'og_postal_code', + 'og:region' => 'og_region', + 'og:see_also' => 'og_see_also', + 'og:site_name' => 'og_site_name', + 'og:street_address' => 'og_street_address', + 'og:title' => 'og_title', + 'og:type' => 'og_type', + 'og:updated_time' => 'og_updated_time', + 'og:url' => 'og_url', + // @todo '' => 'og_video', + // https://www.drupal.org/project/metatag/issues/3089445 + // @todo '' => 'og_video_duration', + 'og:video:height' => 'og_video_height', + 'og:video:secure_url' => 'og_video_secure_url', + 'og:video:type' => 'og_video_type', + 'og:video:url' => 'og_video_url', + 'og:video:width' => 'og_video_width', + 'profile:first_name' => 'profile_first_name', + 'profile:gender' => 'profile_gender', + 'profile:last_name' => 'profile_last_name', + 'profile:username' => 'profile_username', + 'video:actor' => 'video_actor', + 'video:actor:role' => 'video_actor_role', + 'video:director' => 'video_director', + // @todo 'video:duration' => '', + 'video:release_date' => 'video_release_date', + 'video:series' => 'video_series', + 'video:tag' => 'video_tag', + 'video:writer' => 'video_writer', + + // From metatag_opengraph_products.metatag.inc: + 'product:price:amount' => 'product_price_amount', + 'product:price:currency' => 'product_price_currency', + // Not supported in D7. + // @todo '' => 'product_retailer_item_id, + // Not yet supported in D9. + // @todo 'product:availability' => '', + // @todo 'product:brand' => '', + // @todo 'product:upc' => '', + // @todo 'product:ean' => '', + // @todo 'product:isbn' => '', + // @todo 'product:plural_title' => '', + // @todo 'product:retailer' => '', + // @todo 'product:retailer_title' => '', + // @todo 'product:retailer_part_no' => '', + // @todo 'product:mfr_part_no' => '', + // @todo 'product:size' => '', + // @todo 'product:product_link' => '', + // @todo 'product:category' => '', + // @todo 'product:color' => '', + // @todo 'product:material' => '', + // @todo 'product:pattern' => '', + // @todo 'product:shipping_cost:amount' => '', + // @todo 'product:shipping_cost:currency' => '', + // @todo 'product:weight:value' => '', + // @todo 'product:weight:units' => '', + // @todo 'product:shipping_weight:value' => '', + // @todo 'product:shipping_weight:units' => '', + // @todo 'product:expiration_time' => '', + // @todo 'product:condition' => '', + // Pinterest. + // @todo '' => 'pinterest_id', + // @todo '' => 'pinterest_description', + // @todo '' => 'pinterest_nohover', + // @todo '' => 'pinterest_url', + // @todo '' => 'pinterest_media', + // @todo '' => 'pinterest_nopin', + // @todo '' => 'pinterest_nosearch', + // From metatag_twitter_cards.metatag.inc: + 'twitter:app:country' => 'twitter_cards_app_store_country', + 'twitter:app:id:googleplay' => 'twitter_cards_app_id_googleplay', + 'twitter:app:id:ipad' => 'twitter_cards_app_id_ipad', + 'twitter:app:id:iphone' => 'twitter_cards_app_id_iphone', + 'twitter:app:name:googleplay' => 'twitter_cards_app_name_googleplay', + 'twitter:app:name:ipad' => 'twitter_cards_app_name_ipad', + 'twitter:app:name:iphone' => 'twitter_cards_app_name_iphone', + 'twitter:app:url:googleplay' => 'twitter_cards_app_url_googleplay', + 'twitter:app:url:ipad' => 'twitter_cards_app_url_ipad', + 'twitter:app:url:iphone' => 'twitter_cards_app_url_iphone', + 'twitter:card' => 'twitter_cards_type', + 'twitter:creator' => 'twitter_cards_creator', + 'twitter:creator:id' => 'twitter_cards_creator_id', + 'twitter:data1' => 'twitter_cards_data1', + 'twitter:data2' => 'twitter_cards_data2', + 'twitter:description' => 'twitter_cards_description', + 'twitter:dnt' => 'twitter_cards_donottrack', + 'twitter:image' => 'twitter_cards_image', + 'twitter:image0' => 'twitter_cards_gallery_image0', + 'twitter:image1' => 'twitter_cards_gallery_image1', + 'twitter:image2' => 'twitter_cards_gallery_image2', + 'twitter:image3' => 'twitter_cards_gallery_image3', + 'twitter:image:alt' => 'twitter_cards_image_alt', + 'twitter:image:height' => 'twitter_cards_image_height', + 'twitter:image:width' => 'twitter_cards_image_width', + 'twitter:label1' => 'twitter_cards_label1', + 'twitter:label2' => 'twitter_cards_label2', + 'twitter:player' => 'twitter_cards_player', + 'twitter:player:height' => 'twitter_cards_player_height', + 'twitter:player:stream' => 'twitter_cards_player_stream', + 'twitter:player:stream:content_type' => 'twitter_cards_player_stream_content_type', + 'twitter:player:width' => 'twitter_cards_player_width', + 'twitter:site' => 'twitter_cards_site', + 'twitter:site:id' => 'twitter_cards_site_id', + 'twitter:title' => 'twitter_cards_title', + 'twitter:url' => 'twitter_cards_page_url', + + // From metatag_verification.metatag.inc: + 'baidu-site-verification' => 'baidu', + 'facebook-domain-verification' => 'facebook_domain_verification', + 'google-site-verification' => 'google_site_verification', + 'msvalidate.01' => 'bing', + 'norton-safeweb-site-verification' => 'norton_safe_web', + 'p:domain_verify' => 'pinterest', + // @todo '' => 'pocket', + 'yandex-verification' => 'yandex', + ]; + + // Trigger hook_metatag_migrate_metatagd7_tags_map_alter(). + // Allow modules to override tags or the entity used for token replacements. + \Drupal::service('module_handler')->alter('metatag_migrate_metatagd7_tags_map', $map); + + return $map; + } + +} diff --git a/web/modules/metatag/src/Plugin/migrate/process/d6/NodewordsEntities.php b/web/modules/metatag/src/Plugin/migrate/process/d6/NodewordsEntities.php index 167691bbfef2b90e7c28cdc7b2d3a2364e5fa269..0674b04645e9bc0029c561b68134ddc3ccabc58c 100644 --- a/web/modules/metatag/src/Plugin/migrate/process/d6/NodewordsEntities.php +++ b/web/modules/metatag/src/Plugin/migrate/process/d6/NodewordsEntities.php @@ -39,7 +39,10 @@ public function transform($value, MigrateExecutableInterface $migrate_executable // Re-shape D6 entries into for D8 entries. $old_tags = array_map(static function ($value) { - return unserialize($value, ['allowed_classes' => FALSE]); + // Shouldn't need to hide the errors, but this started to fail despite + // no relevant code changes. + // @todo Is there a better way of handling this? + return @unserialize($value, ['allowed_classes' => FALSE]); }, $value); foreach ($old_tags as $d6_metatag_name => $metatag_value) { diff --git a/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagConfigId.php b/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagConfigId.php new file mode 100644 index 0000000000000000000000000000000000000000..583182bd806c443e8ff4132de2eb80d76d2299bc --- /dev/null +++ b/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagConfigId.php @@ -0,0 +1,37 @@ +<?php + +namespace Drupal\metatag\Plugin\migrate\process\d7; + +use Drupal\Component\Serialization\Json; +use Drupal\metatag\Plugin\migrate\MigrateMetatagD7Trait; +use Drupal\migrate\MigrateException; +use Drupal\migrate\MigrateExecutableInterface; +use Drupal\migrate\ProcessPluginBase; +use Drupal\migrate\Row; + +/** + * Convert the ID of a Metatag config definition from the D7 syntax. + * + * Config IDs in D7 had the format "ENTITY:BUNDLE". In D8+ they have the + * format "ENTITY__BUNDLE". + * + * @MigrateProcessPlugin( + * id = "d7_metatag_config_id", + * handle_multiples = TRUE + * ) + */ +class MetatagConfigId extends ProcessPluginBase { + + /** + * {@inheritdoc} + */ + public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + // If there's no data, there's no need to store anything. + if (empty($value)) { + return NULL; + } + + return str_replace(':', '__', $value); + } + +} diff --git a/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagConfigLabel.php b/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagConfigLabel.php new file mode 100644 index 0000000000000000000000000000000000000000..5c7756ba1ad9df384b6710220bd70421887cd444 --- /dev/null +++ b/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagConfigLabel.php @@ -0,0 +1,40 @@ +<?php + +namespace Drupal\metatag\Plugin\migrate\process\d7; + +use Drupal\Component\Serialization\Json; +use Drupal\metatag\Plugin\migrate\MigrateMetatagD7Trait; +use Drupal\migrate\MigrateException; +use Drupal\migrate\MigrateExecutableInterface; +use Drupal\migrate\ProcessPluginBase; +use Drupal\migrate\Row; + +/** + * Convert the label of a Metatag config definition from the D7 syntax. + * + * Config labels on D7 config entities were based on entity definitions, whereas + * in D8+ they need a label. + * + * @MigrateProcessPlugin( + * id = "d7_metatag_config_label", + * handle_multiples = TRUE + * ) + */ +class MetatagConfigLabel extends ProcessPluginBase { + + /** + * {@inheritdoc} + */ + public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + // If there's no data, there's no need to store anything. + if (empty($value)) { + return NULL; + } + + $value = str_replace(':', ': ', $value); + $value = str_replace('_', ' ', $value); + + return ucwords($value); + } + +} diff --git a/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagDefaults.php b/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagDefaults.php new file mode 100644 index 0000000000000000000000000000000000000000..4b00d5b4dfb6613f26566bd27942627de079cd68 --- /dev/null +++ b/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagDefaults.php @@ -0,0 +1,35 @@ +<?php + +namespace Drupal\metatag\Plugin\migrate\process\d7; + +use Drupal\Component\Serialization\Json; +use Drupal\migrate\MigrateExecutableInterface; +use Drupal\migrate\Row; + +/** + * Migrate default configurations from Metatag on D7. + * + * @MigrateProcessPlugin( + * id = "d7_metatag_defaults", + * handle_multiples = TRUE + * ) + */ +class MetatagDefaults extends MetatagEntities { + + /** + * {@inheritdoc} + */ + public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + // If there's no data, there's no need to store anything. + if (empty($value)) { + return NULL; + } + + // Leverage the entities migration logic. + $new_tags = parent::transform($value, $migrate_executable, $row, $destination_property); + + // Data from the parent transformation will be in JSON format, so decode it. + return Json::decode($new_tags); + } + +} diff --git a/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagEntities.php b/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagEntities.php index 3f13c2b978d4b74a61484f4dd2ae10746f656dcb..730a9de644e5f09efcbe682c2602019f2aabfcb3 100644 --- a/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagEntities.php +++ b/web/modules/metatag/src/Plugin/migrate/process/d7/MetatagEntities.php @@ -3,7 +3,7 @@ namespace Drupal\metatag\Plugin\migrate\process\d7; use Drupal\Component\Serialization\Json; -use Drupal\Component\Utility\Unicode; +use Drupal\metatag\Plugin\migrate\MigrateMetatagD7Trait; use Drupal\migrate\MigrateException; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\ProcessPluginBase; @@ -12,6 +12,22 @@ /** * Migrate entity data from Metatag on D7. * + * Can also be used as follows to migrate the defaults from Metatag-D7, instead + * of using the separate defaults migration plugin; this is purely academic but + * might be useful for educational purposes. + * @code + * process: + * tags: + * - + * source: config + * plugin: d7_metatag_entities + * - + * plugin: callback + * callable: + * - '\Drupal\Component\Serialization\Json' + * - 'decode' + * @endcode + * * @MigrateProcessPlugin( * id = "d7_metatag_entities", * handle_multiples = TRUE @@ -19,6 +35,8 @@ */ class MetatagEntities extends ProcessPluginBase { + use MigrateMetatagD7Trait; + /** * {@inheritdoc} */ @@ -31,445 +49,11 @@ public function transform($value, MigrateExecutableInterface $migrate_executable // Re-shape D7 entries into for D8 entries. $old_tags = $this->decodeValue($value); - $tags_map = $this->tagsMap(); - - $metatags = []; - - foreach ($old_tags as $d7_metatag_name => $metatag_value) { - // If there's no data for this tag, ignore everything. - if (empty($metatag_value)) { - continue; - } - - // @todo Skip these values for now, maybe some version supported these? - if (!is_array($metatag_value) || empty($metatag_value['value'])) { - continue; - } - - // Convert the D7 meta tag name to the D8 equivalent. If this meta tag - // is not recognized, skip it. - if (empty($tags_map[$d7_metatag_name])) { - continue; - } - $d8_metatag_name = $tags_map[$d7_metatag_name]; - - // Convert the nested arrays to a flat structure. - if (is_array($metatag_value['value'])) { - // Remove empty values. - $metatag_value['value'] = array_filter($metatag_value['value']); - // Convert the array into a comma-separated list. - $metatag_value = implode(', ', $metatag_value['value']); - } - else { - $metatag_value = $metatag_value['value']; - } - if (!Unicode::validateUtf8($metatag_value)) { - $metatag_value = Unicode::convertToUtf8($metatag_value, 'Windows-1252'); - } - - // Keep the entire data structure. - $metatags[$d8_metatag_name] = $metatag_value; - } - - // Sort the meta tags alphabetically to make testing easier. - ksort($metatags); - - return Json::encode($metatags); - } - - /** - * Match Metatag-D7 meta tags with their D8 counterparts. - * - * @return array - * An array of D7 tags to their D8 counterparts. - */ - protected function tagsMap(): array { - $map = [ - // From the main Metatag module. - 'abstract' => 'abstract', - 'cache-control' => 'cache_control', - 'canonical' => 'canonical_url', - 'description' => 'description', - 'expires' => 'expires', - 'generator' => 'generator', - 'geo.placename' => 'geo_placename', - 'geo.position' => 'geo_position', - 'geo.region' => 'geo_region', - 'icbm' => 'icbm', - 'image_src' => 'image_src', - 'keywords' => 'keywords', - 'next' => 'next', - 'original-source' => 'original_source', - 'pragma' => 'pragma', - 'prev' => 'prev', - 'rating' => 'rating', - 'referrer' => 'referrer', - 'refresh' => 'refresh', - 'revisit-after' => 'revisit_after', - 'rights' => 'rights', - 'robots' => 'robots', - 'set_cookie' => 'set_cookie', - 'shortlink' => 'shortlink', - 'title' => 'title', - - // From metatag_app_links.metatag.inc: - 'al:android:app_name' => 'al_android_app_name', - 'al:android:class' => 'al_android_class', - 'al:android:package' => 'al_android_package', - 'al:android:url' => 'al_android_url', - 'al:ios:app_name' => 'al_ios_app_name', - 'al:ios:app_store_id' => 'al_ios_app_store_id', - 'al:ios:url' => 'al_ios_url', - 'al:ipad:app_name' => 'al_ipad_app_name', - 'al:ipad:app_store_id' => 'al_ipad_app_store_id', - 'al:ipad:url' => 'al_ipad_url', - 'al:iphone:app_name' => 'al_iphone_app_name', - 'al:iphone:app_store_id' => 'al_iphone_app_store_id', - 'al:iphone:url' => 'al_iphone_url', - 'al:web:should_fallback' => 'al_web_should_fallback', - 'al:web:url' => 'al_web_url', - 'al:windows:app_id' => 'al_windows_app_id', - 'al:windows:app_name' => 'al_windows_app_name', - 'al:windows:url' => 'al_windows_url', - 'al:windows_phone:app_id' => 'al_windows_phone_app_id', - 'al:windows_phone:app_name' => 'al_windows_phone_app_name', - 'al:windows_phone:url' => 'al_windows_phone_url', - 'al:windows_universal:app_id' => 'al_windows_universal_app_id', - 'al:windows_universal:app_name' => 'al_windows_universal_app_name', - 'al:windows_universal:url' => 'al_windows_universal_url', - - // From metatag_dc.metatag.inc: - 'dcterms.contributor' => 'dcterms_contributor', - 'dcterms.coverage' => 'dcterms_coverage', - 'dcterms.creator' => 'dcterms_creator', - 'dcterms.date' => 'dcterms_date', - 'dcterms.description' => 'dcterms_description', - 'dcterms.format' => 'dcterms_format', - 'dcterms.identifier' => 'dcterms_identifier', - 'dcterms.language' => 'dcterms_language', - 'dcterms.publisher' => 'dcterms_publisher', - 'dcterms.relation' => 'dcterms_relation', - 'dcterms.rights' => 'dcterms_rights', - 'dcterms.source' => 'dcterms_source', - 'dcterms.subject' => 'dcterms_subject', - 'dcterms.title' => 'dcterms_title', - 'dcterms.type' => 'dcterms_type', - - // From metatag_dc_advanced.metatag.inc: - 'dcterms.abstract' => 'dcterms_abstract', - 'dcterms.accessRights' => 'dcterms_access_rights', - 'dcterms.accrualMethod' => 'dcterms_accrual_method', - 'dcterms.accrualPeriodicity' => 'dcterms_accrual_periodicity', - 'dcterms.accrualPolicy' => 'dcterms_accrual_policy', - 'dcterms.alternative' => 'dcterms_alternative', - 'dcterms.audience' => 'dcterms_audience', - 'dcterms.available' => 'dcterms_available', - 'dcterms.bibliographicCitation' => 'dcterms_bibliographic_citation', - 'dcterms.conformsTo' => 'dcterms_conforms_to', - 'dcterms.created' => 'dcterms_created', - 'dcterms.dateAccepted' => 'dcterms_date_accepted', - 'dcterms.dateCopyrighted' => 'dcterms_date_copyrighted', - 'dcterms.dateSubmitted' => 'dcterms_date_submitted', - 'dcterms.educationLevel' => 'dcterms_education_level', - 'dcterms.extent' => 'dcterms_extent', - 'dcterms.hasFormat' => 'dcterms_has_format', - 'dcterms.hasPart' => 'dcterms_has_part', - 'dcterms.hasVersion' => 'dcterms_has_version', - 'dcterms.instructionalMethod' => 'dcterms_instructional_method', - 'dcterms.isFormatOf' => 'dcterms_is_format_of', - 'dcterms.isPartOf' => 'dcterms_is_part_of', - 'dcterms.isReferencedBy' => 'dcterms_is_referenced_by', - 'dcterms.isReplacedBy' => 'dcterms_is_replaced_by', - 'dcterms.isRequiredBy' => 'dcterms_is_required_by', - 'dcterms.issued' => 'dcterms_issued', - 'dcterms.isVersionOf' => 'dcterms_is_version_of', - 'dcterms.license' => 'dcterms_license', - 'dcterms.mediator' => 'dcterms_mediator', - 'dcterms.medium' => 'dcterms_medium', - 'dcterms.modified' => 'dcterms_modified', - 'dcterms.provenance' => 'dcterms_provenance', - 'dcterms.references' => 'dcterms_references', - 'dcterms.replaces' => 'dcterms_replaces', - 'dcterms.requires' => 'dcterms_requires', - 'dcterms.rightsHolder' => 'dcterms_rights_holder', - 'dcterms.spatial' => 'dcterms_spatial', - 'dcterms.tableOfContents' => 'dcterms_table_of_contents', - 'dcterms.temporal' => 'dcterms_temporal', - 'dcterms.valid' => 'dcterms_valid', - - // From metatag_facebook.metatag.inc: - 'fb:admins' => 'fb_admins', - 'fb:app_id' => 'fb_app_id', - 'fb:pages' => 'fb_pages', - - // From metatag_favicons.metatag.inc: - 'apple-touch-icon' => 'apple_touch_icon', - 'apple-touch-icon-precomposed' => 'apple_touch_icon_precomposed', - 'apple-touch-icon-precomposed_114x114' => 'apple_touch_icon_precomposed_114x114', - 'apple-touch-icon-precomposed_120x120' => 'apple_touch_icon_precomposed_120x120', - 'apple-touch-icon-precomposed_144x144' => 'apple_touch_icon_precomposed_144x144', - 'apple-touch-icon-precomposed_152x152' => 'apple_touch_icon_precomposed_152x152', - 'apple-touch-icon-precomposed_180x180' => 'apple_touch_icon_precomposed_180x180', - 'apple-touch-icon-precomposed_72x72' => 'apple_touch_icon_precomposed_72x72', - 'apple-touch-icon-precomposed_76x76' => 'apple_touch_icon_precomposed_76x76', - 'apple-touch-icon_114x114' => 'apple_touch_icon_114x114', - 'apple-touch-icon_120x120' => 'apple_touch_icon_120x120', - 'apple-touch-icon_144x144' => 'apple_touch_icon_144x144', - 'apple-touch-icon_152x152' => 'apple_touch_icon_152x152', - 'apple-touch-icon_180x180' => 'apple_touch_icon_180x180', - 'apple-touch-icon_72x72' => 'apple_touch_icon_72x72', - 'apple-touch-icon_76x76' => 'apple_touch_icon_76x76', - 'icon_16x16' => 'icon_16x16', - 'icon_192x192' => 'icon_192x192', - 'icon_32x32' => 'icon_32x32', - 'icon_96x96' => 'icon_96x96', - 'mask-icon' => 'mask-icon', - 'shortcut icon' => 'shortcut_icon', - - // From metatag_google_cse.metatag.inc: - 'audience' => 'audience', - 'department' => 'department', - 'doc_status' => 'doc_status', - 'google_rating' => 'google_rating', - 'thumbnail' => 'thumbnail', - - // From metatag_google_plus.metatag.inc; not doing these, Google+ closed. - 'itemtype' => '', - 'itemprop:name' => '', - 'itemprop:description' => '', - 'itemprop:image' => '', - 'author' => '', - 'publisher' => '', - - // From metatag_hreflang.metatag.inc: - 'hreflang_xdefault' => 'hreflang_xdefault', - // @todo https://www.drupal.org/project/metatag/issues/3077778 - // 'hreflang_' . $langcode => 'hreflang_per_language', - // From metatag_mobile.metatag.inc: - 'alternate_handheld' => 'alternate_handheld', - 'android-app-link-alternative' => 'android_app_link_alternative', - 'android-manifest' => 'android_manifest', - 'apple-itunes-app' => 'apple_itunes_app', - 'apple-mobile-web-app-capable' => 'apple_mobile_web_app_capable', - 'apple-mobile-web-app-status-bar-style' => 'apple_mobile_web_app_status_bar_style', - 'apple-mobile-web-app-title' => 'apple_mobile_web_app_title', - 'application-name' => 'application_name', - 'cleartype' => 'cleartype', - 'format-detection' => 'format_detection', - 'HandheldFriendly' => 'handheldfriendly', - 'ios-app-link-alternative' => 'ios_app_link_alternative', - 'MobileOptimized' => 'mobileoptimized', - 'msapplication-allowDomainApiCalls' => 'msapplication_allowDomainApiCalls', - 'msapplication-allowDomainMetaTags' => 'msapplication_allowDomainMetaTags', - 'msapplication-badge' => 'msapplication_badge', - 'msapplication-config' => 'msapplication_config', - 'msapplication-navbutton-color' => 'msapplication_navbutton_color', - 'msapplication-notification' => 'msapplication_notification', - 'msapplication-square150x150logo' => 'msapplication_square150x150logo', - 'msapplication-square310x310logo' => 'msapplication_square310x310logo', - 'msapplication-square70x70logo' => 'msapplication_square70x70logo', - 'msapplication-starturl' => 'msapplication_starturl', - 'msapplication-task' => 'msapplication_task', - 'msapplication-task-separator' => 'msapplication_task_separator', - 'msapplication-tilecolor' => 'msapplication_tilecolor', - 'msapplication-tileimage' => 'msapplication_tileimage', - 'msapplication-tooltip' => 'msapplication_tooltip', - 'msapplication-wide310x150logo' => 'msapplication_wide310x150logo', - 'msapplication-window' => 'msapplication_window', - 'theme-color' => 'theme_color', - 'viewport' => 'viewport', - 'x-ua-compatible' => 'x_ua_compatible', - - // From metatag_opengraph.metatag.inc: - // https://www.drupal.org/project/metatag/issues/3077782 - 'article:author' => 'article_author', - 'article:expiration_time' => 'article_expiration_time', - 'article:modified_time' => 'article_modified_time', - 'article:published_time' => 'article_published_time', - 'article:publisher' => 'article_publisher', - 'article:section' => 'article_section', - 'article:tag' => 'article_tag', - 'book:author' => 'book_author', - 'book:isbn' => 'book_isbn', - 'book:release_date' => 'book_release_date', - 'book:tag' => 'book_tag', - 'og:audio' => 'og_audio', - 'og:audio:secure_url' => 'og_audio_secure_url', - 'og:audio:type' => 'og_audio_type', - 'og:country_name' => 'og_country_name', - 'og:description' => 'og_description', - 'og:determiner' => 'og_determiner', - 'og:email' => 'og_email', - 'og:fax_number' => 'og_fax_number', - 'og:image' => 'og_image', - // @todo '' => 'og_image_alt', - 'og:image:height' => 'og_image_height', - 'og:image:secure_url' => 'og_image_secure_url', - 'og:image:type' => 'og_image_type', - 'og:image:url' => 'og_image_url', - 'og:image:width' => 'og_image_width', - 'og:latitude' => 'og_latitude', - 'og:locale' => 'og_locale', - 'og:locale:alternate' => 'og_locale_alternative', - 'og:locality' => 'og_locality', - 'og:longitude' => 'og_longitude', - 'og:phone_number' => 'og_phone_number', - 'og:postal_code' => 'og_postal_code', - 'og:region' => 'og_region', - 'og:see_also' => 'og_see_also', - 'og:site_name' => 'og_site_name', - 'og:street_address' => 'og_street_address', - 'og:title' => 'og_title', - 'og:type' => 'og_type', - 'og:updated_time' => 'og_updated_time', - 'og:url' => 'og_url', - // @todo '' => 'og_video', - // https://www.drupal.org/project/metatag/issues/3089445 - // @todo '' => 'og_video_duration', - 'og:video:height' => 'og_video_height', - 'og:video:secure_url' => 'og_video_secure_url', - 'og:video:type' => 'og_video_type', - 'og:video:url' => 'og_video_url', - 'og:video:width' => 'og_video_width', - 'profile:first_name' => 'profile_first_name', - 'profile:gender' => 'profile_gender', - 'profile:last_name' => 'profile_last_name', - 'profile:username' => 'profile_username', - 'video:actor' => 'video_actor', - 'video:actor:role' => 'video_actor_role', - 'video:director' => 'video_director', - // @todo 'video:duration' => '', - 'video:release_date' => 'video_release_date', - 'video:series' => 'video_series', - 'video:tag' => 'video_tag', - 'video:writer' => 'video_writer', - - // From metatag_opengraph_products.metatag.inc: - 'product:price:amount' => 'product_price_amount', - 'product:price:currency' => 'product_price_currency', - // Not supported in D7. - // @todo '' => 'product_retailer_item_id, - // Not yet supported in D9. - // @todo 'product:availability' => '', - // @todo 'product:brand' => '', - // @todo 'product:upc' => '', - // @todo 'product:ean' => '', - // @todo 'product:isbn' => '', - // @todo 'product:plural_title' => '', - // @todo 'product:retailer' => '', - // @todo 'product:retailer_title' => '', - // @todo 'product:retailer_part_no' => '', - // @todo 'product:mfr_part_no' => '', - // @todo 'product:size' => '', - // @todo 'product:product_link' => '', - // @todo 'product:category' => '', - // @todo 'product:color' => '', - // @todo 'product:material' => '', - // @todo 'product:pattern' => '', - // @todo 'product:shipping_cost:amount' => '', - // @todo 'product:shipping_cost:currency' => '', - // @todo 'product:weight:value' => '', - // @todo 'product:weight:units' => '', - // @todo 'product:shipping_weight:value' => '', - // @todo 'product:shipping_weight:units' => '', - // @todo 'product:expiration_time' => '', - // @todo 'product:condition' => '', - // Pinterest. - // @todo '' => 'pinterest_id', - // @todo '' => 'pinterest_description', - // @todo '' => 'pinterest_nohover', - // @todo '' => 'pinterest_url', - // @todo '' => 'pinterest_media', - // @todo '' => 'pinterest_nopin', - // @todo '' => 'pinterest_nosearch', - // From metatag_twitter_cards.metatag.inc: - 'twitter:app:country' => 'twitter_cards_app_store_country', - 'twitter:app:id:googleplay' => 'twitter_cards_app_id_googleplay', - 'twitter:app:id:ipad' => 'twitter_cards_app_id_ipad', - 'twitter:app:id:iphone' => 'twitter_cards_app_id_iphone', - 'twitter:app:name:googleplay' => 'twitter_cards_app_name_googleplay', - 'twitter:app:name:ipad' => 'twitter_cards_app_name_ipad', - 'twitter:app:name:iphone' => 'twitter_cards_app_name_iphone', - 'twitter:app:url:googleplay' => 'twitter_cards_app_url_googleplay', - 'twitter:app:url:ipad' => 'twitter_cards_app_url_ipad', - 'twitter:app:url:iphone' => 'twitter_cards_app_url_iphone', - 'twitter:card' => 'twitter_cards_type', - 'twitter:creator' => 'twitter_cards_creator', - 'twitter:creator:id' => 'twitter_cards_creator_id', - 'twitter:data1' => 'twitter_cards_data1', - 'twitter:data2' => 'twitter_cards_data2', - 'twitter:description' => 'twitter_cards_description', - 'twitter:dnt' => 'twitter_cards_donottrack', - 'twitter:image' => 'twitter_cards_image', - 'twitter:image0' => 'twitter_cards_gallery_image0', - 'twitter:image1' => 'twitter_cards_gallery_image1', - 'twitter:image2' => 'twitter_cards_gallery_image2', - 'twitter:image3' => 'twitter_cards_gallery_image3', - 'twitter:image:alt' => 'twitter_cards_image_alt', - 'twitter:image:height' => 'twitter_cards_image_height', - 'twitter:image:width' => 'twitter_cards_image_width', - 'twitter:label1' => 'twitter_cards_label1', - 'twitter:label2' => 'twitter_cards_label2', - 'twitter:player' => 'twitter_cards_player', - 'twitter:player:height' => 'twitter_cards_player_height', - 'twitter:player:stream' => 'twitter_cards_player_stream', - 'twitter:player:stream:content_type' => 'twitter_cards_player_stream_content_type', - 'twitter:player:width' => 'twitter_cards_player_width', - 'twitter:site' => 'twitter_cards_site', - 'twitter:site:id' => 'twitter_cards_site_id', - 'twitter:title' => 'twitter_cards_title', - 'twitter:url' => 'twitter_cards_page_url', - - // From metatag_verification.metatag.inc: - 'baidu-site-verification' => 'baidu', - 'facebook-domain-verification' => 'facebook_domain_verification', - 'google-site-verification' => 'google_site_verification', - 'msvalidate.01' => 'bing', - 'norton-safeweb-site-verification' => 'norton_safe_web', - 'p:domain_verify' => 'pinterest', - // @todo '' => 'pocket', - 'yandex-verification' => 'yandex', - ]; - - // Trigger hook_metatag_migrate_metatagd7_tags_map_alter(). - // Allow modules to override tags or the entity used for token replacements. - \Drupal::service('module_handler')->alter('metatag_migrate_metatagd7_tags_map', $map); - - return $map; - } - - /** - * Decode the different versions of encoded values supported by Metatag. - * - * Metatag v1 stored data in serialized arrays. Metatag v2 stores data in - * JSON-encoded strings. - * - * @param string $string - * The string to decode. - * - * @return array - * A Metatag values array. - */ - private function decodeValue($string): array { - $data = []; - - // Serialized arrays from Metatag v1. - if (substr($string, 0, 2) === 'a:') { - $data = @unserialize($string, ['allowed_classes' => FALSE]); - } - - // Encoded JSON from Metatag v2. - elseif (substr($string, 0, 2) === '{"') { - // @todo Handle non-array responses. - $data = json_decode($string, TRUE); - } - - // This is expected to be an array, if it isn't then something went wrong. - if (!is_array($data)) { - throw new MigrateException('Data from Metatag-D7 was not a serialized array or a JSON-encoded string.'); - } + // Offload the transformation logic. + $new_tags = $this->transformTags($old_tags, FALSE); - return $data; + // For entity data this needs to be in a JSON encoded string. + return Json::encode($new_tags); } } diff --git a/web/modules/metatag/src/Plugin/migrate/source/d6/NodewordsFieldInstance.php b/web/modules/metatag/src/Plugin/migrate/source/d6/NodewordsFieldInstance.php index aca64b1ae1e37a1555b8bd129d61952da48c267e..618694fee4d42dbdf2a2220c405a6b8564c386ce 100644 --- a/web/modules/metatag/src/Plugin/migrate/source/d6/NodewordsFieldInstance.php +++ b/web/modules/metatag/src/Plugin/migrate/source/d6/NodewordsFieldInstance.php @@ -120,7 +120,7 @@ public function getIds() { /** * {@inheritdoc} */ - public function count($refresh = FALSE) { + public function count($refresh = FALSE): int { /** @var \ArrayIterator $items */ $items = $this->initializeIterator(); return $items->count(); diff --git a/web/modules/metatag/src/Plugin/migrate/source/d7/MetatagDefaults.php b/web/modules/metatag/src/Plugin/migrate/source/d7/MetatagDefaults.php new file mode 100644 index 0000000000000000000000000000000000000000..63d81294a4b0bc1c4c8fd079cf6fc2d495313dda --- /dev/null +++ b/web/modules/metatag/src/Plugin/migrate/source/d7/MetatagDefaults.php @@ -0,0 +1,48 @@ +<?php + +namespace Drupal\metatag\Plugin\migrate\source\d7; + +use Drupal\metatag\Plugin\migrate\MigrateMetatagD7Trait; +use Drupal\migrate\Row; +use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase; + +/** + * Drupal 7 Metatag configuration. + * + * @MigrateSource( + * id = "d7_metatag_defaults", + * source_module = "metatag" + * ) + */ +class MetatagDefaults extends DrupalSqlBase { + + use MigrateMetatagD7Trait; + + /** + * {@inheritdoc} + */ + public function query() { + return $this->select('metatag_config', 'm') + ->fields('m', ['instance', 'config']); + } + + /** + * {@inheritdoc} + */ + public function fields() { + $fields = [ + 'instance' => $this->t('Configuration instance'), + 'config' => $this->t('Meta tag configuration, stored as either a serialized array or a JSON-encoded string.'), + ]; + return $fields; + } + + /** + * {@inheritdoc} + */ + public function getIds() { + $ids['instance']['type'] = 'string'; + return $ids; + } + +} diff --git a/web/modules/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstance.php b/web/modules/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstance.php index 23e322a5e701e37289e58fae1b3bffca48306c05..6d36aa296c34ee31a31aa2346248f5d1a6bed5a8 100644 --- a/web/modules/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstance.php +++ b/web/modules/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstance.php @@ -140,7 +140,7 @@ public function getIds() { /** * {@inheritdoc} */ - public function count($refresh = FALSE) { + public function count($refresh = FALSE): int { /** @var \ArrayIterator $iterator */ $iterator = $this->initializeIterator(); return $iterator->count(); diff --git a/web/modules/metatag/tests/fixtures/d7_metatag.php b/web/modules/metatag/tests/fixtures/d7_metatag.php index 50739f54c99d0b543473bae55661e5a477fc36fd..d26886b953b2be9a3b93032285b170484bdd7bf1 100644 --- a/web/modules/metatag/tests/fixtures/d7_metatag.php +++ b/web/modules/metatag/tests/fixtures/d7_metatag.php @@ -9,6 +9,7 @@ $connection = Database::getConnection(); +// Primary records for Metatag entity data. $connection->schema()->createTable('metatag', [ 'fields' => [ 'entity_type' => [ @@ -140,6 +141,148 @@ ]) ->execute(); +// Metatag global configuration. +$connection->schema()->createTable('metatag_config', [ + 'fields' => [ + 'cid' => [ + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The primary identifier for a metatag configuration set.', + 'no export' => TRUE, + ], + 'instance' => [ + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The machine-name of the configuration, typically entity-type:bundle.', + ], + 'config' => [ + 'type' => 'blob', + 'size' => 'big', + 'not null' => TRUE, + 'serialize' => TRUE, + 'description' => 'Serialized data containing the meta tag configuration.', + 'translatable' => TRUE, + ], + ], + 'primary key' => ['cid'], + 'unique keys' => [ + 'instance' => ['instance'], + ], + 'mysql_character_set' => 'utf8', +]); + +$connection->insert('metatag_config') + ->fields(['instance', 'config']) + ->values([ + 'instance' => 'global', + 'config' => serialize([ + 'title' => [ + 'value' => 'I\'m in heaven!', + ], + 'description' => [ + 'value' => 'Mango heaven!', + ], + 'robots' => [ + 'value' => [ + 'nofollow' => 'nofollow', + 'noindex' => 'noindex', + ], + ], + ]), + ]) + ->values([ + 'instance' => 'node', + 'config' => serialize([ + 'title' => [ + 'value' => '[node:title]', + ], + 'description' => [ + 'value' => 'The summary is: [node:field_summary]', + ], + 'keywords' => [ + 'value' => 'mango, ', + ], + 'robots' => [ + 'value' => [ + 'follow' => 'follow', + 'index' => 'index', + ], + ], + ]), + ]) + ->values([ + 'instance' => 'node:article', + 'config' => serialize([ + 'keywords' => [ + 'value' => 'Alphonso, Angie, Julie', + ], + 'robots' => [ + 'value' => [ + 'nofollow' => 'nofollow', + 'noindex' => 'noindex', + ], + ], + ]), + ]) + ->values([ + 'instance' => 'taxonomy_term', + 'config' => serialize([ + 'title' => [ + 'value' => '[term:name]', + ], + 'description' => [ + 'value' => 'The summary is: [term;description]', + ], + 'keywords' => [ + 'value' => 'mango, ', + ], + 'robots' => [ + 'value' => [ + 'follow' => 'follow', + 'index' => 'index', + ], + ], + ]), + ]) + ->values([ + 'instance' => 'taxonomy_term:tags', + 'config' => serialize([ + 'keywords' => [ + 'value' => 'Alphonso, Angie, Julie', + ], + 'robots' => [ + 'value' => [ + 'nofollow' => 'nofollow', + 'noindex' => 'noindex', + ], + ], + ]), + ]) + ->values([ + 'instance' => 'user', + 'config' => serialize([ + 'title' => [ + 'value' => '[user:name]', + ], + 'description' => [ + 'value' => 'The summary is: [user;name]', + ], + 'keywords' => [ + 'value' => 'mango, ', + ], + 'robots' => [ + 'value' => [ + 'follow' => 'follow', + 'index' => 'index', + ], + ], + ]), + ]) + ->execute(); + $connection->insert('node') ->fields([ 'nid', @@ -257,3 +400,33 @@ 'info' => 'a:12:{s:4:"name";s:7:"Metatag";s:11:"description";s:47:"Adds support and an API to implement meta tags.";s:7:"package";s:3:"SEO";s:4:"core";s:3:"7.x";s:12:"dependencies";a:3:{i:0;s:23:"drupal:system (>= 7.40)";i:1;s:13:"ctools:ctools";i:2;s:11:"token:token";}s:9:"configure";s:28:"admin/config/search/metatags";s:5:"files";a:30:{i:0;s:11:"metatag.inc";i:1;s:19:"metatag.migrate.inc";i:2;s:22:"metatag.search_api.inc";i:3;s:25:"tests/metatag.helper.test";i:4;s:23:"tests/metatag.unit.test";i:5;s:30:"tests/metatag.tags_helper.test";i:6;s:23:"tests/metatag.tags.test";i:7;s:23:"tests/metatag.node.test";i:8;s:23:"tests/metatag.term.test";i:9;s:23:"tests/metatag.user.test";i:10;s:35:"tests/metatag.core_tag_removal.test";i:11;s:30:"tests/metatag.bulk_revert.test";i:12;s:34:"tests/metatag.string_handling.test";i:13;s:44:"tests/metatag.string_handling_with_i18n.test";i:14;s:22:"tests/metatag.xss.test";i:15;s:33:"tests/metatag.output_caching.test";i:16;s:24:"tests/metatag.image.test";i:17;s:25:"tests/metatag.locale.test";i:18;s:33:"tests/metatag.node.with_i18n.test";i:19;s:33:"tests/metatag.term.with_i18n.test";i:20;s:35:"tests/metatag.with_i18n_output.test";i:21;s:37:"tests/metatag.with_i18n_disabled.test";i:22;s:35:"tests/metatag.with_i18n_config.test";i:23;s:26:"tests/metatag.with_me.test";i:24;s:29:"tests/metatag.with_media.test";i:25;s:30:"tests/metatag.with_panels.test";i:26;s:32:"tests/metatag.with_profile2.test";i:27;s:34:"tests/metatag.with_search_api.test";i:28;s:44:"tests/metatag.with_workbench_moderation.test";i:29;s:29:"tests/metatag.with_views.test";}s:17:"test_dependencies";a:14:{i:0;s:11:"devel:devel";i:1;s:33:"imagecache_token:imagecache_token";i:2;s:37:"entity_translation:entity_translation";i:3;s:9:"i18n:i18n";i:4;s:5:"me:me";i:5;s:23:"file_entity:file_entity";i:6;s:27:"media:media (>= 2.0, < 3.0)";i:7;s:13:"panels:panels";i:8;s:17:"profile2:profile2";i:9;s:13:"entity:entity";i:10;s:21:"search_api:search_api";i:11;s:41:"workbench_moderation:workbench_moderation";i:12;s:11:"views:views";i:13;s:15:"context:context";}s:5:"mtime";i:1550007449;s:7:"version";N;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', ]) ->execute(); + +// Add variables for the Metatag-D7 settings. These will be converted to +// values on the "metatag.settings" config object. +$connection->insert('variable') + ->fields(['name', 'value']) + ->values([ + 'name' => 'metatag_separator', + 'value' => serialize('||'), + ]) + ->values([ + 'name' => 'metatag_use_maxlength', + 'value' => serialize(FALSE), + ]) + ->values([ + 'name' => 'metatag_maxlength_title', + 'value' => serialize(50), + ]) + ->values([ + 'name' => 'metatag_maxlength_description', + 'value' => serialize(200), + ]) + ->values([ + 'name' => 'metatag_maxlength_abstract', + 'value' => serialize(150), + ]) + ->values([ + 'name' => 'metatag_maxlength_keywords', + 'value' => serialize(1000), + ]) + ->execute(); diff --git a/web/modules/metatag/tests/fixtures/d8_metatag_v1.php b/web/modules/metatag/tests/fixtures/d8_metatag_v1.php index f26064bf8086a14d247ba23a03350dd55a3cebdc..871d6fbb9b255f9871c9151ebcf35afb6f058c34 100644 --- a/web/modules/metatag/tests/fixtures/d8_metatag_v1.php +++ b/web/modules/metatag/tests/fixtures/d8_metatag_v1.php @@ -412,7 +412,7 @@ $key_value = unserialize($key_value, [ 'allowed_classes' => $allowed_classes, ]); -$key_value['field_meta_tags'] = unserialize('O:38:"Drupal\field\Entity\FieldStorageConfig":35:{s:5:"' . "\0" . '*' . "\0" . 'id";s:20:"node.field_meta_tags";s:13:"' . "\0" . '*' . "\0" . 'field_name";s:15:"field_meta_tags";s:14:"' . "\0" . '*' . "\0" . 'entity_type";s:4:"node";s:7:"' . "\0" . '*' . "\0" . 'type";s:7:"metatag";s:9:"' . "\0" . '*' . "\0" . 'module";s:7:"metatag";s:11:"' . "\0" . '*' . "\0" . 'settings";a:0:{}s:14:"' . "\0" . '*' . "\0" . 'cardinality";i:1;s:15:"' . "\0" . '*' . "\0" . 'translatable";b:1;s:9:"' . "\0" . '*' . "\0" . 'locked";b:0;s:25:"' . "\0" . '*' . "\0" . 'persist_with_no_fields";b:0;s:14:"custom_storage";b:0;s:10:"' . "\0" . '*' . "\0" . 'indexes";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'deleted";b:0;s:13:"' . "\0" . '*' . "\0" . 'originalId";s:20:"node.field_meta_tags";s:9:"' . "\0" . '*' . "\0" . 'status";b:1;s:7:"' . "\0" . '*' . "\0" . 'uuid";s:36:"6aaab457-3728-4319-afa3-938e753ed342";s:11:"' . "\0" . '*' . "\0" . 'langcode";s:2:"en";s:23:"' . "\0" . '*' . "\0" . 'third_party_settings";a:0:{}s:8:"' . "\0" . '*' . "\0" . '_core";a:0:{}s:14:"' . "\0" . '*' . "\0" . 'trustedData";b:0;s:15:"' . "\0" . '*' . "\0" . 'entityTypeId";s:20:"field_storage_config";s:15:"' . "\0" . '*' . "\0" . 'enforceIsNew";N;s:12:"' . "\0" . '*' . "\0" . 'typedData";N;s:16:"' . "\0" . '*' . "\0" . 'cacheContexts";a:0:{}s:12:"' . "\0" . '*' . "\0" . 'cacheTags";a:0:{}s:14:"' . "\0" . '*' . "\0" . 'cacheMaxAge";i:-1;s:14:"' . "\0" . '*' . "\0" . '_serviceIds";a:0:{}s:18:"' . "\0" . '*' . "\0" . '_entityStorages";a:0:{}s:15:"' . "\0" . '*' . "\0" . 'dependencies";a:1:{s:6:"module";a:2:{i:0;s:7:"metatag";i:1;s:4:"node";}}s:12:"' . "\0" . '*' . "\0" . 'isSyncing";b:0;s:18:"cardinality_number";i:1;s:6:"submit";O:48:"Drupal\Core\StringTranslation\TranslatableMarkup":3:{s:9:"' . "\0" . '*' . "\0" . 'string";s:19:"Save field settings";s:12:"' . "\0" . '*' . "\0" . 'arguments";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'options";a:0:{}}s:13:"form_build_id";s:48:"form-LK9HeARuUzcwIVvCAA4jG2MscwGjLAUJ9GLYxuzSo7o";s:10:"form_token";s:43:"eengi9MkLSqT-YFMEKD18fJ6cOvVyS_XRq1He7qhq4s";s:7:"form_id";s:30:"field_storage_config_edit_form";}}', [ +$key_value['field_meta_tags'] = @unserialize('O:38:"Drupal\field\Entity\FieldStorageConfig":35:{s:5:"' . "\0" . '*' . "\0" . 'id";s:20:"node.field_meta_tags";s:13:"' . "\0" . '*' . "\0" . 'field_name";s:15:"field_meta_tags";s:14:"' . "\0" . '*' . "\0" . 'entity_type";s:4:"node";s:7:"' . "\0" . '*' . "\0" . 'type";s:7:"metatag";s:9:"' . "\0" . '*' . "\0" . 'module";s:7:"metatag";s:11:"' . "\0" . '*' . "\0" . 'settings";a:0:{}s:14:"' . "\0" . '*' . "\0" . 'cardinality";i:1;s:15:"' . "\0" . '*' . "\0" . 'translatable";b:1;s:9:"' . "\0" . '*' . "\0" . 'locked";b:0;s:25:"' . "\0" . '*' . "\0" . 'persist_with_no_fields";b:0;s:14:"custom_storage";b:0;s:10:"' . "\0" . '*' . "\0" . 'indexes";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'deleted";b:0;s:13:"' . "\0" . '*' . "\0" . 'originalId";s:20:"node.field_meta_tags";s:9:"' . "\0" . '*' . "\0" . 'status";b:1;s:7:"' . "\0" . '*' . "\0" . 'uuid";s:36:"6aaab457-3728-4319-afa3-938e753ed342";s:11:"' . "\0" . '*' . "\0" . 'langcode";s:2:"en";s:23:"' . "\0" . '*' . "\0" . 'third_party_settings";a:0:{}s:8:"' . "\0" . '*' . "\0" . '_core";a:0:{}s:14:"' . "\0" . '*' . "\0" . 'trustedData";b:0;s:15:"' . "\0" . '*' . "\0" . 'entityTypeId";s:20:"field_storage_config";s:15:"' . "\0" . '*' . "\0" . 'enforceIsNew";N;s:12:"' . "\0" . '*' . "\0" . 'typedData";N;s:16:"' . "\0" . '*' . "\0" . 'cacheContexts";a:0:{}s:12:"' . "\0" . '*' . "\0" . 'cacheTags";a:0:{}s:14:"' . "\0" . '*' . "\0" . 'cacheMaxAge";i:-1;s:14:"' . "\0" . '*' . "\0" . '_serviceIds";a:0:{}s:18:"' . "\0" . '*' . "\0" . '_entityStorages";a:0:{}s:15:"' . "\0" . '*' . "\0" . 'dependencies";a:1:{s:6:"module";a:2:{i:0;s:7:"metatag";i:1;s:4:"node";}}s:12:"' . "\0" . '*' . "\0" . 'isSyncing";b:0;s:18:"cardinality_number";i:1;s:6:"submit";O:48:"Drupal\Core\StringTranslation\TranslatableMarkup":3:{s:9:"' . "\0" . '*' . "\0" . 'string";s:19:"Save field settings";s:12:"' . "\0" . '*' . "\0" . 'arguments";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'options";a:0:{}}s:13:"form_build_id";s:48:"form-LK9HeARuUzcwIVvCAA4jG2MscwGjLAUJ9GLYxuzSo7o";s:10:"form_token";s:43:"eengi9MkLSqT-YFMEKD18fJ6cOvVyS_XRq1He7qhq4s";s:7:"form_id";s:30:"field_storage_config_edit_form";}}', [ 'allowed_classes' => $allowed_classes, ]); $connection->update('key_value') diff --git a/web/modules/metatag/tests/modules/metatag_test_custom_route/metatag_test_custom_route.info.yml b/web/modules/metatag/tests/modules/metatag_test_custom_route/metatag_test_custom_route.info.yml index d881fa5c582929ccdc8afde50dd4e81d78e34d85..8c2a12e822b7ae493248adcc3689bbf6f77e2d29 100644 --- a/web/modules/metatag/tests/modules/metatag_test_custom_route/metatag_test_custom_route.info.yml +++ b/web/modules/metatag/tests/modules/metatag_test_custom_route/metatag_test_custom_route.info.yml @@ -5,7 +5,7 @@ package: Testing dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/tests/modules/metatag_test_integration/metatag_test_integration.info.yml b/web/modules/metatag/tests/modules/metatag_test_integration/metatag_test_integration.info.yml index dec5cc84def2647b17a735688769f5bdb2c7db91..787a6b7de45684b5c623764bf5ca109a0c4316ad 100644 --- a/web/modules/metatag/tests/modules/metatag_test_integration/metatag_test_integration.info.yml +++ b/web/modules/metatag/tests/modules/metatag_test_integration/metatag_test_integration.info.yml @@ -5,7 +5,7 @@ package: Testing dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/tests/modules/metatag_test_tag/metatag_test_tag.info.yml b/web/modules/metatag/tests/modules/metatag_test_tag/metatag_test_tag.info.yml index 580613c2409fe8041de3f77626fd91dd9dbb137e..0b933061674278892c90b60b7821fbbe85ccb83c 100644 --- a/web/modules/metatag/tests/modules/metatag_test_tag/metatag_test_tag.info.yml +++ b/web/modules/metatag/tests/modules/metatag_test_tag/metatag_test_tag.info.yml @@ -5,7 +5,7 @@ package: Testing dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2024-08-05 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2024-11-07 +version: '2.1.0' project: 'metatag' -datestamp: 1722869775 +datestamp: 1731004045 diff --git a/web/modules/metatag/tests/src/Functional/EntityTestMetatagTest.php b/web/modules/metatag/tests/src/Functional/EntityTestMetatagTest.php index ae106296fe373d919356a21afbc1085f9ae1b2d8..b40fef9a08db3ca0d90ea25747ea89fcbee99501 100644 --- a/web/modules/metatag/tests/src/Functional/EntityTestMetatagTest.php +++ b/web/modules/metatag/tests/src/Functional/EntityTestMetatagTest.php @@ -91,9 +91,9 @@ protected function createEntity() { 'name' => 'Llama', 'type' => 'entity_test', 'field_metatag' => [ - 'value' => [ + 'value' => Json::encode([ 'description' => 'This is a description for use in Search Engines', - ], + ]), ], ]); $entity_test->setOwnerId(0); diff --git a/web/modules/metatag/tests/src/Functional/MetatagAdminTest.php b/web/modules/metatag/tests/src/Functional/MetatagAdminTest.php index 8ebf28ff15710b0928695719cc41e49af1213bc7..1fa174df93a9e1713e4fd8acc85e7ac2014b3c01 100644 --- a/web/modules/metatag/tests/src/Functional/MetatagAdminTest.php +++ b/web/modules/metatag/tests/src/Functional/MetatagAdminTest.php @@ -25,7 +25,6 @@ class MetatagAdminTest extends BrowserTestBase { // @see testAvailableConfigEntities 'block', 'block_content', - 'comment', 'contact', 'field_ui', 'menu_link_content', @@ -190,7 +189,6 @@ public function testAvailableConfigEntities() { // Check through the values that are in the 'select' list, make sure that // unwanted items are not present. $this->assertArrayNotHasKey('block_content', $types, 'Custom block entities are not supported.'); - $this->assertArrayNotHasKey('comment', $types, 'Comment entities are not supported.'); $this->assertArrayNotHasKey('menu_link_content', $types, 'Menu link entities are not supported.'); $this->assertArrayNotHasKey('shortcut', $types, 'Shortcut entities are not supported.'); $this->assertArrayHasKey('node__page', $types, 'Nodes are supported.'); diff --git a/web/modules/metatag/tests/src/Functional/MetatagConfigTranslationTest.php b/web/modules/metatag/tests/src/Functional/MetatagConfigTranslationTest.php index d8ec87898fa0d07dadc0c02accd1249bde0e3f1e..1260468ec919f92c13e8c583a6ccd82937a657e9 100644 --- a/web/modules/metatag/tests/src/Functional/MetatagConfigTranslationTest.php +++ b/web/modules/metatag/tests/src/Functional/MetatagConfigTranslationTest.php @@ -106,10 +106,8 @@ public function testConfigTranslationsExist() { foreach ($defaults as $config_name) { $config_entity = $config_manager->loadConfigEntityByName($config_name); $this->assertNotNull($config_entity); - if (!empty($config_entity)) { - $this->drupalGet('admin/config/search/metatag/' . $config_entity->id() . '/translate'); - $session->statusCodeEquals(200); - } + $this->drupalGet('admin/config/search/metatag/' . $config_entity->id() . '/translate'); + $session->statusCodeEquals(200); } } diff --git a/web/modules/metatag/tests/src/Functional/Update/V2UpdatesTest.php b/web/modules/metatag/tests/src/Functional/Update/V2UpdatesTest.php index afe749fc0c548a3813cc91ccda96d29a9de287e5..d7218a4b3e345c8585b58e9ffd55eaefbbc33310 100644 --- a/web/modules/metatag/tests/src/Functional/Update/V2UpdatesTest.php +++ b/web/modules/metatag/tests/src/Functional/Update/V2UpdatesTest.php @@ -40,17 +40,17 @@ class V2UpdatesTest extends UpdatePathTestBase { * {@inheritdoc} */ protected function setDatabaseDumpFiles() { - // Drupal 9.5+ uses the 9.4.0 data dump, 9.4 uses the 9.3.0 data dump. - $core93 = static::getDrupalRoot() . '/core/modules/system/tests/fixtures/update/drupal-9.3.0.bare.standard.php.gz'; - $core94 = static::getDrupalRoot() . '/core/modules/system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz'; - if (file_exists($core94)) { + // Drupal 10 uses the D9 core fixture, D11 uses the D10 fixture. + $core9 = static::getDrupalRoot() . '/core/modules/system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz'; + $core10 = static::getDrupalRoot() . '/core/modules/system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz'; + if (file_exists($core9)) { $this->databaseDumpFiles = [ - $core94, + $core9, ]; } else { $this->databaseDumpFiles = [ - $core93, + $core10, ]; } diff --git a/web/modules/metatag/tests/src/FunctionalJavascript/MetatagAvailableTokensTest.php b/web/modules/metatag/tests/src/FunctionalJavascript/MetatagAvailableTokensTest.php index 9b10b97455b06e264769192203701ff03af2c90b..67106f5e42d789bbde783c018c6ce59b7c051815 100644 --- a/web/modules/metatag/tests/src/FunctionalJavascript/MetatagAvailableTokensTest.php +++ b/web/modules/metatag/tests/src/FunctionalJavascript/MetatagAvailableTokensTest.php @@ -35,7 +35,9 @@ public function testNodeMetatagDefaultsPage() { // @todo This method exists on JsWebAssert() and is not available from the // assertSession() object? - $this->assertSession()->assertWaitOnAjaxRequest(); + /** @var \Drupal\FunctionalJavascriptTests\JSWebAssert $assert_session */ + $assert_session = $this->assertSession(); + $assert_session->assertWaitOnAjaxRequest(); $token_dialog = $page->find('css', '.token-tree-dialog'); diff --git a/web/modules/metatag/tests/src/Kernel/Form/MetatagSettingsFormTest.php b/web/modules/metatag/tests/src/Kernel/Form/MetatagSettingsFormTest.php index 39f5c033bff5493ec4112f2188a56fc70237a9ae..98bf3c228a33a51448d4a168efa1f5562a3b2785 100644 --- a/web/modules/metatag/tests/src/Kernel/Form/MetatagSettingsFormTest.php +++ b/web/modules/metatag/tests/src/Kernel/Form/MetatagSettingsFormTest.php @@ -48,7 +48,8 @@ protected function setUp(): void { $this->installConfig(static::$modules); $this->metatagSettingsForm = new MetatagSettingsForm( - $this->container->get('config.factory') + $this->container->get('config.factory'), + $this->container->get('config.typed') ); } diff --git a/web/modules/metatag/tests/src/Kernel/MetatagManagerTest.php b/web/modules/metatag/tests/src/Kernel/MetatagManagerTest.php index fa11e2cb09aec860c38ef68de93e2072b8074507..e2fcf993feaada762d95a52ba288bd2d8cc135ca 100644 --- a/web/modules/metatag/tests/src/Kernel/MetatagManagerTest.php +++ b/web/modules/metatag/tests/src/Kernel/MetatagManagerTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\metatag\Kernel; +use Drupal\metatag\MetatagSeparator; use Drupal\KernelTests\KernelTestBase; /** @@ -11,6 +12,8 @@ */ class MetatagManagerTest extends KernelTestBase { + use MetatagSeparator; + /** * {@inheritdoc} */ @@ -46,7 +49,7 @@ class MetatagManagerTest extends KernelTestBase { /** * Config factory. * - * @var Drupal\Core\Config\ConfigFactoryInterface + * @var \Drupal\Core\Config\ConfigFactoryInterface */ protected $configFactory; @@ -220,7 +223,7 @@ public function testSeparator() { $this->assertEquals($expected, $value); // Confirm that if it's empty it falls back to ','. - $value = $this->metatagManager->getSeparator(); + $value = $this->getSeparator(); $expected = ','; $this->assertEquals($expected, $value); @@ -228,7 +231,7 @@ public function testSeparator() { $tags = $this->metatagManager->generateElements([ 'og_image_width' => 100, 'og_image_height' => 100, - 'og_image_url' => 'https://www.example.com/example/foo.png' . $this->metatagManager->getSeparator() . 'https://www.example.com/example/foo2.png', + 'og_image_url' => 'https://www.example.com/example/foo.png' . $this->getSeparator() . 'https://www.example.com/example/foo2.png', ]); $expected = [ @@ -287,7 +290,7 @@ public function testSeparator() { $this->assertEquals($expected, $value); // Make sure Metatag Manager correctly picks up the new value. - $value = $this->metatagManager->getSeparator(); + $value = $this->getSeparator(); $expected = '||'; $this->assertEquals($expected, $value); @@ -295,7 +298,7 @@ public function testSeparator() { $tags = $this->metatagManager->generateElements([ 'og_image_width' => 100, 'og_image_height' => 100, - 'og_image_url' => 'https://www.example.com/example/foo.png' . $this->metatagManager->getSeparator() . 'https://www.example.com/example/foo2.png', + 'og_image_url' => 'https://www.example.com/example/foo.png' . $this->getSeparator() . 'https://www.example.com/example/foo2.png', ]); $expected = [ diff --git a/web/modules/metatag/tests/src/Kernel/MetatagSerializationTest.php b/web/modules/metatag/tests/src/Kernel/MetatagSerializationTest.php index 4b65396e02c5fac23058e7922531c35df21fc620..576cc03bcef297beef93a01f209f62d45c32c83d 100644 --- a/web/modules/metatag/tests/src/Kernel/MetatagSerializationTest.php +++ b/web/modules/metatag/tests/src/Kernel/MetatagSerializationTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\metatag\Kernel; +use Drupal\Component\Serialization\Json; use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; @@ -95,9 +96,9 @@ public function testJsonapiNormalization() { 'name' => 'Llama', 'type' => 'entity_test', 'field_test' => [ - 'value' => [ + 'value' => Json::encode([ 'description' => 'This is a description for use in Search Engines', - ], + ]), ], ]); assert($entity instanceof EntityTest); diff --git a/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagDefaultsTest.php b/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagDefaultsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ede332a02b7a6338d6628637471cd82339d48126 --- /dev/null +++ b/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagDefaultsTest.php @@ -0,0 +1,136 @@ +<?php + +namespace Drupal\Tests\metatag\Kernel\Migrate\d7; + +use Drupal\metatag\Entity\MetatagDefaults; +use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase; + +/** + * Tests Metatag-D7 configuration source plugin. + * + * @group metatag + * @covers \Drupal\metatag\Plugin\migrate\source\d7\MetatagDefaults + */ +class MetatagDefaultsTest extends MigrateDrupal7TestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = [ + // Contrib modules. + 'token', + + // This module. + 'metatag', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->loadFixture(__DIR__ . '/../../../../fixtures/d7_metatag.php'); + + $this->installConfig(static::$modules); + $this->installSchema('system', ['sequences']); + $this->installEntitySchema('metatag_defaults'); + + // Run the Metatag defaults migration. + $this->executeMigrations([ + 'd7_metatag_defaults', + ]); + } + + /** + * Test Metatag default configuration migration from Drupal 7 to 8. + */ + public function testMetatag() { + // The expected structure of the config items. + $expected_configs = [ + 'global' => [ + 'langcode' => 'en', + 'label' => 'Global', + 'tags' => [ + 'description' => 'Mango heaven!', + 'robots' => 'nofollow, noindex', + 'title' => 'I\'m in heaven!', + ] + ], + 'node' => [ + 'langcode' => 'en', + 'label' => 'Node', + 'tags' => [ + 'description' => 'The summary is: [node:field_summary]', + 'keywords' => 'mango, ', + 'robots' => 'follow, index', + 'title' => '[node:title]', + ], + ], + 'node__article' => [ + 'langcode' => 'en', + 'label' => 'Node: Article', + 'tags' => [ + 'keywords' =>'Alphonso, Angie, Julie', + 'robots' => 'nofollow, noindex', + ], + ], + 'taxonomy_term' => [ + 'langcode' => 'en', + 'label' => 'Taxonomy Term', + 'tags' => [ + 'description' => 'The summary is: [term;description]', + 'keywords' => 'mango, ', + 'robots' => 'follow, index', + 'title' => '[term:name]', + ], + ], + 'taxonomy_term__tags' => [ + 'langcode' => 'en', + 'label' => 'Taxonomy Term: Tags', + 'tags' => [ + 'keywords' => 'Alphonso, Angie, Julie', + 'robots' => 'nofollow, noindex', + ], + ], + 'user' => [ + 'langcode' => 'en', + 'label' => 'User', + 'tags' => [ + 'description' => 'The summary is: [user;name]', + 'keywords' => 'mango, ', + 'robots' => 'follow, index', + 'title' => '[user:name]', + ], + ], + '404' => [ + 'langcode' => 'en', + 'label' => '404 page not found', + ], + '404' => [ + 'langcode' => 'en', + 'label' => '404 page not found', + ], + ]; + + foreach ($expected_configs as $config_name => $config) { + $defaults = MetatagDefaults::load($config_name); + $this->assertNotNull($defaults); + // Convert the two names to strings because some of the config items are + // numeric. + $this->assertSame((string) $config_name, $defaults ? (string) $defaults->id() : ''); + + $this->assertSame($config['langcode'], $defaults->language()->getId()); + $this->assertSame($config['label'], $defaults->label()); + + // If a resultant tags value was expected, compare it against the migrated + // value. + if (!empty($config['tags'])) { + foreach ($config['tags'] as $tag_name => $value) { + $this->assertTrue($defaults->hasTag($tag_name)); + $this->assertSame($value, $defaults->getTag($tag_name)); + } + } + } + } + +} diff --git a/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagEntitiesTest.php b/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagEntitiesTest.php index b50ada28ba6bf63dfc570edbb66e8a40a4890f02..99d6534ba62096e6568f8492c84933c8fe1c801b 100644 --- a/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagEntitiesTest.php +++ b/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagEntitiesTest.php @@ -113,9 +113,9 @@ protected function setUp(): void { } /** - * Test Metatag migration from Drupal 7 to 8. + * Test Metatag entity data migration from Drupal 7 to 8. */ - public function testMetatag() { + public function testMetatagEntities() { /** @var \Drupal\node\Entity\Node $node */ $node = Node::load(998); $this->assertInstanceOf(NodeInterface::class, $node); diff --git a/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagFieldTest.php b/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagFieldTest.php index 80559dfdc0ec6ee0c9969d98825f9eeee340d93a..6b798c2344b2b31acda0638efae89d6d1042ae37 100644 --- a/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagFieldTest.php +++ b/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagFieldTest.php @@ -24,7 +24,7 @@ class MetatagFieldTest extends MigrateSqlSourceTestBase { /** * {@inheritdoc} */ - public function providerSource() { + public static function providerSource() { $tests = []; $tests[0]['source_data']['metatag'] = [ [ diff --git a/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagSettingsTest.php b/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagSettingsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7e52c9cd8168821e60fda5b95167b164666d1e6f --- /dev/null +++ b/web/modules/metatag/tests/src/Kernel/Migrate/d7/MetatagSettingsTest.php @@ -0,0 +1,64 @@ +<?php + +namespace Drupal\Tests\metatag\Kernel\Migrate\d7; + +use Drupal\Component\Serialization\Json; +use Drupal\Core\StreamWrapper\PublicStream; +use Drupal\Core\StreamWrapper\StreamWrapperInterface; +use Drupal\node\Entity\Node; +use Drupal\node\NodeInterface; +use Drupal\taxonomy\Entity\Term; +use Drupal\taxonomy\TermInterface; +use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase; +use Drupal\user\Entity\User; +use Drupal\user\UserInterface; + +/** + * Tests migration of misc variables from Metatag-D7. + * + * @group metatag + */ +class MetatagSettingsTest extends MigrateDrupal7TestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = [ + // Contrib modules. + 'token', + + // This module. + 'metatag', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->loadFixture(__DIR__ . '/../../../../fixtures/d7_metatag.php'); + + // Runs the Metatag-D7 settings migration. + $this->executeMigrations(['d7_metatag_settings']); + } + + /** + * Test Metatag settings migration from Drupal 7 to 8. + */ + public function testSettings() { + // Load the Metatag config object. + $config = \Drupal::config('metatag.settings'); + + // Compare the settings in the config object with what was migrated. + $this->assertSame($config->get('separator'), '||'); + $this->assertSame($config->get('use_maxlength'), FALSE); + + // Compare each of the maxlength trim options. + $trims = $config->get('tag_trim_maxlength'); + $this->assertSame($trims['title'], 50); + $this->assertSame($trims['description'], 200); + $this->assertSame($trims['abstract'], 150); + $this->assertSame($trims['keywords'], 1000); + } + +} diff --git a/web/modules/metatag/tests/src/Kernel/Plugin/migrate/source/d7/MetatagFieldInstanceTest.php b/web/modules/metatag/tests/src/Kernel/Plugin/migrate/source/d7/MetatagFieldInstanceTest.php index fcb2a988317fe3905137c6db31507a4178440f14..7d23e998621070ce8b3c7cdf9f71fc51caa47265 100644 --- a/web/modules/metatag/tests/src/Kernel/Plugin/migrate/source/d7/MetatagFieldInstanceTest.php +++ b/web/modules/metatag/tests/src/Kernel/Plugin/migrate/source/d7/MetatagFieldInstanceTest.php @@ -78,7 +78,7 @@ public function setUp(): void { /** * {@inheritdoc} */ - public function providerSource() { + public static function providerSource(): array { $tests[0]['source_data']['metatag'] = [ [ 'entity_type' => 'node', diff --git a/web/modules/metatag/tests/src/Unit/MetatagSupportedEntitiesTest.php b/web/modules/metatag/tests/src/Unit/MetatagSupportedEntitiesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0081a27b0962945f301ca5b9f258084af668fae3 --- /dev/null +++ b/web/modules/metatag/tests/src/Unit/MetatagSupportedEntitiesTest.php @@ -0,0 +1,80 @@ +<?php + +namespace Drupal\Tests\metatag\Unit; + +use Drupal\Core\Entity\ContentEntityType; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\metatag\Form\MetatagDefaultsForm; +use Drupal\Tests\UnitTestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This class validates all the entities types that are supported by metatag. + * + * @group metatag + */ +class MetatagSupportedEntitiesTest extends UnitTestCase { + + /** + * Tests the getSupportedEntityTypes method from MetatagDefaultsForm. + */ + public function testGetSupportedEntityTypes() { + // Create a mock entity type manager. + $entity_type_manager = $this->createMock(EntityTypeManagerInterface::class); + + // Create a mock entity type definition. + $example_entity_type = $this->createMock(ContentEntityType::class); + $example_entity_type + ->method('get') + ->withAnyParameters() + ->willReturn( + [ + 'links' => [], + ], + ); + $example_entity_type + ->method('getLabel') + ->withAnyParameters() + ->willReturn('Example entity type'); + + $entity_type_manager->method('getDefinitions') + ->withAnyParameters() + ->willReturn([ + // The only supported entity type. + 'page' => $example_entity_type, + // The list bellow is the same as the one in the original method. + 'block_content' => $example_entity_type, + 'contact_message' => $example_entity_type, + 'menu_link_content' => $example_entity_type, + 'path_alias' => $example_entity_type, + 'shortcut' => $example_entity_type, + 'commerce_order' => $example_entity_type, + 'commerce_payment' => $example_entity_type, + 'commerce_payment_method' => $example_entity_type, + 'commerce_promotion' => $example_entity_type, + 'commerce_promotion_coupon' => $example_entity_type, + 'commerce_shipment' => $example_entity_type, + 'commerce_shipping_method' => $example_entity_type, + 'commerce_stock_location' => $example_entity_type, + 'linkcheckerlink' => $example_entity_type, + 'redirect' => $example_entity_type, + 'salesforce_mapped_object' => $example_entity_type, + 'webform_submission' => $example_entity_type, + ]); + + // Instantiate drupal container. + $container = new ContainerBuilder(); + $container->set('entity_type.manager', $entity_type_manager); + \Drupal::setContainer($container); + + // Call the method under test. + $result = MetatagDefaultsForm::getSupportedEntityTypes(); + + // Assert that the result contains the expected supported entity types. + $this->assertArrayHasKey('page', $result, 'Only the page entity type should be supported.'); + + // Only one supported entity type should be returned. + $this->assertCount(1, $result, 'Only one entity type should be supported.'); + } + +} diff --git a/web/modules/metatag/tests/src/Unit/MetatagTrimmerTest.php b/web/modules/metatag/tests/src/Unit/MetatagTrimmerTest.php index fd4820e4481ce202a1d08802aee46e1e742ff13f..a40b387406074044250b7108e6462b9cd79fafe9 100644 --- a/web/modules/metatag/tests/src/Unit/MetatagTrimmerTest.php +++ b/web/modules/metatag/tests/src/Unit/MetatagTrimmerTest.php @@ -90,4 +90,39 @@ public function testTrimByMethod() { $this->assertEquals('Test 123', $trimResult3); } + /** + * Tests how the end of the string is trimmed. + */ + public function testEndOfTheWordTrimming() { + // Test standard end char trimming: + $trimResult = $this->metatagTrimmer->trimEndChars('Test '); + $this->assertEquals('Test', $trimResult); + + $trimResult = $this->metatagTrimmer->trimEndChars("Test\n"); + $this->assertEquals('Test', $trimResult); + + // Test end char trimming with specific chars provided: + $trimEndChars = '|"'; + $trimResult = $this->metatagTrimmer->trimEndChars('Test|', $trimEndChars); + $this->assertEquals('Test', $trimResult); + + $trimEndChars .= "\\n"; + $trimResult = $this->metatagTrimmer->trimEndChars("Test\\n", $trimEndChars); + $this->assertEquals("Test", $trimResult); + + $trimResult = $this->metatagTrimmer->trimEndChars('Test"', $trimEndChars); + $this->assertEquals('Test', $trimResult); + + $trimEndChars .= "'"; + $trimResult = $this->metatagTrimmer->trimEndChars("Test'", $trimEndChars); + $this->assertEquals('Test', $trimResult); + + $trimEndChars .= '&'; + $trimResult = $this->metatagTrimmer->trimEndChars("Test&'", $trimEndChars); + $this->assertEquals('Test', $trimResult); + + $trimResult = $this->metatagTrimmer->trimEndChars("Test&|'", $trimEndChars); + $this->assertEquals('Test', $trimResult); + } + } diff --git a/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml b/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..04a6cf2c75bfe3171a287b5cff628e0ed45794a9 --- /dev/null +++ b/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml @@ -0,0 +1,12 @@ +type: module +name: 'Actions Permissions' +description: 'Adds access permissions on all actions allowing admins to restrict access on a per-role basis.' +package: 'Views Bulk Operations' +core_version_requirement: ^9 || ^10 || ^11 +dependencies: + - drupal:views_bulk_operations + +# Information added by Drupal.org packaging script on 2024-11-08 +version: '4.3.2' +project: 'views_bulk_operations' +datestamp: 1731070021 diff --git a/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.permissions.yml b/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.permissions.yml new file mode 100644 index 0000000000000000000000000000000000000000..e2ccdd1f01915b1a0210014861b8e9bfc01ea57a --- /dev/null +++ b/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.permissions.yml @@ -0,0 +1,2 @@ +permission_callbacks: + - \Drupal\actions_permissions\ActionsPermissions::permissions diff --git a/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.services.yml b/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..a00f9ec1e55d33c1f3dec68f5c798eb1e0828eb5 --- /dev/null +++ b/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.services.yml @@ -0,0 +1,8 @@ +services: + _defaults: + autowire: true + + actions_permissions.views_bulk_operations_actions: + class: Drupal\actions_permissions\EventSubscriber\ActionsPermissionsEventSubscriber + tags: + - { name: event_subscriber } diff --git a/web/modules/views_bulk_operations/modules/actions_permissions/src/ActionsPermissions.php b/web/modules/views_bulk_operations/modules/actions_permissions/src/ActionsPermissions.php new file mode 100644 index 0000000000000000000000000000000000000000..8c64bee55c9dcb8be24473974bdd60596c770f93 --- /dev/null +++ b/web/modules/views_bulk_operations/modules/actions_permissions/src/ActionsPermissions.php @@ -0,0 +1,83 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\actions_permissions; + +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Create permissions for existing actions. + */ +final class ActionsPermissions implements ContainerInjectionInterface { + + use StringTranslationTrait; + + /** + * Constructor. + */ + public function __construct( + protected readonly ViewsBulkOperationsActionManager $actionManager, + protected readonly EntityTypeManagerInterface $entityTypeManager, + ) {} + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.views_bulk_operations_action'), + $container->get('entity_type.manager') + ); + } + + /** + * Get permissions for Actions. + * + * @return array + * Permissions array. + */ + public function permissions(): array { + $permissions = []; + $entity_type_definitions = $this->entityTypeManager->getDefinitions(); + + // Get definitions that will not be altered by actions_permissions. + foreach ($this->actionManager->getDefinitions([ + 'skip_actions_permissions' => TRUE, + 'nocache' => TRUE, + ]) as $definition) { + + // Skip actions that define their own requirements. + if (!empty($definition['requirements'])) { + continue; + } + + $id = 'execute ' . $definition['id']; + $entity_type = NULL; + if (empty($definition['type'])) { + $entity_type = $this->t('all entity types'); + $id .= ' all'; + } + elseif (isset($entity_type_definitions[$definition['type']])) { + $entity_type = $entity_type_definitions[$definition['type']]->getLabel(); + $id .= ' ' . $definition['type']; + } + + if (isset($entity_type)) { + $permissions[$id] = [ + 'title' => $this->t('Execute the %action action on %type.', [ + '%action' => $definition['label'], + '%type' => $entity_type, + ]), + ]; + } + } + + return $permissions; + } + +} diff --git a/web/modules/views_bulk_operations/modules/actions_permissions/src/EventSubscriber/ActionsPermissionsEventSubscriber.php b/web/modules/views_bulk_operations/modules/actions_permissions/src/EventSubscriber/ActionsPermissionsEventSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..f22a701c9c0538e41384e3f307a56cababc7c838 --- /dev/null +++ b/web/modules/views_bulk_operations/modules/actions_permissions/src/EventSubscriber/ActionsPermissionsEventSubscriber.php @@ -0,0 +1,73 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\actions_permissions\EventSubscriber; + +use Drupal\Component\EventDispatcher\Event; +use Drupal\Core\Session\AccountInterface; +use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Defines module event subscriber class. + * + * Alters actions to make use of permissions created by the module. + */ +final class ActionsPermissionsEventSubscriber implements EventSubscriberInterface { + + // Subscribe to the VBO event with low priority + // to let other modules alter requirements first. + private const PRIORITY = -999; + + /** + * Constructor. + */ + public function __construct( + private readonly AccountInterface $currentUser, + ) {} + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[ViewsBulkOperationsActionManager::ALTER_ACTIONS_EVENT][] = [ + 'alterActions', + self::PRIORITY, + ]; + return $events; + } + + /** + * Alter the actions' definitions. + * + * @var \Drupal\Component\EventDispatcher\Event $event + * The event to respond to. + */ + public function alterActions(Event $event): void { + + // Don't alter definitions if this is invoked by the + // own permissions creating method. + if (!empty($event->alterParameters['skip_actions_permissions'])) { + return; + } + + foreach ($event->definitions as $action_id => $definition) { + + // Only process actions that don't define their own requirements. + if (empty($definition['requirements'])) { + $permission_id = 'execute ' . $definition['id']; + if (empty($definition['type'])) { + $permission_id .= ' all'; + } + else { + $permission_id .= ' ' . $definition['type']; + } + if (!$this->currentUser->hasPermission($permission_id)) { + unset($event->definitions[$action_id]); + } + } + } + } + +} diff --git a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml index ebbffa12525048ad3ef54b974a685cfeec38acd9..ca97693910bca7bfa6390b6e0ecf7719df176ea4 100644 --- a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml +++ b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml @@ -7,7 +7,7 @@ hidden: true dependencies: - drupal:views_bulk_operations -# Information added by Drupal.org packaging script on 2024-10-23 -version: '4.3.1' +# Information added by Drupal.org packaging script on 2024-11-08 +version: '4.3.2' project: 'views_bulk_operations' -datestamp: 1729683246 +datestamp: 1731070021 diff --git a/web/modules/views_bulk_operations/src/Form/ConfigureAction.php b/web/modules/views_bulk_operations/src/Form/ConfigureAction.php index 4cea420e449838f6192472ff31e04386a5754cd9..ddeecfee19611a9bda054b645e980df07736b1b8 100644 --- a/web/modules/views_bulk_operations/src/Form/ConfigureAction.php +++ b/web/modules/views_bulk_operations/src/Form/ConfigureAction.php @@ -2,6 +2,7 @@ namespace Drupal\views_bulk_operations\Form; +use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; @@ -17,6 +18,11 @@ class ConfigureAction extends FormBase { use ViewsBulkOperationsFormTrait; + // We need this if we want to keep the readonly in constructor property + // promotion and not have errors in plugins that use AJAX in their + // buildConfigurationForm() method. + use DependencySerializationTrait; + /** * Constructor. * diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php index a9dc3537ba2ef4035ef2ddacfa0f89a9032292e7..64e0bce6e83b21dcc0f203fc8af6c0445ec289ae 100644 --- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php +++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php @@ -96,7 +96,6 @@ protected function findDefinitions() { $this->processDefinition($definition, $plugin_id); } - $this->alterDefinitions($definitions); foreach ($definitions as $plugin_id => $plugin_definition) { // If the plugin definition is an object, attempt to convert it to an // array, if that is not possible, skip further processing. @@ -127,12 +126,16 @@ public function getDefinitions(array $parameters = []) { $definitions = $this->getCachedDefinitions(); } if (!isset($definitions)) { - $this->alterParameters = $parameters; $definitions = $this->findDefinitions($parameters); $this->setCachedDefinitions($definitions); } + // Alter definitions after retrieving all from the cache for maximum + // flexibility. + $this->alterParameters = $parameters; + $this->alterDefinitions($definitions); + return $definitions; } diff --git a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml index c73dce5b442ca24a790e8a8dfddb8ada1fc7f862..5dee5eaa09e693dd6bd3bffd3a40bbebfe9d32c6 100644 --- a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml +++ b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml @@ -7,7 +7,7 @@ dependencies: - drupal:views_bulk_operations - drupal:node -# Information added by Drupal.org packaging script on 2024-10-23 -version: '4.3.1' +# Information added by Drupal.org packaging script on 2024-11-08 +version: '4.3.2' project: 'views_bulk_operations' -datestamp: 1729683246 +datestamp: 1731070021 diff --git a/web/modules/views_bulk_operations/views_bulk_operations.info.yml b/web/modules/views_bulk_operations/views_bulk_operations.info.yml index bd9694448df1ce08c67d6150b5e9d28f95afd307..e06e76b8c421a2f7c64fc7930c1523459382e71e 100644 --- a/web/modules/views_bulk_operations/views_bulk_operations.info.yml +++ b/web/modules/views_bulk_operations/views_bulk_operations.info.yml @@ -6,7 +6,7 @@ core_version_requirement: ^10.3 || ^11 dependencies: - drupal:views -# Information added by Drupal.org packaging script on 2024-10-23 -version: '4.3.1' +# Information added by Drupal.org packaging script on 2024-11-08 +version: '4.3.2' project: 'views_bulk_operations' -datestamp: 1729683246 +datestamp: 1731070021