class-yoast-notification.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. <?php
  2. /**
  3. * WPSEO plugin file.
  4. *
  5. * @package WPSEO\Admin\Notifications
  6. * @since 1.5.3
  7. */
  8. /**
  9. * Implements individual notification.
  10. */
  11. class Yoast_Notification {
  12. /**
  13. * Type of capability check.
  14. *
  15. * @var string
  16. */
  17. const MATCH_ALL = 'all';
  18. /**
  19. * Type of capability check.
  20. *
  21. * @var string
  22. */
  23. const MATCH_ANY = 'any';
  24. /**
  25. * Notification type.
  26. *
  27. * @var string
  28. */
  29. const ERROR = 'error';
  30. /**
  31. * Notification type.
  32. *
  33. * @var string
  34. */
  35. const WARNING = 'warning';
  36. /**
  37. * Notification type.
  38. *
  39. * @var string
  40. */
  41. const UPDATED = 'updated';
  42. /**
  43. * Options of this Notification.
  44. *
  45. * Contains optional arguments:
  46. *
  47. * - type: The notification type, i.e. 'updated' or 'error'
  48. * - id: The ID of the notification
  49. * - nonce: Security nonce to use in case of dismissible notice.
  50. * - priority: From 0 to 1, determines the order of Notifications.
  51. * - dismissal_key: Option name to save dismissal information in, ID will be used if not supplied.
  52. * - capabilities: Capabilities that a user must have for this Notification to show.
  53. * - capability_check: How to check capability pass: all or any.
  54. * - wpseo_page_only: Only display on wpseo page or on every page.
  55. *
  56. * @var array
  57. */
  58. private $options = [];
  59. /**
  60. * Contains default values for the optional arguments.
  61. *
  62. * @var array
  63. */
  64. private $defaults = [
  65. 'type' => self::UPDATED,
  66. 'id' => '',
  67. 'nonce' => null,
  68. 'priority' => 0.5,
  69. 'data_json' => [],
  70. 'dismissal_key' => null,
  71. 'capabilities' => [],
  72. 'capability_check' => self::MATCH_ALL,
  73. 'yoast_branding' => false,
  74. ];
  75. /**
  76. * The message for the notification.
  77. *
  78. * @var string
  79. */
  80. private $message;
  81. /**
  82. * Notification class constructor.
  83. *
  84. * @param string $message Message string.
  85. * @param array $options Set of options.
  86. */
  87. public function __construct( $message, $options = [] ) {
  88. $this->message = $message;
  89. $this->options = $this->normalize_options( $options );
  90. }
  91. /**
  92. * Retrieve notification ID string.
  93. *
  94. * @return string
  95. */
  96. public function get_id() {
  97. return $this->options['id'];
  98. }
  99. /**
  100. * Retrieve nonce identifier.
  101. *
  102. * @return null|string Nonce for this Notification.
  103. */
  104. public function get_nonce() {
  105. if ( $this->options['id'] && empty( $this->options['nonce'] ) ) {
  106. $this->options['nonce'] = wp_create_nonce( $this->options['id'] );
  107. }
  108. return $this->options['nonce'];
  109. }
  110. /**
  111. * Make sure the nonce is up to date.
  112. */
  113. public function refresh_nonce() {
  114. if ( $this->options['id'] ) {
  115. $this->options['nonce'] = wp_create_nonce( $this->options['id'] );
  116. }
  117. }
  118. /**
  119. * Get the type of the notification.
  120. *
  121. * @return string
  122. */
  123. public function get_type() {
  124. return $this->options['type'];
  125. }
  126. /**
  127. * Priority of the notification.
  128. *
  129. * Relative to the type.
  130. *
  131. * @return float Returns the priority between 0 and 1.
  132. */
  133. public function get_priority() {
  134. return $this->options['priority'];
  135. }
  136. /**
  137. * Get the User Meta key to check for dismissal of notification.
  138. *
  139. * @return string User Meta Option key that registers dismissal.
  140. */
  141. public function get_dismissal_key() {
  142. if ( empty( $this->options['dismissal_key'] ) ) {
  143. return $this->options['id'];
  144. }
  145. return $this->options['dismissal_key'];
  146. }
  147. /**
  148. * Is this Notification persistent.
  149. *
  150. * @return bool True if persistent, False if fire and forget.
  151. */
  152. public function is_persistent() {
  153. $id = $this->get_id();
  154. return ! empty( $id );
  155. }
  156. /**
  157. * Check if the notification is relevant for the current user.
  158. *
  159. * @return bool True if a user needs to see this notification, false if not.
  160. */
  161. public function display_for_current_user() {
  162. // If the notification is for the current page only, always show.
  163. if ( ! $this->is_persistent() ) {
  164. return true;
  165. }
  166. // If the current user doesn't match capabilities.
  167. return $this->match_capabilities();
  168. }
  169. /**
  170. * Does the current user match required capabilities.
  171. *
  172. * @return bool
  173. */
  174. public function match_capabilities() {
  175. // Super Admin can do anything.
  176. if ( is_multisite() && is_super_admin() ) {
  177. return true;
  178. }
  179. /**
  180. * Filter capabilities that enable the displaying of this notification.
  181. *
  182. * @param array $capabilities The capabilities that must be present for this notification.
  183. * @param Yoast_Notification $notification The notification object.
  184. *
  185. * @return array Array of capabilities or empty for no restrictions.
  186. *
  187. * @since 3.2
  188. */
  189. $capabilities = apply_filters( 'wpseo_notification_capabilities', $this->options['capabilities'], $this );
  190. // Should be an array.
  191. if ( ! is_array( $capabilities ) ) {
  192. $capabilities = (array) $capabilities;
  193. }
  194. /**
  195. * Filter capability check to enable all or any capabilities.
  196. *
  197. * @param string $capability_check The type of check that will be used to determine if an capability is present.
  198. * @param Yoast_Notification $notification The notification object.
  199. *
  200. * @return string self::MATCH_ALL or self::MATCH_ANY.
  201. *
  202. * @since 3.2
  203. */
  204. $capability_check = apply_filters( 'wpseo_notification_capability_check', $this->options['capability_check'], $this );
  205. if ( ! in_array( $capability_check, [ self::MATCH_ALL, self::MATCH_ANY ], true ) ) {
  206. $capability_check = self::MATCH_ALL;
  207. }
  208. if ( ! empty( $capabilities ) ) {
  209. $has_capabilities = array_filter( $capabilities, [ $this, 'has_capability' ] );
  210. switch ( $capability_check ) {
  211. case self::MATCH_ALL:
  212. return $has_capabilities === $capabilities;
  213. case self::MATCH_ANY:
  214. return ! empty( $has_capabilities );
  215. }
  216. }
  217. return true;
  218. }
  219. /**
  220. * Array filter function to find matched capabilities.
  221. *
  222. * @param string $capability Capability to test.
  223. *
  224. * @return bool
  225. */
  226. private function has_capability( $capability ) {
  227. return current_user_can( $capability );
  228. }
  229. /**
  230. * Return the object properties as an array.
  231. *
  232. * @return array
  233. */
  234. public function to_array() {
  235. return [
  236. 'message' => $this->message,
  237. 'options' => $this->options,
  238. ];
  239. }
  240. /**
  241. * Adds string (view) behaviour to the notification.
  242. *
  243. * @return string
  244. */
  245. public function __toString() {
  246. return $this->render();
  247. }
  248. /**
  249. * Renders the notification as a string.
  250. *
  251. * @return string The rendered notification.
  252. */
  253. public function render() {
  254. $attributes = [];
  255. // Default notification classes.
  256. $classes = [
  257. 'yoast-alert',
  258. ];
  259. // Maintain WordPress visualisation of alerts when they are not persistent.
  260. if ( ! $this->is_persistent() ) {
  261. $classes[] = 'notice';
  262. $classes[] = $this->get_type();
  263. }
  264. if ( ! empty( $classes ) ) {
  265. $attributes['class'] = implode( ' ', $classes );
  266. }
  267. // Combined attribute key and value into a string.
  268. array_walk( $attributes, [ $this, 'parse_attributes' ] );
  269. $message = null;
  270. if ( $this->options['yoast_branding'] ) {
  271. $message = $this->wrap_yoast_seo_icon( $this->message );
  272. }
  273. if ( $message === null ) {
  274. $message = wpautop( $this->message );
  275. }
  276. // Build the output DIV.
  277. return '<div ' . implode( ' ', $attributes ) . '>' . $message . '</div>' . PHP_EOL;
  278. }
  279. /**
  280. * Wraps the message with a Yoast SEO icon.
  281. *
  282. * @param string $message The message to wrap.
  283. *
  284. * @return string The wrapped message.
  285. */
  286. private function wrap_yoast_seo_icon( $message ) {
  287. $out = sprintf(
  288. '<img src="%1$s" height="%2$d" width="%3$d" class="yoast-seo-icon" />',
  289. esc_url( plugin_dir_url( WPSEO_FILE ) . 'images/Yoast_SEO_Icon.svg' ),
  290. 60,
  291. 60
  292. );
  293. $out .= '<div class="yoast-seo-icon-wrap">';
  294. $out .= $message;
  295. $out .= '</div>';
  296. return $out;
  297. }
  298. /**
  299. * Get the JSON if provided.
  300. *
  301. * @return false|string
  302. */
  303. public function get_json() {
  304. if ( empty( $this->options['data_json'] ) ) {
  305. return '';
  306. }
  307. return WPSEO_Utils::format_json_encode( $this->options['data_json'] );
  308. }
  309. /**
  310. * Make sure we only have values that we can work with.
  311. *
  312. * @param array $options Options to normalize.
  313. *
  314. * @return array
  315. */
  316. private function normalize_options( $options ) {
  317. $options = wp_parse_args( $options, $this->defaults );
  318. // Should not exceed 0 or 1.
  319. $options['priority'] = min( 1, max( 0, $options['priority'] ) );
  320. // Set default capabilities when not supplied.
  321. if ( empty( $options['capabilities'] ) || [] === $options['capabilities'] ) {
  322. $options['capabilities'] = [ 'wpseo_manage_options' ];
  323. }
  324. return $options;
  325. }
  326. /**
  327. * Format HTML element attributes.
  328. *
  329. * @param string $value Attribute value.
  330. * @param string $key Attribute name.
  331. */
  332. private function parse_attributes( &$value, $key ) {
  333. $value = sprintf( '%s="%s"', sanitize_key( $key ), esc_attr( $value ) );
  334. }
  335. }