PluginProbe
WooCommerce / 11.1.0
WooCommerce v11.1.0
11.1.0 11.1.0-rc.2 11.1.0-rc.1 11.1.0-beta.2 11.1.0-beta.1 11.0.1 11.0.0 11.0.0-rc.3 11.0.0-rc.2 11.0.0-rc.1 11.0.0-beta.2 11.0.0-beta.1 10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 All 648 releases
woocommerce / src / Admin / Notes / Note.php
Note.php
741 lines 20.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WooCommerce Admin (Dashboard) Notes.
4 *
5 * The WooCommerce admin notes class gets admin notes data from storage and checks validity.
6 */
7
8 namespace Automattic\WooCommerce\Admin\Notes;
9
10 defined( 'ABSPATH' ) || exit;
11
12 /**
13 * Note class.
14 */
15 class Note extends \WC_Data {
16
17 // Note types.
18 const E_WC_ADMIN_NOTE_ERROR = 'error'; // used for presenting error conditions.
19 const E_WC_ADMIN_NOTE_WARNING = 'warning'; // used for presenting warning conditions.
20 const E_WC_ADMIN_NOTE_UPDATE = 'update'; // i.e. used when a new version is available.
21 const E_WC_ADMIN_NOTE_INFORMATIONAL = 'info'; // used for presenting informational messages.
22 const E_WC_ADMIN_NOTE_MARKETING = 'marketing'; // used for adding marketing messages.
23 const E_WC_ADMIN_NOTE_SURVEY = 'survey'; // used for adding survey messages.
24 const E_WC_ADMIN_NOTE_EMAIL = 'email'; // used for adding notes that will be sent by email.
25
26 // Note status codes.
27 const E_WC_ADMIN_NOTE_PENDING = 'pending'; // the note is pending - hidden but not actioned.
28 const E_WC_ADMIN_NOTE_UNACTIONED = 'unactioned'; // the note has not yet been actioned by a user.
29 const E_WC_ADMIN_NOTE_ACTIONED = 'actioned'; // the note has had its action completed by a user.
30 const E_WC_ADMIN_NOTE_SNOOZED = 'snoozed'; // the note has been snoozed by a user.
31 const E_WC_ADMIN_NOTE_SENT = 'sent'; // the note has been sent by email to the user.
32
33 /**
34 * This is the name of this object type.
35 *
36 * @var string
37 */
38 protected $object_type = 'admin-note';
39
40 /**
41 * Cache group.
42 *
43 * @var string
44 */
45 protected $cache_group = 'admin-note';
46
47 /**
48 * Note constructor. Loads note data.
49 *
50 * @param mixed $data Note data, object, or ID.
51 */
52 public function __construct( $data = '' ) {
53 // Set default data here to allow `content_data` to be an object.
54 $this->data = array(
55 'name' => '-',
56 'type' => self::E_WC_ADMIN_NOTE_INFORMATIONAL,
57 'locale' => 'en_US',
58 'title' => '-',
59 'content' => '-',
60 'content_data' => new \stdClass(),
61 'status' => self::E_WC_ADMIN_NOTE_UNACTIONED,
62 'source' => 'woocommerce',
63 'date_created' => '0000-00-00 00:00:00',
64 'date_reminder' => null,
65 'is_snoozable' => false,
66 'actions' => array(),
67 'layout' => 'plain',
68 'image' => '',
69 'is_deleted' => false,
70 'is_read' => false,
71 );
72
73 parent::__construct( $data );
74
75 if ( $data instanceof Note ) {
76 $this->set_id( absint( $data->get_id() ) );
77 } elseif ( is_numeric( $data ) ) {
78 $this->set_id( $data );
79 } elseif ( is_object( $data ) && ! empty( $data->note_id ) ) {
80 $this->set_id( $data->note_id );
81 unset( $data->icon ); // Icons are deprecated.
82 // `image` and `layout` are deprecated — skip them so set_props doesn't dispatch to the deprecated setters.
83 unset( $data->image, $data->layout );
84 $this->set_props( (array) $data );
85 $this->set_object_read( true );
86 } else {
87 $this->set_object_read( true );
88 }
89
90 $this->data_store = Notes::load_data_store();
91 if ( $this->get_id() > 0 ) {
92 $this->data_store->read( $this );
93 }
94 }
95
96 /**
97 * Merge changes with data and clear.
98 *
99 * @since 3.0.0
100 */
101 public function apply_changes() {
102 $this->data = array_replace_recursive( $this->data, $this->changes ); // @codingStandardsIgnoreLine
103
104 // Note actions need to be replaced wholesale.
105 // Merging arrays doesn't allow for deleting note actions.
106 if ( isset( $this->changes['actions'] ) ) {
107 $this->data['actions'] = $this->changes['actions'];
108 }
109
110 $this->changes = array();
111 }
112
113 /*
114 |--------------------------------------------------------------------------
115 | Helpers
116 |--------------------------------------------------------------------------
117 |
118 | Methods for getting allowed types, statuses.
119 |
120 */
121
122 /**
123 * Get deprecated types.
124 *
125 * @return array
126 */
127 public static function get_deprecated_types() {
128 return array(
129 self::E_WC_ADMIN_NOTE_EMAIL,
130 );
131 }
132
133 /**
134 * Get allowed types.
135 *
136 * @return array
137 */
138 public static function get_allowed_types() {
139 $allowed_types = array(
140 self::E_WC_ADMIN_NOTE_ERROR,
141 self::E_WC_ADMIN_NOTE_WARNING,
142 self::E_WC_ADMIN_NOTE_UPDATE,
143 self::E_WC_ADMIN_NOTE_INFORMATIONAL,
144 self::E_WC_ADMIN_NOTE_MARKETING,
145 self::E_WC_ADMIN_NOTE_SURVEY,
146 );
147
148 return apply_filters( 'woocommerce_note_types', $allowed_types );
149 }
150
151 /**
152 * Get allowed statuses.
153 *
154 * @return array
155 */
156 public static function get_allowed_statuses() {
157 $allowed_statuses = array(
158 self::E_WC_ADMIN_NOTE_PENDING,
159 self::E_WC_ADMIN_NOTE_ACTIONED,
160 self::E_WC_ADMIN_NOTE_UNACTIONED,
161 self::E_WC_ADMIN_NOTE_SNOOZED,
162 self::E_WC_ADMIN_NOTE_SENT,
163 );
164
165 return apply_filters( 'woocommerce_note_statuses', $allowed_statuses );
166 }
167
168
169 /*
170 |--------------------------------------------------------------------------
171 | Getters
172 |--------------------------------------------------------------------------
173 |
174 | Methods for getting data from the note object.
175 |
176 */
177
178 /**
179 * Returns all data for this object.
180 *
181 * Override \WC_Data::get_data() to avoid errantly including meta data
182 * from ID collisions with the posts table.
183 *
184 * @return array
185 */
186 public function get_data() {
187 return array_merge( array( 'id' => $this->get_id() ), $this->data );
188 }
189
190 /**
191 * Get note name.
192 *
193 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
194 * @return string
195 */
196 public function get_name( $context = 'view' ) {
197 return $this->get_prop( 'name', $context );
198 }
199
200 /**
201 * Get note type.
202 *
203 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
204 * @return string
205 */
206 public function get_type( $context = 'view' ) {
207 return $this->get_prop( 'type', $context );
208 }
209
210 /**
211 * Get note locale.
212 *
213 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
214 * @return string
215 */
216 public function get_locale( $context = 'view' ) {
217 return $this->get_prop( 'locale', $context );
218 }
219
220 /**
221 * Get note title.
222 *
223 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
224 * @return string
225 */
226 public function get_title( $context = 'view' ) {
227 return $this->get_prop( 'title', $context );
228 }
229
230 /**
231 * Get note content.
232 *
233 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
234 * @return string
235 */
236 public function get_content( $context = 'view' ) {
237 return $this->get_prop( 'content', $context );
238 }
239
240 /**
241 * Get note content data (i.e. values that would be needed for re-localization)
242 *
243 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
244 * @return object
245 */
246 public function get_content_data( $context = 'view' ) {
247 return $this->get_prop( 'content_data', $context );
248 }
249
250 /**
251 * Get note status.
252 *
253 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
254 * @return string
255 */
256 public function get_status( $context = 'view' ) {
257 return $this->get_prop( 'status', $context );
258 }
259
260 /**
261 * Get note source.
262 *
263 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
264 * @return string
265 */
266 public function get_source( $context = 'view' ) {
267 return $this->get_prop( 'source', $context );
268 }
269
270 /**
271 * Get date note was created.
272 *
273 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
274 * @return WC_DateTime|NULL object if the date is set or null if there is no date.
275 */
276 public function get_date_created( $context = 'view' ) {
277 return $this->get_prop( 'date_created', $context );
278 }
279
280 /**
281 * Get date on which user should be reminded of the note (if any).
282 *
283 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
284 * @return WC_DateTime|NULL object if the date is set or null if there is no date.
285 */
286 public function get_date_reminder( $context = 'view' ) {
287 return $this->get_prop( 'date_reminder', $context );
288 }
289
290 /**
291 * Get note snoozability.
292 *
293 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
294 * @return bool Whether or not the note can be snoozed.
295 */
296 public function get_is_snoozable( $context = 'view' ) {
297 return $this->get_prop( 'is_snoozable', $context );
298 }
299
300 /**
301 * Get actions on the note (if any).
302 *
303 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
304 * @return array
305 */
306 public function get_actions( $context = 'view' ) {
307 return $this->get_prop( 'actions', $context );
308 }
309
310 /**
311 * Get action by action name on the note.
312 *
313 * @param string $action_name The action name.
314 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
315 * @return object the action.
316 */
317 public function get_action( $action_name, $context = 'view' ) {
318 $actions = $this->get_prop( 'actions', $context );
319
320 $matching_action = null;
321 foreach ( $actions as $i => $action ) {
322 if ( $action->name === $action_name ) {
323 $matching_action =& $actions[ $i ];
324 break;
325 }
326 }
327 return $matching_action;
328 }
329
330 /**
331 * Get note layout.
332 *
333 * @deprecated 10.8.0 Inbox notes no longer support layout variants; only 'plain' is valid and the field will be removed in a future release.
334 *
335 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
336 * @return string
337 */
338 public function get_layout( $context = 'view' ) {
339 wc_deprecated_function( __METHOD__, '10.8.0' );
340 return $this->get_prop( 'layout', $context );
341 }
342
343 /**
344 * Get note image.
345 *
346 * @deprecated 10.8.0 Inbox notes no longer render images; the field will be removed in a future release.
347 *
348 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
349 * @return string
350 */
351 public function get_image( $context = 'view' ) {
352 wc_deprecated_function( __METHOD__, '10.8.0' );
353 return $this->get_prop( 'image', $context );
354 }
355
356 /**
357 * Get deleted status.
358 *
359 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
360 * @return bool
361 */
362 public function get_is_deleted( $context = 'view' ) {
363 return $this->get_prop( 'is_deleted', $context );
364 }
365
366 /**
367 * Get is_read status.
368 *
369 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
370 * @return bool
371 */
372 public function get_is_read( $context = 'view' ) {
373 return $this->get_prop( 'is_read', $context );
374 }
375
376 /*
377 |--------------------------------------------------------------------------
378 | Setters
379 |--------------------------------------------------------------------------
380 |
381 | Methods for setting note data. These should not update anything in the
382 | database itself and should only change what is stored in the class
383 | object.
384 |
385 */
386
387 /**
388 * Set note name.
389 *
390 * @param string $name Note name.
391 */
392 public function set_name( $name ) {
393 // Don't allow empty names.
394 if ( empty( $name ) ) {
395 $this->error( 'admin_note_invalid_data', __( 'The admin note name prop cannot be empty.', 'woocommerce' ) );
396 }
397
398 $this->set_prop( 'name', $name );
399 }
400
401 /**
402 * Set note type.
403 *
404 * @param string $type Note type.
405 */
406 public function set_type( $type ) {
407 if ( empty( $type ) ) {
408 $this->error( 'admin_note_invalid_data', __( 'The admin note type prop cannot be empty.', 'woocommerce' ) );
409 }
410
411 if ( in_array( $type, self::get_deprecated_types(), true ) ) {
412 $this->error(
413 'admin_note_invalid_data',
414 __( 'The admin note type prop is deprecated.', 'woocommerce' )
415 );
416 }
417
418 if ( ! in_array( $type, self::get_allowed_types(), true ) ) {
419 $this->error(
420 'admin_note_invalid_data',
421 sprintf(
422 /* translators: %s: admin note type. */
423 __( 'The admin note type prop (%s) is not one of the supported types.', 'woocommerce' ),
424 $type
425 )
426 );
427 }
428
429 $this->set_prop( 'type', $type );
430 }
431
432 /**
433 * Set note locale.
434 *
435 * @param string $locale Note locale.
436 */
437 public function set_locale( $locale ) {
438 if ( empty( $locale ) ) {
439 $this->error( 'admin_note_invalid_data', __( 'The admin note locale prop cannot be empty.', 'woocommerce' ) );
440 }
441
442 $this->set_prop( 'locale', $locale );
443 }
444
445 /**
446 * Set note title.
447 *
448 * @param string $title Note title.
449 */
450 public function set_title( $title ) {
451 if ( empty( $title ) ) {
452 $this->error( 'admin_note_invalid_data', __( 'The admin note title prop cannot be empty.', 'woocommerce' ) );
453 }
454
455 $this->set_prop( 'title', $title );
456 }
457
458 /**
459 * Set note icon (Deprecated).
460 *
461 * @param string $icon Note icon.
462 */
463 public function set_icon( $icon ) {
464 wc_deprecated_function( 'set_icon', '4.3' );
465 }
466
467 /**
468 * Set note content.
469 *
470 * @param string $content Note content.
471 */
472 public function set_content( $content ) {
473 $allowed_html = array(
474 'br' => array(),
475 'em' => array(),
476 'strong' => array(),
477 'a' => array(
478 'href' => true,
479 'rel' => true,
480 'name' => true,
481 'target' => true,
482 'download' => array(
483 'valueless' => 'y',
484 ),
485 ),
486 'p' => array(),
487 );
488
489 $content = wp_kses( $content, $allowed_html );
490
491 if ( empty( $content ) ) {
492 $this->error( 'admin_note_invalid_data', __( 'The admin note content prop cannot be empty.', 'woocommerce' ) );
493 }
494
495 $this->set_prop( 'content', $content );
496 }
497
498 /**
499 * Set note data for potential re-localization.
500 *
501 * @todo Set a default empty array? https://github.com/woocommerce/woocommerce-admin/pull/1763#pullrequestreview-212442921.
502 * @param object $content_data Note data.
503 */
504 public function set_content_data( $content_data ) {
505 $allowed_type = false;
506
507 // Make sure $content_data is stdClass Object or an array.
508 if ( ! ( $content_data instanceof \stdClass ) ) {
509 $this->error( 'admin_note_invalid_data', __( 'The admin note content_data prop must be an instance of stdClass.', 'woocommerce' ) );
510 }
511
512 $this->set_prop( 'content_data', $content_data );
513 }
514
515 /**
516 * Set note status.
517 *
518 * @param string $status Note status.
519 */
520 public function set_status( $status ) {
521 if ( empty( $status ) ) {
522 $this->error( 'admin_note_invalid_data', __( 'The admin note status prop cannot be empty.', 'woocommerce' ) );
523 }
524
525 if ( ! in_array( $status, self::get_allowed_statuses(), true ) ) {
526 $this->error(
527 'admin_note_invalid_data',
528 sprintf(
529 /* translators: %s: admin note status property. */
530 __( 'The admin note status prop (%s) is not one of the supported statuses.', 'woocommerce' ),
531 $status
532 )
533 );
534 }
535
536 $this->set_prop( 'status', $status );
537 }
538
539 /**
540 * Set note source.
541 *
542 * @param string $source Note source.
543 */
544 public function set_source( $source ) {
545 if ( empty( $source ) ) {
546 $this->error( 'admin_note_invalid_data', __( 'The admin note source prop cannot be empty.', 'woocommerce' ) );
547 }
548
549 $this->set_prop( 'source', $source );
550 }
551
552 /**
553 * Set date note was created. NULL is not allowed
554 *
555 * @param string|integer $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed.
556 */
557 public function set_date_created( $date ) {
558 if ( empty( $date ) ) {
559 $this->error( 'admin_note_invalid_data', __( 'The admin note date prop cannot be empty.', 'woocommerce' ) );
560 }
561
562 if ( is_string( $date ) && ! is_numeric( $date ) ) {
563 $date = wc_string_to_timestamp( $date );
564 }
565 $this->set_date_prop( 'date_created', $date );
566 }
567
568 /**
569 * Set date admin should be reminded of note. NULL IS allowed
570 *
571 * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date.
572 */
573 public function set_date_reminder( $date ) {
574 if ( is_string( $date ) && ! is_numeric( $date ) ) {
575 $date = wc_string_to_timestamp( $date );
576 }
577 $this->set_date_prop( 'date_reminder', $date );
578 }
579
580 /**
581 * Set note snoozability.
582 *
583 * @param bool $is_snoozable Whether or not the note can be snoozed.
584 */
585 public function set_is_snoozable( $is_snoozable ) {
586 return $this->set_prop( 'is_snoozable', $is_snoozable );
587 }
588
589 /**
590 * Clear actions from a note.
591 */
592 public function clear_actions() {
593 $this->set_prop( 'actions', array() );
594 }
595
596 /**
597 * Set note layout.
598 *
599 * @deprecated 10.8.0 Inbox notes no longer support layout variants; only 'plain' is valid and the field will be removed in a future release.
600 *
601 * @param string $layout Note layout.
602 */
603 public function set_layout( $layout ) {
604 wc_deprecated_function( __METHOD__, '10.8.0' );
605
606 // 'thumbnail' was previously a valid value but is no longer rendered. Coerce it to 'plain'
607 // so existing callers don't break, and surface a deprecation warning so they update.
608 if ( 'thumbnail' === $layout ) {
609 wc_deprecated_argument( __METHOD__, '10.8.0', "The 'thumbnail' layout is no longer supported; coerced to 'plain'." );
610 $layout = 'plain';
611 }
612
613 if ( empty( $layout ) ) {
614 $layout = 'plain';
615 }
616
617 if ( 'plain' === $layout ) {
618 $this->set_prop( 'layout', $layout );
619 } else {
620 $this->error( 'admin_note_invalid_data', __( 'The admin note layout has a wrong prop value.', 'woocommerce' ) );
621 }
622 }
623
624 /**
625 * Set note image.
626 *
627 * @deprecated 10.8.0 Inbox notes no longer render images; the field will be removed in a future release.
628 *
629 * @param string $image Note image.
630 */
631 public function set_image( $image ) {
632 wc_deprecated_function( __METHOD__, '10.8.0' );
633 $this->set_prop( 'image', $image );
634 }
635
636 /**
637 * Set note deleted status. NULL is not allowed
638 *
639 * @param bool $is_deleted Note deleted status.
640 */
641 public function set_is_deleted( $is_deleted ) {
642 $this->set_prop( 'is_deleted', $is_deleted );
643 }
644
645 /**
646 * Set note is_read status. NULL is not allowed
647 *
648 * @param bool $is_read Note is_read status.
649 */
650 public function set_is_read( $is_read ) {
651 $this->set_prop( 'is_read', $is_read );
652 }
653
654 /**
655 * Add an action to the note
656 *
657 * @param string $name Action name (not presented to user).
658 * @param string $label Action label (presented as button label).
659 * @param string $url Action URL, if navigation needed. Optional.
660 * @param string $status Status to transition parent Note to upon click. Defaults to 'actioned'.
661 * @param boolean $primary Deprecated since version 3.4.0.
662 * @param string $actioned_text The label to display after the note has been actioned but before it is dismissed in the UI.
663 */
664 public function add_action(
665 $name,
666 $label,
667 $url = '',
668 $status = self::E_WC_ADMIN_NOTE_ACTIONED,
669 $primary = false,
670 $actioned_text = ''
671 ) {
672 $name = wc_clean( $name );
673 $label = wc_clean( $label );
674 $query = esc_url_raw( $url );
675 $status = wc_clean( $status );
676 $actioned_text = wc_clean( $actioned_text );
677
678 if ( empty( $name ) ) {
679 $this->error( 'admin_note_invalid_data', __( 'The admin note action name prop cannot be empty.', 'woocommerce' ) );
680 }
681
682 if ( empty( $label ) ) {
683 $this->error( 'admin_note_invalid_data', __( 'The admin note action label prop cannot be empty.', 'woocommerce' ) );
684 }
685
686 $action = array(
687 'name' => $name,
688 'label' => $label,
689 'query' => $query,
690 'status' => $status,
691 'actioned_text' => $actioned_text,
692 'nonce_name' => null,
693 'nonce_action' => null,
694 );
695
696 $note_actions = $this->get_prop( 'actions', 'edit' );
697 $note_actions[] = (object) $action;
698 $this->set_prop( 'actions', $note_actions );
699 }
700
701 /**
702 * Set actions on a note.
703 *
704 * @param array $actions Note actions.
705 */
706 public function set_actions( $actions ) {
707 $this->set_prop( 'actions', $actions );
708 }
709
710 /**
711 * Add a nonce to an existing note action.
712 *
713 * @link https://codex.wordpress.org/WordPress_Nonces
714 *
715 * @param string $note_action_name Name of action to add a nonce to.
716 * @param string $nonce_action The nonce action.
717 * @param string $nonce_name The nonce Name. This is used as the parameter name in the resulting URL for the action.
718 * @return void
719 * @throws \Exception If note name cannot be found.
720 */
721 public function add_nonce_to_action( string $note_action_name, string $nonce_action, string $nonce_name ) {
722 $actions = $this->get_prop( 'actions', 'edit' );
723
724 $matching_action = null;
725 foreach ( $actions as $i => $action ) {
726 if ( $action->name === $note_action_name ) {
727 $matching_action =& $actions[ $i ];
728 }
729 }
730
731 if ( empty( $matching_action ) ) {
732 throw new \Exception( sprintf( 'Could not find action %s in note %s', $note_action_name, $this->get_name() ) );
733 }
734
735 $matching_action->nonce_action = $nonce_action;
736 $matching_action->nonce_name = $nonce_name;
737
738 $this->set_actions( $actions );
739 }
740 }
741