Tag: filter-hook

  • Stop WordPress From Sending Emails (And Log Them Too!)

    Stop WordPress From Sending Emails (And Log Them Too!)

    While doing some development work recently, I wanted to make sure that I disabled all email sending in my test site so that no users imported would get any weird emails.

    To do this I had ChatGPT whip me up a quick plugin, and after cleaning it up here it is to share with the world:

    <?php
    /**
     * Plugin Name: Restrict and Log Emails
     * Description: Blocks emails for users and logs all email attempts.
     * Version: 1.3
     * Author: Emrikol
     */
    
    if ( ! defined( 'ABSPATH' ) ) {
    	exit; // Prevent direct access.
    }
    define( 'REL_EMAIL_LOG_DB_VERSION', '1.2' );
    
    /**
     * Database installation and upgrade function.
     * Creates or updates the email_log table if needed based on a stored version option.
     *
     * @return void
     */
    function rel_install() {
    	global $wpdb;
    	$table_name      = $wpdb->prefix . 'email_log';
    	$charset_collate = $wpdb->get_charset_collate();
    
    	$sql = "CREATE TABLE $table_name (
    	   id mediumint(9) NOT NULL AUTO_INCREMENT,
    	   recipient_email varchar(100) NOT NULL,
    	   subject text NOT NULL,
    	   message text NOT NULL,
    	   status varchar(20) NOT NULL,
    	   sent_at datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
    	   PRIMARY KEY (id)
    	) $charset_collate;";
    
    	require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    	dbDelta( $sql );
    
    	// Update the database version option.
    	update_option( 'rel_email_log_db_version', REL_EMAIL_LOG_DB_VERSION, false );
    }
    register_activation_hook( __FILE__, 'rel_install' );
    
    /**
     * Adds the Email Log submenu to Tools in the WordPress admin area.
     *
     * @return void
     */
    function rel_add_admin_menu(): void {
    	if ( function_exists( 'add_submenu_page' ) ) {
    		add_submenu_page(
    			'tools.php',
    			'Email Log',
    			'Email Log',
    			'manage_options',
    			'email-log',
    			'rel_email_log_page'
    		);
    	}
    }
    add_action( 'admin_menu', 'rel_add_admin_menu' );
    
    /**
     * Displays the Email Log page in the WordPress admin area.
     *
     * @return void
     */
    function rel_email_log_page(): void {
    	global $wpdb;
    	$table_name = $wpdb->prefix . 'email_log';
    	$logs       = $wpdb->get_results( "SELECT * FROM $table_name ORDER BY sent_at DESC LIMIT 50" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    
    	echo '<div class="wrap">';
    	echo '<h1>Email Log</h1>';
    
    	if ( current_user_can( 'manage_options' ) ) {
    		// Process toggle form submission.
    		if ( isset( $_POST['rel_toggle_blocking'] ) && check_admin_referer( 'rel_toggle_blocking_action', 'rel_toggle_blocking_nonce' ) ) {
    			// Sanitize and update the blocking option.
    			if ( isset( $_POST['rel_email_blocking_enabled'] ) && in_array( $_POST['rel_email_blocking_enabled'], array( 'enabled', 'disabled' ), true ) ) {
    				$blocking = $_POST['rel_email_blocking_enabled']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    			} else {
    				$blocking = 'enabled';
    			}
    
    			update_option( 'rel_email_blocking_enabled', $blocking, false );
    			echo '<div class="updated notice"><p>Email blocking has been ' . ( 'enabled' === $blocking ? 'enabled' : 'disabled' ) . '.</p></div>';
    		}
    		$current_blocking = get_option( 'rel_email_blocking_enabled', true );
    
    		echo '<form method="post" style="margin-bottom:20px;">';
    		wp_nonce_field( 'rel_toggle_blocking_action', 'rel_toggle_blocking_nonce' );
    		echo '<p>Email blocking is currently <strong>' . ( 'enabled' === $current_blocking ? 'enabled' : 'disabled' ) . '</strong>.';
    		echo '<br/><br/><label for="rel_email_blocking_enabled">Toggle: </label>';
    		echo '<select id="rel_email_blocking_enabled" name="rel_email_blocking_enabled">';
    		echo '<option value="enabled"' . selected( $current_blocking, 'enabled', false ) . '>Enabled</option>';
    		echo '<option value="disabled"' . selected( $current_blocking, 'disabled', false ) . '>Disabled</option>';
    		echo '</select> ';
    		echo '<input type="submit" name="rel_toggle_blocking" value="Update" class="button-primary" />';
    		echo '</p>';
    		echo '</form>';
    	}
    
    	echo '<table class="widefat fixed" cellspacing="0">';
    	echo '<thead><tr><th>ID</th><th>Email</th><th>Subject</th><th>Message</th><th>Status</th><th>Sent At</th></tr></thead>';
    	echo '<tbody>';
    
    	if ( $logs ) {
    		foreach ( $logs as $log ) {
    			$truncated_message = ( strlen( $log->message ) > 30 ) ? substr( $log->message, 0, 30 ) . '…' : $log->message;
    			echo wp_kses_post(
    				'<tr>
    					<td>' . $log->id . '</td>
    					<td>' . $log->recipient_email . '</td>
    					<td>' . $log->subject . '</td>
    					<td>' . $truncated_message . '</td>
    					<td>' . $log->status . '</td>
    					<td>' . $log->sent_at . '</td>
    				  </tr>'
    			);
    		}
    	} else {
    		echo '<tr><td colspan="6">No emails logged yet.</td></tr>';
    	}
    
    	echo '</tbody></table>';
    	echo '</div>';
    }
    
    /**
     * Intercepts email sending attempts, restricts emails for users without the 'manage_options' capability, and logs the attempt.
     *
     * @param null|bool $short_circuit Default value if email is allowed.
     * @param array     $atts An array of email attributes (to, subject, etc.).
     * @return bool|null Returns a non-null value to short-circuit email sending if restricted.
     */
    function rel_prevent_email( $short_circuit, $atts ) {
    	global $wpdb;
    	$table_name = $wpdb->prefix . 'email_log';
    
    	$recipient_email = isset( $atts['to'] ) ? $atts['to'] : '';
    	$subject         = isset( $atts['subject'] ) ? $atts['subject'] : '';
    	$message         = isset( $atts['message'] ) ? $atts['message'] : '';
    	$sent_at         = current_time( 'mysql' );
    
    	// Check if email blocking is enabled (default true).
    	$blocking = get_option( 'rel_email_blocking_enabled', 'enabled' );
    	switch ( $blocking ) {
    		case 'enabled':
    			$blocking_enabled = true;
    			break;
    		case 'disabled':
    			$blocking_enabled = false;
    			break;
    		default:
    			$blocking_enabled = true;
    			break;
    	}
    
    	// Determine status based on blocking setting.
    	if ( true === $blocking_enabled ) {
    		$status = 'Blocked';
    	} else {
    		$status = 'Sent';
    	}
    
    	// Log the email attempt.
    	$wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
    		$table_name,
    		array(
    			'recipient_email' => $recipient_email,
    			'subject'         => $subject,
    			'message'         => $message,
    			'status'          => $status,
    			'sent_at'         => $sent_at,
    		)
    	);
    
    	// If blocking is enabled and the current user cannot manage options, block the email, otherwise allow sending.
    	if ( $blocking_enabled ) {
    		return true;
    	}
    	return null;
    }
    add_filter( 'pre_wp_mail', 'rel_prevent_email', 10, 2 );
    
    /**
     * Checks the current database version and updates the email_log table if necessary.
     *
     * @return void
     */
    function rel_check_db_version() {
    	if ( get_option( 'rel_email_log_db_version' ) !== REL_EMAIL_LOG_DB_VERSION ) {
    		rel_install();
    	}
    }
    add_action( 'init', 'rel_check_db_version' );
    
    Code language: PHP (php)

    As usual, don’t use this slop, nor anything you read here because this is my blarg and I can’t be trusted to do anything correct.

  • How To Restrict User to Self Posts in WordPress

    How To Restrict User to Self Posts in WordPress

    I recently had to work with a third-party integration that used the WordPress REST API to interact with a website. We use this tool internally to move data around from other integrations, and finally into WordPress.

    One of the things that I was worried about was the fact that this plugin would then have full access to the website, which is a private site. We only wanted to use it to post and then update WordPress posts, but there was always the concern that if the third party were to be hacked, then someone could read all of our posts on the private site through the REST API.

    My solution was to hook into user_has_cap so that the user that was set up for the plugin to integrate with, through an application password, would only have access to read and edit its own posts. A bonus is that we wanted to be able to change the author of a post so that it would show up as specific users or project owners. That meant the authorized plugin user would also need access to these posts after the author was changed–so to get past that I scoured each post revision, and if the plugin user was the author of a revision it was also allowed access.

    Finally, to make sure no other published posts were readable, I hooked into posts_results to set any post that didn’t meat the above criteria were marked as private. Below is a cleaned up version of that as an example if anyone else needs this type of functionality–feel free to use it as a starting point:

    <?php
    /**
     * Restricts post capabilities for a specific user.
     *
     * @param bool[]   $allcaps The current user capabilities.
     * @param string[] $caps    The requested capabilities.
     * @param array    $args {
     *     Arguments that accompany the requested capability check.
     *
     *     @type string    $0 Requested capability.
     *     @type int       $1 Concerned user ID.
     *     @type mixed  ...$2 Optional second and further parameters, typically object ID.
     * }
     * @param WP_User  $user    The user object.
     *
     * @return bool[] The modified user capabilities.
     */
    function emrikol_restrict_post_capabilities( array $allcaps, array $caps, array $args, WP_User $user ): array {
    	$post_id = isset( $args[2] ) ? absint( $args[2] ) : false;
    
    	if ( false === $post_id || ! get_post( $post_id ) ) {
    		return $allcaps;
    	}
    
    	if ( 'restricted' === get_user_meta( $user->ID, 'emrikol_restricted_post_capabilities', true ) ) {
    		$allowed_caps  = array( 'read', 'read_private_posts', 'read_post', 'edit_post', 'delete_post', 'edit_others_posts', 'delete_others_posts' );
    		$requested_cap = isset( $caps[0] ) ? $caps[0] : '';
    
    		if ( in_array( $requested_cap, $allowed_caps, true ) ) {
    			if ( emrikol_user_is_author_or_revision_author( $user->ID, $post_id ) ) {
    				$allcaps[ $requested_cap ] = true;
    			} else {
    				$allcaps[ $requested_cap ] = false;
    			}
    		}
    	}
    
    	return $allcaps;
    }
    add_filter( 'user_has_cap', 'emrikol_restrict_post_capabilities', 10, 4 );
    
    /**
     * Restricts the public posts results based on the query.
     *
     * @param WP_Post[] $posts  The array of posts returned by the query.
     * @param WP_Query  $query  The WP_Query instance (passed by reference).
     *
     * @return array           The filtered array of posts.
     */
    function emrikol_restrict_public_posts_results( array $posts, WP_Query $query ): array {
    	if ( ! is_admin() && $query->is_main_query() ) {
    		$current_user = wp_get_current_user();
    
    		if ( 'restricted' === get_user_meta( $user->ID, 'emrikol_restricted_post_capabilities', true ) ) {
    			foreach ( $posts as $key => $post ) {
    				if ( ! emrikol_user_is_author_or_revision_author( $current_user->ID, $post->ID ) ) {
    					$posts[ $key ]->post_status = 'private';
    				}
    			}
    		}
    	}
    
    	return $posts;
    }
    add_filter( 'posts_results', 'emrikol_restrict_public_posts_results', 10, 2 );
    
    /**
     * Checks if the user is the author of the post or the author of a revision.
     *
     * @param int $user_id The ID of the user.
     * @param int $post_id The ID of the post.
     *
     * @return bool True if the user is the author or revision author, false otherwise.
     */
    function emrikol_user_is_author_or_revision_author( int $user_id, int $post_id ): bool {
    	$post_author_id = (int) get_post_field( 'post_author', $post_id );
    
    	if ( $user_id === $post_author_id ) {
    		return true;
    	}
    
    	$revisions = wp_get_post_revisions( $post_id );
    
    	foreach ( $revisions as $revision ) {
    		if ( $user_id === $revision->post_author ) {
    			return true;
    		}
    	}
    
    	return false;
    }
    Code language: PHP (php)
  • Silly Ideas: Cache WordPress Excerpts

    Silly Ideas: Cache WordPress Excerpts

    I recently worked on profiling a customer site for performance problems, and one of the issues that I had seen was that calls to get_the_excerpt() were running HORRIBLY slow due to filters.

    I ended up writing a really hacky workaround to cache excerpts to help reduce the burden they had on the page generation time, but we ended up not using this solution. Instead we found another filter in a plugin that removed the painfully slow stuff happening in the excerpt.

    So below is one potential way to cache your excerpt, but be warned it’s only barely tested:

    /**
     * Checks the excerpt cache for a post and returns the cached excerpt if available.
     * If the post is password protected, the original post excerpt is returned.
     *
     * @param string  $post_excerpt The original post excerpt.
     * @param WP_Post $post         The post object.
     *
     * @return string The post excerpt, either from the cache or the original.
     */
    function blarg_check_excerpt_cache( string $post_excerpt, WP_Post $post ): string {
    	// We do not want to cache password protected posts.
    	if ( post_password_required( $post ) ) {
    		return $post_excerpt;
    	}
    
    	$cache_key   = $post->ID;
    	$cache_group = 'blarg_cached_post_excerpt';
    	$cache_data  = wp_cache_get( $cache_key, $cache_group );
    
    	if ( false !== $cache_data ) {
    		remove_all_filters( 'get_the_excerpt' );
    		add_filter( 'get_the_excerpt', 'blarg_check_excerpt_cache', PHP_INT_MIN, 2 );
    		$post_excerpt = $cache_data;
    	} else {
    		add_filter( 'get_the_excerpt', 'blarg_cache_the_excerpt', PHP_INT_MAX, 2 );
    	}
    
    	// At this point, do not modify anything and return.
    	return $post_excerpt;
    }
    add_filter( 'get_the_excerpt', 'blarg_check_excerpt_cache', PHP_INT_MIN, 2 );
    
    /**
     * Caches the post excerpt in the WordPress object cache.
     *
     * @param string  $post_excerpt The post excerpt to cache.
     * @param WP_Post $post         The post object.
     *
     * @return string The cached post excerpt.
     */
    function blarg_cache_the_excerpt( string $post_excerpt, WP_Post $post ): string {
    	$cache_key   = $post->ID;
    	$cache_group = 'blarg_cached_post_excerpt';
    	wp_cache_set( $cache_key, $post_excerpt, $cache_group );
    
    	return $post_excerpt;
    }
    
    /**
     * Deletes the cached excerpt for a given post.
     *
     * @param int|WP_Post $post The post ID or WP_Post object.
     *
     * @return void
     */
    function blarg_delete_the_excerpt_cache( int|WP_Post $post ): void {
    	if ( $post instanceof WP_Post ) {
    		$post_id = $post->ID;
    	} else {
    		$post_id = $post;
    	}
    
    	$cache_key   = $post_id;
    	$cache_group = 'blarg_cached_post_excerpt';
    	wp_cache_delete( $cache_key, $cache_group );
    }
    add_action( 'clean_post_cache', 'blarg_delete_the_excerpt_cache', 10, 1 );
    Code language: PHP (php)

    This should automatically clear out the cache any time the post is updated, but if the excerpt gets updated in any other way you may need to purge the cache in other ways.

    Good luck with this monstrosity!

  • Half Baked Idea: Limiting WordPress Image Upload Sizes

    Half Baked Idea: Limiting WordPress Image Upload Sizes

    If you want to be able to limit images (or any attachments) from being uploaded into the media library if they are too large, you can use the wp_handle_upload_prefilter filter to do this.

    Below is a really basic example I whipped up in a few minutes that I shared with a customer not too long ago:

    /**
     * Limits the maximum size of images that can be uploaded.
     *
     * @param array $file {
     *     Reference to a single element from `$_FILES`.
     *
     *     @type string $name     The original name of the file on the client machine.
     *     @type string $type     The mime type of the file, if the browser provided this information.
     *     @type string $tmp_name The temporary filename of the file in which the uploaded file was stored on the server.
     *     @type int    $size     The size, in bytes, of the uploaded file.
     *     @type int    $error    The error code associated with this file upload.
     * }
     *
     * @return array       Filtered file array, with a possible error if the file is too large.
     */
    function blarg_limit_max_image_upload_size( $file ) {
        if ( str_starts_with( $file['type'], 'image/' ) ) {
            $max_size = 1 * 1024; // 1kb.
    
            if ( $file['size'] > $max_size ) {
                // translators: %s: Human readable maximum image file size.
                $file['error'] .= sprintf( __( 'Uploaded images must be smaller than %s.' ), size_format( $max_size ) );
            }
        }
    
        return $file;
    
    }
    add_filter( 'wp_handle_upload_prefilter', 'blarg_limit_max_image_upload_size', 10, 1 );Code language: PHP (php)

    Good luck if you use any of my hot garbage in production 😀

  • Quick Tip: Script Debugging in WordPress

    Quick Tip: Script Debugging in WordPress

    If you’re debugging core WordPress scripts, one thing you might run into is dealing with cached copies of the script. Due to how script-loader.php enqueues the core files, their versions are “hard coded” and short of editing script-loader.php as well, there’s a way to fix this via a filter:

    add_filter( 'script_loader_src', function( $src, $handle ) {
         if ( false !== strpos( $src, 'ver=' ) ) {
             $src = remove_query_arg( 'ver', $src );
             $src = add_query_arg( array( 'ver', rawurlencode( uniqid( $handle ) . '-' ) ), $src );
         }
         
         <span style="background-color: inherit; color: rgb(248, 248, 242); font-size: inherit; letter-spacing: -0.015em;">return $src;</span>
     }, -1, 2 );Code language: PHP (php)

    This will apply a unique ver argument to the core scripts on each refresh, so no matter what you’re editing you should get the most recent version from both any page cache you may have and also the browser cache (🤞).

    Also, don’t forget to enable SCRIPT_DEBUG if you’re hacking away at core scripts to debug issues.

    I couldn’t find a good related image, so enjoy this delicious toilet paper.

  • Logging Failed Redirects

    Logging Failed Redirects

    WordPress has a built-in function called wp_safe_redirect().  This allows you to create redirects in code, but only to whitelisted domains (via the allowed_redirect_hosts filter).

    The downside to this is that you have to remember to whitelist the domains.  It’s easy to forget if you’re doing a lot of redirects, for instance with the WPCOM Legacy Redirector plugin.

    When this happens, all un-whitelisted redirects will be redirected by default to /wp-admin/ instead, and can cause a headache trying to figure out what’s going wrong.

    I had an idea to solve this problem.  A simple logging plugin that logs failed redirects and adds a dashboard widget to show the domains and number of times the redirect has failed:

    The code behind this:

    <?php
    class Emrikol_WSRD_Dashboard {
    	public static function instance() {
    		static $instance = false;
    		if ( ! $instance ) {
    			$instance = new Emrikol_WSRD_Dashboard();
    		}
    		return $instance;
    	}
    
    	public function __construct() {
    		add_action( 'init', array( $this, 'init' ) );
    		add_filter( 'allowed_redirect_hosts', array( $this, 'check_redirect' ), PHP_INT_MAX, 2 );
    	}
    
    	public function init() {
    		if ( $this->is_admin() && isset( $_GET['wsrd_delete'] ) && check_admin_referer( 'wsrd_delete' ) && isset( $_GET['ID'] ) ) {
    			$post_id = (int) $_GET['ID'];
    
    			if ( 'wsrd' !== get_post_type( $post_id ) ) {
    				// This isn't the right post type, abort!
    				add_action( 'admin_notices', array( $this, 'message_log_not_deleted' ) );
    				return;
    			}
    
    			$delete = wp_delete_post( $post_id, true );
    			wp_cache_delete( 'wsrd_report' );
    
    			if ( $delete ) {
    				add_action( 'admin_notices', array( $this, 'message_log_deleted' ) );
    			} else {
    				add_action( 'admin_notices', array( $this, 'message_log_not_deleted' ) );
    			}
    		}
    
    		$args = array(
    			'supports' => array( 'title' ),
    			'public'   => false,
    		);
    		register_post_type( 'wsrd', $args );
    
    		add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widgets' ) );
    	}
    
    	public function add_dashboard_widgets() {
    		if ( $this->is_admin() ) {
    			wp_add_dashboard_widget( 'emrikol_wsrd_dashboard', 'Failed Safe Redirects', array( $this, 'show_admin_dashboard' ) );
    		}
    	}
    
    	public function check_redirect( $allowed_hosts, $redirect_host ) {
    		if ( ! in_array( $redirect_host, $allowed_hosts, true ) ) {
    			// No redirect, please record.
    			$found_host = new WP_Query( array(
    				'fields'                 => 'ids',
    				'name'                   => md5( $redirect_host ),
    				'post_type'              => 'wsrd',
    				'post_status'            => 'any',
    				'no_found_rows'          => true,
    				'posts_per_page'         => 1,
    				'update_post_term_cache' => false,
    				'update_post_meta_cache' => false,
    			) );
    
    			if ( empty( $found_host->posts ) ) {
    				// No past redirect log found, create one.
    				$args   = array(
    					'post_name'  => md5( $redirect_host ),
    					'post_title' => $redirect_host,
    					'post_type'  => 'wsrd',
    					'meta_input' => array(
    						'count' => 1,
    					),
    				);
    				$insert = wp_insert_post( $args );
    			} else {
    				// Found!  Update count.
    				$count = absint( get_post_meta( $found_host->posts[0], 'count', true ) );
    				$count++;
    				update_post_meta( $found_host->posts[0], 'count', $count );
    			}
    		}
    		// We don't want to modify, always return allowed hosts unharmed.
    		return $allowed_hosts;
    	}
    
    	public function show_admin_dashboard() {
    		global $wpdb;
    
    		$report = wp_cache_get( 'wsrd_report' );
    		if ( false === $report ) {
    			$report = $wpdb->get_results( "SELECT ID, post_title AS host, meta_value AS count FROM $wpdb->posts LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id ) WHERE post_type='wsrd'  ORDER BY ABS( count ) DESC LIMIT 20;" );
    			wp_cache_set( 'wsrd_report', $report, 'default', MINUTE_IN_SECONDS * 5 );
    		}
    
    		?>
    		<style>
    			table#wsrd {
    				border-collapse: collapse;
    				width: 100%;
    			}
    			table#wsrd th {
    				background: #f5f5f5;
    			}
    
    			table#wsrd th, table#wsrd td {
    				border: 1px solid #f5f5f5;
    				padding: 8px;
    			}
    
    			table#wsrd tr:nth-child(even) {
    				background: #fafafa;
    			}
    		</style>
    		<div class="activity-block">
    			<?php if ( empty( $report ) ) : ?>
    			<p><strong>None Found!</strong></p>
    			<?php else : ?>
    			<table id="wsrd">
    				<thead>
    					<tr>
    						<th>Domain</th>
    						<th>Count</th>
    						<th>Control</th>
    					</tr>
    				</thead>
    				<tbody>
    					<?php foreach ( $report as $line ) : ?>
    						<tr>
    							<td><?php echo esc_html( $line->host ); ?></td>
    							<td><?php echo esc_html( $line->count ); ?></td>
    							<td><a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'wsrd_delete' => true, 'ID' => rawurlencode( $line->ID ) ), admin_url() ), 'wsrd_delete' ) ); ?>">Delete</a></td>
    						</tr>
    					<?php endforeach; ?>
    				</tbody>
    			</table>
    			<?php endif; ?>
    		</div>
    		<?php
    	}
    
    	public function message_log_deleted() {
    		echo '<div id="message" class="notice notice-success is-dismissible"><p>Redirect log deleted!</p></div>';
    	}
    
    	public function message_log_not_deleted() {
    		echo '<div id="message" class="notice notice-error is-dismissible"><p>Redirect log delete failed!</p></div>';
    	}
    
    
    	private function is_admin() {
    		if ( current_user_can( 'manage_options' ) ) {
    			return true;
    		}
    		return false;
    	}
    }
    Emrikol_WSRD_Dashboard::instance();
    Code language: HTML, XML (xml)

  • Quick Tip: Force Enable Auto-Updates in WordPress

    Quick Tip: Force Enable Auto-Updates in WordPress

    I know that auto-updates are a bit of a (#wpdrama) touchy subject, but I believe in them.

    In an mu-plugin I enable all auto-updates like so:

    <?php
    // Turn on auto-updates for everything
    if ( ! defined( 'IS_PRESSABLE' ) || ! IS_PRESSABLE ) {
    	add_filter( 'allow_major_auto_core_updates', '__return_true' );
    	add_filter( 'allow_minor_auto_core_updates', '__return_true' );
    }
    
    add_filter( 'auto_update_core', '__return_true' );
    add_filter( 'auto_update_plugin', '__return_true' );
    add_filter( 'auto_update_theme', '__return_true' );
    add_filter( 'auto_update_translation', '__return_true' );
    Code language: HTML, XML (xml)
  • Disabling plugin deactivation in WordPress

    Disabling plugin deactivation in WordPress

    The problem came up recently about how to make sure plugins activated in the WordPress plugin UI don’t get deactivated if they are necessary for a site to function.  I thought that was an interesting thought puzzle worth spending 15 minutes on, so I came up with this function as a solution:

    function dt_force_plugin_active( $plugin ) {
    add_filter( 'pre_update_option_active_plugins', function ( $active_plugins ) use ( $plugin ) {
    // Match if properly named: wp-plugin (wp-plugin/wp-plugin.php).
    $proper_plugin_name = $plugin . '/' . $plugin . '.php';
    <pre><code>    if (
            file_exists( WP_PLUGIN_DIR . '/' . $proper_plugin_name )
            &amp;amp;&amp;amp; is_file( WP_PLUGIN_DIR . '/' . $proper_plugin_name )
            &amp;amp;&amp;amp; ! in_array( $proper_plugin_name, $active_plugins, true )
        ) {
            $active_plugins[] = $proper_plugin_name;
            return array_unique( $active_plugins );
        }
    
        // Match if improperly named: wp-plugin/cool-plugin.php.
        if (
            file_exists( WP_PLUGIN_DIR . '/' . $plugin )
            &amp;amp;&amp;amp; is_file( WP_PLUGIN_DIR . '/' . $plugin )
            &amp;amp;&amp;amp; ! in_array( $plugin, $active_plugins, true )
        ) {
            $active_plugins[] = $plugin;
            return array_unique( $active_plugins );
        }
    
        return array_unique( $active_plugins );
    }, 1000, 1 );Code language: PHP (php)

    Which can be activated in your theme’s functions.php like so:

    dt_force_plugin_active( 'akismet' ); or dt_force_plugin_active( 'wordpress-seo/wp-seo.php' );

    The only downside that I’ve seen so far is that you still get the Plugin deactivated. message in the admin notices.