618 lines
17 KiB
PHP
618 lines
17 KiB
PHP
<?php
|
|
namespace AIOSEO\Plugin\Common\Admin\Notices;
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
use AIOSEO\Plugin\Common\Models;
|
|
|
|
/**
|
|
* Abstract class that Pro and Lite both extend.
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
class Notices {
|
|
/**
|
|
* Source of notifications content.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @var string
|
|
*/
|
|
private $url = 'https://plugin-cdn.aioseo.com/wp-content/notifications.json';
|
|
|
|
/**
|
|
* Review class instance.
|
|
*
|
|
* @since 4.2.7
|
|
*
|
|
* @var Review
|
|
*/
|
|
private $review = null;
|
|
|
|
/**
|
|
* Migration class instance.
|
|
*
|
|
* @since 4.2.7
|
|
*
|
|
* @var Migration
|
|
*/
|
|
private $migration = null;
|
|
|
|
/**
|
|
* Import class instance.
|
|
*
|
|
* @since 4.2.7
|
|
*
|
|
* @var Import
|
|
*/
|
|
private $import = null;
|
|
|
|
/**
|
|
* DeprecatedWordPress class instance.
|
|
*
|
|
* @since 4.2.7
|
|
*
|
|
* @var DeprecatedWordPress
|
|
*/
|
|
private $deprecatedWordPress = null;
|
|
|
|
/**
|
|
* ConflictingPlugins class instance.
|
|
*
|
|
* @since 4.5.1
|
|
*
|
|
* @var ConflictingPlugins
|
|
*/
|
|
private $conflictingPlugins = null;
|
|
|
|
/**
|
|
* The action name for the notifications update.
|
|
*
|
|
* @since 4.9.4.2
|
|
*
|
|
* @var string
|
|
*/
|
|
private $actionName = 'aioseo_admin_notifications_update';
|
|
|
|
/**
|
|
* Class Constructor.
|
|
*
|
|
* @since 4.0.0
|
|
* @version 4.9.4.2 Schedule notifications update as a daily recurring action.
|
|
*/
|
|
public function __construct() {
|
|
add_action( 'admin_init', [ $this, 'scheduleNotificationsUpdate' ] );
|
|
add_action( $this->actionName, [ $this, 'update' ] );
|
|
|
|
if ( ! is_admin() ) {
|
|
return;
|
|
}
|
|
|
|
add_action( 'updated_option', [ $this, 'maybeResetBlogVisibility' ], 10, 3 );
|
|
add_action( 'init', [ $this, 'init' ], 2 );
|
|
|
|
$this->review = new Review();
|
|
$this->migration = new Migration();
|
|
$this->import = new Import();
|
|
$this->deprecatedWordPress = new DeprecatedWordPress();
|
|
$this->conflictingPlugins = new ConflictingPlugins();
|
|
|
|
add_action( 'admin_notices', [ $this, 'notices' ] );
|
|
}
|
|
|
|
/**
|
|
* Schedules the daily recurring notifications update.
|
|
*
|
|
* @since 4.9.4.2
|
|
*
|
|
* @return void
|
|
*/
|
|
public function scheduleNotificationsUpdate() {
|
|
if ( aioseo()->actionScheduler->isScheduled( $this->actionName ) ) {
|
|
return;
|
|
}
|
|
|
|
aioseo()->actionScheduler->scheduleRecurrent( $this->actionName, 0, DAY_IN_SECONDS );
|
|
}
|
|
|
|
/**
|
|
* Initialize notifications.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function init() {
|
|
// If our tables do not exist, create them now.
|
|
if ( ! aioseo()->core->db->tableExists( 'aioseo_notifications' ) ) {
|
|
aioseo()->updates->addInitialCustomTablesForV4();
|
|
}
|
|
|
|
$this->initInternalNotices();
|
|
$this->deleteInternalNotices();
|
|
}
|
|
|
|
/**
|
|
* Update Notifications from the server.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function update() {
|
|
$notifications = $this->fetch();
|
|
foreach ( $notifications as $notification ) {
|
|
// First, let's check to see if this notification already exists. If so, we want to override it.
|
|
$n = aioseo()->core->db
|
|
->start( 'aioseo_notifications' )
|
|
->where( 'notification_id', $notification->id )
|
|
->run()
|
|
->model( 'AIOSEO\\Plugin\\Common\\Models\\Notification' );
|
|
|
|
$buttons = [
|
|
'button1' => [
|
|
'label' => ! empty( $notification->btns->main->text ) ? $notification->btns->main->text : null,
|
|
'url' => ! empty( $notification->btns->main->url ) ? $notification->btns->main->url : null
|
|
],
|
|
'button2' => [
|
|
'label' => ! empty( $notification->btns->alt->text ) ? $notification->btns->alt->text : null,
|
|
'url' => ! empty( $notification->btns->alt->url ) ? $notification->btns->alt->url : null
|
|
]
|
|
];
|
|
|
|
if ( $n->exists() ) {
|
|
$n->title = $notification->title;
|
|
$n->content = $notification->content;
|
|
$n->type = ! empty( $notification->notification_type ) ? $notification->notification_type : 'info';
|
|
$n->level = $notification->type;
|
|
$n->notification_id = $notification->id;
|
|
$n->start = ! empty( $notification->start ) ? $notification->start : null;
|
|
$n->end = ! empty( $notification->end ) ? $notification->end : null;
|
|
$n->button1_label = $buttons['button1']['label'];
|
|
$n->button1_action = $buttons['button1']['url'];
|
|
$n->button2_label = $buttons['button2']['label'];
|
|
$n->button2_action = $buttons['button2']['url'];
|
|
$n->save();
|
|
continue;
|
|
}
|
|
|
|
$n = new Models\Notification();
|
|
$n->slug = uniqid();
|
|
$n->title = $notification->title;
|
|
$n->content = $notification->content;
|
|
$n->type = ! empty( $notification->notification_type ) ? $notification->notification_type : 'info';
|
|
$n->level = $notification->type;
|
|
$n->notification_id = $notification->id;
|
|
$n->start = ! empty( $notification->start ) ? $notification->start : null;
|
|
$n->end = ! empty( $notification->end ) ? $notification->end : null;
|
|
$n->button1_label = $buttons['button1']['label'];
|
|
$n->button1_action = $buttons['button1']['url'];
|
|
$n->button2_label = $buttons['button2']['label'];
|
|
$n->button2_action = $buttons['button2']['url'];
|
|
$n->dismissed = 0;
|
|
$n->save();
|
|
|
|
// Since we've added a new remote notification, let's show the notification drawer.
|
|
aioseo()->core->cache->update( 'show_notifications_drawer', true );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches the feed of notifications.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return array An array of notifications.
|
|
*/
|
|
private function fetch() {
|
|
$cacheKey = 'notifications_last_fetched';
|
|
if ( null !== aioseo()->core->cache->get( $cacheKey ) ) {
|
|
return [];
|
|
}
|
|
|
|
aioseo()->core->cache->update( $cacheKey, true, 12 * HOUR_IN_SECONDS );
|
|
|
|
$response = aioseo()->helpers->wpRemoteGet( $this->getUrl() );
|
|
|
|
if ( is_wp_error( $response ) ) {
|
|
return [];
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body( $response );
|
|
if ( empty( $body ) ) {
|
|
return [];
|
|
}
|
|
|
|
return $this->verify( json_decode( $body ) );
|
|
}
|
|
|
|
/**
|
|
* Verify notification data before it is saved.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param array $notifications Array of notifications items to verify.
|
|
* @return array An array of verified notifications.
|
|
*/
|
|
private function verify( $notifications ) {
|
|
$data = [];
|
|
if ( ! is_array( $notifications ) || empty( $notifications ) ) {
|
|
return $data;
|
|
}
|
|
|
|
foreach ( $notifications as $notification ) {
|
|
// The message and license should never be empty, if they are, ignore.
|
|
if ( empty( $notification->content ) || empty( $notification->type ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ! is_array( $notification->type ) ) {
|
|
$notification->type = [ $notification->type ];
|
|
}
|
|
foreach ( $notification->type as $type ) {
|
|
// Ignore if type does not match.
|
|
if ( ! $this->validateType( $type ) ) {
|
|
continue 2;
|
|
}
|
|
}
|
|
|
|
// Ignore if expired.
|
|
if ( ! empty( $notification->end ) && time() > strtotime( $notification->end ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Ignore if notification existed before installing AIOSEO.
|
|
// Prevents bombarding the user with notifications after activation.
|
|
$activated = aioseo()->internalOptions->internal->firstActivated( time() );
|
|
if (
|
|
! empty( $notification->start ) &&
|
|
$activated > strtotime( $notification->start )
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
$data[] = $notification;
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Validates the notification type.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param string $type The notification type we are targeting.
|
|
* @return boolean True if yes, false if no.
|
|
*/
|
|
public function validateType( $type ) {
|
|
$validated = false;
|
|
|
|
if ( 'all' === $type ) {
|
|
$validated = true;
|
|
}
|
|
|
|
// Store notice if version matches.
|
|
if ( $this->versionMatch( aioseo()->version, $type ) ) {
|
|
$validated = true;
|
|
}
|
|
|
|
return $validated;
|
|
}
|
|
|
|
/**
|
|
* Version Compare.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param string $currentVersion The current version being used.
|
|
* @param string|array $compareVersion The version to compare with.
|
|
* @return bool True if we match, false if not.
|
|
*/
|
|
public function versionMatch( $currentVersion, $compareVersion ) {
|
|
if ( is_array( $compareVersion ) ) {
|
|
foreach ( $compareVersion as $compare_single ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
$recursiveResult = $this->versionMatch( $currentVersion, $compare_single ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
if ( $recursiveResult ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
$currentParse = explode( '.', $currentVersion );
|
|
if ( strpos( $compareVersion, '-' ) ) {
|
|
$compareParse = explode( '-', $compareVersion );
|
|
} elseif ( strpos( $compareVersion, '.' ) ) {
|
|
$compareParse = explode( '.', $compareVersion );
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
$currentCount = count( $currentParse );
|
|
$compareCount = count( $compareParse );
|
|
for ( $i = 0; $i < $currentCount || $i < $compareCount; $i++ ) {
|
|
if ( isset( $compareParse[ $i ] ) && 'x' === strtolower( $compareParse[ $i ] ) ) {
|
|
unset( $compareParse[ $i ] );
|
|
}
|
|
|
|
if ( ! isset( $currentParse[ $i ] ) ) {
|
|
unset( $compareParse[ $i ] );
|
|
} elseif ( ! isset( $compareParse[ $i ] ) ) {
|
|
unset( $currentParse[ $i ] );
|
|
}
|
|
}
|
|
|
|
foreach ( $compareParse as $index => $subNumber ) {
|
|
if ( $currentParse[ $index ] !== $subNumber ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the URL for the notifications api.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return string The URL to use for the api requests.
|
|
*/
|
|
private function getUrl() {
|
|
if ( defined( 'AIOSEO_NOTIFICATIONS_URL' ) ) {
|
|
return AIOSEO_NOTIFICATIONS_URL;
|
|
}
|
|
|
|
return $this->url;
|
|
}
|
|
|
|
/**
|
|
* Add notices.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function notices() {
|
|
// Double check we're actually in the admin before outputting anything.
|
|
if ( ! is_admin() ) {
|
|
return;
|
|
}
|
|
|
|
$this->review->maybeShowNotice();
|
|
$this->migration->maybeShowNotice();
|
|
$this->import->maybeShowNotice();
|
|
$this->deprecatedWordPress->maybeShowNotice();
|
|
$this->conflictingPlugins->maybeShowNotice();
|
|
}
|
|
|
|
/**
|
|
* Initialize the internal notices.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function initInternalNotices() {
|
|
$this->blogVisibility();
|
|
$this->descriptionFormat();
|
|
}
|
|
|
|
/**
|
|
* Deletes internal notices we no longer need.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function deleteInternalNotices() {
|
|
$pluginData = aioseo()->helpers->getPluginData();
|
|
if ( $pluginData['miPro']['installed'] || $pluginData['miLite']['installed'] ) {
|
|
$notification = Models\Notification::getNotificationByName( 'install-mi' );
|
|
if ( ! $notification->exists() ) {
|
|
return;
|
|
}
|
|
|
|
Models\Notification::deleteNotificationByName( 'install-mi' );
|
|
}
|
|
|
|
if ( $pluginData['optinMonster']['installed'] ) {
|
|
$notification = Models\Notification::getNotificationByName( 'install-om' );
|
|
if ( ! $notification->exists() ) {
|
|
return;
|
|
}
|
|
|
|
Models\Notification::deleteNotificationByName( 'install-om' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extends a notice by a (default) 1 week start date.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param string $notice The notice to extend.
|
|
* @param string $start How long to extend.
|
|
* @return void
|
|
*/
|
|
public function remindMeLater( $notice, $start = '+1 week' ) {
|
|
$notification = Models\Notification::getNotificationByName( $notice );
|
|
if ( ! $notification->exists() ) {
|
|
return;
|
|
}
|
|
|
|
$notification->start = gmdate( 'Y-m-d H:i:s', strtotime( $start ) );
|
|
$notification->save();
|
|
}
|
|
|
|
/**
|
|
* Add a notice if the blog is set to hidden.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
private function blogVisibility() {
|
|
$notification = Models\Notification::getNotificationByName( 'blog-visibility' );
|
|
if ( get_option( 'blog_public' ) ) {
|
|
if ( $notification->exists() ) {
|
|
Models\Notification::deleteNotificationByName( 'blog-visibility' );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( $notification->exists() || ! current_user_can( 'manage_options' ) ) {
|
|
return;
|
|
}
|
|
|
|
Models\Notification::addNotification( [
|
|
'slug' => uniqid(),
|
|
'notification_name' => 'blog-visibility',
|
|
'title' => __( 'Search Engines Blocked', 'all-in-one-seo-pack' ),
|
|
'content' => sprintf(
|
|
// Translators: 1 - The plugin short name ("AIOSEO").
|
|
__( 'Warning: %1$s has detected that you are blocking access to search engines. You can change this in Settings > Reading if this was unintended.', 'all-in-one-seo-pack' ),
|
|
AIOSEO_PLUGIN_SHORT_NAME
|
|
),
|
|
'type' => 'error',
|
|
'level' => [ 'all' ],
|
|
'button1_label' => __( 'Fix Now', 'all-in-one-seo-pack' ),
|
|
'button1_action' => admin_url( 'options-reading.php' ),
|
|
'button2_label' => __( 'Remind Me Later', 'all-in-one-seo-pack' ),
|
|
'button2_action' => 'http://action#notification/blog-visibility-reminder',
|
|
'start' => gmdate( 'Y-m-d H:i:s' )
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* Add a notice if the description format is missing the Description tag.
|
|
*
|
|
* @since 4.0.5
|
|
*
|
|
* @return void
|
|
*/
|
|
private function descriptionFormat() {
|
|
$notification = Models\Notification::getNotificationByName( 'description-format' );
|
|
if ( ! in_array( 'descriptionFormat', aioseo()->internalOptions->deprecatedOptions, true ) ) {
|
|
if ( $notification->exists() ) {
|
|
Models\Notification::deleteNotificationByName( 'description-format' );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
$descriptionFormat = aioseo()->options->deprecated->searchAppearance->global->descriptionFormat;
|
|
if ( false !== strpos( $descriptionFormat, '#description' ) ) {
|
|
if ( $notification->exists() ) {
|
|
Models\Notification::deleteNotificationByName( 'description-format' );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( $notification->exists() ) {
|
|
return;
|
|
}
|
|
|
|
Models\Notification::addNotification( [
|
|
'slug' => uniqid(),
|
|
'notification_name' => 'description-format',
|
|
'title' => __( 'Invalid Description Format', 'all-in-one-seo-pack' ),
|
|
'content' => sprintf(
|
|
// Translators: 1 - The plugin short name ("AIOSEO").
|
|
__( 'Warning: %1$s has detected that you may have an invalid description format. This could lead to descriptions not being properly applied to your content.', 'all-in-one-seo-pack' ),
|
|
AIOSEO_PLUGIN_SHORT_NAME
|
|
) . ' ' . __( 'A Description tag is required in order to properly display your meta descriptions on your site.', 'all-in-one-seo-pack' ),
|
|
'type' => 'error',
|
|
'level' => [ 'all' ],
|
|
'button1_label' => __( 'Fix Now', 'all-in-one-seo-pack' ),
|
|
'button1_action' => 'http://route#aioseo-search-appearance&aioseo-scroll=description-format&aioseo-highlight=description-format:advanced',
|
|
'button2_label' => __( 'Remind Me Later', 'all-in-one-seo-pack' ),
|
|
'button2_action' => 'http://action#notification/description-format-reminder',
|
|
'start' => gmdate( 'Y-m-d H:i:s' )
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* Check if blog visibility is changing and add/delete the appropriate notification.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param string $optionName The name of the option we are checking.
|
|
* @param mixed $oldValue The old value.
|
|
* @param mixed $newValue The new value.
|
|
* @return void
|
|
*/
|
|
public function maybeResetBlogVisibility( $optionName, $oldValue = '', $newValue = '' ) {
|
|
if ( 'blog_public' === $optionName ) {
|
|
if ( 1 === intval( $newValue ) ) {
|
|
$notification = Models\Notification::getNotificationByName( 'blog-visibility' );
|
|
if ( ! $notification->exists() ) {
|
|
return;
|
|
}
|
|
|
|
Models\Notification::deleteNotificationByName( 'blog-visibility' );
|
|
|
|
return;
|
|
}
|
|
|
|
$this->blogVisibility();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a notice if the blog is set to hidden.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function conflictingPlugins( $plugins = [] ) {
|
|
if ( empty( $plugins ) ) {
|
|
return;
|
|
}
|
|
|
|
$content = sprintf(
|
|
// Translators: 1 - The plugin short name ("AIOSEO").
|
|
__( 'Warning: %1$s has detected other active SEO or sitemap plugins. We recommend that you deactivate the following plugins to prevent any conflicts:', 'all-in-one-seo-pack' ),
|
|
AIOSEO_PLUGIN_SHORT_NAME
|
|
) . '<ul>';
|
|
|
|
foreach ( $plugins as $pluginName => $pluginPath ) {
|
|
$content .= '<li><strong>' . $pluginName . '</strong></li>';
|
|
}
|
|
|
|
$content .= '</ul>';
|
|
|
|
// Update an existing notice.
|
|
$notification = Models\Notification::getNotificationByName( 'conflicting-plugins' );
|
|
if ( $notification->exists() ) {
|
|
$notification->content = $content;
|
|
$notification->save();
|
|
|
|
return;
|
|
}
|
|
|
|
// Create a new one if it doesn't exist.
|
|
Models\Notification::addNotification( [
|
|
'slug' => uniqid(),
|
|
'notification_name' => 'conflicting-plugins',
|
|
'title' => __( 'Conflicting Plugins Detected', 'all-in-one-seo-pack' ),
|
|
'content' => $content,
|
|
'type' => 'error',
|
|
'level' => [ 'all' ],
|
|
'button1_label' => __( 'Fix Now', 'all-in-one-seo-pack' ),
|
|
'button1_action' => 'http://action#sitemap/deactivate-conflicting-plugins?refresh',
|
|
'button2_label' => __( 'Remind Me Later', 'all-in-one-seo-pack' ),
|
|
'button2_action' => 'http://action#notification/conflicting-plugins-reminder',
|
|
'start' => gmdate( 'Y-m-d H:i:s' )
|
|
] );
|
|
}
|
|
} |