PageRenderTime 58ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/wordpress-seo/admin/class-yoast-notification-center.php

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