File "Notices.php"
Full Path: /home/romayxjt/public_html/wp-content/plugins/the-events-calendar/common/src/Tribe/Admin/Notices.php
File size: 21.07 KB
MIME-type: text/x-php
Charset: utf-8
<?php
use Tribe__Date_Utils as Dates;
use TEC\Common\StellarWP\DB\DB;
use function Crontrol\Event\delete;
/**
* @since 4.3
*/
class Tribe__Admin__Notices {
/**
* The name of the transient that will store transient notices.
*
* @since 4.3
*
* @var string
*/
public static $transient_notices_name = '_tribe_admin_notices';
/**
* Whether, in this request, transient notices have been pruned already or not.
*
* @since 4.3
*
* @var bool
*/
protected $did_prune_transients = false;
/**
* Static singleton variable
*
* @since 4.3
*
* @var self
*/
private static $instance;
/**
* Static Singleton Factory Method
*
* @since 4.3
*
* @return self
*/
public static function instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self;
}
return self::$instance;
}
/**
* User Meta Key that stores which notices have been dismissed.
*
* @since 4.3
*
* @var string
*/
public static $meta_key = 'tribe-dismiss-notice';
/**
* Request param used to pass the nonce for dismissal.
*
* @since 5.2.1
*
* @var string
*/
public static string $nonce_param = 'tec-dismiss-notice-nonce';
/**
* User Meta Key prefix that stores when notices have been dismissed.
*
* @since 4.13.0
*
* @var string
*/
public static $meta_key_time_prefix = 'tribe-dismiss-notice-time-';
/**
* Stores all the Notices and it's configurations
*
* @since 4.3
*
* @var array
*/
protected $notices = [];
/**
* Register the Methods in the correct places
*
* @since 4.3
*
*/
private function __construct() {
// Not in the admin we don't even care
if ( ! is_admin() ) {
return;
}
// Before we bail on the
add_action( 'wp_ajax_tribe_notice_dismiss', [ $this, 'maybe_dismiss' ] );
// Doing AJAX? bail.
if ( tribe( 'context' )->doing_ajax() ) {
return;
}
// Hook the actual rendering of notices
add_action( 'current_screen', [ $this, 'hook' ], 20 );
// Add our notice dismissal script
tec_asset(
Tribe__Main::instance(),
'tribe-notice-dismiss',
'notice-dismiss.js',
[ 'jquery' ],
null,
[
'groups' => 'tec-admin-notices',
]
);
}
/**
* This will happen on the `current_screen` and will hook to the correct actions and display the notices
*
* @since 4.3
*
* @return void
*/
public function hook() {
$transients = $this->get_transients();
foreach ( $transients as $slug => $transient ) {
if ( $this->transient_notice_expired( $slug ) ) {
continue;
}
[ $html, $args, $expire ] = $transients[ $slug ];
$this->register( $slug, $html, $args );
}
foreach ( $this->notices as $notice ) {
if ( ! $this->showing_notice( $notice->slug ) ) {
continue;
}
add_action( $notice->action, $notice->callback, $notice->priority );
}
}
/**
* This will allow the user to Dismiss the Notice using JS.
*
* We will dismiss the notice without checking to see if the slug was already
* registered (via a call to exists()) for the reason that, during dismissal
* ajax request, some valid notices may not have been registered yet.
*
* @since 4.3
*
* @return void
*/
public function maybe_dismiss() {
$slug = tribe_get_request_var( self::$meta_key, false );
if ( empty( $slug ) ) {
wp_send_json( false );
}
$slug = sanitize_key( $slug );
$nonce = tribe_get_request_var( self::$nonce_param, false );
$nonce_action = $this->get_nonce_action( $slug );
if ( ! wp_verify_nonce( $nonce, $nonce_action ) ) {
wp_send_json( false );
}
// Send a JSON answer with the status of dismissal
wp_send_json( $this->dismiss( $slug ) );
}
/**
* Allows a Magic to remove the Requirement of creating a callback
*
* @since 4.3
*
* @param string $name Name of the method used to create the slug of the notice.
* @param array $arguments Which arguments were used, normally empty.
*
* @return string
*/
public function __call( $name, $arguments ) {
// Transform from Method name to Notice number
$slug = preg_replace( '/render_/', '', $name, 1 );
if ( ! $this->exists( $slug ) ) {
return false;
}
$notice = $this->get( $slug );
if (
empty( $notice->active_callback )
|| (
is_callable( $notice->active_callback )
&& true == call_user_func( $notice->active_callback )
)
) {
$content = $notice->content;
$wrap = isset( $notice->wrap ) ? $notice->wrap : false;
if ( is_array( $content ) && isset( $content[0] ) && $content[0] instanceof __PHP_Incomplete_Class ) {
// From a class that no longer exists (e.g. the plugin is not active), clean and bail.
$this->remove( $slug );
$this->remove_transient( $slug );
return false;
}
if ( is_callable( $content ) ) {
$content = call_user_func_array( $content, [ $notice ] );
}
if ( empty( $content ) ) {
// There is nothing to render, let's avoid the empty notice frame.
return false;
}
tribe_asset_enqueue_group( 'tec-admin-notices' );
// Return the rendered HTML.
$html = $this->render( $slug, $content, false, $wrap );
// If the notice is dismissible, we need to check if it has been dismissed before removing it.
if ( $notice->dismiss ) {
return $html;
}
// Remove the notice and the transient (if any) since it's been rendered.
$this->remove( $slug );
$this->remove_transient( $slug );
return $html;
}
return false;
}
/**
* This is a helper to actually print the Message
*
* @since 4.3
*
* @param string $slug The name of the notice.
* @param string $content The content of the notice.
* @param boolean $return Echo or return the content.
* @param string|bool $wrap An optional HTML tag to wrap the content.
*
* @return bool|string
*/
public function render( $slug, $content = null, $return = true, $wrap = false ) {
if ( ! $this->exists( $slug ) ) {
return false;
}
// Bail if we already rendered
if ( $this->is_rendered( $slug ) ) {
if ( $this->is_rendered_html( $slug, $content ) && ! $return ) {
echo $content;
}
return false;
}
$notice = $this->get( $slug );
// Mark the notice as rendered.
$this->notices[ $slug ]->is_rendered = true;
$classes = [ 'tribe-dismiss-notice', 'notice' ];
$classes[] = sanitize_html_class( 'notice-' . $notice->type );
$classes[] = sanitize_html_class( 'tribe-notice-' . $notice->slug );
if ( $notice->dismiss ) {
$classes[] = 'is-dismissible';
}
if ( $notice->inline ) {
$classes[] = 'inline';
}
$content ??= $notice->content;
// Prevents Empty Notices
if ( empty( $content ) ) {
return false;
}
if ( is_string( $wrap ) ) {
$content = sprintf( '<%1$s>' . $content . '</%1$s>', $wrap );
}
$nonce = wp_create_nonce( $this->get_nonce_action( $notice->slug ) );
$html = sprintf(
'<div class="%s" data-ref="%s" data-dismiss-nonce="%s">%s</div>',
implode( ' ', $classes ),
$notice->slug,
$nonce,
$content
);
tribe_asset_enqueue_group( 'tec-admin-notices' );
if ( ! $return ) {
echo $html;
}
return $html;
}
/**
* This is a helper to print the message surrounded by `p` tags.
*
* @since 4.3
*
* @param string $slug The name of the notice.
* @param string $content The content of the notice.
* @param boolean $return Echo or return the content.
*
* @return boolean|string
*/
public function render_paragraph( $slug, $content = null, $return = true ) {
return $this->render( $slug, $content, $return, 'p' );
}
/**
* Checks if a given notice is rendered
*
* @since 4.7.10
*
* @param string $slug Which notice to check.
*
* @return boolean
*/
public function is_rendered( $slug ) {
if ( ! $this->exists( $slug ) ) {
return false;
}
$notice = $this->get( $slug );
return isset( $notice->is_rendered ) ? $notice->is_rendered : false;
}
/**
* Checks if a given string is a notice rendered
*
* @since 4.7.10
*
* @param string $slug Which notice to check.
* @param string $html Which html string we are check.
*
* @return boolean
*/
public function is_rendered_html( $slug, $html ) {
if ( ! $this->exists( $slug ) ) {
return false;
}
$search = sprintf( 'data-ref="%s"', $slug );
return false !== strpos( $html, $search );
}
/**
* Checks if a given user has dismissed a given notice.
*
* @since 4.13.0
*
* @param string $slug The name of the notice.
* @param int|null $user_id The user ID.
*
* @return boolean
*/
public function has_user_dismissed( $slug, $user_id = null ) {
if ( is_null( $user_id ) ) {
$user_id = get_current_user_id();
}
$dismissed_notices = get_user_meta( $user_id, self::$meta_key );
if ( ! is_array( $dismissed_notices ) ) {
return false;
}
if ( ! in_array( $slug, $dismissed_notices ) ) {
return false;
}
$notice = $this->get( $slug );
if (
is_object( $notice )
&& $notice->recurring
&& $this->should_recurring_notice_show( $slug, $user_id )
) {
return false;
}
return true;
}
/**
* Gets the last Dismissal for a given notice slug and user.
*
* @since 4.13.0
*
* @param string $slug Slug of the notice to look for.
* @param int|null $user_id Which user? If null will default to current user.
*
* @return false|\Tribe\Utils\Date_I18n
*/
public function get_last_dismissal( $slug, $user_id = null ) {
if ( is_null( $user_id ) ) {
$user_id = get_current_user_id();
}
$dismissed_time = get_user_meta( $user_id, static::$meta_key_time_prefix . $slug, true );
if ( ! is_numeric( $dismissed_time ) ) {
return false;
}
return Dates::build_date_object( $dismissed_time );
}
/**
* Determines if a given notice needs to be re-displayed in case of recurring notice.
*
* @since 4.13.0
*
* @param string $slug Slug of the notice to look for.
* @param int|null $user_id Which user? If null will default to current user.
*
* @return false|\Tribe\Utils\Date_I18n
*/
public function should_recurring_notice_show( $slug, $user_id = null ) {
$notice = $this->get( $slug );
if ( ! is_object( $notice ) ) {
return false;
}
if ( ! $notice->recurring || ! $notice->recurring_interval ) {
return false;
}
if ( is_null( $user_id ) ) {
$user_id = get_current_user_id();
}
$interval = Dates::interval( $notice->recurring_interval );
$last_dismissal = $this->get_last_dismissal( $slug, $user_id );
if ( ! $last_dismissal ) {
return false;
}
$next_dismissal = $last_dismissal->add( $interval );
$now = Dates::build_date_object( 'now' );
if ( $now >= $next_dismissal ) {
delete_user_meta( $user_id, self::$meta_key, $slug );
return true;
}
return false;
}
/**
* A Method to actually add the Meta value telling that this notice has been dismissed
*
* @since 4.3
*
* @param string $slug The Name of the Notice
* @param int|null $user_id The user ID
*
* @return boolean
*/
public function dismiss( $slug, $user_id = null ) {
if ( is_null( $user_id ) ) {
$user_id = get_current_user_id();
}
// If this user has dismissed we don't care either
if ( $this->has_user_dismissed( $slug, $user_id ) ) {
return true;
}
update_user_meta( $user_id, static::$meta_key_time_prefix . $slug, time() );
return add_user_meta( $user_id, self::$meta_key, $slug, false );
}
/**
* Removes the User meta holding if a notice was dismissed
*
* @param string $slug The Name of the Notice
* @param int|null $user_id The user ID
*
* @return boolean
*/
public function undismiss( $slug, $user_id = null ) {
if ( is_null( $user_id ) ) {
$user_id = get_current_user_id();
}
// If this user has dismissed we don't care either
if ( ! $this->has_user_dismissed( $slug, $user_id ) ) {
return false;
}
return delete_user_meta( $user_id, self::$meta_key, $slug );
}
/**
* Undismisses the specified notice for all users.
*
* @since 4.3
*
* @param string $slug
*
* @return int
*/
public function undismiss_for_all( $slug ) {
$user_query = new WP_User_Query( [
'meta_key' => self::$meta_key,
'meta_value' => $slug,
] );
$affected = 0;
foreach ( $user_query->get_results() as $user ) {
if ( $this->undismiss( $slug, $user->ID ) ) {
$affected ++;
}
}
return $affected;
}
/**
* Register a Notice and attach a callback to the required action to display it correctly
*
* @since 4.3
*
* @param string $slug Slug to save the notice
* @param callable|string $callback A callable Method/Function to actually display the notice
* @param array $arguments Arguments to Setup a notice
* @param callable|null $active_callback An optional callback that should return bool values
* to indicate whether the notice should display or not.
*
* @return stdClass
*/
public function register( $slug, $callback, $arguments = [], $active_callback = null ) {
// Prevent weird stuff here
$slug = sanitize_key( $slug );
$defaults = [
'callback' => null,
'content' => null,
'action' => 'admin_notices',
'priority' => 10,
'expire' => false,
'dismiss' => false,
'inline' => false,
'recurring' => false,
'recurring_interval' => null,
'type' => 'error',
'is_rendered' => false,
'wrap' => false,
];
$defaults['callback'] = [ $this, 'render_' . $slug ];
$defaults['content'] = $callback;
if ( is_callable( $active_callback ) ) {
$defaults['active_callback'] = $active_callback;
}
// Merge Arguments
$notice = (object) wp_parse_args( $arguments, $defaults );
// Enforce this one
$notice->slug = $slug;
// Clean these
$notice->priority = absint( $notice->priority );
$notice->expire = (bool) $notice->expire;
$notice->recurring = (bool) $notice->recurring;
if ( ! is_callable( $notice->dismiss ) ) {
$notice->dismiss = (bool) $notice->dismiss;
}
if ( ! is_callable( $notice->inline ) ) {
$notice->inline = (bool) $notice->inline;
}
// Set the Notice on the array of notices
$this->notices[ $slug ] = $notice;
// Return the notice Object because it might be modified
return $notice;
}
/**
* Create a transient Admin Notice easily.
*
* A transient admin notice is a "fire-and-forget" admin notice that will display once registered and
* until dismissed (if dismissible) without need, on the side of the source code, to register it on each request.
*
* @since 4.7.7
*
* @param string $slug Slug to save the notice
* @param string $html The notice output HTML code
* @param array $arguments Arguments to Setup a notice
* @param int $expire After how much time (in seconds) the notice will stop showing.
*
* @return stdClass Which notice was registered
*/
public function register_transient( $slug, $html, $arguments = [], $expire = null ) {
$notices = $this->get_transients();
$notices[ $slug ] = [ $html, $arguments, time() + $expire ];
$this->set_transients( $notices );
}
/**
* Removes a transient notice based on its slug.
*
* @since 4.7.7
*
* @param string $slug
*/
public function remove_transient( $slug ) {
$notices = $this->get_transients();
unset( $notices[ $slug ] );
$this->set_transients( $notices );
}
/**
* Removes a notice based on its slug.
*
* @since 4.3
*
* @param string $slug
*
* @return bool
*/
public function remove( $slug ) {
if ( ! $this->exists( $slug ) ) {
return false;
}
unset( $this->notices[ $slug ] );
return true;
}
/**
* Gets the configuration for the Notices
*
* @since 4.3
*
* @param string $slug
*
* @return object|array|null
*/
public function get( $slug = null ) {
if ( is_null( $slug ) ) {
return $this->notices;
}
// Prevent weird stuff here
$slug = sanitize_key( $slug );
if ( ! empty( $this->notices[ $slug ] ) ) {
// I want to avoid modifying the registered value.
$notice = $this->notices[ $slug ];
if ( is_callable( $notice->inline ) ) {
$notice->inline = call_user_func( $notice->inline, $notice );
}
if ( is_callable( $notice->dismiss ) ) {
$notice->dismiss = call_user_func( $notice->dismiss, $notice );
}
return $notice;
}
return null;
}
/**
* Checks if a given notice exists
*
* @since 4.3
*
* @param string $slug
*
* @return bool
*/
public function exists( $slug ) {
return is_object( $this->get( $slug ) ) ? true : false;
}
/**
* Returns an array of registered transient notices.
*
* @since 4.7.7
*
* @return array An associative array in the shape [ <slug> => [ <html>, <args>, <expire timestamp> ] ]
*/
protected function get_transients() {
$cached = tribe( 'cache' )['transient_admin_notices'];
if ( false !== $cached ) {
return $cached;
}
$transient = self::$transient_notices_name;
// Manually grab the option from the database so unserialize doesn't happen automatically.
$transient_value = DB::get_var(
DB::table( 'options' )
->select( 'option_value' )
->where( 'option_name', "_transient_{$transient}" )
->getSQL()
);
// Check for serialized classes and if they exist. If any do not, wipe the transient and return an empty array.
if (
! empty( $transient_value )
&& preg_match_all( '/"[^"]*(TEC\\\\[A-Za-z0-9_\\\\]+)/', $transient_value, $matches )
&& ! empty( $matches[1] )
) {
foreach( $matches[1] as $class ) {
if ( class_exists( $class, false ) ) {
continue;
}
delete_transient( $transient );
return [];
}
}
$notices = get_transient( $transient );
$notices = is_array( $notices ) ? $notices : [];
if ( ! $this->did_prune_transients ) {
$this->did_prune_transients = true;
foreach ( $notices as $key => $notice ) {
[ $html, $args, $expire_at ] = $notice;
if ( $expire_at < time() ) {
unset( $notices[ $key ] );
}
}
}
tribe( 'cache' )['transient_admin_notices'] = $notices;
return $notices;
}
/**
* Returns the nonce action for a given notice slug.
*
* @since 5.2.1
*
* @param string $slug Which notice we are handling.
*
* @return string
*/
protected function get_nonce_action( string $slug ): string {
return 'tec_notice_dismiss_' . $slug;
}
/**
* Updates/sets the transient notices transient.
*
* @since 4.7.7
*
* @param array $notices An associative array in the shape [ <slug> => [ <html>, <args>, <expire timestamp> ] ]
*/
protected function set_transients( $notices ) {
$transient = self::$transient_notices_name;
set_transient( $transient, $notices, MONTH_IN_SECONDS );
// Manually memoize the value so we don't have to fetch it again.
tribe( 'cache' )['transient_admin_notices'] = $notices;
}
/**
* Checks whether a specific transient admin notices is being shown or not, depending on its expiration and
* dismissible status.
*
*
* @since 4.11.1
*
* @param string|array $slug The slug, or slugs, of the transient notices to check. This is the same slug used
* to register the transient notice in the `tribe_transient_notice` function or the
* `Tribe__Admin__Notices::register_transient()` method.
*
* @return bool Whether the transient notice is showing or not.
*/
public function showing_transient_notice( $slug ) {
$transient_notices = (array) $this->get_transients();
return isset( $transient_notices[ $slug ] )
&& ! $this->has_user_dismissed( $slug )
&& ! $this->transient_notice_expired( $slug );
}
/**
* Checks whether a transient notice expired or not.
*
* @since 4.11.1
*
* @param string|array $slug The slug, or slugs, of the transient notices to check. This is the same slug used
* to register the transient notice in the `tribe_transient_notice` function or the
* `Tribe__Admin__Notices::register_transient()` method.
*
* @return bool Whether the transient notice is expired or not.
*/
protected function transient_notice_expired( $slug ) {
$transients = (array) $this->get_transients();
if ( ! isset( $transients[ $slug ] ) ) {
return true;
}
[ $html, $args, $expire ] = $transients[ $slug ];
if ( $expire < time() ) {
return true;
}
return false;
}
/**
* Checks whether a notice is being shown or not; the result takes the notice callback and dismissible status into
* account.
*
* @since 4.11.1
*
* @param string|array $slug The slug, or slugs, of the transient notices to check. This is the same slug used
* to register the transient notice in the `tribe_transient_notice` function or the
* `Tribe__Admin__Notices::register_transient()` method.
*
* @return bool Whether the notice is showing or not.
*/
public function showing_notice( $slug ) {
if ( ! isset( $this->notices[ $slug ] ) ) {
return false;
}
$notice = $this->notices[ $slug ];
if ( $notice->dismiss && $this->has_user_dismissed( $notice->slug ) ) {
return false;
}
if (
! empty( $notice->active_callback )
&& is_callable( $notice->active_callback )
&& false == call_user_func( $notice->active_callback )
) {
return false;
}
return true;
}
}