diff --git a/docs/automated-testing.md b/docs/automated-testing.md index e2d0f474d9490..e6ffa2c1ed21f 100644 --- a/docs/automated-testing.md +++ b/docs/automated-testing.md @@ -25,7 +25,7 @@ These tests are used to test both plugins and packages code. Depending on develo Unit tests are used to test individual units of code, such as a function or a class. They are meant to be run in isolation, without any external dependencies. They are fast and easy to write. This is a preferred approach to PHP testing, that leads to decoupled and testable code. -Refer to PHPUnit [documentation](https://phpunit.readthedocs.io/en/9.5/writing-tests-for-phpunit.html) for more details on how to write tests. Also, there are various examples in the repo such as [here](/projects/packages/a8c-mc-stats/tests/php/test_Stats.php). +Refer to PHPUnit [documentation](https://phpunit.readthedocs.io/en/9.5/writing-tests-for-phpunit.html) for more details on how to write tests. Also, there are various examples in the repo such as [here](/projects/packages/a8c-mc-stats/tests/php/StatsTest.php). Note: Jetpack monorepo is using a bit different code style to one that used in documentation examples. @@ -43,7 +43,7 @@ Normally, integration tests for packages rely on various mocking solutions avail - [brain/monkey](https://packagist.org/packages/brain/monkey) - For mocking and stubbing WordPress functions and classes. - [automattic/jetpack-test-environment](../projects/packages/test-environment/README.md) is used to pull in WordPress for testing. It calls in a lightweight version of WordPress (via the [WorDBless](https://packagist.org/packages/automattic/wordbless) package) and provides a way to run tests in a WordPress environment. We use the jetpack-test-environment package within the monorepo to only need one install of WordPress for the entire monorepo. -There are a lot of examples on how to use these tools in the `/projects/packages` folder, such as [here](/projects/packages/connection/tests/php/test_Manager_integration.php). +There are a lot of examples on how to use these tools in the `/projects/packages` folder, such as [here](/projects/packages/connection/tests/php/ManagerIntegrationTest.php). ## Javascript tests @@ -51,7 +51,7 @@ Monorepo provides support for `jest` as testing framework, and `@testing-library ### React components -There are examples scattered through the monorepo such as [connection-status-card](/projects/packages/my-jetpack/_inc/components/connection-status-card/test/component.jsx) card. Refer to [documentation](https://testing-library.com/docs/react-testing-library/intro) for more details. +There are examples scattered through the monorepo such as [connection-status-card](/projects/packages/my-jetpack/_inc/components/connection-status-card/index.tsx) card. Refer to [documentation](https://testing-library.com/docs/react-testing-library/intro) for more details. ### Gutenberg blocks diff --git a/projects/plugins/jetpack/.phan/baseline.php b/projects/plugins/jetpack/.phan/baseline.php index 09953cf3ad956..159ddc499af41 100644 --- a/projects/plugins/jetpack/.phan/baseline.php +++ b/projects/plugins/jetpack/.phan/baseline.php @@ -33,7 +33,6 @@ // PhanTypeArraySuspicious : 20+ occurrences // PhanTypeMismatchDimFetch : 20+ occurrences // PhanSuspiciousMagicConstant : 15+ occurrences - // PhanTypeExpectedObjectPropAccessButGotNull : 15+ occurrences // PhanTypeMismatchPropertyDefault : 15+ occurrences // PhanTypeSuspiciousNonTraversableForeach : 15+ occurrences // PhanPluginDuplicateExpressionAssignmentOperation : 10+ occurrences @@ -56,7 +55,6 @@ // PhanCommentAbstractOnInheritedMethod : 6 occurrences // PhanDeprecatedClass : 5 occurrences // PhanImpossibleCondition : 5 occurrences - // PhanNonClassMethodCall : 5 occurrences // PhanTypeMismatchDimAssignment : 5 occurrences // PhanAccessMethodInternal : 4 occurrences // PhanTypeInvalidLeftOperandOfAdd : 4 occurrences @@ -72,6 +70,7 @@ // PhanTypeObjectUnsetDeclaredProperty : 3 occurrences // PhanUndeclaredMethodInCallable : 3 occurrences // PhanImpossibleConditionInLoop : 2 occurrences + // PhanNonClassMethodCall : 2 occurrences // PhanParamTooMany : 2 occurrences // PhanParamTooManyCallable : 2 occurrences // PhanPluginDuplicateSwitchCaseLooseEquality : 2 occurrences @@ -80,7 +79,6 @@ // PhanUndeclaredClassInCallable : 2 occurrences // PhanUndeclaredClassMethod : 2 occurrences // PhanDeprecatedPartiallySupportedCallable : 1 occurrence - // PhanParamTooFewInternal : 1 occurrence // PhanPluginDuplicateSwitchCase : 1 occurrence // PhanPluginInvalidPregRegex : 1 occurrence // PhanPluginUseReturnValueInternalKnown : 1 occurrence @@ -192,7 +190,7 @@ 'extensions/blocks/story/story.php' => ['PhanImpossibleCondition', 'PhanPluginDuplicateConditionalNullCoalescing', 'PhanPossiblyUndeclaredVariable'], 'extensions/blocks/subscriptions/subscriptions.php' => ['PhanPluginDuplicateConditionalNullCoalescing'], 'extensions/blocks/top-posts/top-posts.php' => ['PhanTypeMismatchReturnProbablyReal'], - 'extensions/blocks/wordads/wordads.php' => ['PhanNonClassMethodCall', 'PhanTypeExpectedObjectPropAccessButGotNull', 'PhanTypeMismatchArgument'], + 'extensions/blocks/wordads/wordads.php' => ['PhanTypeMismatchArgument'], 'extensions/plugins/sharing/sharing.php' => ['PhanRedundantCondition'], 'functions.compat.php' => ['PhanRedefineFunction'], 'functions.global.php' => ['PhanRedefineFunction', 'PhanRedundantCondition', 'PhanSuspiciousMagicConstant', 'PhanTypeMismatchArgument'], @@ -446,11 +444,11 @@ 'modules/woocommerce-analytics/classes/class-jetpack-woocommerce-analytics-my-account.php' => ['PhanDeprecatedFunction', 'PhanDeprecatedTrait'], 'modules/woocommerce-analytics/classes/class-jetpack-woocommerce-analytics-trait.php' => ['PhanDeprecatedFunction', 'PhanTypeSuspiciousNonTraversableForeach', 'PhanUndeclaredMethod'], 'modules/woocommerce-analytics/classes/class-jetpack-woocommerce-analytics-universal.php' => ['PhanDeprecatedFunction', 'PhanDeprecatedTrait', 'PhanPluginRedundantAssignment'], - 'modules/wordads/class-wordads.php' => ['PhanNonClassMethodCall', 'PhanPluginRedundantAssignment', 'PhanTypeExpectedObjectPropAccessButGotNull', 'PhanTypeMismatchArgument', 'PhanTypeMismatchPropertyProbablyReal'], + 'modules/wordads/class-wordads.php' => ['PhanPluginRedundantAssignment', 'PhanTypeMismatchArgument'], 'modules/wordads/php/class-wordads-admin.php' => ['PhanTypeMismatchArgumentProbablyReal'], 'modules/wordads/php/class-wordads-api.php' => ['PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal'], 'modules/wordads/php/class-wordads-params.php' => ['PhanTypeMismatchReturn'], - 'modules/wordads/php/class-wordads-sidebar-widget.php' => ['PhanTypeExpectedObjectPropAccessButGotNull', 'PhanTypeMismatchArgument'], + 'modules/wordads/php/class-wordads-sidebar-widget.php' => ['PhanTypeMismatchArgument'], 'sal/class.json-api-date.php' => ['PhanPluginDuplicateExpressionAssignmentOperation', 'PhanRedundantCondition', 'PhanTypeMismatchArgumentInternalProbablyReal'], 'sal/class.json-api-links.php' => ['PhanTypeMismatchArgumentInternal', 'PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal'], 'sal/class.json-api-post-base.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanRedundantCondition', 'PhanTypeMismatchArgument', 'PhanTypeMismatchReturnNullable', 'PhanTypeMismatchReturnProbablyReal', 'PhanTypePossiblyInvalidDimOffset', 'PhanTypeSuspiciousNonTraversableForeach'], diff --git a/projects/plugins/jetpack/changelog/add-aditude-support-for-gutenberg b/projects/plugins/jetpack/changelog/add-aditude-support-for-gutenberg new file mode 100644 index 0000000000000..a249d961ec08f --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-aditude-support-for-gutenberg @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +WordAds: Support Gutenberg Ad block for Aditude diff --git a/projects/plugins/jetpack/modules/wordads/class-wordads.php b/projects/plugins/jetpack/modules/wordads/class-wordads.php index d57bf2e5471de..3e725f2b77617 100644 --- a/projects/plugins/jetpack/modules/wordads/class-wordads.php +++ b/projects/plugins/jetpack/modules/wordads/class-wordads.php @@ -20,6 +20,7 @@ require_once WORDADS_ROOT . '/php/class-wordads-california-privacy.php'; require_once WORDADS_ROOT . '/php/class-wordads-ccpa-do-not-sell-link-widget.php'; require_once WORDADS_ROOT . '/php/class-wordads-consent-management-provider.php'; +require_once WORDADS_ROOT . '/php/class-wordads-formats.php'; require_once WORDADS_ROOT . '/php/class-wordads-smart.php'; require_once WORDADS_ROOT . '/php/class-wordads-shortcode.php'; @@ -31,7 +32,7 @@ class WordAds { /** * Ads parameters. * - * @var null + * @var WordAds_Params */ public $params = null; @@ -269,8 +270,14 @@ public static function is_infinite_scroll() { */ private function insert_adcode() { add_filter( 'wp_resource_hints', array( $this, 'resource_hints' ), 10, 2 ); + + // These 'wp_head' actions add the IPONWEB JavaScript snippets to the page. + // We need to remove these calls whenever migration to Aditude is complete. add_action( 'wp_head', array( $this, 'insert_head_meta' ), 20 ); + + // TODO: Remove this function after June 30th, 2025. add_action( 'wp_head', array( $this, 'insert_head_iponweb' ), 30 ); + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_filter( 'wordads_ads_txt', array( $this, 'insert_custom_adstxt' ) ); @@ -441,14 +448,14 @@ public function insert_head_meta() { // phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped echo << - var sas_fallback = sas_fallback || []; - sas_fallback.push( - { tag: "$tag_inline", type: 'inline' }, - { tag: "$tag_belowpost", type: 'belowpost' }, - { tag: "$tag_top", type: 'top' } - ); - + HTML; } @@ -465,10 +472,12 @@ public function insert_head_iponweb() { $data_tags = ( $this->params->cloudflare ) ? ' data-cfasync="false"' : ''; ?> type="text/javascript"> + function loadIPONWEB() { // TODO: Remove this after June 30th, 2025 (function(){var g=Date.now||function(){return+new Date};function h(a,b){a:{for(var c=a.length,d="string"==typeof a?a.split(""):a,e=0;eb?null:"string"==typeof a?a.charAt(b):a[b]};function k(a,b,c){c=null!=c?"="+encodeURIComponent(String(c)):"";if(b+=c){c=a.indexOf("#");0>c&&(c=a.length);var d=a.indexOf("?");if(0>d||d>c){d=c;var e=""}else e=a.substring(d+1,c);a=[a.substr(0,d),e,a.substr(c)];c=a[1];a[1]=b?c?c+"&"+b:b:c;a=a[0]+(a[1]?"?"+a[1]:"")+a[2]}return a};var l=0;function m(a,b){var c=document.createElement("script");c.src=a;c.onload=function(){b&&b(void 0)};c.onerror=function(){b&&b("error")};a=document.getElementsByTagName("head");var d;a&&0!==a.length?d=a[0]:d=document.documentElement;d.appendChild(c)}function n(a){var b=void 0===b?document.cookie:b;return(b=h(b.split("; "),function(c){return-1!=c.indexOf(a+"=")}))?b.split("=")[1]:""}function p(a){return"string"==typeof a&&0
- __ATA.cmd.push(function() { - __ATA.initSlot('atatags-{$ad_number}', { - collapseEmpty: 'before', - sectionId: '{$section_id}', - location: {$loc_id}, - width: {$width}, - height: {$height} - }); - }); + window.getAdSnippetCallback = function () { + if ( true === ( window.isWatlV1 ?? false ) ) { + // Use IPONWEB scripts. + window.__ATA = window.__ATA || {}; + window.__ATA.cmd = __ATA.cmd || []; + window.__ATA.cmd.push(function() { + window.__ATA.initSlot('atatags-{$ad_number}', { + collapseEmpty: 'before', + sectionId: '{$section_id}', + location: {$loc_id}, + width: {$width}, + height: {$height} + }); + }); + } else { + // Use Aditude scripts. + window.tudeMappings = window.tudeMappings || []; + window.tudeMappings.push( { + divId: 'atatags-{$ad_number}', + format: '{$format}', + width: {$width}, + height: {$height}, + } ); + } + } + + if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', window.getAdSnippetCallback ); + } else { + window.getAdSnippetCallback(); + }
@@ -781,12 +821,9 @@ public function get_ad_snippet( $section_id, $height, $width, $location = '', $c * @since 8.7 */ public function get_dynamic_ad_snippet( $section_id, $form_factor = 'square', $location = '', $relocate = '', $id = null ) { + $is_location_enabled = in_array( $location, array( 'top', 'belowpost', 'inline' ), true ); - // Allow overriding and printing of the tag parsed by the WATL. - // phpcs:disable WordPress.Security.NonceVerification.Recommended - $is_location_enabled = isset( $_GET['wordads-logging'] ) && isset( $_GET[ $location ] ) && 'true' === $_GET[ $location ]; - - if ( ( 'top' === $location || 'belowpost' === $location ) && $is_location_enabled ) { + if ( $is_location_enabled ) { return self::get_watl_ad_html_tag( $location ); } @@ -965,7 +1002,32 @@ public function get_house_ad( $unit = 'mrec' ) { * @since 8.7 */ public static function get_watl_ad_html_tag( string $slot_type ): string { - return "
"; + $uid = uniqid( 'atatags-dynamic-' . $slot_type . '-' ); + + return << +
+ +
+ +HTML; } /** diff --git a/projects/plugins/jetpack/modules/wordads/js/adflow-loader.js b/projects/plugins/jetpack/modules/wordads/js/adflow-loader.js index 67d51d14f3a3e..8e05e84754c34 100644 --- a/projects/plugins/jetpack/modules/wordads/js/adflow-loader.js +++ b/projects/plugins/jetpack/modules/wordads/js/adflow-loader.js @@ -11,11 +11,26 @@ function a8c_adflow_callback( data ) { } // Load each adflow script. + window.isWatlV1 = window.isWatlV1 ?? false; data.scripts.forEach( function ( scriptUrl ) { let script = document.createElement( 'script' ); script.src = scriptUrl; document.head.appendChild( script ); + if ( scriptUrl.indexOf( 'watl.js' ) !== -1 ) { + window.isWatlV1 = true; + } } ); + + window.loadIPONWEB = window.loadIPONWEB ?? function () {}; // Satisfy linter + + if ( window.isWatlV1 ) { + // Then load IPONWEB scripts. + if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', window.loadIPONWEB ); + } else { + window.loadIPONWEB(); + } + } } } window.a8c_adflow_callback = a8c_adflow_callback; diff --git a/projects/plugins/jetpack/modules/wordads/php/class-wordads-formats.php b/projects/plugins/jetpack/modules/wordads/php/class-wordads-formats.php new file mode 100644 index 0000000000000..42e0719e06077 --- /dev/null +++ b/projects/plugins/jetpack/modules/wordads/php/class-wordads-formats.php @@ -0,0 +1,46 @@ +get_config_url() ), + $this->get_config_url(), // The URL is not escaped because we need two parameters to pass. array( 'adflow_script_loader' ), JETPACK__VERSION, false @@ -248,8 +248,9 @@ public function resource_hints( $hints, $relation_type ) { */ private function get_config_url(): string { return sprintf( - 'https://public-api.wordpress.com/wpcom/v2/sites/%1$d/adflow/conf/?_jsonp=a8c_adflow_callback', - $this->params->blog_id + 'https://public-api.wordpress.com/wpcom/v2/sites/%1$d/adflow/conf/?_jsonp=a8c_adflow_callback&is_singular_post=%2$d&', + $this->params->blog_id, + (int) is_singular( 'post' ) ); } diff --git a/projects/plugins/jetpack/phpunit.11.xml.dist b/projects/plugins/jetpack/phpunit.11.xml.dist index b53439e40f979..14884b5a5787b 100644 --- a/projects/plugins/jetpack/phpunit.11.xml.dist +++ b/projects/plugins/jetpack/phpunit.11.xml.dist @@ -69,6 +69,9 @@ tests/php/modules/widget-visibility + + tests/php/modules/wordads + tests/php/modules/sitemaps diff --git a/projects/plugins/jetpack/phpunit.9.xml.dist b/projects/plugins/jetpack/phpunit.9.xml.dist index fcb7905fa8bb7..0921a17477597 100644 --- a/projects/plugins/jetpack/phpunit.9.xml.dist +++ b/projects/plugins/jetpack/phpunit.9.xml.dist @@ -60,6 +60,9 @@ tests/php/modules/widget-visibility + + tests/php/modules/wordads + tests/php/modules/sitemaps diff --git a/projects/plugins/jetpack/tests/php/modules/wordads/WordAds_Formats_Test.php b/projects/plugins/jetpack/tests/php/modules/wordads/WordAds_Formats_Test.php new file mode 100644 index 0000000000000..b66b78185bbab --- /dev/null +++ b/projects/plugins/jetpack/tests/php/modules/wordads/WordAds_Formats_Test.php @@ -0,0 +1,67 @@ + array( + 300, + 250, + 'gutenberg_rectangle', + ), + 'gutenberg_leaderboard' => array( + 728, + 90, + 'gutenberg_leaderboard', + ), + 'gutenberg_mobile_leaderboard' => array( + 320, + 50, + 'gutenberg_mobile_leaderboard', + ), + 'gutenberg_skyscraper' => array( + 160, + 600, + 'gutenberg_skyscraper', + ), + 'unknown_format' => array( + 400, + 300, + '', + ), + ); + } + + /** + * Test the widget method that outputs the markup. + * + * @dataProvider get_format_data + * @param int $width The block's width. + * @param int $height The block's height. + * @param string $expected The expected format slug. + */ + #[DataProvider( 'get_format_data' )] + public function test_get_format_slug( int $width, int $height, string $expected ): void { + $this->assertEquals( + $expected, + WordAds_Formats::get_format_slug( $width, $height ) + ); + } +}