Meta Box - Version 5.1.0

Version Description

  • 2019-08-19 =

Fixed

  • Fatal error with RWMB_About::redirect()
  • Ensure change event fires when editors change
  • Fix rwmb_{$field_id}_choice_label not working for cloneable fields
  • Fix missing dependency (underscore) for datepicker JS
  • Fix unindex notice for key_value field
  • Fix alignment for video field

Changed

  • Update notification sytem
  • Improve sanitization for fields. See documentation for details.
Download this release

Release Info

Developer rilwis
Plugin Icon 128x128 Meta Box
Version 5.1.0
Comparing to
See all releases

Code changes from version 5.0.1 to 5.1.0

css/style.css CHANGED
@@ -60,19 +60,20 @@
60
  }
61
  .rwmb-button.remove-clone {
62
  text-decoration: none;
63
- color: #fff;
64
- background: #e74c3c;
65
- border-radius: 50%;
66
  display: inline-block;
67
  position: absolute;
68
  top: 0;
69
  right: 0;
70
  width: 20px;
71
  height: 20px;
 
72
  }
73
  .rwmb-button.remove-clone .dashicons {
74
- font-size: 14px;
75
- line-height: 20px;
 
 
76
  }
77
  .remove-clone:focus {
78
  outline: 0;
60
  }
61
  .rwmb-button.remove-clone {
62
  text-decoration: none;
63
+ color: #ccc;
 
 
64
  display: inline-block;
65
  position: absolute;
66
  top: 0;
67
  right: 0;
68
  width: 20px;
69
  height: 20px;
70
+ transition: color 200ms;
71
  }
72
  .rwmb-button.remove-clone .dashicons {
73
+ font-size: 20px;
74
+ }
75
+ .rwmb-button.remove-clone:hover {
76
+ color: #dc3232;
77
  }
78
  .remove-clone:focus {
79
  outline: 0;
css/video.css CHANGED
@@ -1,9 +1,8 @@
1
- /* Video */
2
  .rwmb-video-item {
3
  position: relative;
4
  float: left;
5
- padding: 0;
6
- margin: 0 5px 5px 0;
7
  box-sizing: border-box;
8
  width: 300px;
9
  }
@@ -11,7 +10,7 @@
11
  .rwmb-video-item .rwmb-media-preview {
12
  width: 100%;
13
  float: none;
14
- background: #EEE;
15
  }
16
 
17
  .rwmb-video-item video {
@@ -21,5 +20,5 @@
21
 
22
  .rwmb-video-item .rwmb-media-info {
23
  margin-left: 0;
24
- padding: 10px;
25
  }
 
1
  .rwmb-video-item {
2
  position: relative;
3
  float: left;
4
+ padding: 8px;
5
+ margin: 0;
6
  box-sizing: border-box;
7
  width: 300px;
8
  }
10
  .rwmb-video-item .rwmb-media-preview {
11
  width: 100%;
12
  float: none;
13
+ background: #eee;
14
  }
15
 
16
  .rwmb-video-item video {
20
 
21
  .rwmb-video-item .rwmb-media-info {
22
  margin-left: 0;
23
+ padding: 10px 0;
24
  }
inc/about/about.php CHANGED
@@ -152,7 +152,7 @@ class RWMB_About {
152
  * @param bool $network_wide Whether to enable the plugin for all sites in the network
153
  * or just the current site. Multisite only. Default is false.
154
  */
155
- public function redirect( $plugin, $network_wide ) {
156
  if ( 'cli' !== php_sapi_name() && ! $network_wide && 'meta-box/meta-box.php' === $plugin && ! $this->is_bundled() ) {
157
  wp_safe_redirect( $this->get_menu_link() );
158
  die;
152
  * @param bool $network_wide Whether to enable the plugin for all sites in the network
153
  * or just the current site. Multisite only. Default is false.
154
  */
155
+ public function redirect( $plugin, $network_wide = false ) {
156
  if ( 'cli' !== php_sapi_name() && ! $network_wide && 'meta-box/meta-box.php' === $plugin && ! $this->is_bundled() ) {
157
  wp_safe_redirect( $this->get_menu_link() );
158
  die;
inc/clone.php CHANGED
@@ -71,26 +71,26 @@ class RWMB_Clone {
71
  /**
72
  * Set value of meta before saving into database
73
  *
74
- * @param mixed $new The submitted meta value.
75
- * @param mixed $old The existing meta value.
76
- * @param int $post_id The post ID.
77
- * @param array $field The field parameters.
78
  *
79
  * @return mixed
80
  */
81
- public static function value( $new, $old, $post_id, $field ) {
82
  if ( ! is_array( $new ) ) {
83
  $new = array();
84
  }
85
 
86
  if ( in_array( $field['type'], array( 'file', 'image' ), true ) ) {
87
- return RWMB_Field::call( $field, 'value', $new, '', $post_id );
88
  }
89
 
90
  foreach ( $new as $key => $value ) {
91
  $old_value = isset( $old[ $key ] ) ? $old[ $key ] : null;
92
- $value = RWMB_Field::call( $field, 'value', $value, $old_value, $post_id );
93
- $new[ $key ] = RWMB_Field::filter( 'sanitize', $value, $field );
94
  }
95
 
96
  // Remove empty clones.
@@ -123,7 +123,7 @@ class RWMB_Clone {
123
  * @return string $html
124
  */
125
  public static function remove_clone_button( $field ) {
126
- $text = RWMB_Field::filter( 'remove_clone_button_text', '<i class="dashicons dashicons-minus"></i>', $field );
127
  return '<a href="#" class="rwmb-button remove-clone">' . $text . '</a>';
128
  }
129
  }
71
  /**
72
  * Set value of meta before saving into database
73
  *
74
+ * @param mixed $new The submitted meta value.
75
+ * @param mixed $old The existing meta value.
76
+ * @param int $object_id The object ID.
77
+ * @param array $field The field parameters.
78
  *
79
  * @return mixed
80
  */
81
+ public static function value( $new, $old, $object_id, $field ) {
82
  if ( ! is_array( $new ) ) {
83
  $new = array();
84
  }
85
 
86
  if ( in_array( $field['type'], array( 'file', 'image' ), true ) ) {
87
+ return RWMB_Field::call( $field, 'value', $new, '', $object_id );
88
  }
89
 
90
  foreach ( $new as $key => $value ) {
91
  $old_value = isset( $old[ $key ] ) ? $old[ $key ] : null;
92
+ $value = RWMB_Field::call( $field, 'value', $value, $old_value, $object_id );
93
+ $new[ $key ] = RWMB_Field::filter( 'sanitize', $value, $field, $old_value, $object_id );
94
  }
95
 
96
  // Remove empty clones.
123
  * @return string $html
124
  */
125
  public static function remove_clone_button( $field ) {
126
+ $text = RWMB_Field::filter( 'remove_clone_button_text', '<span class="dashicons dashicons-dismiss"></span>', $field );
127
  return '<a href="#" class="rwmb-button remove-clone">' . $text . '</a>';
128
  }
129
  }
inc/field.php CHANGED
@@ -246,6 +246,28 @@ abstract class RWMB_Field {
246
  return is_array( $meta ) ? array_map( __METHOD__, $meta ) : esc_attr( $meta );
247
  }
248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  /**
250
  * Set value of meta before saving into database.
251
  *
@@ -337,9 +359,16 @@ abstract class RWMB_Field {
337
  'required' => false,
338
  'autofocus' => false,
339
  'attributes' => array(),
 
 
340
  )
341
  );
342
 
 
 
 
 
 
343
  if ( $field['clone_default'] ) {
344
  $field['attributes'] = wp_parse_args(
345
  $field['attributes'],
@@ -574,8 +603,9 @@ abstract class RWMB_Field {
574
  'rwmb_' . $name,
575
  'rwmb_' . $field['type'] . '_' . $name,
576
  );
577
- if ( isset( $field['id'] ) ) {
578
- $filters[] = 'rwmb_' . $field['id'] . '_' . $name;
 
579
  }
580
 
581
  // Filter params: value, field, other params. Note: value is changed after each run.
246
  return is_array( $meta ) ? array_map( __METHOD__, $meta ) : esc_attr( $meta );
247
  }
248
 
249
+ /**
250
+ * Process the submitted value before saving into the database.
251
+ *
252
+ * @param mixed $value The submitted value.
253
+ * @param int $object_id The object ID.
254
+ * @param array $field The field settings.
255
+ */
256
+ public static function process_value( $value, $object_id, $field ) {
257
+ $old_value = self::call( $field, 'raw_meta', $object_id );
258
+
259
+ // Allow field class change the value.
260
+ if ( $field['clone'] ) {
261
+ $value = RWMB_Clone::value( $value, $old_value, $object_id, $field );
262
+ } else {
263
+ $value = self::call( $field, 'value', $value, $old_value, $object_id );
264
+ $value = self::filter( 'sanitize', $value, $field, $old_value, $object_id );
265
+ }
266
+ $value = self::filter( 'value', $value, $field, $old_value, $object_id );
267
+
268
+ return $value;
269
+ }
270
+
271
  /**
272
  * Set value of meta before saving into database.
273
  *
359
  'required' => false,
360
  'autofocus' => false,
361
  'attributes' => array(),
362
+
363
+ 'sanitize_callback' => null,
364
  )
365
  );
366
 
367
+ // Store the original ID to run correct filters for the clonable field.
368
+ if ( $field['clone'] ) {
369
+ $field['_original_id'] = $field['id'];
370
+ }
371
+
372
  if ( $field['clone_default'] ) {
373
  $field['attributes'] = wp_parse_args(
374
  $field['attributes'],
603
  'rwmb_' . $name,
604
  'rwmb_' . $field['type'] . '_' . $name,
605
  );
606
+ if ( $field['id'] ) {
607
+ $field_id = $field['clone'] ? $field['_original_id'] : $field['id'];
608
+ $filters[] = 'rwmb_' . $field_id . '_' . $name;
609
  }
610
 
611
  // Filter params: value, field, other params. Note: value is changed after each run.
inc/fields/datetime.php CHANGED
@@ -67,8 +67,8 @@ class RWMB_Datetime_Field extends RWMB_Text_Field {
67
  wp_register_script( 'jquery-ui-timepicker', "$url/jquery-ui-timepicker-addon.min.js", array( 'jquery-ui-datepicker', 'jquery-ui-slider' ), '1.5.0', true );
68
  wp_register_script( 'jquery-ui-timepicker-i18n', "$url/jquery-ui-timepicker-addon-i18n.min.js", array( 'jquery-ui-timepicker' ), '1.5.0', true );
69
 
70
- wp_register_script( 'rwmb-datetime', RWMB_JS_URL . 'datetime.js', array( 'jquery-ui-datepicker', 'jquery-ui-timepicker-i18n' ), RWMB_VER, true );
71
- wp_register_script( 'rwmb-date', RWMB_JS_URL . 'date.js', array( 'jquery-ui-datepicker' ), RWMB_VER, true );
72
  wp_register_script( 'rwmb-time', RWMB_JS_URL . 'time.js', array( 'jquery-ui-timepicker-i18n' ), RWMB_VER, true );
73
 
74
  $handles = array( 'datetime', 'time' );
67
  wp_register_script( 'jquery-ui-timepicker', "$url/jquery-ui-timepicker-addon.min.js", array( 'jquery-ui-datepicker', 'jquery-ui-slider' ), '1.5.0', true );
68
  wp_register_script( 'jquery-ui-timepicker-i18n', "$url/jquery-ui-timepicker-addon-i18n.min.js", array( 'jquery-ui-timepicker' ), '1.5.0', true );
69
 
70
+ wp_register_script( 'rwmb-datetime', RWMB_JS_URL . 'datetime.js', array( 'jquery-ui-datepicker', 'jquery-ui-timepicker-i18n', 'underscore' ), RWMB_VER, true );
71
+ wp_register_script( 'rwmb-date', RWMB_JS_URL . 'date.js', array( 'jquery-ui-datepicker', 'underscore' ), RWMB_VER, true );
72
  wp_register_script( 'rwmb-time', RWMB_JS_URL . 'time.js', array( 'jquery-ui-timepicker-i18n' ), RWMB_VER, true );
73
 
74
  $handles = array( 'datetime', 'time' );
inc/fields/key-value.php CHANGED
@@ -124,9 +124,10 @@ class RWMB_Key_Value_Field extends RWMB_Text_Field {
124
  * @return array
125
  */
126
  public static function normalize( $field ) {
127
- $field = parent::normalize( $field );
128
  $field['clone'] = true;
129
  $field['multiple'] = true;
 
 
130
  $field['attributes']['type'] = 'text';
131
  $field['placeholder'] = wp_parse_args(
132
  (array) $field['placeholder'],
124
  * @return array
125
  */
126
  public static function normalize( $field ) {
 
127
  $field['clone'] = true;
128
  $field['multiple'] = true;
129
+ $field = parent::normalize( $field );
130
+
131
  $field['attributes']['type'] = 'text';
132
  $field['placeholder'] = wp_parse_args(
133
  (array) $field['placeholder'],
inc/fields/taxonomy-advanced.php CHANGED
@@ -34,26 +34,8 @@ class RWMB_Taxonomy_Advanced_Field extends RWMB_Taxonomy_Field {
34
  * @param array $field The field parameters.
35
  */
36
  public static function save( $new, $old, $post_id, $field ) {
37
- if ( empty( $field['id'] ) || ! $field['save_field'] ) {
38
- return;
39
- }
40
- $storage = $field['storage'];
41
-
42
- if ( ! $new ) {
43
- $storage->delete( $post_id, $field['id'] );
44
- return;
45
- }
46
-
47
- if ( ! $field['clone'] || ! $field['clone_as_multiple'] ) {
48
- $storage->update( $post_id, $field['id'], $new );
49
- return;
50
- }
51
-
52
- // clone and clone_as_multiple.
53
- $storage->delete( $post_id, $field['id'] );
54
- foreach ( $new as $value ) {
55
- $storage->add( $post_id, $field['id'], $value );
56
- }
57
  }
58
 
59
  /**
34
  * @param array $field The field parameters.
35
  */
36
  public static function save( $new, $old, $post_id, $field ) {
37
+ $field['multiple'] = false; // Force to save in 1 row in the database.
38
+ RWMB_Field::save( $new, $old, $post_id, $field );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  }
40
 
41
  /**
inc/loader.php CHANGED
@@ -18,7 +18,7 @@ class RWMB_Loader {
18
  */
19
  protected function constants() {
20
  // Script version, used to add version for scripts and styles.
21
- define( 'RWMB_VER', '5.0.1' );
22
 
23
  list( $path, $url ) = self::get_path( dirname( dirname( __FILE__ ) ) );
24
 
@@ -96,8 +96,8 @@ class RWMB_Loader {
96
  // Validation module.
97
  new RWMB_Validation();
98
 
99
- $sanitize = new RWMB_Sanitizer();
100
- $sanitize->init();
101
 
102
  $media_modal = new RWMB_Media_Modal();
103
  $media_modal->init();
@@ -107,10 +107,13 @@ class RWMB_Loader {
107
  $wpml->init();
108
 
109
  // Update.
110
- $update_checker = new RWMB_Update_Checker();
 
111
  $update_checker->init();
112
- $update_settings = new RWMB_Update_Settings( $update_checker );
113
  $update_settings->init();
 
 
114
 
115
  // Public functions.
116
  require_once RWMB_INC_DIR . 'functions.php';
18
  */
19
  protected function constants() {
20
  // Script version, used to add version for scripts and styles.
21
+ define( 'RWMB_VER', '5.1.0' );
22
 
23
  list( $path, $url ) = self::get_path( dirname( dirname( __FILE__ ) ) );
24
 
96
  // Validation module.
97
  new RWMB_Validation();
98
 
99
+ $sanitizer = new RWMB_Sanitizer();
100
+ $sanitizer->init();
101
 
102
  $media_modal = new RWMB_Media_Modal();
103
  $media_modal->init();
107
  $wpml->init();
108
 
109
  // Update.
110
+ $update_option = new RWMB_Update_Option();
111
+ $update_checker = new RWMB_Update_Checker( $update_option );
112
  $update_checker->init();
113
+ $update_settings = new RWMB_Update_Settings( $update_checker, $update_option );
114
  $update_settings->init();
115
+ $update_notification = new RWMB_Update_Notification( $update_checker, $update_option );
116
+ $update_notification->init();
117
 
118
  // Public functions.
119
  require_once RWMB_INC_DIR . 'functions.php';
inc/media-modal.php CHANGED
@@ -81,17 +81,9 @@ class RWMB_Media_Modal {
81
  foreach ( $this->fields as $field ) {
82
  $key = $field['id'];
83
 
84
- $old = RWMB_Field::call( $field, 'raw_meta', $post['ID'] );
85
  $new = isset( $attachment[ $key ] ) ? $attachment[ $key ] : '';
86
 
87
- // Allow field class change the value.
88
- if ( $field['clone'] ) {
89
- $new = RWMB_Clone::value( $new, $old, $post['ID'], $field );
90
- } else {
91
- $new = RWMB_Field::call( $field, 'value', $new, $old, $post['ID'] );
92
- $new = RWMB_Field::filter( 'sanitize', $new, $field );
93
- }
94
- $new = RWMB_Field::filter( 'value', $new, $field, $old );
95
 
96
  // Call defined method to save meta value, if there's no methods, call common one.
97
  RWMB_Field::call( $field, 'save', $new, $old, $post['ID'] );
81
  foreach ( $this->fields as $field ) {
82
  $key = $field['id'];
83
 
 
84
  $new = isset( $attachment[ $key ] ) ? $attachment[ $key ] : '';
85
 
86
+ $new = RWMB_Field::process_value( $new, $post['ID'], $field );
 
 
 
 
 
 
 
87
 
88
  // Call defined method to save meta value, if there's no methods, call common one.
89
  RWMB_Field::call( $field, 'save', $new, $old, $post['ID'] );
inc/meta-box.php CHANGED
@@ -303,15 +303,7 @@ class RW_Meta_Box {
303
  $old = RWMB_Field::call( $field, 'raw_meta', $this->object_id );
304
  // @codingStandardsIgnoreLine
305
  $new = isset( $_POST[ $field['id'] ] ) ? $_POST[ $field['id'] ] : ( $single ? '' : array() );
306
-
307
- // Allow field class change the value.
308
- if ( $field['clone'] ) {
309
- $new = RWMB_Clone::value( $new, $old, $this->object_id, $field );
310
- } else {
311
- $new = RWMB_Field::call( $field, 'value', $new, $old, $this->object_id );
312
- $new = RWMB_Field::filter( 'sanitize', $new, $field );
313
- }
314
- $new = RWMB_Field::filter( 'value', $new, $field, $old );
315
 
316
  // Filter to allow the field to be modified.
317
  $field = RWMB_Field::filter( 'field', $field, $field, $new, $old );
303
  $old = RWMB_Field::call( $field, 'raw_meta', $this->object_id );
304
  // @codingStandardsIgnoreLine
305
  $new = isset( $_POST[ $field['id'] ] ) ? $_POST[ $field['id'] ] : ( $single ? '' : array() );
306
+ $new = RWMB_Field::process_value( $new, $this->object_id, $field );
 
 
 
 
 
 
 
 
307
 
308
  // Filter to allow the field to be modified.
309
  $field = RWMB_Field::filter( 'field', $field, $field, $new, $old );
inc/sanitizer.php CHANGED
@@ -9,34 +9,94 @@
9
  * Sanitize class.
10
  */
11
  class RWMB_Sanitizer {
 
 
 
 
 
 
12
 
13
  /**
14
- * Built-in callbacks for some specific types.
15
  *
16
- * @var array
 
 
 
17
  */
18
- protected $callbacks = array(
19
- 'email' => 'sanitize_email',
20
- 'file_input' => 'esc_url_raw',
21
- 'oembed' => 'esc_url_raw',
22
- 'url' => 'esc_url_raw',
23
- );
 
 
 
 
24
 
25
  /**
26
- * Register hook to sanitize field value.
 
 
 
27
  */
28
- public function init() {
29
- // Built-in callback.
30
- foreach ( $this->callbacks as $type => $callback ) {
31
- add_filter( "rwmb_{$type}_sanitize", $callback );
32
  }
33
 
34
- // Custom callback.
35
- $methods = array_diff( get_class_methods( __CLASS__ ), array( 'init' ) );
36
- foreach ( $methods as $method ) {
37
- $type = substr( $method, 9 );
38
- add_filter( "rwmb_{$type}_sanitize", array( $this, $method ) );
39
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
 
42
  /**
@@ -47,18 +107,162 @@ class RWMB_Sanitizer {
47
  * @param string $value Checkbox value.
48
  * @return int
49
  */
50
- public function sanitize_checkbox( $value ) {
51
  return (int) ! empty( $value );
52
  }
53
 
54
  /**
55
- * Set the value of switch to 1 or 0 instead of 'checked' and empty string.
56
- * This prevents using default value once the switch has been unchecked.
57
  *
58
- * @param string $value Switch value.
59
- * @return int
60
  */
61
- public function sanitize_switch( $value ) {
62
- return (int) ! empty( $value );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  }
64
  }
9
  * Sanitize class.
10
  */
11
  class RWMB_Sanitizer {
12
+ /**
13
+ * Register hook to sanitize field value.
14
+ */
15
+ public function init() {
16
+ add_filter( 'rwmb_sanitize', array( $this, 'sanitize' ), 10, 4 );
17
+ }
18
 
19
  /**
20
+ * Sanitize a field value.
21
  *
22
+ * @param mixed $value The submitted new value.
23
+ * @param array $field The field settings.
24
+ * @param mixed $old_value The old field value in the database.
25
+ * @param int $object_id The object ID.
26
  */
27
+ public function sanitize( $value, $field, $old_value, $object_id ) {
28
+ // Allow developers to bypass the sanitization.
29
+ if ( 'none' === $field['sanitize_callback'] ) {
30
+ return $value;
31
+ }
32
+
33
+ $callback = $this->get_callback( $field );
34
+
35
+ return is_callable( $callback ) ? call_user_func( $callback, $value, $field, $old_value, $object_id ) : $value;
36
+ }
37
 
38
  /**
39
+ * Get sanitize callback for a field.
40
+ *
41
+ * @param array $field Field settings.
42
+ * @return callable
43
  */
44
+ private function get_callback( $field ) {
45
+ // User-defined callback.
46
+ if ( is_callable( $field['sanitize_callback'] ) ) {
47
+ return $field['sanitize_callback'];
48
  }
49
 
50
+ $callbacks = array(
51
+ 'autocomplete' => array( $this, 'sanitize_choice' ),
52
+ 'background' => array( $this, 'sanitize_background' ),
53
+ 'button_group' => array( $this, 'sanitize_choice' ),
54
+ 'checkbox' => array( $this, 'sanitize_checkbox' ),
55
+ 'checkbox_list' => array( $this, 'sanitize_choice' ),
56
+ 'color' => array( $this, 'sanitize_color' ),
57
+ 'date' => array( $this, 'sanitize_datetime' ),
58
+ 'datetime' => array( $this, 'sanitize_datetime' ),
59
+ 'email' => 'sanitize_email',
60
+ 'fieldset_text' => array( $this, 'sanitize_text' ),
61
+ 'file' => array( $this, 'sanitize_file' ),
62
+ 'file_advanced' => array( $this, 'sanitize_object' ),
63
+ 'file_input' => 'esc_url_raw',
64
+ 'file_upload' => array( $this, 'sanitize_object' ),
65
+ 'hidden' => 'sanitize_text_field',
66
+ 'image' => array( $this, 'sanitize_file' ),
67
+ 'image_advanced' => array( $this, 'sanitize_object' ),
68
+ 'image_select' => array( $this, 'sanitize_choice' ),
69
+ 'image_upload' => array( $this, 'sanitize_object' ),
70
+ 'key_value' => array( $this, 'sanitize_text' ),
71
+ 'map' => array( $this, 'sanitize_map' ),
72
+ 'number' => array( $this, 'sanitize_number' ),
73
+ 'oembed' => 'esc_url_raw',
74
+ 'osm' => array( $this, 'sanitize_map' ),
75
+ 'password' => 'sanitize_text_field',
76
+ 'post' => array( $this, 'sanitize_object' ),
77
+ 'radio' => array( $this, 'sanitize_choice' ),
78
+ 'range' => array( $this, 'sanitize_number' ),
79
+ 'select' => array( $this, 'sanitize_choice' ),
80
+ 'select_advanced' => array( $this, 'sanitize_choice' ),
81
+ 'sidebar' => array( $this, 'sanitize_text' ),
82
+ 'single_image' => 'absint',
83
+ 'slider' => array( $this, 'sanitize_slider' ),
84
+ 'switch' => array( $this, 'sanitize_checkbox' ),
85
+ 'taxonomy' => array( $this, 'sanitize_object' ),
86
+ 'taxonomy_advanced' => array( $this, 'sanitize_taxonomy_advanced' ),
87
+ 'text' => 'sanitize_text_field',
88
+ 'text_list' => array( $this, 'sanitize_text' ),
89
+ 'textarea' => 'wp_kses_post',
90
+ 'time' => 'sanitize_text_field',
91
+ 'url' => 'esc_url_raw',
92
+ 'user' => array( $this, 'sanitize_object' ),
93
+ 'video' => array( $this, 'sanitize_object' ),
94
+ 'wysiwyg' => 'wp_kses_post',
95
+ );
96
+
97
+ $type = $field['type'];
98
+
99
+ return isset( $callbacks[ $type ] ) ? $callbacks[ $type ] : null;
100
  }
101
 
102
  /**
107
  * @param string $value Checkbox value.
108
  * @return int
109
  */
110
+ private function sanitize_checkbox( $value ) {
111
  return (int) ! empty( $value );
112
  }
113
 
114
  /**
115
+ * Sanitize numeric value.
 
116
  *
117
+ * @param int|float $value The number value.
118
+ * @return int|float
119
  */
120
+ private function sanitize_number( $value ) {
121
+ return is_numeric( $value ) ? $value : 0;
122
+ }
123
+
124
+ /**
125
+ * Sanitize color value.
126
+ *
127
+ * @param string $value The color value.
128
+ * @return string
129
+ */
130
+ private function sanitize_color( $value ) {
131
+ if ( false === strpos( $value, 'rgba' ) ) {
132
+ return sanitize_hex_color( $value );
133
+ }
134
+
135
+ // rgba value.
136
+ $red = '';
137
+ $green = '';
138
+ $blue = '';
139
+ $alpha = '';
140
+ sscanf( $value, 'rgba(%d,%d,%d,%f)', $red, $green, $blue, $alpha );
141
+
142
+ return 'rgba(' . $red . ',' . $green . ',' . $blue . ',' . $alpha . ')';
143
+ }
144
+
145
+ /**
146
+ * Sanitize value for a choice field.
147
+ *
148
+ * @param string|array $value The submitted value.
149
+ * @param array $field The field settings.
150
+ * @return string|array
151
+ */
152
+ private function sanitize_choice( $value, $field ) {
153
+ $options = $field['options'];
154
+ return is_array( $value ) ? array_intersect( $value, array_keys( $options ) ) : ( isset( $options[ $value ] ) ? $value : '' );
155
+ }
156
+
157
+ /**
158
+ * Sanitize object & media field.
159
+ *
160
+ * @param int|array $value The submitted value.
161
+ * @return int|array
162
+ */
163
+ private function sanitize_object( $value ) {
164
+ return is_array( $value ) ? array_map( 'absint', $value ) : absint( $value );
165
+ }
166
+
167
+ /**
168
+ * Sanitize background field.
169
+ *
170
+ * @param array $value The submitted value.
171
+ * @return array
172
+ */
173
+ private function sanitize_background( $value ) {
174
+ $value = wp_parse_args(
175
+ $value,
176
+ array(
177
+ 'color' => '',
178
+ 'image' => '',
179
+ 'repeat' => '',
180
+ 'attachment' => '',
181
+ 'position' => '',
182
+ 'size' => '',
183
+ )
184
+ );
185
+ $value['color'] = $this->sanitize_color( $value['color'] );
186
+ $value['image'] = esc_url_raw( $value['image'] );
187
+
188
+ $value['repeat'] = in_array( $value['repeat'], array( 'no-repeat', 'repeat', 'repeat-x', 'repeat-y', 'inherit' ), true ) ? $value['repeat'] : '';
189
+ $value['position'] = in_array( $value['repeat'], array( 'top left', 'top center', 'top right', 'center left', 'center center', 'center right', 'bottom left', 'bottom center', 'bottom right' ), true ) ? $value['position'] : '';
190
+ $value['attachment'] = in_array( $value['repeat'], array( 'fixed', 'scroll', 'inherit' ), true ) ? $value['attachment'] : '';
191
+ $value['size'] = in_array( $value['repeat'], array( 'inherit', 'cover', 'contain' ), true ) ? $value['attachment'] : '';
192
+
193
+ return $value;
194
+ }
195
+
196
+ /**
197
+ * Sanitize text field.
198
+ *
199
+ * @param string|array $value The submitted value.
200
+ * @return string|array
201
+ */
202
+ private function sanitize_text( $value ) {
203
+ return is_array( $value ) ? array_map( __METHOD__, $value ) : sanitize_text_field( $value );
204
+ }
205
+
206
+ /**
207
+ * Sanitize file, image field.
208
+ *
209
+ * @param array $value The submitted value.
210
+ * @param array $field The field settings.
211
+ * @return array
212
+ */
213
+ private function sanitize_file( $value, $field ) {
214
+ return $field['upload_dir'] ? array_map( 'esc_url_raw', $value ) : array_map( 'absint', $value );
215
+ }
216
+
217
+ /**
218
+ * Sanitize slider field.
219
+ *
220
+ * @param mixed $value The submitted value.
221
+ * @param array $field The field settings.
222
+ * @return string|int|float
223
+ */
224
+ private function sanitize_slider( $value, $field ) {
225
+ return true === $field['js_options']['range'] ? sanitize_text_field( $value ) : $this->sanitize_number( $value );
226
+ }
227
+
228
+ /**
229
+ * Sanitize datetime field.
230
+ *
231
+ * @param mixed $value The submitted value.
232
+ * @param array $field The field settings.
233
+ * @return float|string
234
+ */
235
+ private function sanitize_datetime( $value, $field ) {
236
+ return $field['timestamp'] ? floor( abs( (float) $value ) ) : sanitize_text_field( $value );
237
+ }
238
+
239
+ /**
240
+ * Sanitize map field.
241
+ *
242
+ * @param mixed $value The submitted value.
243
+ * @return string
244
+ */
245
+ private function sanitize_map( $value ) {
246
+ $value = sanitize_text_field( $value );
247
+ list( $latitude, $longitude, $zoom ) = explode( ',', $value . ',,' );
248
+
249
+ $latitude = (float) $latitude;
250
+ $longitude = (float) $longitude;
251
+ $zoom = (int) $zoom;
252
+
253
+ return "$latitude,$longitude,$zoom";
254
+ }
255
+
256
+ /**
257
+ * Sanitize taxonomy advanced field.
258
+ *
259
+ * @param mixed $value The submitted value.
260
+ * @return string
261
+ */
262
+ private function sanitize_taxonomy_advanced( $value ) {
263
+ $value = RWMB_Helpers_Array::from_csv( $value );
264
+ $value = array_map( 'absint', $value );
265
+
266
+ return implode( ',', $value );
267
  }
268
  }
inc/update/checker.php CHANGED
@@ -6,7 +6,7 @@
6
  */
7
 
8
  /**
9
- * The updater class for Meta Box extensions
10
  *
11
  * @package Meta Box
12
  */
@@ -19,11 +19,20 @@ class RWMB_Update_Checker {
19
  private $api_url = 'https://metabox.io/index.php';
20
 
21
  /**
22
- * The update option.
23
  *
24
- * @var string
 
 
 
 
 
 
 
25
  */
26
- private $option = 'meta_box_updater';
 
 
27
 
28
  /**
29
  * Add hooks to check plugin updates.
@@ -48,6 +57,16 @@ class RWMB_Update_Checker {
48
  * @return bool
49
  */
50
  public function has_extensions() {
 
 
 
 
 
 
 
 
 
 
51
  if ( ! function_exists( 'get_plugins' ) ) {
52
  require_once ABSPATH . 'wp-admin/includes/plugin.php';
53
  }
@@ -76,9 +95,7 @@ class RWMB_Update_Checker {
76
  $plugins = get_plugins();
77
  $plugins = array_map( 'dirname', array_keys( $plugins ) );
78
 
79
- $installed_extensions = array_intersect( $extensions, $plugins );
80
-
81
- return ! empty( $installed_extensions );
82
  }
83
 
84
  /**
@@ -89,14 +106,14 @@ class RWMB_Update_Checker {
89
  * @return mixed
90
  */
91
  public function check_updates( $data ) {
92
- static $plugins = null;
93
 
94
  // Make sure to send remote request once.
95
- if ( null === $plugins ) {
96
- $plugins = $this->request( 'action=check_updates' );
97
  }
98
 
99
- if ( false === $plugins ) {
100
  return $data;
101
  }
102
 
@@ -104,18 +121,21 @@ class RWMB_Update_Checker {
104
  $data->response = array();
105
  }
106
 
107
- $plugins = array_filter( $plugins, array( $this, 'has_update' ) );
108
  foreach ( $plugins as $plugin ) {
 
 
 
 
109
  $data->response[ $plugin->plugin ] = $plugin;
110
  }
111
 
112
- $option = $this->get_option();
113
- $option['plugins'] = array_keys( $plugins );
114
- if ( is_multisite() ) {
115
- update_site_option( $this->option, $option );
116
- } else {
117
- update_option( $this->option, $option );
118
- }
119
 
120
  return $data;
121
  }
@@ -130,20 +150,19 @@ class RWMB_Update_Checker {
130
  * @return mixed
131
  */
132
  public function get_info( $data, $action, $args ) {
133
- $option = $this->get_option();
134
- $plugins = isset( $option['plugins'] ) ? $option['plugins'] : array();
135
  if ( 'plugin_information' !== $action || ! isset( $args->slug ) || ! in_array( $args->slug, $plugins, true ) ) {
136
  return $data;
137
  }
138
 
139
- $info = $this->request(
140
  array(
141
  'action' => 'get_info',
142
  'product' => $args->slug,
143
  )
144
  );
145
 
146
- return false === $info ? $data : $info;
147
  }
148
 
149
  /**
@@ -154,10 +173,13 @@ class RWMB_Update_Checker {
154
  * @return mixed
155
  */
156
  public function request( $args = '' ) {
157
- // Add email and API key to the request params.
158
- $option = $this->get_option();
159
- $args = wp_parse_args( $args, $option );
160
- $args = array_filter( $args );
 
 
 
161
 
162
  $request = wp_remote_post(
163
  $this->api_url,
@@ -167,13 +189,7 @@ class RWMB_Update_Checker {
167
  );
168
 
169
  $response = wp_remote_retrieve_body( $request );
170
- if ( $response ) {
171
- $data = @unserialize( $response );
172
-
173
- return $data;
174
- }
175
-
176
- return false;
177
  }
178
 
179
  /**
@@ -190,11 +206,11 @@ class RWMB_Update_Checker {
190
  }
191
 
192
  /**
193
- * Get update option.
194
  *
195
- * @return array
196
  */
197
- private function get_option() {
198
- return is_multisite() ? get_site_option( $this->option, array() ) : get_option( $this->option, array() );
199
  }
200
  }
6
  */
7
 
8
  /**
9
+ * The update checker class for Meta Box extensions
10
  *
11
  * @package Meta Box
12
  */
19
  private $api_url = 'https://metabox.io/index.php';
20
 
21
  /**
22
+ * The update option object.
23
  *
24
+ * @var object
25
+ */
26
+ private $option;
27
+
28
+ /**
29
+ * Constructor.
30
+ *
31
+ * @param object $option Update option object.
32
  */
33
+ public function __construct( $option ) {
34
+ $this->option = $option;
35
+ }
36
 
37
  /**
38
  * Add hooks to check plugin updates.
57
  * @return bool
58
  */
59
  public function has_extensions() {
60
+ $extensions = $this->get_extensions();
61
+ return ! empty( $extensions );
62
+ }
63
+
64
+ /**
65
+ * Get installed premium extensions.
66
+ *
67
+ * @return array Array of extension slugs.
68
+ */
69
+ public function get_extensions() {
70
  if ( ! function_exists( 'get_plugins' ) ) {
71
  require_once ABSPATH . 'wp-admin/includes/plugin.php';
72
  }
95
  $plugins = get_plugins();
96
  $plugins = array_map( 'dirname', array_keys( $plugins ) );
97
 
98
+ return array_intersect( $extensions, $plugins );
 
 
99
  }
100
 
101
  /**
106
  * @return mixed
107
  */
108
  public function check_updates( $data ) {
109
+ static $response = null;
110
 
111
  // Make sure to send remote request once.
112
+ if ( null === $response ) {
113
+ $response = $this->request( array( 'action' => 'check_updates' ) );
114
  }
115
 
116
+ if ( false === $response ) {
117
  return $data;
118
  }
119
 
121
  $data->response = array();
122
  }
123
 
124
+ $plugins = array_filter( $response['data'], array( $this, 'has_update' ) );
125
  foreach ( $plugins as $plugin ) {
126
+ if ( empty( $plugin->package ) ) {
127
+ $plugin->upgrade_notice = __( 'UPDATE UNAVAILABLE! Please enter a valid license key to enable automatic updates.', 'meta-box' );
128
+ }
129
+
130
  $data->response[ $plugin->plugin ] = $plugin;
131
  }
132
 
133
+ $this->option->update(
134
+ array(
135
+ 'status' => $response['status'],
136
+ 'plugins' => array_keys( $plugins ),
137
+ )
138
+ );
 
139
 
140
  return $data;
141
  }
150
  * @return mixed
151
  */
152
  public function get_info( $data, $action, $args ) {
153
+ $plugins = $this->option->get( 'plugins', array() );
 
154
  if ( 'plugin_information' !== $action || ! isset( $args->slug ) || ! in_array( $args->slug, $plugins, true ) ) {
155
  return $data;
156
  }
157
 
158
+ $response = $this->request(
159
  array(
160
  'action' => 'get_info',
161
  'product' => $args->slug,
162
  )
163
  );
164
 
165
+ return false === $response ? $data : $response['data'];
166
  }
167
 
168
  /**
173
  * @return mixed
174
  */
175
  public function request( $args = '' ) {
176
+ $args = wp_parse_args(
177
+ $args,
178
+ array(
179
+ 'api_key' => $this->get_api_key(),
180
+ )
181
+ );
182
+ $args = array_filter( $args );
183
 
184
  $request = wp_remote_post(
185
  $this->api_url,
189
  );
190
 
191
  $response = wp_remote_retrieve_body( $request );
192
+ return $response ? @unserialize( $response ) : false;
 
 
 
 
 
 
193
  }
194
 
195
  /**
206
  }
207
 
208
  /**
209
+ * Get the API key.
210
  *
211
+ * @return string
212
  */
213
+ public function get_api_key() {
214
+ return defined( 'META_BOX_KEY' ) ? META_BOX_KEY : $this->option->get( 'api_key' );
215
  }
216
  }
inc/update/notification.php ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This class notifies users to enter or update license key.
4
+ *
5
+ * @package Meta Box
6
+ */
7
+
8
+ /**
9
+ * Meta Box Update Notification class
10
+ *
11
+ * @package Meta Box
12
+ */
13
+ class RWMB_Update_Notification {
14
+ /**
15
+ * The update option object.
16
+ *
17
+ * @var object
18
+ */
19
+ private $option;
20
+
21
+ /**
22
+ * Settings page URL.
23
+ *
24
+ * @var string
25
+ */
26
+ private $settings_page;
27
+
28
+ /**
29
+ * The update checker object.
30
+ *
31
+ * @var object
32
+ */
33
+ private $checker;
34
+
35
+ /**
36
+ * Constructor.
37
+ *
38
+ * @param object $checker Update checker object.
39
+ * @param object $option Update option object.
40
+ */
41
+ public function __construct( $checker, $option ) {
42
+ $this->checker = $checker;
43
+ $this->option = $option;
44
+
45
+ $this->settings_page = is_multisite() ? network_admin_url( 'settings.php?page=meta-box-updater' ) : admin_url( 'admin.php?page=meta-box-updater' );
46
+ }
47
+
48
+ /**
49
+ * Add hooks to show admin notice.
50
+ */
51
+ public function init() {
52
+ if ( ! $this->checker->has_extensions() ) {
53
+ return;
54
+ }
55
+
56
+ // Show update message on Plugins page.
57
+ $extensions = $this->checker->get_extensions();
58
+ foreach ( $extensions as $extension ) {
59
+ $file = "{$extension}/{$extension}.php";
60
+ add_action( "in_plugin_update_message-$file", array( $this, 'show_update_message' ), 10, 2 );
61
+ add_filter( "plugin_action_links_$file", array( $this, 'plugin_links' ), 20 );
62
+ }
63
+
64
+ // Show global update notification.
65
+ if ( $this->is_dismissed() ) {
66
+ return;
67
+ }
68
+
69
+ $admin_notices_hook = is_multisite() ? 'network_admin_notices' : 'admin_notices';
70
+ add_action( $admin_notices_hook, array( $this, 'notify' ) );
71
+
72
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
73
+ add_action( 'wp_ajax_mb_dismiss_notification', array( $this, 'dismiss' ) );
74
+ }
75
+
76
+ /**
77
+ * Enqueue the notification script.
78
+ */
79
+ public function enqueue() {
80
+ wp_enqueue_script( 'mb-notification', RWMB_JS_URL . 'notification.js', array( 'jquery' ), RWMB_VER, true );
81
+ wp_localize_script( 'mb-notification', 'MBNotification', array( 'nonce' => wp_create_nonce( 'dismiss' ) ) );
82
+ }
83
+
84
+ /**
85
+ * Dismiss the notification permanently via ajax.
86
+ */
87
+ public function dismiss() {
88
+ check_ajax_referer( 'dismiss', 'nonce' );
89
+
90
+ $this->option->update(
91
+ array(
92
+ 'notification_dismissed' => 1,
93
+ 'notification_dismissed_time' => time(),
94
+ )
95
+ );
96
+
97
+ wp_send_json_success();
98
+ }
99
+
100
+ /**
101
+ * Notify users to enter license key.
102
+ */
103
+ public function notify() {
104
+ // Do not show notification on License page.
105
+ $screen = get_current_screen();
106
+ if ( 'meta-box_page_meta-box-updater' === $screen->id ) {
107
+ return;
108
+ }
109
+
110
+ $messages = array(
111
+ // Translators: %1$s - URL to the settings page, %2$s - URL to the pricing page.
112
+ 'no_key' => __( 'You have not set your Meta Box license key yet, which means you are missing out on automatic updates and support! Please <a href="%1$s">enter your license key</a> or <a href="%2$s" target="_blank">get a new one here</a>.', 'meta-box' ),
113
+ // Translators: %1$s - URL to the settings page, %2$s - URL to the pricing page.
114
+ 'invalid' => __( 'Your license key for Meta Box is <b>invalid</b>. Please <a href="%1$s">update your license key</a> or <a href="%2$s" target="_blank">get a new one</a> to enable automatic updates.', 'meta-box' ),
115
+ // Translators: %1$s - URL to the settings page, %2$s - URL to the pricing page.
116
+ 'error' => __( 'Your license key for Meta Box is <b>invalid</b>. Please <a href="%1$s">update your license key</a> or <a href="%2$s" target="_blank">get a new one</a> to enable automatic updates.', 'meta-box' ),
117
+ // Translators: %3$s - URL to the My Account page.
118
+ 'expired' => __( 'Your license key for Meta Box is <b>expired</b>. Please <a href="%3$s" target="_blank">renew your license</a> to get automatic updates and premium support.', 'meta-box' ),
119
+ );
120
+ $status = $this->get_license_status();
121
+ if ( ! isset( $messages[ $status ] ) ) {
122
+ return;
123
+ }
124
+
125
+ echo '<div id="meta-box-notification" class="notice notice-warning is-dismissible"><p><span class="dashicons dashicons-warning" style="color: #f56e28"></span> ', wp_kses_post( sprintf( $messages[ $status ], $this->settings_page, 'https://metabox.io/pricing/', 'https://metabox.io/my-account/' ) ), '</p></div>';
126
+ }
127
+
128
+ /**
129
+ * Show update message on Plugins page.
130
+ *
131
+ * @param array $plugin_data Plugin data.
132
+ * @param object $response Available plugin update data.
133
+ */
134
+ public function show_update_message( $plugin_data, $response ) {
135
+ // Users have an active license.
136
+ if ( ! empty( $response->package ) ) {
137
+ return;
138
+ }
139
+
140
+ $messages = array(
141
+ // Translators: %1$s - URL to the settings page, %2$s - URL to the pricing page.
142
+ 'no_key' => __( 'Please <a href="%1$s">enter your license key</a> or <a href="%2$s" target="_blank">get a new one here</a>.', 'meta-box' ),
143
+ // Translators: %1$s - URL to the settings page, %2$s - URL to the pricing page.
144
+ 'invalid' => __( 'Your license key is <b>invalid</b>. Please <a href="%1$s">update your license key</a> or <a href="%2$s" target="_blank">get a new one here</a>.', 'meta-box' ),
145
+ // Translators: %1$s - URL to the settings page, %2$s - URL to the pricing page.
146
+ 'error' => __( 'Your license key is <b>invalid</b>. Please <a href="%1$s">update your license key</a> or <a href="%2$s" target="_blank">get a new one here</a>.', 'meta-box' ),
147
+ // Translators: %3$s - URL to the My Account page.
148
+ 'expired' => __( 'Your license key is <b>expired</b>. Please <a href="%3$s" target="_blank">renew your license</a>.', 'meta-box' ),
149
+ );
150
+ $status = $this->get_license_status();
151
+ if ( ! isset( $messages[ $status ] ) ) {
152
+ return;
153
+ }
154
+
155
+ echo '<br><span style="width: 26px; height: 20px; display: inline-block;">&nbsp;</span>' . wp_kses_post( sprintf( $messages[ $status ], $this->settings_page, 'https://metabox.io/pricing/', 'https://metabox.io/my-account/' ) );
156
+ }
157
+
158
+ /**
159
+ * Add link for activate or update the license key.
160
+ *
161
+ * @param array $links Array of plugin links.
162
+ *
163
+ * @return array
164
+ */
165
+ public function plugin_links( $links ) {
166
+ $status = $this->get_license_status();
167
+ if ( 'active' === $status ) {
168
+ return $links;
169
+ }
170
+
171
+ $text = 'no_key' === $status ? __( 'Activate License', 'meta-box' ) : __( 'Update License', 'meta-box' );
172
+ $links[] = '<a href="' . esc_url( $this->settings_page ) . '" style="color: #39b54a; font-weight: bold">' . esc_html( $text ) . '</a>';
173
+
174
+ return $links;
175
+ }
176
+
177
+ /**
178
+ * Get license status.
179
+ */
180
+ private function get_license_status() {
181
+ return $this->checker->get_api_key() ? $this->option->get( 'status', 'active' ) : 'no_key';
182
+ }
183
+
184
+ /**
185
+ * Check if the global notification is dismissed.
186
+ * Auto re-enable the notification every 2 weeks after it's dissmissed.
187
+ *
188
+ * @return bool
189
+ */
190
+ private function is_dismissed() {
191
+ $time = $this->option->get( 'notification_dismissed_time' );
192
+
193
+ return $this->option->get( 'notification_dismissed' ) && time() - $time < 14 * DAY_IN_SECONDS;
194
+ }
195
+ }
inc/update/option.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This class handles getting and saving the updater option.
4
+ *
5
+ * @package Meta Box
6
+ */
7
+
8
+ /**
9
+ * Meta Box Update Option class
10
+ *
11
+ * @package Meta Box
12
+ */
13
+ class RWMB_Update_Option {
14
+ /**
15
+ * Option name.
16
+ *
17
+ * @var string
18
+ */
19
+ private $option = 'meta_box_updater';
20
+
21
+ /**
22
+ * Get an option.
23
+ *
24
+ * @param string $name Option name. Pass null to return the option array.
25
+ * @param mixed $default Default value.
26
+ *
27
+ * @return mixed Option value or option array.
28
+ */
29
+ public function get( $name = null, $default = null ) {
30
+ $option = is_multisite() ? get_site_option( $this->option, array() ) : get_option( $this->option, array() );
31
+
32
+ return null === $name ? $option : ( isset( $option[ $name ] ) ? $option[ $name ] : $default );
33
+ }
34
+
35
+ /**
36
+ * Update the option array.
37
+ *
38
+ * @param array $option Option value.
39
+ */
40
+ public function update( $option ) {
41
+ $old_option = (array) $this->get();
42
+
43
+ $option = array_merge( $old_option, $option );
44
+ if ( is_multisite() ) {
45
+ update_site_option( $this->option, $option );
46
+ } else {
47
+ update_option( $this->option, $option );
48
+ }
49
+ }
50
+ }
inc/update/settings.php CHANGED
@@ -12,25 +12,11 @@
12
  */
13
  class RWMB_Update_Settings {
14
  /**
15
- * Update option.
16
  *
17
- * @var string
18
- */
19
- private $option = 'meta_box_updater';
20
-
21
- /**
22
- * Settings page ID.
23
- *
24
- * @var string
25
- */
26
- private $page_id = 'meta-box-updater';
27
-
28
- /**
29
- * Settings page hook.
30
- *
31
- * @var string
32
  */
33
- private $page_hook;
34
 
35
  /**
36
  * The update checker object
@@ -43,51 +29,53 @@ class RWMB_Update_Settings {
43
  * Constructor.
44
  *
45
  * @param object $checker Update checker object.
 
46
  */
47
- public function __construct( $checker ) {
48
  $this->checker = $checker;
 
49
  }
50
 
51
  /**
52
- * Add hooks to create the settings page and show admin notice.
53
  */
54
  public function init() {
55
  // Whether to enable Meta Box menu. Priority 1 makes sure it runs before adding Meta Box menu.
56
  add_action( 'admin_menu', array( $this, 'enable_menu' ), 1 );
57
-
58
- // Add submenu. Use priority 80 to show it just above the About page (priority = 90).
59
- $admin_menu_hook = is_multisite() ? 'network_admin_menu' : 'admin_menu';
60
- add_action( $admin_menu_hook, array( $this, 'add_settings_page' ), 80 );
61
-
62
- $admin_notices_hook = is_multisite() ? 'network_admin_notices' : 'admin_notices';
63
- add_action( $admin_notices_hook, array( $this, 'notify' ) );
64
  }
65
 
66
  /**
67
- * Whether to enable Meta Box menu.
68
  */
69
  public function enable_menu() {
70
- if ( $this->checker->has_extensions() ) {
71
- add_filter( 'rwmb_admin_menu', '__return_true' );
72
  }
 
 
 
 
 
 
 
73
  }
74
 
75
  /**
76
  * Add settings page.
77
  */
78
  public function add_settings_page() {
79
- $parent = is_multisite() ? 'settings.php' : 'meta-box';
80
- $capability = is_multisite() ? 'manage_network_options' : 'manage_options';
81
- $title = is_multisite() ? esc_html__( 'Meta Box Updater', 'meta-box-updater' ) : esc_html__( 'License', 'meta-box-updater' );
82
- $this->page_hook = add_submenu_page(
83
  $parent,
84
  $title,
85
  $title,
86
  $capability,
87
- $this->page_id,
88
  array( $this, 'render' )
89
  );
90
- add_action( "load-{$this->page_hook}", array( $this, 'save' ) );
91
  }
92
 
93
  /**
@@ -96,34 +84,46 @@ class RWMB_Update_Settings {
96
  public function render() {
97
  ?>
98
  <div class="wrap">
99
- <h1><?php esc_html_e( 'Meta Box License' ); ?></h1>
100
- <p><?php esc_html_e( 'Please enter your license key to receive automatic updates for Meta Box extensions.', 'meta-box-updater' ); ?></p>
101
  <p>
102
  <?php
103
  printf(
104
- // Translators: %s - URL to MetaBox.io website.
105
- wp_kses_post( __( 'To get the license key, please visit your profile page at <a href="%s" target="_blank">metabox.io website</a>.', 'meta-box-updater' ) ),
106
- 'https://metabox.io/my-account/'
 
107
  );
108
  ?>
109
  </p>
110
 
111
  <form action="" method="post">
112
- <?php wp_nonce_field( 'meta-box-updater' ); ?>
113
-
114
- <?php
115
- $option = is_multisite() ? get_site_option( $this->option ) : get_option( $this->option );
116
- $key = isset( $option['api_key'] ) ? $option['api_key'] : '';
117
- ?>
118
 
119
  <table class="form-table">
120
  <tr>
121
- <th scope="row"><?php esc_html_e( 'License Key', 'meta-box-updater' ); ?></th>
122
- <td><input required class="regular-text" name="<?php echo esc_attr( $this->option ); ?>[api_key]" value="<?php echo esc_attr( $key ); ?>" type="password"></td>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  </tr>
124
  </table>
125
 
126
- <?php submit_button( __( 'Save Changes', 'meta-box-updater' ) ); ?>
127
  </form>
128
  </div>
129
  <?php
@@ -133,79 +133,44 @@ class RWMB_Update_Settings {
133
  * Save update settings.
134
  */
135
  public function save() {
136
- static $message_shown = false;
137
-
138
  if ( empty( $_POST['submit'] ) ) {
139
  return;
140
  }
141
- check_admin_referer( 'meta-box-updater' );
142
 
143
  // @codingStandardsIgnoreLine
144
- $option = isset( $_POST[ $this->option ] ) ? $_POST[ $this->option ] : array();
145
  $option = (array) $option;
146
- $option['status'] = 'success';
147
 
148
  $args = $option;
149
  $args['action'] = 'check_license';
150
- $message = $this->checker->request( $args );
151
-
152
- if ( $message ) {
153
- add_settings_error( '', 'invalid', $message );
154
- $option['status'] = 'error';
 
 
 
 
 
 
 
 
155
  } else {
156
- add_settings_error( '', 'success', __( 'Settings saved.', 'meta-box-updater' ), 'updated' );
157
- }
 
158
 
159
- // Non-multisite auto shows update message. See wp-admin/options-head.php.
160
- if ( is_multisite() ) {
161
- add_action( 'network_admin_notices', array( $this, 'show_update_message' ) );
162
  }
163
 
164
- if ( is_multisite() ) {
165
- update_site_option( $this->option, $option );
166
- } else {
167
- update_option( $this->option, $option );
168
- }
169
- }
170
 
171
- /**
172
- * Show update message.
173
- */
174
- public function show_update_message() {
175
- settings_errors();
176
- }
177
-
178
- /**
179
- * Notify users to enter license key.
180
- */
181
- public function notify() {
182
- if ( ! $this->checker->has_extensions() ) {
183
- return;
184
- }
185
- $messages = array(
186
- // Translators: %1$s - URL to Meta Box Updater settings page, %2$s - URL to MetaBox.io website.
187
- 'no_key' => __( '<b>Warning!</b> You have not set your Meta Box license key yet, which means you are missing out on automatic updates and support! <a href="%1$s">Enter your license key</a> or <a href="%2$s" target="_blank">get one here</a>.', 'meta-box-updater' ),
188
- // Translators: %1$s - URL to Meta Box Updater settings page, %2$s - URL to MetaBox.io website.
189
- 'invalid' => __( '<b>Warning!</b> Your license key for Meta Box is invalid or expired. Please <a href="%1$s">fix it</a> or <a href="%2$s" target="_blank">renew</a> to receive automatic updates and premium support.', 'meta-box-updater' ),
190
- );
191
- $status = $this->get_license_status();
192
- $admin_url = is_multisite() ? network_admin_url( "settings.php?page={$this->page_id}" ) : admin_url( "admin.php?page={$this->page_id}" );
193
- if ( isset( $messages[ $status ] ) ) {
194
- echo '<div class="notice notice-warning"><p>', wp_kses_post( sprintf( $messages[ $status ], $admin_url, 'https://metabox.io/pricing/' ) ), '</p></div>';
195
- }
196
- }
197
 
198
- /**
199
- * Get license status.
200
- */
201
- public function get_license_status() {
202
- $option = is_multisite() ? get_site_option( $this->option ) : get_option( $this->option );
203
- if ( empty( $option['api_key'] ) ) {
204
- return 'no_key';
205
- }
206
- if ( isset( $option['status'] ) && 'success' !== $option['status'] ) {
207
- return 'invalid';
208
- }
209
- return 'valid';
210
  }
211
  }
12
  */
13
  class RWMB_Update_Settings {
14
  /**
15
+ * The update option object.
16
  *
17
+ * @var object
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  */
19
+ private $option;
20
 
21
  /**
22
  * The update checker object
29
  * Constructor.
30
  *
31
  * @param object $checker Update checker object.
32
+ * @param object $option Update option object.
33
  */
34
+ public function __construct( $checker, $option ) {
35
  $this->checker = $checker;
36
+ $this->option = $option;
37
  }
38
 
39
  /**
40
+ * Add hooks to create the settings page.
41
  */
42
  public function init() {
43
  // Whether to enable Meta Box menu. Priority 1 makes sure it runs before adding Meta Box menu.
44
  add_action( 'admin_menu', array( $this, 'enable_menu' ), 1 );
 
 
 
 
 
 
 
45
  }
46
 
47
  /**
48
+ * Enable Meta Box menu when a premium extension is installed.
49
  */
50
  public function enable_menu() {
51
+ if ( ! $this->checker->has_extensions() ) {
52
+ return;
53
  }
54
+
55
+ // Enable Meta Box menu.
56
+ add_filter( 'rwmb_admin_menu', '__return_true' );
57
+
58
+ // Add submenu. Priority 90 makes it the last sub-menu item.
59
+ $admin_menu_hook = is_multisite() ? 'network_admin_menu' : 'admin_menu';
60
+ add_action( $admin_menu_hook, array( $this, 'add_settings_page' ), 90 );
61
  }
62
 
63
  /**
64
  * Add settings page.
65
  */
66
  public function add_settings_page() {
67
+ $parent = is_multisite() ? 'settings.php' : 'meta-box';
68
+ $capability = is_multisite() ? 'manage_network_options' : 'manage_options';
69
+ $title = is_multisite() ? esc_html__( 'Meta Box License', 'meta-box' ) : esc_html__( 'License', 'meta-box' );
70
+ $page_hook = add_submenu_page(
71
  $parent,
72
  $title,
73
  $title,
74
  $capability,
75
+ 'meta-box-updater',
76
  array( $this, 'render' )
77
  );
78
+ add_action( "load-{$page_hook}", array( $this, 'save' ) );
79
  }
80
 
81
  /**
84
  public function render() {
85
  ?>
86
  <div class="wrap">
87
+ <h1><?php esc_html_e( 'Meta Box License', 'meta-box' ); ?></h1>
88
+ <p><?php esc_html_e( 'Please enter your license key to enable automatic updates for Meta Box extensions.', 'meta-box' ); ?></p>
89
  <p>
90
  <?php
91
  printf(
92
+ // Translators: %1$s - URL to the My Account page, %2$s - URL to the pricing page.
93
+ wp_kses_post( __( 'To get the license key, visit the <a href="%1$s" target="_blank">My Account</a> page on metabox.io website. If you have not purchased any extension yet, please <a href="%2$s" target="_blank">get a new license here</a>.', 'meta-box' ) ),
94
+ 'https://metabox.io/my-account/',
95
+ 'https://metabox.io/pricing/'
96
  );
97
  ?>
98
  </p>
99
 
100
  <form action="" method="post">
101
+ <?php wp_nonce_field( 'meta-box' ); ?>
 
 
 
 
 
102
 
103
  <table class="form-table">
104
  <tr>
105
+ <th scope="row"><?php esc_html_e( 'License Key', 'meta-box' ); ?></th>
106
+ <td>
107
+ <input required class="regular-text" name="meta_box_updater[api_key]" value="<?php echo esc_attr( $this->option->get( 'api_key' ) ); ?>" type="password">
108
+ <?php
109
+ $messages = array(
110
+ // Translators: %1$s - URL to the pricing page.
111
+ 'invalid' => __( 'Your license key is <b>invalid</b>. Please update your license key or <a href="%1$s" target="_blank">get a new one here</a>.', 'meta-box' ),
112
+ // Translators: %1$s - URL to the pricing page.
113
+ 'error' => __( 'Your license key is <b>invalid</b>. Please update your license key or <a href="%1$s" target="_blank">get a new one here</a>.', 'meta-box' ),
114
+ // Translators: %2$s - URL to the My Account page.
115
+ 'expired' => __( 'Your license key is <b>expired</b>. Please <a href="%2$s" target="_blank">renew your license</a>.', 'meta-box' ),
116
+ );
117
+ $status = $this->checker->get_api_key() ? $this->option->get( 'status', 'active' ) : 'no_key';
118
+ if ( isset( $messages[ $status ] ) ) {
119
+ echo '<p class="description">', wp_kses_post( sprintf( $messages[ $status ], 'https://metabox.io/pricing/', 'https://metabox.io/my-account/' ) ), '</p>';
120
+ }
121
+ ?>
122
+ </td>
123
  </tr>
124
  </table>
125
 
126
+ <?php submit_button( __( 'Save Changes', 'meta-box' ) ); ?>
127
  </form>
128
  </div>
129
  <?php
133
  * Save update settings.
134
  */
135
  public function save() {
 
 
136
  if ( empty( $_POST['submit'] ) ) {
137
  return;
138
  }
139
+ check_admin_referer( 'meta-box' );
140
 
141
  // @codingStandardsIgnoreLine
142
+ $option = isset( $_POST['meta_box_updater'] ) ? $_POST['meta_box_updater'] : array();
143
  $option = (array) $option;
144
+ $option['status'] = 'active';
145
 
146
  $args = $option;
147
  $args['action'] = 'check_license';
148
+ $response = $this->checker->request( $args );
149
+ $status = isset( $response['status'] ) ? $response['status'] : 'invalid';
150
+
151
+ if ( false === $response ) {
152
+ add_settings_error( '', 'mb-error', __( 'Something wrong with the connection to metabox.io. Please try again later.', 'meta-box' ) );
153
+ } elseif ( 'active' === $status ) {
154
+ add_settings_error( '', 'mb-success', __( 'Your license is activated.', 'meta-box' ), 'updated' );
155
+ } elseif ( 'expired' === $status ) {
156
+ // Translators: %s - URL to the My Account page.
157
+ $message = __( 'License expired. Please renew on the <a href="%s" target="_blank">My Account</a> page on metabox.io website.', 'meta-box' );
158
+ $message = wp_kses_post( sprintf( $message, 'https://metabox.io/my-account/' ) );
159
+
160
+ add_settings_error( '', 'mb-expired', $message );
161
  } else {
162
+ // Translators: %1$s - URL to the My Account page, %2$s - URL to the pricing page.
163
+ $message = __( 'Invalid license. Please <a href="%1$s" target="_blank">check again</a> or <a href="%2$s" target="_blank">get a new license here</a>.', 'meta-box' );
164
+ $message = wp_kses_post( sprintf( $message, 'https://metabox.io/my-account/', 'https://metabox.io/pricing/' ) );
165
 
166
+ add_settings_error( '', 'mb-invalid', $message );
 
 
167
  }
168
 
169
+ $option['status'] = $status;
 
 
 
 
 
170
 
171
+ $admin_notices_hook = is_multisite() ? 'network_admin_notices' : 'admin_notices';
172
+ add_action( $admin_notices_hook, 'settings_errors' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
+ $this->option->update( $option );
 
 
 
 
 
 
 
 
 
 
 
175
  }
176
  }
js/notification.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ( function( $, document, i18n ) {
2
+ 'use strict';
3
+
4
+ function dismissNotification() {
5
+ $( '#meta-box-notification' ).on( 'click', '.notice-dismiss', function( event ) {
6
+ event.preventDefault();
7
+
8
+ $.post( ajaxurl, {
9
+ action: 'mb_dismiss_notification',
10
+ nonce: MBNotification.nonce
11
+ } );
12
+ } );
13
+ }
14
+
15
+ $( document ).on( 'ready', dismissNotification );
16
+ } )( jQuery, document, MBNotification );
js/wysiwyg.js CHANGED
@@ -26,6 +26,11 @@
26
  if ( window.tinymce ) {
27
  var editor = new tinymce.Editor(id, settings.tinymce, tinymce.EditorManager);
28
  editor.render();
 
 
 
 
 
29
  }
30
 
31
  // Quick tags
26
  if ( window.tinymce ) {
27
  var editor = new tinymce.Editor(id, settings.tinymce, tinymce.EditorManager);
28
  editor.render();
29
+
30
+ editor.on( 'keyup change', function() {
31
+ editor.save();
32
+ $this.trigger( 'change' );
33
+ } );
34
  }
35
 
36
  // Quick tags
meta-box.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: Meta Box
4
  * Plugin URI: https://metabox.io
5
  * Description: Create custom meta boxes and custom fields in WordPress.
6
- * Version: 5.0.1
7
  * Author: MetaBox.io
8
  * Author URI: https://metabox.io
9
  * License: GPL2+
3
  * Plugin Name: Meta Box
4
  * Plugin URI: https://metabox.io
5
  * Description: Create custom meta boxes and custom fields in WordPress.
6
+ * Version: 5.1.0
7
  * Author: MetaBox.io
8
  * Author URI: https://metabox.io
9
  * License: GPL2+
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: meta-box, custom fields, custom field, meta, meta-boxes, admin, advanced,
5
  Requires at least: 4.3
6
  Requires PHP: 5.3
7
  Tested up to: 5.2.2
8
- Stable tag: 5.0.1
9
  License: GPLv2 or later
10
 
11
  Meta Box plugin is a powerful, professional developer toolkit to create custom meta boxes and custom fields for WordPress.
@@ -102,6 +102,7 @@ You'll have ultimate control to add whatever meta box and custom fields in WordP
102
 
103
  #### Premium Extensions
104
 
 
105
  - [Meta Box Builder](https://metabox.io/plugins/meta-box-builder/): Create custom meta boxes and custom fields in WordPress using a user-friendly drag-and-drop interface.
106
  - [Meta Box Group](https://metabox.io/plugins/meta-box-group/): Create repeatable groups of WordPress custom fields for better appearance and structure.
107
  - [MB Settings Page](https://metabox.io/plugins/mb-settings-page/): Create settings pages for themes, plugins or websites with beautiful syntax.
@@ -166,6 +167,22 @@ To getting started with the plugin, please read the [Quick Start Guide](https://
166
 
167
  == Changelog ==
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  = 5.0.1 - 2019-07-25 =
170
 
171
  **Fixed**
5
  Requires at least: 4.3
6
  Requires PHP: 5.3
7
  Tested up to: 5.2.2
8
+ Stable tag: 5.1.0
9
  License: GPLv2 or later
10
 
11
  Meta Box plugin is a powerful, professional developer toolkit to create custom meta boxes and custom fields for WordPress.
102
 
103
  #### Premium Extensions
104
 
105
+ - [MB Blocks](https://metabox.io/plugins/mb-blocks/): Create custom Gutenberg blocks with PHP, using the same syntax in Meta Box.
106
  - [Meta Box Builder](https://metabox.io/plugins/meta-box-builder/): Create custom meta boxes and custom fields in WordPress using a user-friendly drag-and-drop interface.
107
  - [Meta Box Group](https://metabox.io/plugins/meta-box-group/): Create repeatable groups of WordPress custom fields for better appearance and structure.
108
  - [MB Settings Page](https://metabox.io/plugins/mb-settings-page/): Create settings pages for themes, plugins or websites with beautiful syntax.
167
 
168
  == Changelog ==
169
 
170
+ = 5.1.0 - 2019-08-19 =
171
+
172
+ **Fixed**
173
+
174
+ - Fatal error with `RWMB_About::redirect()`
175
+ - Ensure change event fires when editors change
176
+ - Fix `rwmb_{$field_id}_choice_label` not working for cloneable fields
177
+ - Fix missing dependency (underscore) for datepicker JS
178
+ - Fix unindex notice for key_value field
179
+ - Fix alignment for video field
180
+
181
+ **Changed**
182
+
183
+ - Update notification sytem
184
+ - Improve sanitization for fields. See [documentation](https://docs.metabox.io/sanitization/) for details.
185
+
186
  = 5.0.1 - 2019-07-25 =
187
 
188
  **Fixed**