Yoast_Notification_Center

Handles notifications storage and display.

Defined (1)

The class is defined in the following location(s).

/admin/class-yoast-notification-center.php  
  1. class Yoast_Notification_Center { 
  2.  
  3. /** Option name to store notifications on */ 
  4. const STORAGE_KEY = 'yoast_notifications'; 
  5.  
  6. /** @var \Yoast_Notification_Center The singleton instance of this object */ 
  7. private static $instance = null; 
  8.  
  9. /** @var $notifications Yoast_Notification[] */ 
  10. private $notifications = array(); 
  11.  
  12. /** @var array Notifications there are newly added */ 
  13. private $new = array(); 
  14.  
  15. /** @var array Notifications that were resolved this execution */ 
  16. private $resolved = 0; 
  17.  
  18. /** 
  19. * Construct 
  20. */ 
  21. private function __construct() { 
  22.  
  23. $this->retrieve_notifications_from_storage(); 
  24.  
  25. add_action( 'all_admin_notices', array( $this, 'display_notifications' ) ); 
  26.  
  27. add_action( 'wp_ajax_yoast_get_notifications', array( $this, 'ajax_get_notifications' ) ); 
  28.  
  29. add_action( 'wpseo_deactivate', array( $this, 'deactivate_hook' ) ); 
  30. add_action( 'shutdown', array( $this, 'update_storage' ) ); 
  31.  
  32. /** 
  33. * Singleton getter 
  34. * @return Yoast_Notification_Center 
  35. */ 
  36. public static function get() { 
  37.  
  38. if ( null === self::$instance ) { 
  39. self::$instance = new self(); 
  40.  
  41. return self::$instance; 
  42.  
  43. /** 
  44. * Dismiss a notification 
  45. */ 
  46. public static function ajax_dismiss_notification() { 
  47.  
  48. $notification_center = self::get(); 
  49.  
  50. $notification_id = filter_input( INPUT_POST, 'notification' ); 
  51. if ( empty( $notification_id ) ) { 
  52. die( '-1' ); 
  53.  
  54. $notification = $notification_center->get_notification_by_id( $notification_id ); 
  55. if ( false === ( $notification instanceof Yoast_Notification ) ) { 
  56.  
  57. // Permit legacy. 
  58. $notification = new Yoast_Notification( '', array( 
  59. 'id' => $notification_id,  
  60. 'dismissal_key' => $notification_id,  
  61. ) ); 
  62.  
  63. if ( $notification_center->maybe_dismiss_notification( $notification ) ) { 
  64. die( '1' ); 
  65.  
  66. die( '-1' ); 
  67.  
  68. /** 
  69. * Check if the user has dismissed a notification 
  70. * @param Yoast_Notification $notification The notification to check for dismissal. 
  71. * @param null|int $user_id User ID to check on. 
  72. * @return bool 
  73. */ 
  74. public static function is_notification_dismissed( Yoast_Notification $notification, $user_id = null ) { 
  75.  
  76. $user_id = ( ! is_null( $user_id ) ? $user_id : get_current_user_id() ); 
  77. $dismissal_key = $notification->get_dismissal_key(); 
  78.  
  79. $current_value = get_user_meta( $user_id, $dismissal_key, $single = true ); 
  80.  
  81. return ! empty( $current_value ); 
  82.  
  83. /** 
  84. * Check if the nofitication is being dismissed 
  85. * @param string|Yoast_Notification $notification Notification to check dismissal of. 
  86. * @param string $meta_value Value to set the meta value to if dismissed. 
  87. * @return bool True if dismissed. 
  88. */ 
  89. public static function maybe_dismiss_notification( Yoast_Notification $notification, $meta_value = 'seen' ) { 
  90.  
  91. // Only persistent notifications are dismissible. 
  92. if ( ! $notification->is_persistent() ) { 
  93. return false; 
  94.  
  95. // If notification is already dismissed, we're done. 
  96. if ( self::is_notification_dismissed( $notification ) ) { 
  97. return true; 
  98.  
  99. $dismissal_key = $notification->get_dismissal_key(); 
  100. $notification_id = $notification->get_id(); 
  101.  
  102. $is_dismissing = ( $dismissal_key === self::get_user_input( 'notification' ) ); 
  103. if ( ! $is_dismissing ) { 
  104. $is_dismissing = ( $notification_id === self::get_user_input( 'notification' ) ); 
  105.  
  106. // Fallback to ?dismissal_key=1&nonce=bla when JavaScript fails. 
  107. if ( ! $is_dismissing ) { 
  108. $is_dismissing = ( '1' === self::get_user_input( $dismissal_key ) ); 
  109.  
  110. if ( ! $is_dismissing ) { 
  111. return false; 
  112.  
  113. $user_nonce = self::get_user_input( 'nonce' ); 
  114. if ( false === wp_verify_nonce( $user_nonce, $notification_id ) ) { 
  115. return false; 
  116.  
  117. return self::dismiss_notification( $notification, $meta_value ); 
  118.  
  119. /** 
  120. * Clear dismissal information for the specified Notification 
  121. * When a cause is resolved, the next time it is present we want to show 
  122. * the message again. 
  123. * @param string|Yoast_Notification $notification Notification to clear the dismissal of. 
  124. * @return bool 
  125. */ 
  126. public function clear_dismissal( $notification ) { 
  127.  
  128. if ( $notification instanceof Yoast_Notification ) { 
  129. $dismissal_key = $notification->get_dismissal_key(); 
  130.  
  131. if ( is_string( $notification ) ) { 
  132. $dismissal_key = $notification; 
  133.  
  134. if ( empty( $dismissal_key ) ) { 
  135. return false; 
  136.  
  137. // Remove notification dismissal for all users. 
  138. $deleted = delete_metadata( 'user', $user_id = 0, $dismissal_key, $meta_value = '', $delete_all = true ); 
  139.  
  140. return $deleted; 
  141.  
  142. /** 
  143. * Add notification to the cookie 
  144. * @param Yoast_Notification $notification Notification object instance. 
  145. */ 
  146. public function add_notification( Yoast_Notification $notification ) { 
  147.  
  148. // Don't add if the user can't see it. 
  149. if ( ! $notification->display_for_current_user() ) { 
  150. return; 
  151.  
  152. $notification_id = $notification->get_id(); 
  153.  
  154. // Empty notifications are always added. 
  155. if ( $notification_id !== '' ) { 
  156.  
  157. // If notification ID exists in notifications, don't add again. 
  158. $present_notification = $this->get_notification_by_id( $notification_id ); 
  159. if ( ! is_null( $present_notification ) ) { 
  160. $this->remove_notification( $present_notification, false ); 
  161.  
  162. if ( is_null( $present_notification ) ) { 
  163. $this->new[] = $notification_id; 
  164.  
  165. // Add to list. 
  166. $this->notifications[] = $notification; 
  167.  
  168. /** 
  169. * Get the notification by ID 
  170. * @param string $notification_id The ID of the notification to search for. 
  171. * @return null|Yoast_Notification 
  172. */ 
  173. public function get_notification_by_id( $notification_id ) { 
  174.  
  175. foreach ( $this->notifications as & $notification ) { 
  176. if ( $notification_id === $notification->get_id() ) { 
  177. return $notification; 
  178.  
  179. return null; 
  180.  
  181. /** 
  182. * Display the notifications 
  183. */ 
  184. public function display_notifications() { 
  185.  
  186. // Never display notifications for network admin. 
  187. if ( function_exists( 'is_network_admin' ) && is_network_admin() ) { 
  188. return; 
  189.  
  190. $sorted_notifications = $this->get_sorted_notifications(); 
  191. foreach ( $sorted_notifications as $notification ) { 
  192. if ( ! $notification->is_persistent() ) { 
  193. echo $notification; 
  194. $this->remove_notification( $notification ); 
  195.  
  196. /** 
  197. * Remove notification after it has been displayed 
  198. * @param Yoast_Notification $notification Notification to remove. 
  199. * @param bool $resolve Resolve as fixed. 
  200. */ 
  201. public function remove_notification( Yoast_Notification $notification, $resolve = true ) { 
  202.  
  203. $index = false; 
  204.  
  205. // Match persistent Notifications by ID, non persistent by item in the array. 
  206. if ( $notification->is_persistent() ) { 
  207. foreach ( $this->notifications as $current_index => $present_notification ) { 
  208. if ( $present_notification->get_id() === $notification->get_id() ) { 
  209. $index = $current_index; 
  210. break; 
  211. else { 
  212. $index = array_search( $notification, $this->notifications, true ); 
  213.  
  214. if ( false === $index ) { 
  215. return; 
  216.  
  217. if ( $notification->is_persistent() && $resolve ) { 
  218. $this->resolved++; 
  219. $this->clear_dismissal( $notification ); 
  220.  
  221. unset( $this->notifications[ $index ] ); 
  222. $this->notifications = array_values( $this->notifications ); 
  223.  
  224. /** 
  225. * Get the notification count 
  226. * @param bool $dismissed Count dismissed notifications. 
  227. * @return int Number of notifications 
  228. */ 
  229. public function get_notification_count( $dismissed = false ) { 
  230.  
  231. $notifications = $this->get_notifications(); 
  232. $notifications = array_filter( $notifications, array( $this, 'filter_persistent_notifications' ) ); 
  233.  
  234. if ( ! $dismissed ) { 
  235. $notifications = array_filter( $notifications, array( $this, 'filter_dismissed_notifications' ) ); 
  236.  
  237. return count( $notifications ); 
  238.  
  239. /** 
  240. * Get the number of notifications resolved this execution 
  241. * These notifications have been resolved and should be counted when active again. 
  242. * @return int 
  243. */ 
  244. public function get_resolved_notification_count() { 
  245.  
  246. return $this->resolved; 
  247.  
  248. /** 
  249. * Return the notifications sorted on type and priority 
  250. * @return array|Yoast_Notification[] Sorted Notifications 
  251. */ 
  252. public function get_sorted_notifications() { 
  253.  
  254. $notifications = $this->get_notifications(); 
  255. if ( empty( $notifications ) ) { 
  256. return array(); 
  257.  
  258. // Sort by severity, error first. 
  259. usort( $notifications, array( $this, 'sort_notifications' ) ); 
  260.  
  261. return $notifications; 
  262.  
  263. /** 
  264. * AJAX display notifications 
  265. */ 
  266. public function ajax_get_notifications() { 
  267.  
  268. // Display the notices. 
  269. $this->display_notifications(); 
  270.  
  271. // AJAX die. 
  272. exit; 
  273.  
  274. /** 
  275. * Remove storage when the plugin is deactivated 
  276. */ 
  277. public function deactivate_hook() { 
  278.  
  279. $this->clear_notifications(); 
  280.  
  281. /** 
  282. * Save persistent notifications to storage 
  283. * We need to be able to retrieve these so they can be dismissed at any time during the execution. 
  284. * @since 3.2 
  285. * @return void 
  286. */ 
  287. public function update_storage() { 
  288.  
  289. $notifications = $this->get_notifications(); 
  290.  
  291. // No notifications to store, clear storage. 
  292. if ( empty( $notifications ) ) { 
  293. $this->remove_storage(); 
  294.  
  295. return; 
  296.  
  297. $notifications = array_map( array( $this, 'notification_to_array' ), $notifications ); 
  298.  
  299. // Save the notifications to the storage. 
  300. update_user_option( get_current_user_id(), self::STORAGE_KEY, $notifications ); 
  301.  
  302. /** 
  303. * Provide a way to verify present notifications 
  304. * @return array|Yoast_Notification[] Registered notifications. 
  305. */ 
  306. public function get_notifications() { 
  307.  
  308. return $this->notifications; 
  309.  
  310. /** 
  311. * Get newly added notifications 
  312. * @return array 
  313. */ 
  314. public function get_new_notifications() { 
  315.  
  316. return array_map( array( $this, 'get_notification_by_id' ), $this->new ); 
  317.  
  318. /** 
  319. * Get information from the User input 
  320. * @param string $key Key to retrieve. 
  321. * @return mixed value of key if set. 
  322. */ 
  323. private static function get_user_input( $key ) { 
  324.  
  325. $filter_input_type = INPUT_GET; 
  326. if ( 'POST' === strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { 
  327. $filter_input_type = INPUT_POST; 
  328.  
  329. return filter_input( $filter_input_type, $key ); 
  330.  
  331. /** 
  332. * Retrieve the notifications from storage 
  333. * @return array Yoast_Notification[] Notifications 
  334. */ 
  335. private function retrieve_notifications_from_storage() { 
  336.  
  337. $stored_notifications = get_user_option( self::STORAGE_KEY, get_current_user_id() ); 
  338.  
  339. // Check if notifications are stored. 
  340. if ( empty( $stored_notifications ) ) { 
  341. return; 
  342.  
  343. if ( is_array( $stored_notifications ) ) { 
  344. $notifications = array_map( array( $this, 'array_to_notification' ), $stored_notifications ); 
  345. $notifications = array_filter( $notifications, array( $this, 'filter_notification_current_user' ) ); 
  346.  
  347. $this->notifications = $notifications; 
  348.  
  349. /** 
  350. * Sort on type then priority 
  351. * @param Yoast_Notification $a Compare with B. 
  352. * @param Yoast_Notification $b Compare with A. 
  353. * @return int 1, 0 or -1 for sorting offset. 
  354. */ 
  355. private function sort_notifications( Yoast_Notification $a, Yoast_Notification $b ) { 
  356.  
  357. $a_type = $a->get_type(); 
  358. $b_type = $b->get_type(); 
  359.  
  360. if ( $a_type === $b_type ) { 
  361. return WPSEO_Utils::calc( $b->get_priority(), 'compare', $a->get_priority() ); 
  362.  
  363. if ( 'error' === $a_type ) { 
  364. return -1; 
  365.  
  366. if ( 'error' === $b_type ) { 
  367. return 1; 
  368.  
  369. return 0; 
  370.  
  371. /** 
  372. * Dismiss the notification 
  373. * @param Yoast_Notification $notification Notification to dismiss. 
  374. * @param string $meta_value Value to save in the dismissal. 
  375. * @return bool 
  376. */ 
  377. private static function dismiss_notification( Yoast_Notification $notification, $meta_value = 'seen' ) { 
  378. // Dismiss notification. 
  379. return ( false !== update_user_meta( get_current_user_id(), $notification->get_dismissal_key(), $meta_value ) ); 
  380.  
  381. /** 
  382. * Remove all notifications from storage 
  383. */ 
  384. private function remove_storage() { 
  385.  
  386. delete_user_option( get_current_user_id(), self::STORAGE_KEY ); 
  387.  
  388. /** 
  389. * Clear local stored notifications 
  390. */ 
  391. private function clear_notifications() { 
  392.  
  393. $this->notifications = array(); 
  394.  
  395. /** 
  396. * Filter out non-persistent notifications. 
  397. * @param Yoast_Notification $notification Notification to test for persistent. 
  398. * @since 3.2 
  399. * @return bool 
  400. */ 
  401. private function filter_persistent_notifications( Yoast_Notification $notification ) { 
  402.  
  403. return $notification->is_persistent(); 
  404.  
  405. /** 
  406. * Filter out dismissed notifications 
  407. * @param Yoast_Notification $notification Notification to check. 
  408. * @return bool 
  409. */ 
  410. private function filter_dismissed_notifications( Yoast_Notification $notification ) { 
  411.  
  412. return ! $this->maybe_dismiss_notification( $notification ); 
  413.  
  414. /** 
  415. * Convert Notification to array representation 
  416. * @param Yoast_Notification $notification Notification to convert. 
  417. * @since 3.2 
  418. * @return array 
  419. */ 
  420. private function notification_to_array( Yoast_Notification $notification ) { 
  421.  
  422. return $notification->to_array(); 
  423.  
  424. /** 
  425. * Convert stored array to Notification. 
  426. * @param array $notification_data Array to convert to Notification. 
  427. * @return Yoast_Notification 
  428. */ 
  429. private function array_to_notification( $notification_data ) { 
  430.  
  431. return new Yoast_Notification( 
  432. $notification_data['message'],  
  433. $notification_data['options'] 
  434. ); 
  435.  
  436. /** 
  437. * Filter notifications that should not be displayed for the current user 
  438. * @param Yoast_Notification $notification Notification to test. 
  439. * @return bool 
  440. */ 
  441. private function filter_notification_current_user( Yoast_Notification $notification ) { 
  442. return $notification->display_for_current_user(); 
  443.  
  444. /** 
  445. * Write the notifications to a cookie (hooked on shutdown) 
  446. * Function renamed to 'update_storage'. 
  447. * @deprecated 3.2 remove in 3.5 
  448. * @codeCoverageIgnore 
  449. */ 
  450. public function set_transient() { 
  451. _deprecated_function( __METHOD__, 'WPSEO 3.2' );