Skip to content

Display notification badge in the admin menu when there are recommendations #3001

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: feature/GOOWOO-172-pmax-assets-improvements
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Internal/DependencyManagement/AdminServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\Menu\AttributeMapping;
use Automattic\WooCommerce\GoogleListingsAndAds\Menu\Dashboard;
use Automattic\WooCommerce\GoogleListingsAndAds\Menu\NotificationManager;
use Automattic\WooCommerce\GoogleListingsAndAds\Menu\GetStarted;
use Automattic\WooCommerce\GoogleListingsAndAds\Menu\ProductFeed;
use Automattic\WooCommerce\GoogleListingsAndAds\Menu\Reports;
Expand Down Expand Up @@ -57,6 +58,7 @@
ConnectionTest::class => true,
CouponBulkEdit::class => true,
Dashboard::class => true,
NotificationManager::class => true,
GetStarted::class => true,
MetaBoxInterface::class => true,
MetaBoxInitializer::class => true,
Expand Down Expand Up @@ -101,6 +103,7 @@

$this->share_with_tags( AttributeMapping::class );
$this->share_with_tags( Dashboard::class );
$this->share_with_tags( NotificationManager::class );

Check warning on line 106 in src/Internal/DependencyManagement/AdminServiceProvider.php

View check run for this annotation

Codecov / codecov/patch

src/Internal/DependencyManagement/AdminServiceProvider.php#L106

Added line #L106 was not covered by tests
$this->share_with_tags( GetStarted::class );
$this->share_with_tags( ProductFeed::class );
$this->share_with_tags( Reports::class );
Expand Down
4 changes: 3 additions & 1 deletion src/Menu/Dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

public const PATH = '/google/dashboard';

public const MARKETING_MENU_SLUG = 'woocommerce-marketing';

/**
* Register a service.
*/
Expand All @@ -35,7 +37,7 @@
[
'id' => 'google-listings-and-ads',
'title' => __( 'Google for WooCommerce', 'google-listings-and-ads' ),
'parent' => 'woocommerce-marketing',
'parent' => self::MARKETING_MENU_SLUG,

Check warning on line 40 in src/Menu/Dashboard.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/Dashboard.php#L40

Added line #L40 was not covered by tests
'path' => self::PATH,
]
);
Expand Down
196 changes: 196 additions & 0 deletions src/Menu/NotificationManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Menu;

use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Registerable;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\Admin\PageController;

/**
* Class NotificationManager
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Menu
*
* Manages the display of a single, aggregated notification pill in the admin menu.
* It relies on a filter to gather the total count from various contributors.
*/
class NotificationManager implements Service, Registerable {

/**
* Register the service, hooking into admin_menu to display notifications.
*/
public function register(): void {

Check warning on line 23 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L23

Added line #L23 was not covered by tests
// Hook into admin_menu with a high priority (e.g., 20) to ensure
// all other menu items have been registered by WooCommerce and other plugins.
add_action( 'admin_menu', [ $this, 'display_aggregated_notification_pill' ], 20 );

Check warning on line 26 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L26

Added line #L26 was not covered by tests

// Persist the pill across menu switches between the Marketing and Analytics menus.
add_action( 'admin_footer', [ $this, 'persist_pill' ], 10 );

Check warning on line 29 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L29

Added line #L29 was not covered by tests
}

/**
* Determines if the current admin page is a child page within the WooCommerce Marketing section.
* This logic is crucial for deciding where the notification pill should be placed.
*
* @return bool True if the current page is a Marketing child page, false otherwise.
*/
private function is_marketing_page() {
global $pagenow;

Check warning on line 39 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L38-L39

Added lines #L38 - L39 were not covered by tests

$current_page_slug = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
$current_page_path = isset( $_GET['path'] ) ? sanitize_text_field( wp_unslash( $_GET['path'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
$current_post_type = isset( $_GET['post_type'] ) ? sanitize_text_field( wp_unslash( $_GET['post_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification

Check warning on line 43 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L41-L43

Added lines #L41 - L43 were not covered by tests

$post_type_page = add_query_arg(
[
'post_type' => $current_post_type,
],
$pagenow
);

Check warning on line 50 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L45-L50

Added lines #L45 - L50 were not covered by tests

$page_path_fragment = str_replace(
'page=',
'',
build_query(
[
'page' => $current_page_slug,
'path' => $current_page_path,
]
)
);

Check warning on line 61 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L52-L61

Added lines #L52 - L61 were not covered by tests

$page_controller_pages = PageController::get_instance()->get_pages();
$marketing_menu_slug = 'woocommerce-marketing';

Check warning on line 64 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L63-L64

Added lines #L63 - L64 were not covered by tests
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$marketing_menu_slug = 'woocommerce-marketing';
$marketing_menu_slug = Dashboard::MARKETING_MENU_SLUG;


$marketing_menu_pages = array_filter(
$page_controller_pages,
function ( $page ) use ( $marketing_menu_slug ) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function ( $page ) use ( $marketing_menu_slug ) {
static function ( $page ) use ( $marketing_menu_slug ) {

Use static that improve performance.

return isset( $page['parent'] ) && $page['parent'] === $marketing_menu_slug;
}
);

Check warning on line 71 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L66-L71

Added lines #L66 - L71 were not covered by tests

$is_marketing_page = false;

Check warning on line 73 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L73

Added line #L73 was not covered by tests

foreach ( $marketing_menu_pages as $page ) {
if ( isset( $page['path'] ) && in_array( $page['path'], [ $post_type_page, $page_path_fragment ], true ) ) {
$is_marketing_page = true;
break;

Check warning on line 78 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L75-L78

Added lines #L75 - L78 were not covered by tests
}
}

return $is_marketing_page;

Check warning on line 82 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L82

Added line #L82 was not covered by tests
}

/**
* Displays an aggregated notification pill in the admin menu.
* This method is hooked to 'admin_menu'.
*/
public function display_aggregated_notification_pill(): void {
global $menu, $submenu;

Check warning on line 90 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L89-L90

Added lines #L89 - L90 were not covered by tests

// Initialize the count and apply the filter to get the total aggregated count.
// All parts of the plugin (and other plugins) that need to add to the notification
// should hook into this filter.
$total_notification_count = apply_filters( 'google_for_woocommerce_admin_menu_notification_count', 0 );

Check warning on line 95 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L95

Added line #L95 was not covered by tests

// Only proceed if there's at least one notification.
if ( $total_notification_count > 0 ) {
$badge_html = ' <span class="update-plugins count-' . $total_notification_count . '"><span class="update-count">' . $total_notification_count . '</span></span>';

Check warning on line 99 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L98-L99

Added lines #L98 - L99 were not covered by tests

// Determine if the current page being loaded is within the Marketing section.
$is_on_marketing_child_page = $this->is_marketing_page();

Check warning on line 102 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L102

Added line #L102 was not covered by tests

if ( $is_on_marketing_child_page ) {

Check warning on line 104 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L104

Added line #L104 was not covered by tests
// If on a Marketing child page, add the pill to the 'Google for WooCommerce' sub-menu item.
// This means the user has the Marketing menu expanded and is viewing one of its sub-pages.
$marketing_parent_slug = Dashboard::MARKETING_MENU_SLUG; // Use constant for parent slug
$google_for_woocommerce_menu_path = Dashboard::PATH; // Use constant for GfW path

Check warning on line 108 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L107-L108

Added lines #L107 - L108 were not covered by tests

if ( isset( $submenu[ $marketing_parent_slug ] ) ) {
foreach ( $submenu[ $marketing_parent_slug ] as $key => $submenu_item ) {

Check warning on line 111 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L110-L111

Added lines #L110 - L111 were not covered by tests
// Use the submenu's slug (index 2) for robustness against translations.
// The slug will contain the path defined by the plugin.
if ( isset( $submenu_item[2] ) && strpos( $submenu_item[2], $google_for_woocommerce_menu_path ) !== false ) {
$submenu[ $marketing_parent_slug ][ $key ][0] .= $badge_html; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
break;

Check warning on line 116 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L114-L116

Added lines #L114 - L116 were not covered by tests
}
}
}
} else {
foreach ( $menu as $key => $menu_item ) {

Check warning on line 121 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L121

Added line #L121 was not covered by tests
// Use the top-level menu's slug (index 2) for robustness against translations.
if ( isset( $menu_item[2] ) && Dashboard::MARKETING_MENU_SLUG === $menu_item[2] ) {
$menu[ $key ][0] .= $badge_html; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
break;

Check warning on line 125 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L123-L125

Added lines #L123 - L125 were not covered by tests
}
}
}
}
}

/**
* Determines if the current admin page is the Analytics.
*
* @return bool True if the current menu item is the Analytics menu or one of it's sub menus.
*/
private function is_analytics_page(): bool {
$current_page_slug = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
if ( $current_page_slug !== 'wc-admin' ) {
return false;

Check warning on line 140 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L137-L140

Added lines #L137 - L140 were not covered by tests
}

$current_page_path = isset( $_GET['path'] ) ? sanitize_text_field( wp_unslash( $_GET['path'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
$parts = explode( '/', ltrim( $current_page_path, '/' ) );

Check warning on line 144 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L143-L144

Added lines #L143 - L144 were not covered by tests

if ( isset( $parts[0] ) && $parts[0] === 'analytics' ) {
return true;

Check warning on line 147 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L146-L147

Added lines #L146 - L147 were not covered by tests
}

return false;

Check warning on line 150 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L150

Added line #L150 was not covered by tests
}

/**
* Persist the pill in the correct location when switching between the Marketing/Analytics menus.
*/
public function persist_pill(): void {
if ( ! $this->is_marketing_page() && ! $this->is_analytics_page() ) {
return;

Check warning on line 158 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L156-L158

Added lines #L156 - L158 were not covered by tests
}
?>
<script>
(function() {
const marketingMenu = document.getElementById('toplevel_page_woocommerce-marketing');
const topMenu = document.querySelector('.toplevel_page_woocommerce-marketing > a > .wp-menu-name');
const badge = document.querySelector('#toplevel_page_woocommerce-marketing .update-plugins');

Check warning on line 165 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L161-L165

Added lines #L161 - L165 were not covered by tests

const observer = new MutationObserver(function() {
if (marketingMenu.classList.contains('wp-has-current-submenu')) {
const subMenu = document.querySelector('[href="admin.php?page=wc-admin&path=%2Fgoogle%2Fdashboard"]');

Check warning on line 169 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L167-L169

Added lines #L167 - L169 were not covered by tests

if (subMenu && !subMenu.contains(badge)) {
// Ensure there is white space betweem the bade and menu title for visual consistency.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Ensure there is white space betweem the bade and menu title for visual consistency.
// Ensure there is white space between the badge and menu title for visual consistency.

subMenu.textContent.trimEnd();
subMenu.textContent += ' ';

Check warning on line 174 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L171-L174

Added lines #L171 - L174 were not covered by tests

// Move the bade to the correct location.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Move the bade to the correct location.
// Move the badge to the correct location.

subMenu.appendChild(badge);
}
} else {
if (topMenu && !topMenu.contains(badge)) {
// Ensure there is white space betweem the bade and menu title for visual consistency.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Ensure there is white space betweem the bade and menu title for visual consistency.
// Ensure there is white space between the badge and menu title for visual consistency.

topMenu.textContent.trimEnd();
topMenu.textContent += ' ';

Check warning on line 183 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L176-L183

Added lines #L176 - L183 were not covered by tests

// Move the bade to the correct location.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Move the bade to the correct location.
// Move the badge to the correct location.

topMenu.appendChild(badge);
}
}
});

Check warning on line 189 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L185-L189

Added lines #L185 - L189 were not covered by tests

observer.observe(marketingMenu, { attributes: true, attributeFilter: ['class'] });
})();
</script>
<?php

Check warning on line 194 in src/Menu/NotificationManager.php

View check run for this annotation

Codecov / codecov/patch

src/Menu/NotificationManager.php#L191-L194

Added lines #L191 - L194 were not covered by tests
}
}