namespace WPForms\Admin\Notifications;
* Source of notifications content.
const SOURCE_URL = 'https://wpformsapi.com/feeds/v1/notifications';
* Array of license types, that are considered being Elite level.
const LICENSES_ELITE = [ 'agency', 'ultimate', 'elite' ];
public function hooks() {
add_action( 'wpforms_admin_notifications_update', [ $this, 'update' ] );
if ( ! wpforms_is_admin_ajax() && ! is_admin() ) {
add_action( 'wpforms_overview_enqueue', [ $this, 'enqueues' ] );
add_action( 'wpforms_admin_overview_before_table', [ $this, 'output' ] );
add_action( 'deactivate_plugin', [ $this, 'delete' ], 10, 2 );
add_action( 'wp_ajax_wpforms_notification_dismiss', [ $this, 'dismiss' ] );
* Check if user has access and is enabled.
* @since 1.8.2 Added AS task support.
public function has_access() {
$has_access = ! wpforms_setting( 'hide-announcements', false );
if ( ! wp_doing_cron() && ! wpforms_doing_wp_cli() ) {
$has_access = $has_access && wpforms_current_user_can( 'view_forms' );
* Allow modifying state if a user has access.
* @param bool $access True if user has access.
return (bool) apply_filters( 'wpforms_admin_notifications_has_access', $has_access );
* @param bool $cache Reference property cache if available.
public function get_option( $cache = true ) {
if ( $this->option && $cache ) {
$option = (array) get_option( 'wpforms_notifications', [] );
'update' => ! empty( $option['update'] ) ? (int) $option['update'] : 0,
'feed' => ! empty( $option['feed'] ) ? (array) $option['feed'] : [],
'events' => ! empty( $option['events'] ) ? (array) $option['events'] : [],
'dismissed' => ! empty( $option['dismissed'] ) ? (array) $option['dismissed'] : [],
* Fetch notifications from feed.
public function fetch_feed() {
$response = wp_remote_get(
'user-agent' => wpforms_get_default_user_agent(),
if ( is_wp_error( $response ) ) {
$body = wp_remote_retrieve_body( $response );
return $this->verify( json_decode( $body, true ) );
* Verify notification data before it is saved.
* @param array $notifications Array of notifications items to verify.
public function verify( $notifications ) {
if ( ! is_array( $notifications ) || empty( $notifications ) ) {
foreach ( $notifications as $notification ) {
// Ignore if one of the conditional checks is true:
// 1. notification message is empty.
// 2. license type does not match.
// 3. notification is expired.
// 4. notification has already been dismissed.
// 5. notification existed before installing WPForms.
// (Prevents bombarding the user with notifications after activation).
empty( $notification['content'] ) ||
! $this->is_license_type_match( $notification ) ||
$this->is_expired( $notification ) ||
$this->is_dismissed( $notification ) ||
$this->is_existed( $notification )
* Verify saved notification data for active notifications.
* @param array $notifications Array of notifications items to verify.
public function verify_active( $notifications ) {
if ( ! is_array( $notifications ) || empty( $notifications ) ) {
$current_timestamp = time();
// Remove notifications that are not active.
foreach ( $notifications as $key => $notification ) {
( ! empty( $notification['start'] ) && $current_timestamp < strtotime( $notification['start'] ) ) ||
( ! empty( $notification['end'] ) && $current_timestamp > strtotime( $notification['end'] ) )
unset( $notifications[ $key ] );
if ( ! $this->has_access() ) {
$option = $this->get_option();
// Update notifications using async task.
if ( empty( $option['update'] ) || time() > $option['update'] + DAY_IN_SECONDS ) {
$tasks = wpforms()->obj( 'tasks' );
if ( ! $tasks->is_scheduled( 'wpforms_admin_notifications_update' ) !== false ) {
->create( 'wpforms_admin_notifications_update' )
$feed = ! empty( $option['feed'] ) ? $this->verify_active( $option['feed'] ) : [];
$events = ! empty( $option['events'] ) ? $this->verify_active( $option['events'] ) : [];
return array_merge( $feed, $events );
* Get notification count.
public function get_count() {
return count( $this->get() );
* Add a new Event Driven notification.
* @param array $notification Notification data.
public function add( $notification ) {
if ( ! $this->is_valid( $notification ) ) {
$option = $this->get_option();
// Notification ID already exists.
if ( ! empty( $option['events'][ $notification['id'] ] ) ) {
'update' => $option['update'],
'feed' => $option['feed'],
'events' => array_merge( $notification, $option['events'] ),
'dismissed' => $option['dismissed'],
* Determine if notification data is valid.
* @param array $notification Notification data.
public function is_valid( $notification ) {
if ( empty( $notification['id'] ) ) {
return ! empty( $this->verify( [ $notification ] ) );
* Determine if notification has already been dismissed.
* @param array $notification Notification data.
private function is_dismissed( $notification ) {
$option = $this->get_option();
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
return ! empty( $option['dismissed'] ) && in_array( $notification['id'], $option['dismissed'] );
* Determine if license type is match.
* @param array $notification Notification data.
private function is_license_type_match( $notification ) {
// A specific license type is not required.
if ( empty( $notification['type'] ) ) {
return in_array( $this->get_license_type(), (array) $notification['type'], true );
* Determine if notification is expired.
* @param array $notification Notification data.
private function is_expired( $notification ) {
return ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] );
* Determine if notification existed before installing WPForms.
* @param array $notification Notification data.
private function is_existed( $notification ) {
$activated = wpforms_get_activated_timestamp();
return ! empty( $activated ) &&
! empty( $notification['start'] ) &&
$activated > strtotime( $notification['start'] );
* Update notification data from feed.
* @since 1.7.8 Added `wp_cache_flush()` call when the option has been updated.
* @since 1.8.2 Don't fire the update action when it disabled or was fired recently.
public function update() {
if ( ! $this->has_access() ) {
$option = $this->get_option();
// Double-check the last update time to prevent multiple requests.
if ( ! empty( $option['update'] ) && time() < $option['update'] + DAY_IN_SECONDS ) {
'feed' => $this->fetch_feed(),
'events' => $option['events'],
'dismissed' => $option['dismissed'],
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
* Allow changing notification data before it will be updated in database.
* @param array $data New notification data.
$data = (array) apply_filters( 'wpforms_admin_notifications_update_data', $data );
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
$data['update'] = time();
update_option( 'wpforms_notifications', $data );
* Remove notification data from database before a plugin is deactivated.
* @param string $plugin Path to the plugin file relative to the plugins directory.
* @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network
* or just the current site. Multisite only. Default false.
public function delete( $plugin, $network_deactivating ) {
'wpforms-lite/wpforms.php',
if ( ! in_array( $plugin, $wpforms_plugins, true ) ) {
delete_option( 'wpforms_notifications' );
* Enqueue assets on Form Overview admin page.
public function enqueues() {
if ( ! $this->get_count() ) {
$min = wpforms_get_min_suffix();
'wpforms-admin-notifications',
WPFORMS_PLUGIN_URL . "assets/css/admin-notifications{$min}.css",
'wpforms-admin-notifications',
WPFORMS_PLUGIN_URL . "assets/js/admin/admin-notifications{$min}.js",
[ 'jquery', 'wpforms-lity' ],
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css',