271 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			271 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * Sandhills Development Persistent Dismissible Utility
 | 
						|
 *
 | 
						|
 * @package SandhillsDev
 | 
						|
 * @subpackage Utilities
 | 
						|
 */
 | 
						|
namespace Sandhills\Utils;
 | 
						|
 | 
						|
// Exit if accessed directly
 | 
						|
defined( 'ABSPATH' ) || exit;
 | 
						|
 | 
						|
/**
 | 
						|
 * This class_exists() check avoids a fatal error if this class exists in more
 | 
						|
 * than one included plugin/theme, and should not be removed.
 | 
						|
 */
 | 
						|
if ( ! class_exists( 'Sandhills\Utils\Persistent_Dismissible' ) ) :
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Class for encapsulating the logic required to maintain a relationship between
 | 
						|
	 * the database, a dismissible UI element with an optional lifespan, and a
 | 
						|
	 * user's desire to dismiss that UI element.
 | 
						|
	 *
 | 
						|
	 * Think of this like a WordPress Transient, but without in-memory cache support
 | 
						|
	 * and that uses the `wp_usermeta` database table instead of `wp_options`.
 | 
						|
	 *
 | 
						|
	 * @version 1.0.0
 | 
						|
	 */
 | 
						|
	class Persistent_Dismissible {
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Get the value of a persistent dismissible.
 | 
						|
		 *
 | 
						|
		 * @since 1.0.0
 | 
						|
		 * @param array $args See parse_args().
 | 
						|
		 * @return mixed User meta value on success, false on failure.
 | 
						|
		 */
 | 
						|
		public static function get( $args = array() ) {
 | 
						|
 | 
						|
			// Parse arguments.
 | 
						|
			$r = self::parse_args( $args );
 | 
						|
 | 
						|
			// Bail if invalid arguments.
 | 
						|
			if ( ! self::check_args( $r ) ) {
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
 | 
						|
			// Get prefixed option names.
 | 
						|
			$eol_id       = self::get_eol_id( $r );
 | 
						|
			$prefix       = self::get_prefix( $r );
 | 
						|
			$prefixed_id  = $prefix . $r['id'];
 | 
						|
			$prefixed_eol = $prefix . $eol_id;
 | 
						|
 | 
						|
			// Get return value & end-of-life.
 | 
						|
			$retval   = get_user_meta( $r['user_id'], $prefixed_id,  true );
 | 
						|
			$lifespan = get_user_meta( $r['user_id'], $prefixed_eol, true );
 | 
						|
 | 
						|
			// Prefer false over default return value of get_user_meta()
 | 
						|
			if ( '' === $retval ) {
 | 
						|
				$retval = false;
 | 
						|
			}
 | 
						|
 | 
						|
			// If end-of-life, delete it. This needs to be inside get() because we
 | 
						|
			// are not relying on WP Cron for garbage collection. This mirrors
 | 
						|
			// behavior found inside of WordPress core.
 | 
						|
			if ( self::is_eol( $lifespan ) ) {
 | 
						|
				delete_user_option( $r['user_id'], $r['id'], $r['global'] );
 | 
						|
				delete_user_option( $r['user_id'], $eol_id,  $r['global'] );
 | 
						|
				$retval = false;
 | 
						|
			}
 | 
						|
 | 
						|
			// Return the value.
 | 
						|
			return $retval;
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Set the value of a persistent dismissible.
 | 
						|
		 *
 | 
						|
		 * @since 1.0.0
 | 
						|
		 * @param array $args See parse_args().
 | 
						|
		 * @return int|bool User meta ID if the option didn't exist, true on
 | 
						|
		 *                  successful update, false on failure.
 | 
						|
		 */
 | 
						|
		public static function set( $args = array() ) {
 | 
						|
 | 
						|
			// Parse arguments.
 | 
						|
			$r = self::parse_args( $args );
 | 
						|
 | 
						|
			// Bail if invalid arguments.
 | 
						|
			if ( ! self::check_args( $r ) ) {
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
 | 
						|
			// Get lifespan and prefixed option names.
 | 
						|
			$lifespan     = self::get_lifespan( $r );
 | 
						|
			$eol_id       = self::get_eol_id( $r );
 | 
						|
			$prefix       = self::get_prefix( $r );
 | 
						|
			$prefixed_id  = $prefix . $r['id'];
 | 
						|
			$prefixed_eol = $prefix . $eol_id;
 | 
						|
 | 
						|
			// No dismissible data, so add it.
 | 
						|
			if ( '' === get_user_meta( $r['user_id'], $prefixed_id, true ) ) {
 | 
						|
 | 
						|
				// Add lifespan.
 | 
						|
				if ( ! empty( $lifespan ) ) {
 | 
						|
					add_user_meta( $r['user_id'], $prefixed_eol, $lifespan, true );
 | 
						|
				}
 | 
						|
 | 
						|
				// Add dismissible data.
 | 
						|
				$retval = add_user_meta( $r['user_id'], $prefixed_id, $r['value'], true );
 | 
						|
 | 
						|
				// Dismissible data found in database.
 | 
						|
			} else {
 | 
						|
 | 
						|
				// Plan to update.
 | 
						|
				$update = true;
 | 
						|
 | 
						|
				// Dismissible to update has new lifespan.
 | 
						|
				if ( ! empty( $lifespan ) ) {
 | 
						|
 | 
						|
					// If lifespan is requested but the dismissible has no end-of-life,
 | 
						|
					// delete them both and re-create them, to avoid race conditions.
 | 
						|
					if ( '' === get_user_meta( $r['user_id'], $prefixed_eol, true ) ) {
 | 
						|
						delete_user_option( $r['user_id'], $r['id'], $r['global'] );
 | 
						|
						add_user_meta( $r['user_id'], $prefixed_eol, $lifespan, true );
 | 
						|
						$retval = add_user_meta( $r['user_id'], $prefixed_id, $r['value'], true );
 | 
						|
						$update = false;
 | 
						|
 | 
						|
						// Update the lifespan.
 | 
						|
					} else {
 | 
						|
						update_user_option( $r['user_id'], $eol_id, $lifespan, $r['global'] );
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				// Update the dismissible value.
 | 
						|
				if ( ! empty( $update ) ) {
 | 
						|
					$retval = update_user_option( $r['user_id'], $r['id'], $r['value'], $r['global'] );
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// Return the value.
 | 
						|
			return $retval;
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Delete a persistent dismissible.
 | 
						|
		 *
 | 
						|
		 * @since 1.0.0
 | 
						|
		 * @param array $args See parse_args().
 | 
						|
		 * @return bool True on success, false on failure.
 | 
						|
		 */
 | 
						|
		public static function delete( $args = array() ) {
 | 
						|
 | 
						|
			// Parse arguments.
 | 
						|
			$r = self::parse_args( $args );
 | 
						|
 | 
						|
			// Bail if invalid arguments.
 | 
						|
			if ( ! self::check_args( $r ) ) {
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
 | 
						|
			// Get the end-of-life ID.
 | 
						|
			$eol_id = self::get_eol_id( $r );
 | 
						|
 | 
						|
			// Delete.
 | 
						|
			delete_user_option( $r['user_id'], $r['id'], $r['global'] );
 | 
						|
			delete_user_option( $r['user_id'], $eol_id,  $r['global'] );
 | 
						|
 | 
						|
			// Success.
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Parse array of key/value arguments.
 | 
						|
		 *
 | 
						|
		 * Used by get(), set(), and delete(), to ensure default arguments are set.
 | 
						|
		 *
 | 
						|
		 * @since 1.0.0
 | 
						|
		 * @param array|string $args {
 | 
						|
		 *     Array or string of arguments to identify the persistent dismissible.
 | 
						|
		 *
 | 
						|
		 *     @type string      $id       Required. ID of the persistent dismissible.
 | 
						|
		 *     @type string      $user_id  Optional. User ID. Default to current user ID.
 | 
						|
		 *     @type int|string  $value    Optional. Value to store. Default to true.
 | 
						|
		 *     @type int|string  $life     Optional. Lifespan. Default to 0 (infinite)
 | 
						|
		 *     @type bool        $global   Optional. Multisite, all sites. Default true.
 | 
						|
		 * }
 | 
						|
		 * @return array
 | 
						|
		 */
 | 
						|
		private static function parse_args( $args = array() ) {
 | 
						|
			return wp_parse_args( $args, array(
 | 
						|
				'id'      => '',
 | 
						|
				'user_id' => get_current_user_id(),
 | 
						|
				'value'   => true,
 | 
						|
				'life'    => 0,
 | 
						|
				'global'  => true,
 | 
						|
			) );
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Check that required arguments exist.
 | 
						|
		 *
 | 
						|
		 * @since 1.0.0
 | 
						|
		 * @param array $args See parse_args().
 | 
						|
		 * @return bool True on success, false on failure.
 | 
						|
		 */
 | 
						|
		private static function check_args( $args = array() ) {
 | 
						|
			return ! empty( $args['id'] ) && ! empty( $args['user_id'] );
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Get the string used to prefix user meta for non-global dismissibles.
 | 
						|
		 *
 | 
						|
		 * @since 1.0.0
 | 
						|
		 * @global WPDB $wpdb
 | 
						|
		 * @param array $args See parse_args().
 | 
						|
		 * @return string Maybe includes the blog prefix.
 | 
						|
		 */
 | 
						|
		private static function get_prefix( $args = array() ) {
 | 
						|
			global $wpdb;
 | 
						|
 | 
						|
			// Default value
 | 
						|
			$retval = '';
 | 
						|
 | 
						|
			// Maybe append the blog prefix for non-global dismissibles
 | 
						|
			if ( empty( $args['global'] ) ) {
 | 
						|
				$retval = $wpdb->get_blog_prefix();
 | 
						|
			}
 | 
						|
 | 
						|
			// Return
 | 
						|
			return $retval;
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Get the lifespan for a persistent dismissible.
 | 
						|
		 *
 | 
						|
		 * @since 1.0.0
 | 
						|
		 * @param array $args See parse_args().
 | 
						|
		 * @return int
 | 
						|
		 */
 | 
						|
		private static function get_lifespan( $args = array() ) {
 | 
						|
			return ! empty( $args['life'] ) && is_numeric( $args['life'] )
 | 
						|
				? time() + absint( $args['life'] )
 | 
						|
				: 0;
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Get the string used to identify the ID for storing the end-of-life.
 | 
						|
		 *
 | 
						|
		 * @since 1.0.0
 | 
						|
		 * @param array $args See parse_args().
 | 
						|
		 * @return string '_eol' appended to the ID (for its end-of-life timestamp).
 | 
						|
		 */
 | 
						|
		private static function get_eol_id( $args = array() ) {
 | 
						|
			return sanitize_key( $args['id'] ) . '_eol';
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Check whether a timestamp is beyond the current time.
 | 
						|
		 *
 | 
						|
		 * @since 1.0.0
 | 
						|
		 * @param int $timestamp A Unix timestamp. Default 0.
 | 
						|
		 * @return bool True if end-of-life, false if not.
 | 
						|
		 */
 | 
						|
		private static function is_eol( $timestamp = 0 ) {
 | 
						|
			return is_numeric( $timestamp ) && ( $timestamp < time() );
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
endif;
 |