class-yoast-notification.php 8.6 KB

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