first commit

This commit is contained in:
User A0264400
2026-04-01 23:20:16 +03:00
commit a766acdc90
23071 changed files with 4933189 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Alerts\Application\Default_SEO_Data;
use Yoast\WP\SEO\Alerts\Infrastructure\Default_SEO_Data\Default_SEO_Data_Collector;
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
use Yoast\WP\SEO\Helpers\Indexable_Helper;
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
use Yoast\WP\SEO\Helpers\Product_Helper;
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast_Notification;
use Yoast_Notification_Center;
/**
* Default_SEO_Data_Alert class.
*/
class Default_SEO_Data_Alert implements Integration_Interface {
public const NOTIFICATION_ID = 'wpseo-default-seo-data';
/**
* The notifications center.
*
* @var Yoast_Notification_Center
*/
private $notification_center;
/**
* The default SEO data collector.
*
* @var Default_SEO_Data_Collector
*/
private $default_seo_data_collector;
/**
* The short link helper.
*
* @var Short_Link_Helper
*/
private $short_link_helper;
/**
* The product helper.
*
* @var Product_Helper
*/
private $product_helper;
/**
* The indexable helper.
*
* @var Indexable_Helper
*/
private $indexable_helper;
/**
* The post type helper.
*
* @var Post_Type_Helper
*/
private $post_type_helper;
/**
* Default_SEO_Data_Alert constructor.
*
* @param Yoast_Notification_Center $notification_center The notification center.
* @param Default_SEO_Data_Collector $default_seo_data_collector The default SEO data collector.
* @param Short_Link_Helper $short_link_helper The short link helper.
* @param Product_Helper $product_helper The product helper.
* @param Indexable_Helper $indexable_helper The indexable helper.
* @param Post_Type_Helper $post_type_helper The post type helper.
*/
public function __construct(
Yoast_Notification_Center $notification_center,
Default_SEO_Data_Collector $default_seo_data_collector,
Short_Link_Helper $short_link_helper,
Product_Helper $product_helper,
Indexable_Helper $indexable_helper,
Post_Type_Helper $post_type_helper
) {
$this->notification_center = $notification_center;
$this->default_seo_data_collector = $default_seo_data_collector;
$this->short_link_helper = $short_link_helper;
$this->product_helper = $product_helper;
$this->indexable_helper = $indexable_helper;
$this->post_type_helper = $post_type_helper;
}
/**
* Returns the conditionals based on which this loadable should be active.
*
* @return array<string>
*/
public static function get_conditionals() {
return [ Admin_Conditional::class ];
}
/**
* Initializes the integration.
*
* @return void
*/
public function register_hooks() {
\add_action( 'admin_init', [ $this, 'add_notifications' ] );
}
/**
* Adds notifications (when necessary).
*
* We want to show this notification only when there are enough posts that have the default SEO title or meta description, or both.
* If this is not the case we will not show the notification at all since it does not serve a purpose yet.
*
* @return void
*/
public function add_notifications() {
if ( ! $this->indexable_helper->should_index_indexables() ) {
// Do not show the notification when indexables are disabled.
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
return;
}
if ( ! $this->post_type_helper->is_indexable( 'post' ) || ! $this->post_type_helper->has_metabox( 'post' ) ) {
// Do not show the notification when posts are not indexable or have no metabox.
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
return;
}
$posts_with_default_seo_title = $this->default_seo_data_collector->get_posts_with_default_seo_title();
$posts_with_default_seo_description = $this->default_seo_data_collector->get_posts_with_default_seo_description();
$has_enough_posts_with_default_title = \count( $posts_with_default_seo_title ) > 4;
$has_enough_posts_with_default_desc = \count( $posts_with_default_seo_description ) > 4;
if ( ! $has_enough_posts_with_default_title && ! $has_enough_posts_with_default_desc ) {
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
return;
}
$notification = $this->get_default_seo_data_notification( $has_enough_posts_with_default_title, $has_enough_posts_with_default_desc );
$this->notification_center->add_notification( $notification );
}
/**
* Build the default SEO data notification.
*
* @param bool $has_enough_posts_with_default_title Whether there are content types with default SEO title in their most recent posts.
* @param bool $has_enough_posts_with_default_desc Whether there are content types with default SEO description in their most recent posts.
*
* @return Yoast_Notification The notification containing the suggested plugin.
*/
private function get_default_seo_data_notification( $has_enough_posts_with_default_title, $has_enough_posts_with_default_desc ): Yoast_Notification {
$message = $this->get_default_seo_data_message( $has_enough_posts_with_default_title, $has_enough_posts_with_default_desc );
return new Yoast_Notification(
$message,
[
'id' => self::NOTIFICATION_ID,
'type' => Yoast_Notification::WARNING,
'capabilities' => [ 'wpseo_manage_options' ],
],
);
}
/**
* Creates a message to inform users that they are using only default SEO data lately.
*
* @param bool $has_enough_posts_with_default_title Whether there are content types with default SEO title in their most recent posts.
* @param bool $has_enough_posts_with_default_desc Whether there are content types with default SEO description in their most recent posts.
*
* @return string The default SEO data message.
*/
private function get_default_seo_data_message( $has_enough_posts_with_default_title, $has_enough_posts_with_default_desc ): string {
$shortlink = ( $this->product_helper->is_premium() ) ? $this->short_link_helper->get( 'https://yoa.st/ai-generate-alert-premium/' ) : $this->short_link_helper->get( 'https://yoa.st/ai-generate-alert-free/' );
if ( $has_enough_posts_with_default_title && $has_enough_posts_with_default_desc ) {
$default_seo_data = \esc_html__( 'SEO titles and meta descriptions', 'wordpress-seo' );
}
elseif ( $has_enough_posts_with_default_title ) {
$default_seo_data = \esc_html__( 'SEO titles', 'wordpress-seo' );
}
elseif ( $has_enough_posts_with_default_desc ) {
$default_seo_data = \esc_html__( 'meta descriptions', 'wordpress-seo' );
}
else {
$default_seo_data = \esc_html__( 'SEO data', 'wordpress-seo' );
}
/* translators: %1$s expands to "SEO title" or "meta description", %2$s expands to an opening link tag, %3$s expands to an opening strong tag, %4$s expands to a closing strong tag, %5$s expands to a closing link tag. */
$message = ( $this->product_helper->is_premium() ) ? \esc_html__( 'Your recent posts are using default %1$s, which can make them easy to overlook in search results. Update them manually or %2$sfind out how %3$sYoast AI Generate%4$s can improve them for you.%5$s', 'wordpress-seo' ) : \esc_html__( 'Your recent posts are using default %1$s, which can make them easy to overlook in search results. Update them for better visibility or %2$stry %3$sYoast AI Generate%4$s for free to do it faster.%5$s', 'wordpress-seo' );
return \sprintf(
$message,
$default_seo_data,
'<a href="' . \esc_url( $shortlink ) . '" target="_blank">',
'<strong>',
'</strong>',
'</a>',
);
}
}

View File

@@ -0,0 +1,128 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Alerts\Application\Indexables_Disabled;
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
use Yoast\WP\SEO\Helpers\Indexable_Helper;
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast_Notification;
use Yoast_Notification_Center;
/**
* Indexables_Disabled_Alert class.
*/
class Indexables_Disabled_Alert implements Integration_Interface {
public const NOTIFICATION_ID = 'wpseo-indexables-disabled';
/**
* The notifications center.
*
* @var Yoast_Notification_Center
*/
private $notification_center;
/**
* The indexable helper.
*
* @var Indexable_Helper
*/
private $indexable_helper;
/**
* The short link helper.
*
* @var Short_Link_Helper
*/
private $short_link_helper;
/**
* Indexables_Disabled_Alert constructor.
*
* @param Yoast_Notification_Center $notification_center The notification center.
* @param Indexable_Helper $indexable_helper The indexable helper.
* @param Short_Link_Helper $short_link_helper The short link helper.
*/
public function __construct(
Yoast_Notification_Center $notification_center,
Indexable_Helper $indexable_helper,
Short_Link_Helper $short_link_helper
) {
$this->notification_center = $notification_center;
$this->indexable_helper = $indexable_helper;
$this->short_link_helper = $short_link_helper;
}
/**
* Returns the conditionals based on which this loadable should be active.
*
* @return array<string>
*/
public static function get_conditionals() {
return [ Admin_Conditional::class ];
}
/**
* Initializes the integration.
*
* @return void
*/
public function register_hooks() {
\add_action( 'admin_init', [ $this, 'add_notifications' ] );
}
/**
* Adds or removes notification based on whether indexables are disabled.
*
* @return void
*/
public function add_notifications() {
if ( $this->indexable_helper->should_index_indexables() ) {
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
return;
}
$notification = $this->get_indexables_disabled_notification();
$this->notification_center->add_notification( $notification );
}
/**
* Builds the indexables-disabled notification.
*
* @return Yoast_Notification The indexables-disabled notification.
*/
private function get_indexables_disabled_notification(): Yoast_Notification {
$message = $this->get_message();
return new Yoast_Notification(
$message,
[
'id' => self::NOTIFICATION_ID,
'type' => Yoast_Notification::WARNING,
'capabilities' => [ 'wpseo_manage_options' ],
],
);
}
/**
* Returns the notification message as an HTML string.
*
* @return string The HTML string representation of the notification.
*/
private function get_message(): string {
$shortlink = $this->short_link_helper->get( 'https://yoa.st/indexables-disabled' );
$message = \sprintf(
/* translators: %1$s expands to "Yoast", %2$s expands to an opening anchor tag, %3$s expands to a closing anchor tag. */
\esc_html__( '%1$s indexables are disabled because your site is in a non-production environment or custom code is blocking them. This may affect your SEO features. %2$sLearn more about this%3$s.', 'wordpress-seo' ),
'Yoast',
'<a href="' . \esc_url( $shortlink ) . '" target="_blank">',
'</a>',
);
return $message;
}
}

View File

@@ -0,0 +1,183 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Alerts\Application\Ping_Other_Admins;
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Helpers\Product_Helper;
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
use Yoast\WP\SEO\Helpers\User_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast_Notification;
use Yoast_Notification_Center;
/**
* Ping_Other_Admins_Alert class.
*/
class Ping_Other_Admins_Alert implements Integration_Interface {
public const NOTIFICATION_ID = 'wpseo-ping-other-admins';
/**
* The notifications center.
*
* @var Yoast_Notification_Center
*/
private $notification_center;
/**
* The short link helper.
*
* @var Short_Link_Helper
*/
private $short_link_helper;
/**
* The product helper.
*
* @var Product_Helper
*/
private $product_helper;
/**
* The options helper.
*
* @var Options_Helper
*/
private $options_helper;
/**
* The user helper.
*
* @var User_Helper
*/
private $user_helper;
/**
* Ping_Other_Admins_Alert constructor.
*
* @param Yoast_Notification_Center $notification_center The notification center.
* @param Short_Link_Helper $short_link_helper The short link helper.
* @param Product_Helper $product_helper The product helper.
* @param Options_Helper $options_helper The options helper.
* @param User_Helper $user_helper The user helper.
*/
public function __construct(
Yoast_Notification_Center $notification_center,
Short_Link_Helper $short_link_helper,
Product_Helper $product_helper,
Options_Helper $options_helper,
User_Helper $user_helper
) {
$this->notification_center = $notification_center;
$this->short_link_helper = $short_link_helper;
$this->product_helper = $product_helper;
$this->options_helper = $options_helper;
$this->user_helper = $user_helper;
}
/**
* Returns the conditionals based on which this loadable should be active.
*
* @return array<string>
*/
public static function get_conditionals() {
return [ Admin_Conditional::class ];
}
/**
* Initializes the integration.
*
* @return void
*/
public function register_hooks() {
\add_action( 'admin_init', [ $this, 'add_notifications' ] );
}
/**
* Adds notification when user has not installed Yoast SEO themselves and has not resolved the notification yet.
*
* @return void
*/
public function add_notifications() {
if ( $this->has_user_installed_yoast() ) {
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
return;
}
if ( $this->has_notification_been_resolved() ) {
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
return;
}
$notification = $this->get_ping_other_admins_notification();
$this->notification_center->add_notification( $notification );
}
/**
* Returns whether user has installed Yoast SEO themselves.
*
* @return bool Whether the user has installed Yoast SEO themselves.
*/
private function has_user_installed_yoast(): bool {
$first_activated_by = $this->options_helper->get( 'first_activated_by', 0 );
if ( $first_activated_by === 0 ) {
return true; // We cannot be sure, so we assume they did.
}
if ( \get_current_user_id() === $first_activated_by ) {
return true;
}
return false;
}
/**
* Returns whether the alert has been resolved before.
*
* @return bool Whether the alert has been resolved before.
*/
private function has_notification_been_resolved(): bool {
return $this->user_helper->get_meta( \get_current_user_id(), self::NOTIFICATION_ID . '_resolved', true ) === '1';
}
/**
* Build the ping-other-admins notification.
*
* @return Yoast_Notification The ping-other-admins notification.
*/
private function get_ping_other_admins_notification(): Yoast_Notification {
$message = $this->get_message();
return new Yoast_Notification(
$message,
[
'id' => self::NOTIFICATION_ID,
'type' => Yoast_Notification::WARNING,
'capabilities' => [ 'wpseo_manage_options' ],
'priority' => 20,
'resolve_nonce' => \wp_create_nonce( 'wpseo-resolve-alert-nonce' ),
],
);
}
/**
* Returns the notification as an HTML string.
*
* @return string The HTML string representation of the notification.
*/
private function get_message() {
$message = \sprintf(
/* translators: %1$s expands to "Yoast SEO". */
\esc_html__( 'Looks like youre new here. %1$s makes it easy to optimize your website for search engines. Want to keep your site healthy and easier to find? Sign up below to receive practical emails to get you started!', 'wordpress-seo' ),
'Yoast SEO',
);
$notification_text = '<p>' . $message . '</p>';
return $notification_text;
}
}

View File

@@ -0,0 +1,48 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
namespace Yoast\WP\SEO\Alerts\Infrastructure\Default_SEO_Data;
use Yoast\WP\SEO\Helpers\Options_Helper;
/**
* Class that collects default SEO data.
*
* @makePublic
*/
class Default_SEO_Data_Collector {
/**
* Holds the Options_Helper instance.
*
* @var Options_Helper
*/
private $options_helper;
/**
* The constructor.
*
* @param Options_Helper $options_helper The options helper.
*/
public function __construct( Options_Helper $options_helper ) {
$this->options_helper = $options_helper;
}
/**
* Returns the posts with default SEO title in their most recent.
*
* @return string[] The posts with default SEO title in their most recent.
*/
public function get_posts_with_default_seo_title(): array {
return $this->options_helper->get( 'default_seo_title', [] );
}
/**
* Returns the posts with default SEO description in their most recent.
*
* @return string[] The posts with default SEO description in their most recent.
*/
public function get_posts_with_default_seo_description(): array {
return $this->options_helper->get( 'default_seo_meta_desc', [] );
}
}

View File

@@ -0,0 +1,101 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Alerts\User_Interface\Default_SEO_Data;
use Yoast\WP\SEO\Alerts\User_Interface\Default_Seo_Data\Default_SEO_Data_Cron_Scheduler;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
/**
* Cron Callback integration. This handles the actual process of detecting default SEO data in recent posts and updating the relevant options.
*
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
*/
class Default_SEO_Data_Cron_Callback_Integration implements Integration_Interface {
use No_Conditionals;
/**
* The options helper.
*
* @var Options_Helper
*/
private $options_helper;
/**
* The scheduler.
*
* @var Default_SEO_Data_Cron_Scheduler
*/
private $scheduler;
/**
* The indexable repository.
*
* @var Indexable_Repository
*/
private $indexable_repository;
/**
* Constructor.
*
* @param Options_Helper $options_helper The options helper.
* @param Default_SEO_Data_Cron_Scheduler $scheduler The scheduler.
* @param Indexable_Repository $indexable_repository The indexable repository.
*/
public function __construct(
Options_Helper $options_helper,
Default_SEO_Data_Cron_Scheduler $scheduler,
Indexable_Repository $indexable_repository
) {
$this->options_helper = $options_helper;
$this->scheduler = $scheduler;
$this->indexable_repository = $indexable_repository;
}
/**
* Registers the hooks.
*
* @return void
*/
public function register_hooks() {
\add_action(
Default_SEO_Data_Cron_Scheduler::CRON_HOOK,
[
$this,
'detect_default_seo_data_in_recent',
],
);
}
/**
* Detects default SEO data in recent posts and updates the relevant options.
*
* @return void
*/
public function detect_default_seo_data_in_recent(): void {
if ( ! \wp_doing_cron() ) {
return;
}
$recent_posts = $this->indexable_repository->get_recently_modified_posts( 'post', 5, false );
$recent_default_seo_title = [];
$recent_default_seo_meta_desc = [];
foreach ( $recent_posts as $post ) {
if ( $post->title === null ) {
$recent_default_seo_title[] = $post->object_id;
}
if ( $post->description === null ) {
$recent_default_seo_meta_desc[] = $post->object_id;
}
}
$this->options_helper->set( 'default_seo_title', $recent_default_seo_title );
$this->options_helper->set( 'default_seo_meta_desc', $recent_default_seo_meta_desc );
}
}

View File

@@ -0,0 +1,53 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
namespace Yoast\WP\SEO\Alerts\User_Interface\Default_Seo_Data;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Responsible for scheduling and unscheduling the cron.
*/
class Default_SEO_Data_Cron_Scheduler implements Integration_Interface {
use No_Conditionals;
/**
* The name of the cron job.
*/
public const CRON_HOOK = 'wpseo_detect_default_seo_data';
/**
* Register hooks.
*
* @return void
*/
public function register_hooks() {
\add_action( 'admin_init', [ $this, 'schedule_default_seo_data_detection' ] );
\add_action( 'wpseo_deactivate', [ $this, 'unschedule_default_seo_data_detection' ] );
}
/**
* Schedules the default SEO data detection cron.
*
* @return void
*/
public function schedule_default_seo_data_detection(): void {
if ( ! \wp_next_scheduled( self::CRON_HOOK ) ) {
\wp_schedule_event( ( \time() + \DAY_IN_SECONDS ), 'daily', self::CRON_HOOK );
}
}
/**
* Unschedules the default SEO data detection cron.
*
* @return void
*/
public function unschedule_default_seo_data_detection() {
$scheduled = \wp_next_scheduled( self::CRON_HOOK );
if ( $scheduled ) {
\wp_unschedule_event( $scheduled, self::CRON_HOOK );
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Alerts\User_Interface\Default_SEO_Data;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
/**
* This handles the process of checking for non-default SEO in the latest content and updating the relevant options right away.
*/
class Default_SEO_Data_Watcher implements Integration_Interface {
use No_Conditionals;
/**
* The indexable repository.
*
* @var Indexable_Repository
*/
private $indexable_repository;
/**
* The options helper.
*
* @var Options_Helper
*/
private $options_helper;
/**
* Constructor.
*
* @param Indexable_Repository $indexable_repository The indexable repository.
* @param Options_Helper $options_helper The options helper.
*/
public function __construct(
Indexable_Repository $indexable_repository,
Options_Helper $options_helper
) {
$this->indexable_repository = $indexable_repository;
$this->options_helper = $options_helper;
}
/**
* Registers the hooks with WordPress.
*
* @return void
*/
public function register_hooks() {
\add_action( 'wpseo_saved_indexable', [ $this, 'check_for_default_seo_data' ], 10, 1 );
}
/**
* Checks for default SEO data in the saved indexable and the most recently modified posts.
*
* @param Indexable $saved_indexable The saved indexable.
*
* @return void
*/
public function check_for_default_seo_data( $saved_indexable ): void {
// We have activated this feature only for posts for now.
if ( $saved_indexable->object_type !== 'post' || $saved_indexable->object_sub_type !== 'post' ) {
return;
}
// If the title or description is null, it means it's not default SEO data so let's reset the options.
if ( $saved_indexable->title !== null ) {
$this->options_helper->set( 'default_seo_title', [] );
}
if ( $saved_indexable->description !== null ) {
$this->options_helper->set( 'default_seo_meta_desc', [] );
}
}
}

View File

@@ -0,0 +1,92 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Alerts\User_Interface;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Helpers\Capability_Helper;
use Yoast\WP\SEO\Helpers\User_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Registers a route to resolve an alert
*
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
*/
class Resolve_Alert_Route implements Integration_Interface {
use No_Conditionals;
/**
* The user helper.
*
* @var User_Helper
*/
private $user_helper;
/**
* The capability helper.
*
* @var Capability_Helper
*/
private $capability_helper;
/**
* Class constructor.
*
* @param User_Helper $user_helper The user helper.
* @param Capability_Helper $capability_helper The capability helper.
*/
public function __construct(
User_Helper $user_helper,
Capability_Helper $capability_helper
) {
$this->user_helper = $user_helper;
$this->capability_helper = $capability_helper;
}
/**
* Registers all hooks to WordPress.
*
* @return void
*/
public function register_hooks() {
\add_action( 'wp_ajax_wpseo_resolve_alert', [ $this, 'resolve_alert' ] );
}
/**
* Runs the callback to resolve an alert for the current user.
*
* @return void.
*/
public function resolve_alert() {
if ( ! \check_ajax_referer( 'wpseo-resolve-alert-nonce', 'nonce', false ) || ! $this->capability_helper->current_user_can( 'wpseo_manage_options' ) ) {
\wp_send_json_error(
[
'message' => 'Security check failed.',
],
);
return;
}
if ( ! isset( $_POST['alertId'] ) ) {
\wp_send_json_error(
[
'message' => 'Alert ID is missing.',
],
);
return;
}
$alert_id = \sanitize_text_field( \wp_unslash( $_POST['alertId'] ) );
$user_id = \get_current_user_id();
$this->user_helper->update_meta( $user_id, $alert_id . '_resolved', true );
\wp_send_json_success(
[
'message' => 'Alert resolved successfully.',
],
);
}
}