page = new Page();
new Menu();
new Assets();
new Links();
new SiteHealth();
new Notices\RedundantAddons();
new Notices\Review();
new DashboardWidget();
// Late enough to catch notices registered after `current_screen`: `in_admin_header` fires
// immediately before `admin_notices` renders.
add_action( 'in_admin_header', [ $this, 'suppressForeignNotices' ] );
add_filter( 'admin_footer_text', [ $this, 'footerText' ] );
add_filter( 'update_footer', [ $this, 'footerVersions' ], 11 );
}
/**
* Ask for a review in the admin footer, on this plugin's screens only.
*
* Links through the aioseo.com redirect rather than straight to wp.org, so the
* click is measurable and the destination can be changed without a release.
*
* @since 4.0.4
*
* @param string $text The existing footer text.
* @return string Our text on our screens, the original everywhere else.
*/
public function footerText( $text ) {
if ( ! Plugin::isPluginScreen() ) {
return $text;
}
$href = 'https://aioseo.com/404-to-301-rating';
$title = esc_attr__( 'Give us a 5-star rating!', '404-to-301' );
$stars = sprintf(
'★★★★★',
esc_url( $href ),
$title
);
$wporg = sprintf(
'WordPress.org',
esc_url( $href ),
$title
);
return sprintf(
// Translators: 1 - The plugin name, 2 - Five star icons, 3 - "WordPress.org".
esc_html__( 'Please rate %1$s %2$s on %3$s to help us spread the word.', '404-to-301' ),
sprintf( '%s', esc_html( Plugin::name() ) ),
$stars,
$wporg
);
}
/**
* Replace the right-hand footer slot with the WordPress and plugin versions.
*
* Uses the `update_footer` slot rather than printing into `admin_footer_text` and
* unhooking core's callback, so nothing has to be removed and other screens are
* left alone.
*
* @since 4.0.4
*
* @param string $text The existing footer version text.
* @return string Our versions on our screens, the original everywhere else.
*/
public function footerVersions( $text ) {
if ( ! Plugin::isPluginScreen() ) {
return $text;
}
global $wp_version; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
return sprintf(
// Translators: 1 - The WordPress version, 2 - The plugin name, 3 - The plugin version.
esc_html__( 'WordPress %1$s | %2$s %3$s', '404-to-301' ),
esc_html( $wp_version ), // phpcs:ignore Squiz.NamingConventions.ValidVariableName
esc_html( Plugin::name() ),
esc_html( Plugin::version() )
);
}
/**
* Strip other plugins' admin notices from this plugin's own screens.
*
* A settings screen full of unrelated upsells and review nags isn't usable, so - as the parent
* plugin does on its screens - foreign notices are dropped here. This plugin's own notices are
* kept: they're the ones that say something about the page the user is looking at.
*
* @since 4.0.4
*
* @return void
*/
public function suppressForeignNotices() {
if ( ! Plugin::isPluginScreen() ) {
return;
}
foreach ( [ 'admin_notices', 'network_admin_notices', 'all_admin_notices', 'user_admin_notices' ] as $hook ) {
$this->removeForeignCallbacks( $hook );
}
}
/**
* Remove every callback on a hook that doesn't belong to this plugin.
*
* @since 4.0.4
*
* @param string $hook The notice hook to filter.
* @return void
*/
private function removeForeignCallbacks( $hook ) {
global $wp_filter; // phpcs:ignore Squiz.NamingConventions.ValidVariableName -- core global.
if ( empty( $wp_filter[ $hook ] ) || empty( $wp_filter[ $hook ]->callbacks ) ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName -- core global.
return;
}
// Collected first: remove_action() mutates the array this would otherwise be iterating.
$foreign = [];
foreach ( $wp_filter[ $hook ]->callbacks as $priority => $callbacks ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName -- core global.
foreach ( $callbacks as $callback ) {
if ( ! $this->isOwnCallback( $callback['function'] ) ) {
$foreign[] = [ $callback['function'], $priority ];
}
}
}
foreach ( $foreign as $callback ) {
remove_action( $hook, $callback[0], $callback[1] );
}
}
/**
* Whether a callback belongs to this plugin.
*
* NOTE: a closure can't be attributed to a plugin, so it counts as foreign. Ours are all methods.
*
* @since 4.0.4
*
* @param mixed $callback The registered callback.
* @return bool True when the callback is this plugin's.
*/
private function isOwnCallback( $callback ) {
$namespace = 'AIOSEO\\FourNotFour';
if ( is_string( $callback ) ) {
return 0 === strpos( $callback, $namespace );
}
if ( is_array( $callback ) && isset( $callback[0] ) ) {
$class = is_object( $callback[0] ) ? get_class( $callback[0] ) : (string) $callback[0];
return 0 === strpos( $class, $namespace );
}
return false;
}
}