PageRenderTime 58ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://gitlab.com/ngochuynh1991/cuacuon
PHP | 589 lines | 239 code | 119 blank | 231 comment | 33 complexity | 1d619714adffec08212c37b052d44205 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 Yoast_Notification_Condition_Interface[] Registered Notification Conditions */
  16. private $notification_conditions = array();
  17. /**
  18. * Construct
  19. */
  20. private function __construct() {
  21. // Load the notifications from storage.
  22. $this->notifications = $this->get_notifications_from_storage();
  23. add_action( 'admin_init', array( $this, 'register_notifications' ) );
  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. * Initialise global notification conditions
  42. *
  43. * Conditions that don't have dependencies should be registered here.
  44. */
  45. public static function initialize_conditions() {
  46. $instance = self::get();
  47. /**
  48. * Context dependent notifications:
  49. * - Yoast_Not_Indexable_Homepage_Condition - WPSEO_OnPage, needs option information.
  50. * - Yoast_Plugin_Conflict_Condition - Yoast_Plugin_Conflict, needs plugin+conflict information.
  51. */
  52. /**
  53. * Action Register Notification Conditionns
  54. *
  55. * Allow to hook into the notification center conditions registration.
  56. *
  57. * @param $instance Yoast_Notification_Center Instance to register condition on.
  58. */
  59. do_action( 'yoast_register_notification_conditions', $instance );
  60. }
  61. /**
  62. * Register notifications of conditions
  63. *
  64. * This has to happen after the translations have been loaded.
  65. */
  66. public function register_notifications() {
  67. /** @var Yoast_Notification_Condition $condition */
  68. foreach ( $this->notification_conditions as $condition ) {
  69. $notification = $condition->get_notification();
  70. if ( $condition->is_met() ) {
  71. $this->add_notification( $notification );
  72. }
  73. else {
  74. // Remove dismissal so it will be shown next time the condition is met.
  75. $this->clear_dismissal( $notification );
  76. }
  77. }
  78. }
  79. /**
  80. * Dismiss a notification
  81. */
  82. public static function ajax_dismiss_notification() {
  83. $notification_center = self::get();
  84. $notification_id = filter_input( INPUT_POST, 'notification' );
  85. if ( empty( $notification_id ) ) {
  86. die( '-1' );
  87. }
  88. $notification = $notification_center->get_notification_by_id( $notification_id );
  89. if ( false === ( $notification instanceof Yoast_Notification ) ) {
  90. // Permit legacy.
  91. $notification = new Yoast_Notification( '', array(
  92. 'id' => $notification_id,
  93. 'dismissal_key' => $notification_id,
  94. ) );
  95. /*
  96. * Activate when all legacy notifications have been replaced.
  97. *
  98. * die();
  99. */
  100. }
  101. if ( $notification_center->maybe_dismiss_notification( $notification ) ) {
  102. die( '1' );
  103. }
  104. die( '-1' );
  105. }
  106. /**
  107. * Check if the user has dismissed a notification
  108. *
  109. * @param Yoast_Notification $notification The notification to check for dismissal.
  110. * @param null|int $user_id User ID to check on.
  111. *
  112. * @return bool
  113. */
  114. public static function is_notification_dismissed( Yoast_Notification $notification, $user_id = null ) {
  115. $user_id = ( ! is_null( $user_id ) ? $user_id : get_current_user_id() );
  116. $dismissal_key = $notification->get_dismissal_key();
  117. $current_value = get_user_meta( $user_id, $dismissal_key, $single = true );
  118. if ( $notification->get_id() === 'wpseo-dismiss-about' ) {
  119. $seen_about_version = substr( get_user_meta( $user_id, 'wpseo_seen_about_version', true ), 0, 3 );
  120. $last_minor_version = substr( WPSEO_VERSION, 0, 3 );
  121. return version_compare( $seen_about_version, $last_minor_version, '>=' );
  122. }
  123. return ! empty( $current_value );
  124. }
  125. /**
  126. * Check if the nofitication is being dismissed
  127. *
  128. * @param string|Yoast_Notification $notification Notification to check dismissal of.
  129. * @param string $meta_value Value to set the meta value to if dismissed.
  130. *
  131. * @return bool True if dismissed.
  132. */
  133. public static function maybe_dismiss_notification( Yoast_Notification $notification, $meta_value = 'seen' ) {
  134. // If notification is already dismissed, we're done.
  135. if ( self::is_notification_dismissed( $notification ) ) {
  136. return true;
  137. }
  138. $dismissal_key = $notification->get_dismissal_key();
  139. $notification_id = $notification->get_id();
  140. $is_dismissing = ( $dismissal_key === self::get_user_input( 'notification' ) );
  141. if ( ! $is_dismissing ) {
  142. $is_dismissing = ( $notification_id === self::get_user_input( 'notification' ) );
  143. }
  144. // Fallback to ?dismissal_key=1&nonce=bla when JavaScript fails.
  145. if ( ! $is_dismissing ) {
  146. $is_dismissing = ( '1' === self::get_user_input( $dismissal_key ) );
  147. }
  148. if ( ! $is_dismissing ) {
  149. return false;
  150. }
  151. $user_nonce = self::get_user_input( 'nonce' );
  152. if ( false === wp_verify_nonce( $user_nonce, $notification_id ) ) {
  153. return false;
  154. }
  155. return self::dismiss_notification( $notification, $meta_value );
  156. }
  157. /**
  158. * Clear dismissal information for the specified Notification
  159. *
  160. * When a cause is resolved, the next time it is present we want to show
  161. * the message again.
  162. *
  163. * @param string|Yoast_Notification $notification Notification to clear the dismissal of.
  164. *
  165. * @return bool
  166. */
  167. public function clear_dismissal( $notification ) {
  168. if ( $notification instanceof Yoast_Notification ) {
  169. $dismissal_key = $notification->get_dismissal_key();
  170. }
  171. if ( is_string( $notification ) ) {
  172. $dismissal_key = $notification;
  173. }
  174. if ( empty( $dismissal_key ) ) {
  175. return false;
  176. }
  177. // Remove notification dismissal for all users.
  178. $deleted = delete_metadata( 'user', $user_id = 0, $dismissal_key, $meta_value = '', $delete_all = true );
  179. return $deleted;
  180. }
  181. /**
  182. * Add notification to the cookie
  183. *
  184. * @param Yoast_Notification $notification Notification object instance.
  185. */
  186. public function add_notification( Yoast_Notification $notification ) {
  187. $notification_id = $notification->get_id();
  188. // Empty notifications are always added.
  189. if ( $notification_id !== '' ) {
  190. // If notification ID exists in notifications, don't add again.
  191. if ( null !== $this->get_notification_by_id( $notification_id ) ) {
  192. return;
  193. }
  194. }
  195. // Add to list.
  196. $this->notifications[] = $notification;
  197. }
  198. /**
  199. * Get the notification by ID
  200. *
  201. * @param string $notification_id The ID of the notification to search for.
  202. *
  203. * @return null|Yoast_Notification
  204. */
  205. public function get_notification_by_id( $notification_id ) {
  206. foreach ( $this->notifications as $notification ) {
  207. if ( $notification_id === $notification->get_id() ) {
  208. return $notification;
  209. }
  210. }
  211. return null;
  212. }
  213. /**
  214. * Display the notifications
  215. */
  216. public function display_notifications() {
  217. // Never display notifications for network admin.
  218. if ( function_exists( 'is_network_admin' ) && is_network_admin() ) {
  219. return;
  220. }
  221. $sorted_notifications = $this->get_sorted_notifications();
  222. foreach ( $sorted_notifications as $notification ) {
  223. if ( $this->show_notification( $notification ) ) {
  224. echo $notification;
  225. }
  226. }
  227. // Clear the local stored notifications.
  228. if ( ! defined( 'DOING_AJAX' ) ) {
  229. $this->clear_notifications();
  230. }
  231. }
  232. /**
  233. * Return the notifications sorted on type and priority
  234. *
  235. * @return array|Yoast_Notification[] Sorted Notifications
  236. */
  237. public function get_sorted_notifications() {
  238. $notifications = $this->notifications;
  239. if ( empty( $notifications ) ) {
  240. return array();
  241. }
  242. // Sort by severity, error first.
  243. usort( $notifications, array( $this, 'sort_notifications' ) );
  244. return $notifications;
  245. }
  246. /**
  247. * AJAX display notifications
  248. */
  249. public function ajax_get_notifications() {
  250. // Display the notices.
  251. $this->display_notifications();
  252. // AJAX die.
  253. exit;
  254. }
  255. /**
  256. * Remove storage when the plugin is deactivated
  257. */
  258. public function deactivate_hook() {
  259. $this->clear_notification_conditions();
  260. $this->clear_notifications();
  261. }
  262. /**
  263. * Save persistent notifications to storage
  264. *
  265. * We need to be able to retrieve these so they can be dismissed at any time during the execution.
  266. *
  267. * @since 3.2
  268. *
  269. * @return void
  270. */
  271. public function update_storage() {
  272. $notifications = array_filter( $this->notifications, array( $this, 'filter_persistent_notifications' ) );
  273. // No notifications to store, clear storage.
  274. if ( empty( $notifications ) ) {
  275. $this->remove_storage();
  276. return;
  277. }
  278. $notifications = array_map( array( $this, 'notification_to_array' ), $notifications );
  279. // Save the notifications to the storage.
  280. update_option( self::STORAGE_KEY, WPSEO_Utils::json_encode( $notifications ), true );
  281. }
  282. /**
  283. * Provide a way to verify registered conditions
  284. *
  285. * @return array|Yoast_Notification_Condition[] Registered conditions.
  286. */
  287. public function get_notification_conditions() {
  288. return $this->notification_conditions;
  289. }
  290. /**
  291. * Provide a way to verify present notifications
  292. *
  293. * @return array|Yoast_Notification[] Registered notifications.
  294. */
  295. public function get_notifications() {
  296. return $this->notifications;
  297. }
  298. /**
  299. * Get information from the User input
  300. *
  301. * @param string $key Key to retrieve.
  302. *
  303. * @return mixed value of key if set.
  304. */
  305. private static function get_user_input( $key ) {
  306. $filter_input_type = INPUT_GET;
  307. if ( 'POST' === filter_input( INPUT_SERVER, 'REQUEST_METHOD' ) ) {
  308. $filter_input_type = INPUT_POST;
  309. }
  310. return filter_input( $filter_input_type, $key );
  311. }
  312. /**
  313. * Keep a list of conditions so we don't add duplicates
  314. *
  315. * @param Yoast_Notification_Condition $condition Condition to add to the stack.
  316. */
  317. public function add_notification_condition( Yoast_Notification_Condition $condition ) {
  318. // Prevent duplicates.
  319. if ( $this->has_notification_condition( $condition ) ) {
  320. return;
  321. }
  322. $this->notification_conditions[] = $condition;
  323. }
  324. /**
  325. * Check if the notification condition is already registered
  326. *
  327. * @param Yoast_Notification_Condition $condition Condition to check for.
  328. *
  329. * @return bool
  330. */
  331. private function has_notification_condition( Yoast_Notification_Condition $condition ) {
  332. return in_array( $condition, $this->notification_conditions, true );
  333. }
  334. /**
  335. * Check if the notification can be shown for the current user
  336. *
  337. * @param Yoast_Notification $notification Notification to check.
  338. *
  339. * @return bool
  340. */
  341. private function show_notification( Yoast_Notification $notification ) {
  342. // Don't display if it has been dismissed for the current user.
  343. if ( $this->maybe_dismiss_notification( $notification ) ) {
  344. return false;
  345. }
  346. // Don't display if the user doesn't have enough capabilities.
  347. return $notification->display_for_current_user();
  348. }
  349. /**
  350. * Get the notifications from storage
  351. *
  352. * @return array Yoast_Notification[] Notifcations
  353. */
  354. private function get_notifications_from_storage() {
  355. $stored_notifications = get_option( self::STORAGE_KEY, '' );
  356. // Check if notifications are stored.
  357. if ( ! empty( $stored_notifications ) ) {
  358. // Get json notifications from storage.
  359. $stored_notifications = json_decode( $stored_notifications, true );
  360. if ( is_array( $stored_notifications ) ) {
  361. return array_map( array( $this, 'array_to_notification' ), $stored_notifications );
  362. }
  363. }
  364. return array();
  365. }
  366. /**
  367. * Sort on type then priority
  368. *
  369. * @param Yoast_Notification $a Compare with B.
  370. * @param Yoast_Notification $b Compare with A.
  371. *
  372. * @return int 1, 0 or -1 for sorting offset.
  373. */
  374. private function sort_notifications( Yoast_Notification $a, Yoast_Notification $b ) {
  375. $a_type = $a->get_type();
  376. $b_type = $b->get_type();
  377. if ( $a_type === $b_type ) {
  378. return WPSEO_Utils::calc( $b->get_priority(), 'compare', $a->get_priority() );
  379. }
  380. if ( 'error' === $a_type ) {
  381. return -1;
  382. }
  383. if ( 'error' === $b_type ) {
  384. return 1;
  385. }
  386. return 0;
  387. }
  388. /**
  389. * Dismiss the notification
  390. *
  391. * @param Yoast_Notification $notification Notification to dismiss.
  392. * @param string $meta_value Value to save in the dismissal.
  393. *
  394. * @return bool
  395. */
  396. private static function dismiss_notification( Yoast_Notification $notification, $meta_value = 'seen' ) {
  397. $user_id = get_current_user_id();
  398. // Set about version when dismissing about notification.
  399. if ( $notification->get_id() === 'wpseo-dismiss-about' ) {
  400. setcookie( 'wpseo_seen_about_version_' . $user_id, WPSEO_VERSION, ( $_SERVER['REQUEST_TIME'] + YEAR_IN_SECONDS ) );
  401. return ( false !== update_user_meta( $user_id, 'wpseo_seen_about_version', WPSEO_VERSION ) );
  402. }
  403. // Dismiss notification.
  404. return ( false !== update_user_meta( get_current_user_id(), $notification->get_dismissal_key(), $meta_value ) );
  405. }
  406. /**
  407. * Remove all notifications from storage
  408. */
  409. private function remove_storage() {
  410. delete_option( self::STORAGE_KEY );
  411. }
  412. /**
  413. * Clear local stored notifications
  414. */
  415. private function clear_notifications() {
  416. $this->notifications = array();
  417. }
  418. /**
  419. * Clear notification conditions (mostly for testing)
  420. */
  421. private function clear_notification_conditions() {
  422. $this->notification_conditions = array();
  423. }
  424. /**
  425. * Filter out non-persistent notifications.
  426. *
  427. * @param Yoast_Notification $notification Notification to test for persistent.
  428. *
  429. * @since 3.2
  430. *
  431. * @return bool
  432. */
  433. private function filter_persistent_notifications( Yoast_Notification $notification ) {
  434. return $notification->is_persistent();
  435. }
  436. /**
  437. * Convert Notification to array representation
  438. *
  439. * @param Yoast_Notification $notification Notification to convert.
  440. *
  441. * @since 3.2
  442. *
  443. * @return array
  444. */
  445. private function notification_to_array( Yoast_Notification $notification ) {
  446. return $notification->to_array();
  447. }
  448. /**
  449. * Convert stored array to Notification.
  450. *
  451. * @param array $notification_data Array to convert to Notification.
  452. *
  453. * @return Yoast_Notification
  454. */
  455. private function array_to_notification( $notification_data ) {
  456. return new Yoast_Notification(
  457. $notification_data['message'],
  458. $notification_data['options']
  459. );
  460. }
  461. /**
  462. * Write the notifications to a cookie (hooked on shutdown)
  463. *
  464. * Function renamed to 'update_storage'.
  465. *
  466. * @depreacted 3.2 remove in 3.5
  467. */
  468. public function set_transient() {
  469. }
  470. }