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() { if ( ! self::has_access() ) { return; } $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(); $notifications_html = '
'; $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();