From 3f1b4f78ad97c0c733c579a99fb73c951e49f50d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucy=20Tom=C3=A1s?= Date: Wed, 6 Mar 2024 20:59:39 +0100 Subject: [PATCH] fix: hreflang tags is multiliungual sites (#692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nícholas André --- .changeset/fuzzy-kiwis-decide.md | 5 + packages/next/src/components/Yoast.tsx | 4 + projects/wp-nextjs/.env | 2 +- projects/wp-nextjs/.env.development | 2 +- projects/wp-nextjs/headstartwp.config.js | 4 +- projects/wp-nextjs/next.config.js | 3 +- wp/headless-wp/.wp-env.json | 13 +- wp/headless-wp/composer.json | 12 +- wp/headless-wp/composer.lock | 165 +++++------ .../Polylang/PolylangYoastPresenter.php | 158 +++++++++++ .../classes/Integrations/YoastSEO.php | 34 +++ wp/headless-wp/tests/php/bootstrap.php | 6 + .../tests/php/includes/PLLUnitTestCase.php | 152 ++++++++++ .../TestGutenbergIntegration.php | 0 .../php/tests/TestPolylangIntegration.php | 260 ++++++++++++++++++ .../php/{includes => tests}/TestPreview.php | 0 16 files changed, 733 insertions(+), 87 deletions(-) create mode 100644 .changeset/fuzzy-kiwis-decide.md create mode 100644 wp/headless-wp/includes/classes/Integrations/Polylang/PolylangYoastPresenter.php create mode 100644 wp/headless-wp/tests/php/includes/PLLUnitTestCase.php rename wp/headless-wp/tests/php/{includes => tests}/TestGutenbergIntegration.php (100%) create mode 100644 wp/headless-wp/tests/php/tests/TestPolylangIntegration.php rename wp/headless-wp/tests/php/{includes => tests}/TestPreview.php (100%) diff --git a/.changeset/fuzzy-kiwis-decide.md b/.changeset/fuzzy-kiwis-decide.md new file mode 100644 index 000000000..87aa92376 --- /dev/null +++ b/.changeset/fuzzy-kiwis-decide.md @@ -0,0 +1,5 @@ +--- +"@headstartwp/headstartwp": patch +--- + +fix: hreflangs tags on multilingual sites diff --git a/packages/next/src/components/Yoast.tsx b/packages/next/src/components/Yoast.tsx index 6cc0641a3..233ad0bf1 100644 --- a/packages/next/src/components/Yoast.tsx +++ b/packages/next/src/components/Yoast.tsx @@ -77,6 +77,10 @@ export function Yoast({ seo, useHtml = false }: Props) { props.href = convertUrl(props.href, hostUrl, sourceUrl); } + if (props.rel === 'alternate') { + props.href = convertUrl(props.href, hostUrl, sourceUrl); + } + if (props.property === 'og:url') { props.content = convertUrl(props.content, hostUrl, sourceUrl); } diff --git a/projects/wp-nextjs/.env b/projects/wp-nextjs/.env index ab27393b7..b013281ea 100644 --- a/projects/wp-nextjs/.env +++ b/projects/wp-nextjs/.env @@ -1,2 +1,2 @@ NEXT_PUBLIC_HEADLESS_WP_URL=https://js1.10up.com -HOST_URL=https://js1.10up.com \ No newline at end of file +NEXT_PUBLIC_HOST_URL=https://js1.10up.com \ No newline at end of file diff --git a/projects/wp-nextjs/.env.development b/projects/wp-nextjs/.env.development index e56d0dcff..7974a7168 100644 --- a/projects/wp-nextjs/.env.development +++ b/projects/wp-nextjs/.env.development @@ -1,5 +1,5 @@ NEXT_PUBLIC_HEADLESS_WP_URL=http://localhost:8888 -HOST_URL=http://localhost:3000 +NEXT_PUBLIC_HOST_URL=http://localhost:3000 ENABLE_POLYLANG_INTEGRATION=false ENABLE_REQUEST_DEBUG=true ENABLE_REDIRECT_DEBUG=true diff --git a/projects/wp-nextjs/headstartwp.config.js b/projects/wp-nextjs/headstartwp.config.js index ab0c99146..8e2eca688 100644 --- a/projects/wp-nextjs/headstartwp.config.js +++ b/projects/wp-nextjs/headstartwp.config.js @@ -8,7 +8,7 @@ module.exports = { * The WordPress Source Url */ sourceUrl: process.env.NEXT_PUBLIC_HEADLESS_WP_URL, - hostUrl: process.env.HOST_URL, + hostUrl: process.env.NEXT_PUBLIC_HOST_URL, customPostTypes: [ // this is just an example { @@ -43,7 +43,7 @@ module.exports = { enable: true, }, polylang: { - enable: process.env?.ENABLE_POLYLANG_INTEGRATION === 'true', + enable: process?.env?.NEXT_PUBLIC_ENABLE_POLYLANG_INTEGRATION === 'true', }, }, diff --git a/projects/wp-nextjs/next.config.js b/projects/wp-nextjs/next.config.js index a1a9f944c..6dd4239ec 100644 --- a/projects/wp-nextjs/next.config.js +++ b/projects/wp-nextjs/next.config.js @@ -14,10 +14,9 @@ const nextConfig = { ignoreDuringBuilds: true, }, }; - // if you are not using polylang integration you can remove this code // if you are replace the locales with the ones you are using -if (process.env?.ENABLE_POLYLANG_INTEGRATION === 'true') { +if (process.env?.NEXT_PUBLIC_ENABLE_POLYLANG_INTEGRATION === 'true') { nextConfig.i18n = { locales: ['en', 'pt'], defaultLocale: 'en', diff --git a/wp/headless-wp/.wp-env.json b/wp/headless-wp/.wp-env.json index 1d48e55a2..13524fa13 100644 --- a/wp/headless-wp/.wp-env.json +++ b/wp/headless-wp/.wp-env.json @@ -4,5 +4,16 @@ "../local-plugin", "https://downloads.wordpress.org/plugin/wordpress-seo.22.1.zip", "https://downloads.wordpress.org/plugin/safe-redirect-manager.2.1.1.zip" - ] + ], + "env": { + "tests": { + "plugins": [ + ".", + "../local-plugin", + "https://downloads.wordpress.org/plugin/wordpress-seo.22.1.zip", + "https://downloads.wordpress.org/plugin/safe-redirect-manager.2.1.1.zip", + "https://downloads.wordpress.org/plugin/polylang.3.5.4.zip" + ] + } +} } diff --git a/wp/headless-wp/composer.json b/wp/headless-wp/composer.json index b095beae8..c33775371 100644 --- a/wp/headless-wp/composer.json +++ b/wp/headless-wp/composer.json @@ -25,6 +25,14 @@ "reference": "trunk" } } + }, + { + "type": "composer", + "url": "https://wpackagist.org", + "only": [ + "wpackagist-plugin/*", + "wpackagist-theme/*" + ] } ], "require-dev": { @@ -56,7 +64,9 @@ }, "extra": { "installer-paths": { - "vendor/{$name}/": ["type:wordpress-plugin"] + "vendor/{$name}/": [ + "type:wordpress-plugin" + ] } } } diff --git a/wp/headless-wp/composer.lock b/wp/headless-wp/composer.lock index b128a4dd3..e05ab27b6 100644 --- a/wp/headless-wp/composer.lock +++ b/wp/headless-wp/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": "605e4a4bc9887f52888269f17af4203b", + "content-hash": "b21c0dd8f2ff8c117af692b0d6499d90", "packages": [ { "name": "composer/installers", @@ -153,16 +153,16 @@ }, { "name": "yoast/wordpress-seo", - "version": "22.1", + "version": "22.2", "source": { "type": "git", "url": "https://github.com/Yoast-dist/wordpress-seo.git", - "reference": "2b76a34e4289ed777faf38e09e71c0660719f389" + "reference": "9702b0674264b432c14fc24e0c5c15baa05fbecf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast-dist/wordpress-seo/zipball/2b76a34e4289ed777faf38e09e71c0660719f389", - "reference": "2b76a34e4289ed777faf38e09e71c0660719f389", + "url": "https://api.github.com/repos/Yoast-dist/wordpress-seo/zipball/9702b0674264b432c14fc24e0c5c15baa05fbecf", + "reference": "9702b0674264b432c14fc24e0c5c15baa05fbecf", "shasum": "" }, "require": { @@ -222,7 +222,7 @@ "source": "https://github.com/Yoast/wordpress-seo", "wiki": "https://github.com/Yoast/wordpress-seo/wiki" }, - "time": "2024-02-20T09:24:04+00:00" + "time": "2024-03-05T09:06:40+00:00" } ], "packages-dev": [ @@ -268,16 +268,16 @@ }, { "name": "antecedent/patchwork", - "version": "2.1.27", + "version": "2.1.28", "source": { "type": "git", "url": "https://github.com/antecedent/patchwork.git", - "reference": "16a1ab81559aabf14acb616141e801b32777f085" + "reference": "6b30aff81ebadf0f2feb9268d3e08385cebcc08d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antecedent/patchwork/zipball/16a1ab81559aabf14acb616141e801b32777f085", - "reference": "16a1ab81559aabf14acb616141e801b32777f085", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/6b30aff81ebadf0f2feb9268d3e08385cebcc08d", + "reference": "6b30aff81ebadf0f2feb9268d3e08385cebcc08d", "shasum": "" }, "require": { @@ -310,9 +310,9 @@ ], "support": { "issues": "https://github.com/antecedent/patchwork/issues", - "source": "https://github.com/antecedent/patchwork/tree/2.1.27" + "source": "https://github.com/antecedent/patchwork/tree/2.1.28" }, - "time": "2023-12-03T18:46:49+00:00" + "time": "2024-02-06T09:26:11+00:00" }, { "name": "automattic/vipwpcs", @@ -824,16 +824,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.0", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc" + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc", - "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", "shasum": "" }, "require": { @@ -876,26 +876,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" }, - "time": "2024-01-07T17:17:35+00:00" + "time": "2024-03-05T20:51:40+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -936,9 +937,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -993,16 +1000,16 @@ }, { "name": "php-stubs/wordpress-stubs", - "version": "v6.4.1", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "6d6063cf9464a306ca2a0529705d41312b08500b" + "reference": "6105bdab2f26c0204fe90ecc53d5684754550e8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/6d6063cf9464a306ca2a0529705d41312b08500b", - "reference": "6d6063cf9464a306ca2a0529705d41312b08500b", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/6105bdab2f26c0204fe90ecc53d5684754550e8f", + "reference": "6105bdab2f26c0204fe90ecc53d5684754550e8f", "shasum": "" }, "require-dev": { @@ -1011,9 +1018,9 @@ "php": "^7.4 || ~8.0.0", "php-stubs/generator": "^0.8.3", "phpdocumentor/reflection-docblock": "^5.3", - "phpstan/phpstan": "^1.10.12", + "phpstan/phpstan": "^1.10.49", "phpunit/phpunit": "^9.5", - "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.8" + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.11" }, "suggest": { "paragonie/sodium_compat": "Pure PHP implementation of libsodium", @@ -1034,9 +1041,9 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.4.1" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.4.3" }, - "time": "2023-11-10T00:33:47+00:00" + "time": "2024-02-11T18:56:19+00:00" }, { "name": "php-stubs/wordpress-tests-stubs", @@ -1420,16 +1427,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.30", + "version": "9.2.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", "shasum": "" }, "require": { @@ -1486,7 +1493,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" }, "funding": [ { @@ -1494,7 +1501,7 @@ "type": "github" } ], - "time": "2023-12-22T06:47:57+00:00" + "time": "2024-03-02T06:37:42+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1739,16 +1746,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.16", + "version": "9.6.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f" + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f", - "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", "shasum": "" }, "require": { @@ -1822,7 +1829,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.16" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" }, "funding": [ { @@ -1838,20 +1845,20 @@ "type": "tidelift" } ], - "time": "2024-01-19T07:03:14+00:00" + "time": "2024-02-23T13:14:51+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -1886,7 +1893,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -1894,7 +1901,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -2140,16 +2147,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -2194,7 +2201,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -2202,7 +2209,7 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", @@ -2269,16 +2276,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -2334,7 +2341,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -2342,20 +2349,20 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -2398,7 +2405,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -2406,7 +2413,7 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", @@ -2864,16 +2871,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.8.1", + "version": "3.9.0", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "14f5fff1e64118595db5408e946f3a22c75807f7" + "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/14f5fff1e64118595db5408e946f3a22c75807f7", - "reference": "14f5fff1e64118595db5408e946f3a22c75807f7", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", "shasum": "" }, "require": { @@ -2940,20 +2947,20 @@ "type": "open_collective" } ], - "time": "2024-01-11T20:47:48+00:00" + "time": "2024-02-16T15:06:51+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -2982,7 +2989,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -2990,7 +2997,7 @@ "type": "github" } ], - "time": "2023-11-20T00:12:19+00:00" + "time": "2024-03-03T12:36:25+00:00" }, { "name": "wordpress/wordpress-develop", diff --git a/wp/headless-wp/includes/classes/Integrations/Polylang/PolylangYoastPresenter.php b/wp/headless-wp/includes/classes/Integrations/Polylang/PolylangYoastPresenter.php new file mode 100644 index 000000000..10fc4fd0b --- /dev/null +++ b/wp/headless-wp/includes/classes/Integrations/Polylang/PolylangYoastPresenter.php @@ -0,0 +1,158 @@ +get(); + + if ( ! empty( $hreflangs ) ) { + return $hreflangs; + } + } + + return ''; + } + + /** + * Get hreflang tags for the current post. + * https://polylang.pro/hreflang-tag-attributes-and-polylang-everything-you-need-to-know + * + * @return string Hreflang tags or empty string. + */ + public function get() { + $source = $this->presentation->source; + + if ( ! $source instanceof \WP_Post && ! $source instanceof \WP_Term ) { + return ''; + } + + $languages = pll_languages_list(); + + if ( 2 > count( $languages ) ) { + return ''; + } + + $hreflangs_output = ''; + $hreflangs_urls = []; + + $post_id = $source instanceof \WP_Post ? $source->ID : false; + $translations = []; + $is_taxonomy = false; + $is_homepage = false; + $term = null; + + $paged = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_NUMBER_INT ); + + // Don't output anything on paged archives: see https://wordpress.org/support/topic/hreflang-on-page2 + // Don't output anything on paged pages and paged posts + if ( 1 < $paged ) { + return ''; + } + + // Get Term or Post translations. + if ( $source instanceof \WP_term && pll_is_translated_taxonomy( $source->taxonomy ) ) { + $term = $source; + $translations = pll_get_term_translations( $term->term_id ); + $is_taxonomy = true; + } else { + $translations = pll_get_post_translations( $post_id ); + } + + if ( empty( $translations ) ) { + return ''; + } + + // Herflangs for archive first pages. + if ( $is_taxonomy ) { + foreach ( $translations as $language => $translated_id ) { + // Get term object for translations and only include hreflang tags if term has posts. + $term_obj = $translated_id === $term->term_id ? $term : get_term( $translated_id, $term->taxonomy ); + if ( 0 === $term_obj->count ) { + continue; + } + + $hreflangs_urls[ $language ] = get_term_link( $translated_id ); + } + } else { + $homepages = $this->get_homepage_post_ids(); + + // Herflangs for single posts. + foreach ( $translations as $language => $translated_id ) { + + if ( in_array( $translated_id, $homepages, true ) ) { + $is_homepage = true; + } + + // Only add hreflang tags for published posts. + if ( 'publish' !== get_post_status( $translated_id ) ) { + continue; + } + + $hreflangs_urls[ $language ] = get_the_permalink( $translated_id ); + } + } + + // Do not add hreflang tags if page has no translations, count is 1 as it includes the existing post. + if ( empty( $hreflangs_urls ) || ( is_array( $hreflangs_urls ) && count( $hreflangs_urls ) <= 1 ) ) { + return ''; + } + + // Polylang generates an x-default hreflang only for the homepage only as recommended by Google. + $polylang_settings = get_option( 'polylang' ); + if ( $is_homepage && ! $polylang_settings['hide_default'] && $polylang_settings['force_lang'] < 3 ) { + $hreflangs_urls['x-default'] = home_url( '/' ); + } + + /** + * Filters the list of rel hreflang attributes + * + * @param array $hreflangs_urls Array of urls with language codes as keys + */ + $hreflangs_urls = apply_filters( 'tenup_headless_wp_hreflang_attributes', $hreflangs_urls ); + + // Build the hreflang tags if we have translations. + foreach ( $hreflangs_urls as $lang => $url ) { + $hreflangs_output .= sprintf( '' . "\n", esc_url( $url ), esc_attr( $lang ) ); + } + + return $hreflangs_output; + } + + /** + * Return list of homepage IDs for default lang and translated homepages. + * + * @return array List of homepage IDs. + */ + public function get_homepage_post_ids() { + $home_id = get_option( 'page_on_front' ); + if ( empty( $home_id ) ) { + return []; + } + + $result = [ $home_id ]; + + if ( function_exists( 'pll_get_post_translations' ) ) { + $result = pll_get_post_translations( $home_id ); // includes the default homepage lang. + } + + return $result; + } +} diff --git a/wp/headless-wp/includes/classes/Integrations/YoastSEO.php b/wp/headless-wp/includes/classes/Integrations/YoastSEO.php index 060aebd71..f4bdd7dd6 100644 --- a/wp/headless-wp/includes/classes/Integrations/YoastSEO.php +++ b/wp/headless-wp/includes/classes/Integrations/YoastSEO.php @@ -33,6 +33,9 @@ public function register() { add_filter( 'wpseo_title', [ $this, 'override_search_title' ], 10, 1 ); add_filter( 'wpseo_opengraph_title', [ $this, 'override_search_title' ], 10, 1 ); add_filter( 'wpseo_opengraph_url', [ $this, 'override_search_canonical' ], 10, 1 ); + + // Introduce hereflangs presenter to Yoast list of presenters. + add_action( 'rest_api_init', [ $this, 'wpseo_rest_api_hreflang_presenter' ], 10, 0 ); } /** @@ -288,4 +291,35 @@ public function override_search_canonical( $canonical ) { return $canonical; } + + /** + * Register custom presenter to handle hreflang tags in Yoast REST response. + * Called on rest_api_init + * + * Polylang adds hreflang tags by hooking into wp_head which only runs on the front end on a + * traditional WordPress setup. + * + * @return array + */ + public function wpseo_rest_api_hreflang_presenter() { + + $enable_hreflang = apply_filters( 'tenup_headless_wp_enable_hreflangs', true ); + + if ( ! $enable_hreflang ) { + return; + } + + add_filter( + 'wpseo_frontend_presenters', + function ( $presenters ) { + if ( ! class_exists( '\HeadlessWP\Integrations\Polylang\PolylangYoastPresenter' ) ) { + return $presenters; + } + + $presenters[] = new \HeadlessWP\Integrations\Polylang\PolylangYoastPresenter(); + + return $presenters; + } + ); + } } diff --git a/wp/headless-wp/tests/php/bootstrap.php b/wp/headless-wp/tests/php/bootstrap.php index 4883880e8..9c01bfdc0 100644 --- a/wp/headless-wp/tests/php/bootstrap.php +++ b/wp/headless-wp/tests/php/bootstrap.php @@ -18,6 +18,7 @@ require_once PROJECT_ROOT . '/vendor/autoload.php'; require_once PROJECT_ROOT . '/vendor/yoast/wp-test-utils/src/WPIntegration/bootstrap-functions.php'; + $_tests_dir = WPIntegration\get_path_to_wp_test_dir(); require_once $_tests_dir . '/includes/functions.php'; @@ -38,8 +39,11 @@ function tests_add_filter( ...$args ) {} * Manually load the plugin being tested. */ function _manually_load_plugin() { + require_once WP_PLUGIN_DIR . '/polylang/polylang.php'; + require_once WP_PLUGIN_DIR . '/wordpress-seo/wp-seo.php'; require_once dirname( __DIR__, 2 ) . '/plugin.php'; } + tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); // Require all files in the fixtures directory. @@ -48,3 +52,5 @@ function _manually_load_plugin() { } WPIntegration\bootstrap_it(); + +require_once PROJECT_ROOT . '/tests/php/includes/PLLUnitTestCase.php'; diff --git a/wp/headless-wp/tests/php/includes/PLLUnitTestCase.php b/wp/headless-wp/tests/php/includes/PLLUnitTestCase.php new file mode 100644 index 000000000..eefc6ac0c --- /dev/null +++ b/wp/headless-wp/tests/php/includes/PLLUnitTestCase.php @@ -0,0 +1,152 @@ +options = \PLL_Install::get_default_options(); + self::$polylang->options['hide_default'] = 0; // Force option to pre 2.1.5 value otherwise phpunit tests break on Travis + self::$polylang->model = new \PLL_Admin_Model( self::$polylang->options ); + self::$polylang->links_model = self::$polylang->model->get_links_model(); // We always need a links model due to PLL_Language::set_home_url() + + $_SERVER['SCRIPT_FILENAME'] = '/index.php'; // To pass the test in PLL_Choose_Lang::init() by default + + require_once POLYLANG_DIR . '/include/api.php'; + $GLOBALS['polylang'] = &self::$polylang; + } + + /** + * Clears up the test class + * + * @return void + */ + public static function wpTearDownAfterClass() { + self::delete_all_languages(); + } + + /** + * Sets up each test + * + * @return void + */ + public function setUp(): void { + parent::setUp(); + + add_filter( 'wp_doing_ajax', '__return_false' ); + } + + /** + * Clears up each test + * + * @return void + */ + public function tearDown(): void { + parent::tearDown(); + + unset( $GLOBALS['wp_settings_errors'] ); + self::$polylang->model->clean_languages_cache(); // We must do it before database ROLLBACK otherwhise it is impossible to delete the transient + } + + /** + * Helper function to create a language + * + * @param string $locale The locale to create + * @param array $args Arguments for the language + * + * @return void + */ + protected static function create_language( $locale, $args = [] ) { + $languages = include POLYLANG_DIR . '/settings/languages.php'; + $values = $languages[ $locale ]; + + $values['slug'] = $values['code']; + $values['rtl'] = (int) ( 'rtl' === $values['dir'] ); + $values['term_group'] = 0; // default term_group + + $args = array_merge( $values, $args ); + self::$polylang->model->add_language( $args ); + unset( $GLOBALS['wp_settings_errors'] ); // Clean "errors" + } + + /** + * A helper function to delete all languages + * + * @return void + */ + protected static function delete_all_languages() { + $languages = self::$polylang->model->get_languages_list(); + if ( is_array( $languages ) ) { + // Delete the default categories first + $tt = wp_get_object_terms( get_option( 'default_category' ), 'term_translations' ); + $terms = self::$polylang->model->term->get_translations( get_option( 'default_category' ) ); + + wp_delete_term( $tt, 'term_translations' ); + + foreach ( $terms as $t ) { + wp_delete_term( $t, 'category' ); + } + + foreach ( $languages as $lang ) { + self::$polylang->model->delete_language( $lang->term_id ); + unset( $GLOBALS['wp_settings_errors'] ); + } + } + } + + /** + * Switches to the given language + * + * @param string $lang The language to switch to + * + * @return void + */ + protected static function switch_language( string $lang ): void { + $language = \PLL()->model->get_language( $lang ); + + if ( false !== $language ) { + \PLL()->curlang = $language; + } + } + + /** + * Backport assertNotFalse to PHPUnit 3.6.12 which only runs in PHP 5.2. + * + * @param bool $condition The candition + * @param string $message The message + */ + public static function assertNotFalse( $condition, $message = '' ): void { + if ( version_compare( phpversion(), '5.3', '<' ) ) { + self::assertThat( $condition, self::logicalNot( self::isFalse() ), $message ); + } else { + parent::assertNotFalse( $condition, $message ); + } + } +} diff --git a/wp/headless-wp/tests/php/includes/TestGutenbergIntegration.php b/wp/headless-wp/tests/php/tests/TestGutenbergIntegration.php similarity index 100% rename from wp/headless-wp/tests/php/includes/TestGutenbergIntegration.php rename to wp/headless-wp/tests/php/tests/TestGutenbergIntegration.php diff --git a/wp/headless-wp/tests/php/tests/TestPolylangIntegration.php b/wp/headless-wp/tests/php/tests/TestPolylangIntegration.php new file mode 100644 index 000000000..ba4778d91 --- /dev/null +++ b/wp/headless-wp/tests/php/tests/TestPolylangIntegration.php @@ -0,0 +1,260 @@ +links_model ); + self::$polylang->init(); + + self::$rest_server = rest_get_server(); + } + + /** + * Sets up the tests + * + * @return void + */ + public function setUp(): void { + parent::setUp(); + + /** + * The rewrite class + * + * @var \WP_Rewrite $wp_rewrite + */ + global $wp_rewrite; + + $this->wp_rewrite = $wp_rewrite; + + /** + * Change the permalink structure + */ + $this->wp_rewrite->init(); + $this->wp_rewrite->set_permalink_structure( '/%postname%/' ); + } + + /** + * Returns the yoast head for a post + * + * @param integer $post_id The post id + * @param string $post_type The post type + * + * @return string + */ + protected function getYoastHeadForPost( int $post_id, string $post_type = 'post' ): string { + $post_type = get_post_type_object( $post_type ); + $request = new WP_REST_Request( 'GET', "/wp/v2/$post_type->rest_base/$post_id" ); + $response = rest_do_request( $request ); + $data = self::$rest_server->response_to_data( $response, false ); + + return $data['yoast_head'] ?? ''; + } + + /** + * Returns the yoast head for a term + * + * @param string $post_type The post type we're fetching from + * @param int $term_id The term id + * + * @return string + */ + protected function getYoastHeadForTerm( string $post_type, int $term_id ): string { + $post_type = get_post_type_object( $post_type ); + $request = new WP_REST_Request( 'GET', "/wp/v2/$post_type->rest_base", [ 'categories' => $term_id ] ); + $response = rest_do_request( $request ); + $data = self::$rest_server->response_to_data( $response, true ); + + return $data[0]['_embedded']['wp:term'][0][0]['yoast_head'] ?? ''; + } + + /** + * Test hreflang on single posts + * + * @return void + */ + public function test_hreflang_on_single_posts() { + $english_post = $this->factory()->post->create_and_get( + [ + 'post_title' => '[EN] Post', + 'post_status' => 'publish', + 'post_content' => 'english post', + 'post_type' => 'post', + ] + ); + + \pll_set_post_language( $english_post->ID, 'en' ); + + $portuguese_post = $this->factory()->post->create_and_get( + [ + 'post_title' => '[PT_BR] Post', + 'post_status' => 'publish', + 'post_content' => 'portugese post', + 'post_type' => 'post', + ] + ); + + \pll_set_post_language( $portuguese_post->ID, 'pt' ); + + \pll_save_post_translations( + [ + 'en' => $english_post->ID, + 'pt' => $portuguese_post->ID, + ] + ); + + $yoast_head = $this->getYoastHeadForPost( $english_post->ID ); + + $this->assertNotFalse( strpos( $yoast_head, '' ), 'hreflang was not found' ); + $this->assertNotFalse( strpos( $yoast_head, '' ), 'hreflang was not found' ); + + $yoast_head = $this->getYoastHeadForPost( $portuguese_post->ID ); + + $this->assertNotFalse( strpos( $yoast_head, '' ), 'hreflang was not found' ); + $this->assertNotFalse( strpos( $yoast_head, '' ), 'hreflang was not found' ); + } + + /** + * Test hreflang on homepage + * + * @return void + */ + public function test_hreflang_on_homepage() { + $english_page = $this->factory()->post->create( + [ + 'post_title' => '[EN] home page', + 'post_status' => 'publish', + 'post_content' => 'english page', + 'post_type' => 'page', + ] + ); + + \pll_set_post_language( $english_page, 'en' ); + + $portuguese_page = $this->factory()->post->create( + [ + 'post_title' => '[PT_BR] home Page', + 'post_status' => 'publish', + 'post_content' => 'portugese page', + 'post_type' => 'page', + ] + ); + + \pll_set_post_language( $portuguese_page, 'pt' ); + + \pll_save_post_translations( + [ + 'en' => $english_page, + 'pt' => $portuguese_page, + ] + ); + + update_option( 'page_on_front', $english_page ); + + $yoast_head = $this->getYoastHeadForPost( $english_page, 'page' ); + + $this->assertNotFalse( strpos( $yoast_head, '' ), 'hreflang was not found' ); + $this->assertNotFalse( strpos( $yoast_head, '' ), 'hreflang was not found' ); + $this->assertNotFalse( strpos( $yoast_head, '' ), 'hreflang was not found' ); + } + + /** + * Tests hrelang on taxonomy archives + * + * @return void + */ + public function test_hreflang_on_taxonomy_archive() { + $cat_en = $this->factory()->term->create_and_get( + [ + 'taxonomy' => 'category', + ] + ); + + $cat_en_id = $cat_en->term_id; + + \pll_set_term_language( $cat_en_id, 'en' ); + + $cat_pt = $this->factory()->term->create_and_get( + [ + 'taxonomy' => 'category', + ] + ); + + $cat_pt_id = $cat_pt->term_id; + + \pll_set_term_language( $cat_pt_id, 'pt' ); + + $posts_en = $this->factory()->post->create_many( + 5, + [ + 'post_category' => [ $cat_en_id ], + ] + ); + + foreach ( $posts_en as $post_en ) { + \pll_set_post_language( $post_en, 'en' ); + } + + $posts_pt = $this->factory()->post->create_many( + 5, + [ + 'post_category' => [ $cat_pt_id ], + ] + ); + + foreach ( $posts_pt as $post_pt ) { + \pll_set_post_language( $post_pt, 'pt' ); + } + + \pll_save_term_translations( + [ + 'en' => $cat_en_id, + 'pt' => $cat_pt_id, + ] + ); + + $yoast_head = $this->getYoastHeadForTerm( 'post', $cat_pt_id ); + + $this->assertNotFalse( strpos( $yoast_head, sprintf( '', $cat_en_id ) ), 'hreflang was not found' ); + $this->assertNotFalse( strpos( $yoast_head, sprintf( '', $cat_pt_id ) ), 'hreflang was not found' ); + } +} diff --git a/wp/headless-wp/tests/php/includes/TestPreview.php b/wp/headless-wp/tests/php/tests/TestPreview.php similarity index 100% rename from wp/headless-wp/tests/php/includes/TestPreview.php rename to wp/headless-wp/tests/php/tests/TestPreview.php