option && $cache ) { return $this->option; } $option = get_option( 'members_notifications', [] ); $this->option = [ 'update' => ! empty( $option['update'] ) ? $option['update'] : 0, 'events' => ! empty( $option['events'] ) ? $option['events'] : [], 'feed' => ! empty( $option['feed'] ) ? $option['feed'] : [], 'dismissed' => ! empty( $option['dismissed'] ) ? $option['dismissed'] : [], ]; return $this->option; } /** * Make sure the feed is fetched when needed. * * @return void */ public function schedule_fetch() { $option = $this->get_option(); // Update notifications using async task. if ( empty( $option['update'] ) || time() > $option['update'] + 3 * HOUR_IN_SECONDS ) { if ( false === wp_next_scheduled( 'members_admin_notifications_update' ) ) { wp_schedule_single_event( time() + 10, 'members_admin_notifications_update' ); } } } /** * Fetch notifications from remote feed. * * @return array */ public function fetch_feed() { $res = wp_remote_get( self::SOURCE_URL, self::SOURCE_URL_ARGS ); if ( is_wp_error( $res ) ) { return []; } $body = wp_remote_retrieve_body( $res ); if ( empty( $body ) ) { return []; } return $this->verify( json_decode( $body, true ) ); } /** * Verify notification data before it is saved. * * @param array $notifications Array of notifications items to verify. * * @return array */ public function verify( $notifications ) { $data = []; if ( ! is_array( $notifications ) || empty( $notifications ) ) { return $data; } $option = $this->get_option(); foreach ( $notifications as $id => $notification ) { // The message should never be empty - if is, ignore. if ( empty( $notification['content'] ) ) { continue; } // Ignore if expired. if ( ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ) ) { continue; } // Ignore if notifcation has already been dismissed. if ( ! empty( $option['dismissed'] ) && array_key_exists( $notification['id'], $option['dismissed'] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict continue; } // Ignore if notification existed before installing the plugin. // Prevents bombarding the user with notifications after activation. $activated = get_option( 'members_activated' ); if ( empty( $activated ) ) { $activated = time(); update_option( 'members_activated', $activated ); } if ( ! empty( $activated ) && ! empty( $notification['start'] ) && $activated > strtotime( $notification['start'] ) ) { continue; } $data[$id] = $notification; // Check if this notification has already been saved with a timestamp if ( ! empty( $option['feed'][$id] ) ) { // Already exists in feed, so use saved time $data[$id]['saved'] = $option['feed'][$id]['saved']; } else if ( ! empty( $option['events'][$id] ) ) { // Already exists in events, so use saved time $data[$id]['saved'] = $option['events'][$id]['saved']; } else { // Doesn't exist in feed or events, so save current time $data[$id]['saved'] = time(); } } return $data; } /** * Verify saved notification data for active notifications. * * @param array $notifications Array of notifications items to verify. * * @return array */ public function verify_active( $notifications ) { if ( ! is_array( $notifications ) || empty( $notifications ) ) { return []; } // Remove notfications that are not active. foreach ( $notifications as $key => $notification ) { if ( ( ! empty( $notification['start'] ) && strtotime( $notification['start'] . ' America/Denver' ) < strtotime( $notification['start'] . ' America/Denver' ) ) || ( ! empty( $notification['end'] ) && strtotime( $notification['end'] . ' America/Denver' ) > strtotime( $notification['end'] . ' America/Denver' ) ) ) { unset( $notifications[ $key ] ); } } return $notifications; } /** * Get notification data. * * @return array */ public function get() { if ( ! self::has_access() ) { return []; } $option = $this->get_option(); $events = ! empty( $option['events'] ) ? $this->verify_active( $option['events'] ) : array(); $feed = ! empty( $option['feed'] ) ? $this->verify_active( $option['feed'] ) : array(); $notifications = array(); $notifications['active'] = array_merge( $events, $feed ); $notifications['active'] = $this->get_notifications_with_human_readeable_start_time( $notifications['active'] ); $notifications['active'] = $this->get_notifications_with_formatted_content( $notifications['active'] ); $notifications['dismissed'] = ! empty( $option['dismissed'] ) ? $option['dismissed'] : array(); $notifications['dismissed'] = $this->get_notifications_with_human_readeable_start_time( $notifications['dismissed'] ); $notifications['dismissed'] = $this->get_notifications_with_formatted_content( $notifications['dismissed'] ); return $notifications; } /** * Improve format of the content of notifications before display. By default just runs wpautop. * * @param array $notifications The notifications to be parsed. * * @return mixed */ public function get_notifications_with_formatted_content( $notifications ) { if ( ! is_array( $notifications ) || empty( $notifications ) ) { return $notifications; } foreach ( $notifications as $key => $notification ) { if ( ! empty( $notification['content'] ) ) { $notifications[ $key ]['content'] = wpautop( $notification['content'] ); $notifications[ $key ]['content'] = apply_filters( 'members_notification_content_display', $notifications[ $key ]['content'] ); } } return $notifications; } /** * Get notifications start time with human time difference * * @return array $notifications */ public function get_notifications_with_human_readeable_start_time( $notifications ) { if ( ! is_array( $notifications ) || empty( $notifications ) ) { return; } foreach ( $notifications as $key => $notification ) { if ( ! isset( $notification['start'] ) || empty( $notification['start'] ) ) { continue; } // Translators: Readable time to display $modified_start_time = sprintf( __( '%1$s ago', 'members' ), human_time_diff( strtotime( $notification['start'] ), current_time( 'timestamp' ) ) ); $notifications[ $key ]['start'] = $modified_start_time; } return $notifications; } /** * Get notification count. * * @return int */ public function get_count() { return ! empty( $this->get()['active'] ) ? count( $this->get()['active'] ) : 0; } /** * Add an event notification. This is NOT for feed notifications. * Event notifications are for alerting the user to something internally (e.g. recent sales performances). * * @param array $notification Notification data. */ public function add( $notification ) { if ( empty( $notification['id'] ) ) { return; } $option = $this->get_option(); // Already dismissed if ( array_key_exists( $notification['id'], $option['dismissed'] ) ) { return; } // Already in events foreach ( $option['events'] as $item ) { if ( $item['id'] === $notification['id'] ) { return; } } // Associative key is notification id. $notification = $this->verify( [ $notification['id'] => $notification ] ); // The only thing changing here is adding the notification to the events update_option( 'members_notifications', [ 'update' => $option['update'], 'feed' => $option['feed'], 'events' => array_merge( $notification, $option['events'] ), 'dismissed' => $option['dismissed'], ] ); } /** * Update notification data from feed. * This pulls the latest notifications from our remote feed. */ public function update() { $feed = $this->fetch_feed(); $option = $this->get_option(); update_option( 'members_notifications', [ 'update' => time(), 'feed' => $feed, 'events' => $option['events'], 'dismissed' => $option['dismissed'], ] ); } /** * Admin area enqueues. */ public function enqueues() { if ( ! self::has_access() || ! members_is_admin_page() ) { return; } $notifications = $this->get(); if ( empty( $notifications ) ) { return; } wp_enqueue_style( 'members-admin-notifications', members_plugin()->uri . "css/admin-notifications.css", [], '' ); wp_enqueue_script( 'members-admin-notifications', members_plugin()->uri . "js/admin-notifications.js", [ 'jquery' ], '', true ); wp_localize_script( 'members-admin-notifications', 'MembersAdminNotifications', [ "ajax_url" => admin_url('admin-ajax.php'), "nonce" => wp_create_nonce( "members-admin-notifications" ) ] ); } /** * Admin script for adding notification count to the MemberPress admin menu list item. */ public function admin_menu_append_count() { $option = get_option( 'members_notifications' ); $notifications = ! empty( $option['feed'] ) ? $option['feed'] : array(); $active_notifications = isset( $this->get()['active'] ) ? $this->get()['active'] : array(); if ( ! empty( $active_notifications ) && count( $active_notifications ) > 0 ) { ob_start(); ?> get(); if ( empty( $notifications['active'] ) && empty( $notifications['dismissed'] ) ) { return; } $notifications_html = '
'; if ( ! empty( $notifications['active'] ) ) { foreach ( $notifications['active'] as $notification ) { // Buttons HTML. $buttons_html = ''; if ( ! empty( $notification['buttons'] ) && is_array( $notification['buttons'] ) ) { foreach ( $notification['buttons'] as $btn_type => $btn ) { if ( empty( $btn['url'] ) || empty( $btn['text'] ) ) { continue; } $buttons_html .= sprintf( '%4$s', ! empty( $btn['url'] ) ? esc_url( $btn['url'] ) : '', $btn_type === 'main' ? 'primary' : 'secondary', ! empty( $btn['target'] ) && $btn['target'] === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '', ! empty( $btn['text'] ) ? sanitize_text_field( $btn['text'] ) : '' ); } $buttons_html .= sprintf( '', $notification['id'], __( 'Dismiss', 'members' ) ); $buttons_html = ! empty( $buttons_html ) ? '
' . $buttons_html . '
' : ''; } $time_diff = ceil( ( time() - $notification['saved'] ) ); $time_diff_string = ''; if ( $time_diff < MINUTE_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s second ago', '%s seconds ago', $time_diff, 'members' ), $time_diff ); } else if ( $time_diff < HOUR_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s minute ago', '%s minutes ago', ceil( ( $time_diff / MINUTE_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / MINUTE_IN_SECONDS ) ) ); } else if ( $time_diff < DAY_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s hour ago', '%s hours ago', ceil( ( $time_diff / HOUR_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / HOUR_IN_SECONDS ) ) ); } else if ( $time_diff < WEEK_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s day ago', '%s days ago', ceil( ( $time_diff / DAY_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / DAY_IN_SECONDS ) ) ); } else if ( $time_diff < MONTH_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s week ago', '%s weeks ago', ceil( ( $time_diff / WEEK_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / WEEK_IN_SECONDS ) ) ); } else if ( $time_diff < YEAR_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s month ago', '%s months ago', ceil( ( $time_diff / MONTH_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / MONTH_IN_SECONDS ) ) ); } else { $time_diff_string = sprintf( _n( '%s year ago', '%s years ago', ceil( ( $time_diff / YEAR_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / YEAR_IN_SECONDS ) ) ); } // Notification HTML. $notifications_html .= sprintf( '

%1$s

%2$s
%3$s
', ! empty( $notification['title'] ) ? sanitize_text_field( $notification['title'] ) : '', ! empty( $notification['content'] ) ? apply_filters( 'the_content', $notification['content'] ) : '', $buttons_html, ! empty( $notification['id'] ) ? esc_attr( sanitize_text_field( $notification['id'] ) ) : 0, ! empty( $notification['icon'] ) ? esc_url( sanitize_text_field( $notification['icon'] ) ) : '', date( 'Y-m-d G:i a', $notification['saved'] ), $time_diff_string ); } } $notifications_html .= sprintf( '
%s
', empty( $notifications['active'] ) || count( $notifications['active'] ) < 1 ? '' : 'style="display: none;"', __( 'You\'re all caught up!', 'members' ) ); $notifications_html .= '
'; $notifications_html .= '
'; if ( ! empty( $notifications['dismissed'] ) ) { foreach ( $notifications['dismissed'] as $notification ) { // Buttons HTML. $buttons_html = ''; if ( ! empty( $notification['buttons'] ) && is_array( $notification['buttons'] ) ) { foreach ( $notification['buttons'] as $btn_type => $btn ) { if ( empty( $btn['url'] ) || empty( $btn['text'] ) ) { continue; } $buttons_html .= sprintf( '%4$s', ! empty( $btn['url'] ) ? esc_url( $btn['url'] ) : '', $btn_type === 'main' ? 'primary' : 'secondary', ! empty( $btn['target'] ) && $btn['target'] === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '', ! empty( $btn['text'] ) ? sanitize_text_field( $btn['text'] ) : '' ); } $buttons_html .= sprintf( '', $notification['id'], __( 'Dismiss', 'members' ) ); $buttons_html = ! empty( $buttons_html ) ? '
' . $buttons_html . '
' : ''; } $time_diff = ceil( ( time() - $notification['saved'] ) ); $time_diff_string = ''; if ( $time_diff < MINUTE_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s second ago', '%s seconds ago', $time_diff, 'members' ), $time_diff ); } else if ( $time_diff < HOUR_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s minute ago', '%s minutes ago', ceil( ( $time_diff / MINUTE_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / MINUTE_IN_SECONDS ) ) ); } else if ( $time_diff < DAY_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s hour ago', '%s hours ago', ceil( ( $time_diff / HOUR_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / HOUR_IN_SECONDS ) ) ); } else if ( $time_diff < WEEK_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s day ago', '%s days ago', ceil( ( $time_diff / DAY_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / DAY_IN_SECONDS ) ) ); } else if ( $time_diff < MONTH_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s week ago', '%s weeks ago', ceil( ( $time_diff / WEEK_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / WEEK_IN_SECONDS ) ) ); } else if ( $time_diff < YEAR_IN_SECONDS ) { $time_diff_string = sprintf( _n( '%s month ago', '%s months ago', ceil( ( $time_diff / MONTH_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / MONTH_IN_SECONDS ) ) ); } else { $time_diff_string = sprintf( _n( '%s year ago', '%s years ago', ceil( ( $time_diff / YEAR_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / YEAR_IN_SECONDS ) ) ); } // Notification HTML. $notifications_html .= sprintf( '

%1$s

%2$s
%3$s
', ! empty( $notification['title'] ) ? sanitize_text_field( $notification['title'] ) : '', ! empty( $notification['content'] ) ? apply_filters( 'the_content', $notification['content'] ) : '', $buttons_html, ! empty( $notification['id'] ) ? esc_attr( sanitize_text_field( $notification['id'] ) ) : 0, ! empty( $notification['icon'] ) ? esc_url( sanitize_text_field( $notification['icon'] ) ) : '', date( 'Y-m-d G:i a', $notification['saved'] ), $time_diff_string ); } } $notifications_html .= '
'; ?>
get_option(); if ( 'all' === $id ) { // Dismiss all notifications // Feed notifications if ( ! empty( $option['feed'] ) ) { foreach ( $option['feed'] as $key => $notification ) { $option['dismissed'][$key] = $option['feed'][$key]; unset( $option['feed'][$key] ); } } // Event notifications if ( ! empty( $option['events'] ) ) { foreach ( $option['events'] as $key => $notification ) { $option['dismissed'][$key] = $option['events'][$key]; unset( $option['events'][$key] ); } } } else { // Dismiss one notification // Event notifications need a prefix to distinguish them from feed notifications // For a naming convention, we'll use "event_{timestamp}" // If the notification ID includes "event_", we know it's an even notification $type = false !== strpos( $id, 'event_' ) ? 'events' : 'feed'; if( $type == 'events' ){ if( !empty($option[$type]) ){ foreach( $option[$type] as $index => $event_notification ){ if( $event_notification['id'] == $id ){ unset( $option[$type][$index] ); break; } } } }else{ if ( ! empty( $option[$type][$id] ) ) { $option['dismissed'][$id] = $option[$type][$id]; unset( $option[$type][$id] ); } } } update_option( 'members_notifications', $option ); wp_send_json_success(); } public function dismiss_events( $type ) { $option = $this->get_option(); // Event notifications. if ( ! empty( $option['events'] ) ) { $found = 0; foreach ( $option['events'] as $key => $notification ) { // We found event. if( $type === $notification['type'] ){ unset($option['events'][$key]); $found = 1; } } if( $found ){ update_option( 'members_notifications', $option ); } } } } $members_notifications = new Notifications; $members_notifications->init();