Unyson - Version 2.2.8

Version Description

  • Fixed #453
  • Improved option type multi-picker html render #442
  • Option type rgba-color-picker optimizations #442
  • fw_resize() improvements #447
  • Fixed #445, #161, #484, #456
  • Added the possibility to prevent box auto-close #466
  • Fixed the _get_value_from_input() method in some option types #275
  • Added the limit parameter for option type addable-popup #478
  • Fixed popup position in IE #483
  • Created fw_post_options_update action
  • Improved post save: Options are saved in revision and autosave. Restore from revision works.
Download this release

Release Info

Developer Unyson
Plugin Icon 128x128 Unyson
Version 2.2.8
Comparing to
See all releases

Code changes from version 2.2.7 to 2.2.8

Files changed (26) hide show
  1. framework/core/components/backend.php +168 -33
  2. framework/helpers/class-fw-resize.php +19 -3
  3. framework/helpers/class-fw-wp-meta.php +8 -5
  4. framework/helpers/class-fw-wp-option.php +5 -5
  5. framework/helpers/database.php +34 -0
  6. framework/helpers/general.php +12 -8
  7. framework/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php +8 -0
  8. framework/includes/option-types/addable-popup/static/js/addable-popup.js +14 -3
  9. framework/includes/option-types/icon/class-fw-option-type-icon.php +6 -2
  10. framework/includes/option-types/image-picker/class-fw-option-type-image-picker.php +4 -0
  11. framework/includes/option-types/multi-picker/class-fw-option-type-multi-picker.php +57 -12
  12. framework/includes/option-types/multi-picker/static/js/multi-picker.js +17 -0
  13. framework/includes/option-types/multi-select/class-fw-option-type-multi-select.php +419 -351
  14. framework/includes/option-types/multi-upload/views/images-only.php +11 -11
  15. framework/includes/option-types/popup/class-fw-option-type-popup.php +7 -4
  16. framework/includes/option-types/radio-text/class-fw-option-type-radio-text.php +4 -0
  17. framework/includes/option-types/rgba-color-picker/class-fw-option-type-rgba-color-picker.php +5 -3
  18. framework/includes/option-types/rgba-color-picker/static/js/scripts.js +129 -93
  19. framework/includes/option-types/switch/class-fw-option-type-switch.php +0 -1
  20. framework/includes/option-types/upload/class-fw-option-type-upload.php +1 -2
  21. framework/includes/option-types/upload/views/images-only.php +8 -7
  22. framework/manifest.php +1 -1
  23. framework/static/css/fw.css +1 -0
  24. framework/static/js/backend-options.js +1 -1
  25. readme.txt +15 -2
  26. unyson.php +1 -1
framework/core/components/backend.php CHANGED
@@ -6,6 +6,7 @@
6
  * Backend functionality
7
  */
8
  final class _FW_Component_Backend {
 
9
  /** @var callable */
10
  private $print_meta_box_content_callback;
11
 
@@ -128,10 +129,14 @@ final class _FW_Component_Backend {
128
  private function add_actions() {
129
  add_action( 'admin_menu', array( $this, '_action_admin_menu' ) );
130
  add_action( 'add_meta_boxes', array( $this, '_action_create_post_meta_boxes' ), 10, 2 );
131
- add_action( 'save_post', array( $this, '_action_save_post' ), 10, 2 );
132
  add_action( 'init', array( $this, '_action_init' ), 20 );
133
  add_action( 'admin_enqueue_scripts', array( $this, '_action_admin_enqueue_scripts' ), 8 );
134
 
 
 
 
 
 
135
  // render and submit options from javascript
136
  {
137
  add_action( 'wp_ajax_fw_backend_options_render', array( $this, '_action_ajax_options_render' ) );
@@ -592,58 +597,183 @@ final class _FW_Component_Backend {
592
  }
593
 
594
  /**
595
- * @param int $post_id
596
- * @param WP_Post $post
 
 
597
  */
598
- public function _action_save_post( $post_id, $post ) {
599
- if ( ! fw_is_real_post_save( $post_id )
600
- && ! wp_is_post_revision( $post_id )
601
- && ! wp_is_post_autosave( $post_id )
602
- ) {
603
- return;
604
- }
605
-
606
- if ( wp_is_post_autosave( $post_id ) ) {
607
- $original_id = wp_is_post_autosave( $post_id );
608
- $original_post = get_post( $original_id );
609
- } else if ( wp_is_post_revision( $post_id ) ) {
610
- $original_id = wp_is_post_revision( $post_id );
611
- $original_post = get_post( $original_id );
612
- } else {
613
- $original_id = $post_id;
614
- $original_post = $post;
615
- }
616
-
617
- $old_values = (array) fw_get_db_post_option( $original_id );
618
-
619
  $handled_values = array();
620
- $all_options = fw_extract_only_options(fw()->theme->get_post_options($original_post->post_type));
621
- $options_values = fw_get_options_values_from_input( fw()->theme->get_post_options($original_post->post_type) );
622
 
623
- foreach ($all_options as $option_id => $option) {
 
 
 
624
  if (
625
- isset($option['option_handler']) &&
 
626
  $option['option_handler'] instanceof FW_Option_Handler
627
  ) {
628
-
629
  /*
630
  * if the option has a custom option_handler
631
  * the saving is delegated to the handler,
632
  * so it does not go to the post_meta
633
  */
634
- $option['option_handler']->save_option_value($option_id, $option, $options_values[$option_id]);
635
 
636
- $handled_values[$option_id] = $options_values[$option_id];
637
  }
638
  }
639
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
  fw_set_db_post_option(
641
  $post_id,
642
  null,
643
- array_diff_key($options_values, $handled_values) //unset $handled_values
644
  );
 
645
 
646
- do_action( 'fw_save_post_options', $post_id, $post, $old_values );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
647
  }
648
 
649
  /**
@@ -732,6 +862,11 @@ final class _FW_Component_Backend {
732
  return; // this is not real term form submit, abort save
733
  }
734
 
 
 
 
 
 
735
  $old_values = (array) fw_get_db_term_option( $term_id, $taxonomy );
736
 
737
  fw_set_db_term_option(
6
  * Backend functionality
7
  */
8
  final class _FW_Component_Backend {
9
+
10
  /** @var callable */
11
  private $print_meta_box_content_callback;
12
 
129
  private function add_actions() {
130
  add_action( 'admin_menu', array( $this, '_action_admin_menu' ) );
131
  add_action( 'add_meta_boxes', array( $this, '_action_create_post_meta_boxes' ), 10, 2 );
 
132
  add_action( 'init', array( $this, '_action_init' ), 20 );
133
  add_action( 'admin_enqueue_scripts', array( $this, '_action_admin_enqueue_scripts' ), 8 );
134
 
135
+ add_action( 'save_post', array( $this, '_action_save_post' ), 7, 3 );
136
+ add_action( 'wp_restore_post_revision', array( $this, '_action_restore_post_revision' ), 10, 2 );
137
+ add_action( '_wp_put_post_revision', array( $this, '_action__wp_put_post_revision' ) );
138
+ add_action( 'wp_creating_autosave', array( $this, '_action_trigger_wp_create_autosave') );
139
+
140
  // render and submit options from javascript
141
  {
142
  add_action( 'wp_ajax_fw_backend_options_render', array( $this, '_action_ajax_options_render' ) );
597
  }
598
 
599
  /**
600
+ * Experimental custom options save
601
+ * @param array $options
602
+ * @param array $values
603
+ * @return array
604
  */
605
+ private function process_options_handlers($options, $values)
606
+ {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  $handled_values = array();
 
 
608
 
609
+ foreach (
610
+ fw_extract_only_options($options)
611
+ as $option_id => $option
612
+ ) {
613
  if (
614
+ isset($option['option_handler'])
615
+ &&
616
  $option['option_handler'] instanceof FW_Option_Handler
617
  ) {
 
618
  /*
619
  * if the option has a custom option_handler
620
  * the saving is delegated to the handler,
621
  * so it does not go to the post_meta
622
  */
623
+ $option['option_handler']->save_option_value($option_id, $option, $values[$option_id]);
624
 
625
+ $handled_values[$option_id] = true;
626
  }
627
  }
628
 
629
+ return $handled_values;
630
+ }
631
+
632
+ /**
633
+ * @param int $post_id
634
+ * @param WP_Post $post
635
+ * @param bool $update
636
+ */
637
+ public function _action_save_post( $post_id, $post, $update ) {
638
+ if (intval(FW_Request::POST('post_ID')) == $post_id) {
639
+ /**
640
+ * This happens on regular post form submit
641
+ * All data from $_POST belongs this $post
642
+ * so we save them in its post meta
643
+ */
644
+
645
+ static $post_options_save_happened = false;
646
+ if ($post_options_save_happened) {
647
+ /**
648
+ * Prevent multiple options save for same post
649
+ * It can happen from a recursion or wp_update_post() for same post id
650
+ */
651
+ return;
652
+ } else {
653
+ $post_options_save_happened = true;
654
+ }
655
+
656
+ $old_values = (array)fw_get_db_post_option($post_id);
657
+ $current_values = fw_get_options_values_from_input(
658
+ fw()->theme->get_post_options($post->post_type)
659
+ );
660
+
661
+ fw_set_db_post_option(
662
+ $post_id,
663
+ null,
664
+ array_diff_key( // remove handled values
665
+ $current_values,
666
+ $this->process_options_handlers(
667
+ fw()->theme->get_post_options($post->post_type),
668
+ $current_values
669
+ )
670
+ )
671
+ );
672
+
673
+ /**
674
+ * @deprecated
675
+ * Use the 'fw_post_options_update' action
676
+ */
677
+ do_action( 'fw_save_post_options', $post_id, $post, $old_values );
678
+ } elseif ($original_post_id = wp_is_post_revision( $post_id )) {
679
+ /**
680
+ * Do nothing, the
681
+ * - '_wp_put_post_revision'
682
+ * - 'wp_restore_post_revision'
683
+ * - 'wp_creating_autosave'
684
+ * actions will handle this
685
+ */
686
+ } elseif ($original_post_id = wp_is_post_autosave( $post_id )) {
687
+ // fixme: I don't know how to test this. The execution never entered here
688
+ FW_Flash_Messages::add(fw_rand_md5(), 'Unhandled auto-save');
689
+ } else {
690
+ /**
691
+ * This happens on:
692
+ * - post add (auto-draft): do nothing
693
+ * - revision restore: do nothing, that is handled by the 'wp_restore_post_revision' action
694
+ */
695
+ }
696
+ }
697
+
698
+ /**
699
+ * @param array $autosave
700
+ *
701
+ * @internal
702
+ **/
703
+ public function _action_trigger_wp_create_autosave( $autosave ) {
704
+ add_action( 'save_post', array( $this, '_action_update_autosave_options' ), 10, 2 );
705
+ }
706
+
707
+ /**
708
+ * Happens on post Preview
709
+ *
710
+ * @param int $post_id
711
+ * @param WP_Post $post
712
+ *
713
+ * @internal
714
+ **/
715
+ public function _action_update_autosave_options( $post_id, $post ) {
716
+ remove_action( 'save_post', array( $this, '_action_update_autosave_options' ) );
717
+ remove_action( 'save_post', array( $this, '_action_save_post' ) );
718
+
719
+ $parent = get_post($post->post_parent);
720
+
721
+ if ( ! $parent instanceof WP_Post ) {
722
+ return;
723
+ }
724
+
725
+ $current_values = fw_get_options_values_from_input(
726
+ fw()->theme->get_post_options($parent->post_type)
727
+ );
728
+
729
+ fw_set_db_post_option(
730
+ $post->ID,
731
+ null,
732
+ array_diff_key( // remove handled values
733
+ $current_values,
734
+ $this->process_options_handlers(
735
+ fw()->theme->get_post_options($parent->post_type),
736
+ $current_values
737
+ )
738
+ )
739
+ );
740
+
741
+ add_action( 'save_post', array( $this, '_action_save_post' ), 7, 3 );
742
+ }
743
+
744
+ /**
745
+ * @param $post_id
746
+ * @param $revision_id
747
+ */
748
+ public function _action_restore_post_revision($post_id, $revision_id)
749
+ {
750
+ /**
751
+ * Copy options meta from revision to post
752
+ */
753
  fw_set_db_post_option(
754
  $post_id,
755
  null,
756
+ (array)fw_get_db_post_option($revision_id, null, array())
757
  );
758
+ }
759
 
760
+ /**
761
+ * @param $revision_id
762
+ */
763
+ public function _action__wp_put_post_revision($revision_id)
764
+ {
765
+ /**
766
+ * Copy options meta from post to revision
767
+ */
768
+ fw_set_db_post_option(
769
+ $revision_id,
770
+ null,
771
+ (array)fw_get_db_post_option(
772
+ wp_is_post_revision($revision_id),
773
+ null,
774
+ array()
775
+ )
776
+ );
777
  }
778
 
779
  /**
862
  return; // this is not real term form submit, abort save
863
  }
864
 
865
+ if (intval(FW_Request::POST('tag_ID')) != $term_id) {
866
+ // the $_POST values belongs to another term, do not save them into this one
867
+ return;
868
+ }
869
+
870
  $old_values = (array) fw_get_db_term_option( $term_id, $taxonomy );
871
 
872
  fw_set_db_term_option(
framework/helpers/class-fw-resize.php CHANGED
@@ -57,7 +57,7 @@ if ( ! class_exists( 'FW_Resize' ) ) {
57
  }
58
  }
59
 
60
- public function process( $attachment, $width, $height, $crop = false ) {
61
 
62
  $attachment_info = $this->get_attachment_info( $attachment );
63
 
@@ -73,6 +73,22 @@ if ( ! class_exists( 'FW_Resize' ) ) {
73
  $name = wp_basename( $file_path, ".$ext" );
74
  $name = preg_replace( '/(.+)(\-\d+x\d+)$/', '$1', $name );
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  // Suffix applied to filename
77
  $suffix = "{$width}x{$height}";
78
 
@@ -81,7 +97,7 @@ if ( ! class_exists( 'FW_Resize' ) ) {
81
  // No need to resize & create a new image if it already exists
82
  if ( ! file_exists( $destination_file_name ) ) {
83
  //Image Resize
84
- $editor = wp_get_image_editor( $file_path );
85
 
86
  if ( is_wp_error( $editor ) ) {
87
  return new WP_Error( 'wp_image_editor', 'WP Image editor can\'t resize this attachment', $attachment );
@@ -137,7 +153,7 @@ if ( ! class_exists( 'FW_Resize' ) ) {
137
  }
138
 
139
  if ( ! function_exists( 'fw_resize' ) ) {
140
- function fw_resize( $url, $width, $height, $crop = false ) {
141
  $fw_resize = FW_Resize::getInstance();
142
  $response = $fw_resize->process( $url, $width, $height, $crop );
143
 
57
  }
58
  }
59
 
60
+ public function process( $attachment, $width = false, $height = false, $crop = false ) {
61
 
62
  $attachment_info = $this->get_attachment_info( $attachment );
63
 
73
  $name = wp_basename( $file_path, ".$ext" );
74
  $name = preg_replace( '/(.+)(\-\d+x\d+)$/', '$1', $name );
75
 
76
+ {
77
+ if ( ! $width || ! $height ) {
78
+ $editor = wp_get_image_editor( $file_path );
79
+ $size = $editor->get_size();
80
+ $orig_width = $size['width'];
81
+ $orig_height = $size['height'];
82
+ if ( ! $height && $width ) {
83
+ $height = round( ( $orig_height * $width ) / $orig_width );
84
+ } elseif ( ! $width && $height ) {
85
+ $width = round( ( $orig_width * $height ) / $orig_height );
86
+ } else {
87
+ return $attachment;
88
+ }
89
+ }
90
+ }
91
+
92
  // Suffix applied to filename
93
  $suffix = "{$width}x{$height}";
94
 
97
  // No need to resize & create a new image if it already exists
98
  if ( ! file_exists( $destination_file_name ) ) {
99
  //Image Resize
100
+ $editor = (isset($editor)) ? $editor : wp_get_image_editor( $file_path );
101
 
102
  if ( is_wp_error( $editor ) ) {
103
  return new WP_Error( 'wp_image_editor', 'WP Image editor can\'t resize this attachment', $attachment );
153
  }
154
 
155
  if ( ! function_exists( 'fw_resize' ) ) {
156
+ function fw_resize( $url, $width = false, $height = false, $crop = false ) {
157
  $fw_resize = FW_Resize::getInstance();
158
  $response = $fw_resize->process( $url, $width, $height, $crop );
159
 
framework/helpers/class-fw-wp-meta.php CHANGED
@@ -33,10 +33,13 @@ class FW_WP_Meta {
33
  $key = array_shift( $multi_key );
34
  $multi_key = implode( '/', $multi_key );
35
 
 
36
  // Make sure meta is added to the post, not a revision.
 
37
  if ( $meta_type === 'post' && $the_post = wp_is_post_revision( $object_id ) ) {
38
  $object_id = $the_post;
39
  }
 
40
 
41
  $cache_key = self::$cache_key . '/' . $meta_type . '/' . $object_id . '/' . $key;
42
 
@@ -101,10 +104,10 @@ class FW_WP_Meta {
101
  FW_Cache::set( $cache_key, $values );
102
  }
103
 
104
- if ( empty( $multi_key ) ) {
105
- return $values[ $get_original_value ? 'original' : 'prepared' ];
106
- } else {
107
- return fw_akg( $multi_key, $values[ $get_original_value ? 'original' : 'prepared' ], $default_value );
108
- }
109
  }
110
  }
33
  $key = array_shift( $multi_key );
34
  $multi_key = implode( '/', $multi_key );
35
 
36
+ /*
37
  // Make sure meta is added to the post, not a revision.
38
+ // fixme: why make sure? but I want to set post meta for a revision, how to do that?
39
  if ( $meta_type === 'post' && $the_post = wp_is_post_revision( $object_id ) ) {
40
  $object_id = $the_post;
41
  }
42
+ */
43
 
44
  $cache_key = self::$cache_key . '/' . $meta_type . '/' . $object_id . '/' . $key;
45
 
104
  FW_Cache::set( $cache_key, $values );
105
  }
106
 
107
+ return fw_akg(
108
+ ($get_original_value ? 'original' : 'prepared') . (empty($multi_key) ? '' : '/'. $multi_key),
109
+ $values,
110
+ $default_value
111
+ );
112
  }
113
  }
framework/helpers/class-fw-wp-option.php CHANGED
@@ -42,11 +42,11 @@ class FW_WP_Option
42
  FW_Cache::set($cache_key, $values);
43
  }
44
 
45
- if (empty($specific_multi_key)) {
46
- return $values[$get_original_value ? 'original' : 'prepared'];
47
- } else {
48
- return fw_akg($specific_multi_key, $values[$get_original_value ? 'original' : 'prepared'], $default_value);
49
- }
50
  }
51
 
52
  /**
42
  FW_Cache::set($cache_key, $values);
43
  }
44
 
45
+ return fw_akg(
46
+ ($get_original_value ? 'original' : 'prepared') . (empty($specific_multi_key) ? '' : '/'. $specific_multi_key),
47
+ $values,
48
+ $default_value
49
+ );
50
  }
51
 
52
  /**
framework/helpers/database.php CHANGED
@@ -105,6 +105,8 @@
105
  * @param $value
106
  */
107
  function fw_set_db_post_option( $post_id = null, $option_id = null, $value ) {
 
 
108
  if ( ! $post_id ) {
109
  /** @var WP_Post $post */
110
  global $post;
@@ -116,11 +118,43 @@
116
  }
117
  }
118
 
 
 
 
119
  $option_id = 'fw_options' . ( $option_id !== null ? '/' . $option_id : '' );
120
 
121
  FW_WP_Meta::set( 'post', $post_id, $option_id, $value );
122
 
123
  fw()->backend->_sync_post_separate_meta($post_id);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  }
125
  }
126
 
105
  * @param $value
106
  */
107
  function fw_set_db_post_option( $post_id = null, $option_id = null, $value ) {
108
+ $post_id = intval($post_id);
109
+
110
  if ( ! $post_id ) {
111
  /** @var WP_Post $post */
112
  global $post;
118
  }
119
  }
120
 
121
+ $sub_keys = explode('/', $option_id);
122
+ $base_key = array_shift($sub_keys);
123
+
124
  $option_id = 'fw_options' . ( $option_id !== null ? '/' . $option_id : '' );
125
 
126
  FW_WP_Meta::set( 'post', $post_id, $option_id, $value );
127
 
128
  fw()->backend->_sync_post_separate_meta($post_id);
129
+
130
+ /**
131
+ * @since 2.2.8
132
+ */
133
+ do_action('fw_post_options_update',
134
+ $post_id,
135
+ /**
136
+ * Option id
137
+ * First level multi-key
138
+ *
139
+ * For e.g.
140
+ *
141
+ * if $option_id is 'hello/world/7'
142
+ * this will be 'hello'
143
+ */
144
+ $base_key,
145
+ /**
146
+ * The remaining sub-keys
147
+ *
148
+ * For e.g.
149
+ *
150
+ * if $option_id is 'hello/world/7'
151
+ * $option_id_keys will be array('world', '7')
152
+ *
153
+ * if $option_id is 'hello'
154
+ * $option_id_keys will be array()
155
+ */
156
+ $sub_keys
157
+ );
158
  }
159
  }
160
 
framework/helpers/general.php CHANGED
@@ -193,15 +193,16 @@ function fw_print($value) {
193
  div.fw_print_r {
194
  max-height: 500px;
195
  overflow-y: scroll;
196
- background: #111;
197
  margin: 10px 30px;
198
  padding: 0;
199
  border: 1px solid #F5F5F5;
 
200
  }
201
 
202
  div.fw_print_r pre {
203
- color: #47EE47;
204
- background: #111;
205
  text-shadow: 1px 1px 0 #000;
206
  font-family: Consolas, monospace;
207
  font-size: 12px;
@@ -213,9 +214,10 @@ function fw_print($value) {
213
  }
214
 
215
  div.fw_print_r_group {
216
- background: #111;
217
  margin: 10px 30px;
218
  padding: 1px;
 
219
  }
220
  div.fw_print_r_group div.fw_print_r {
221
  margin: 9px;
@@ -786,14 +788,16 @@ function fw_prepare_option_value($value) {
786
  * Used to check if current post save is a regular "Save" button press
787
  * not a revision, auto-save or something else
788
  *
789
- * todo: make sure it is correct to ignore revision and auto-save.
790
- * todo: maybe we use wrong (too simplified) the 'save_post' action, maybe we should do something like this http://bit.ly/1xkbmml ?
791
- *
792
  * @param $post_id
793
  * @return bool
 
 
 
 
 
 
794
  */
795
  function fw_is_real_post_save($post_id) {
796
-
797
  return !(
798
  wp_is_post_revision($post_id)
799
  || wp_is_post_autosave($post_id)
193
  div.fw_print_r {
194
  max-height: 500px;
195
  overflow-y: scroll;
196
+ background: #23282d;
197
  margin: 10px 30px;
198
  padding: 0;
199
  border: 1px solid #F5F5F5;
200
+ border-radius: 3px;
201
  }
202
 
203
  div.fw_print_r pre {
204
+ color: #78FF5B;
205
+ background: #23282d;
206
  text-shadow: 1px 1px 0 #000;
207
  font-family: Consolas, monospace;
208
  font-size: 12px;
214
  }
215
 
216
  div.fw_print_r_group {
217
+ background: #f1f1f1;
218
  margin: 10px 30px;
219
  padding: 1px;
220
+ border-radius: 5px;
221
  }
222
  div.fw_print_r_group div.fw_print_r {
223
  margin: 9px;
788
  * Used to check if current post save is a regular "Save" button press
789
  * not a revision, auto-save or something else
790
  *
 
 
 
791
  * @param $post_id
792
  * @return bool
793
+ *
794
+ * @deprecated
795
+ * save_post action happens also happens on Preview, Revision, Auto-save Restore, ...
796
+ * the verifications in this function simplifies too much the save process,
797
+ * the developers should study and understand better how it works
798
+ * and handle different save cases in their scripts using wp functions
799
  */
800
  function fw_is_real_post_save($post_id) {
 
801
  return !(
802
  wp_is_post_revision($post_id)
803
  || wp_is_post_autosave($post_id)
framework/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php CHANGED
@@ -50,6 +50,7 @@ class FW_Option_Type_Addable_Popup extends FW_Option_Type
50
  'options' => $this->transform_options($option['popup-options']),
51
  'template' => $option['template'],
52
  'size' => $option['size'],
 
53
  ));
54
 
55
  $sortable_image = fw_get_framework_directory_uri('/static/img/sort-vertically.png');
@@ -105,8 +106,14 @@ class FW_Option_Type_Addable_Popup extends FW_Option_Type
105
  return $option['value'];
106
  }
107
 
 
 
108
  $values = array_map('json_decode', $input_value, array_fill(0, count($input_value), true));
109
 
 
 
 
 
110
  return $values;
111
  }
112
 
@@ -133,6 +140,7 @@ class FW_Option_Type_Addable_Popup extends FW_Option_Type
133
  ),
134
  'template' => '',
135
  'popup-title' => null,
 
136
  'size' => 'small' // small, medium, large
137
  );
138
  }
50
  'options' => $this->transform_options($option['popup-options']),
51
  'template' => $option['template'],
52
  'size' => $option['size'],
53
+ 'limit' => $option['limit']
54
  ));
55
 
56
  $sortable_image = fw_get_framework_directory_uri('/static/img/sort-vertically.png');
106
  return $option['value'];
107
  }
108
 
109
+ $option['limit'] = intval($option['limit']);
110
+
111
  $values = array_map('json_decode', $input_value, array_fill(0, count($input_value), true));
112
 
113
+ if($option['limit']){
114
+ $values= array_slice($values, 0 , $option['limit']);
115
+ }
116
+
117
  return $values;
118
  }
119
 
140
  ),
141
  'template' => '',
142
  'popup-title' => null,
143
+ 'limit' => 0,
144
  'size' => 'small' // small, medium, large
145
  );
146
  }
framework/includes/option-types/addable-popup/static/js/addable-popup.js CHANGED
@@ -23,6 +23,10 @@
23
  removeDefaultItem: function () {
24
  nodes.$itemsWrapper.find('.item.default').remove();
25
  },
 
 
 
 
26
  toogleItemsWrapper: function () {
27
 
28
  if (utils.countItems() === 0) {
@@ -31,9 +35,16 @@
31
  nodes.$itemsWrapper.show();
32
  }
33
  },
 
 
 
 
 
 
 
34
  init: function () {
35
  utils.initItemsTemplates();
36
- utils.toogleItemsWrapper();
37
  utils.removeDefaultItem();
38
  utils.initSortable();
39
  },
@@ -101,7 +112,7 @@
101
  e.stopPropagation();
102
  e.preventDefault();
103
  $(this).closest('.item').remove();
104
- utils.toogleItemsWrapper();
105
  });
106
 
107
  nodes.$itemsWrapper.on('click', '.item', function (e) {
@@ -129,7 +140,7 @@
129
  utils.modal.on('change:values', function (modal, values) {
130
  if (!modal.get('edit')) {
131
  utils.addNewItem(values);
132
- utils.toogleItemsWrapper();
133
  } else {
134
  utils.editItem(utils.modal.get('itemRef'), values);
135
  }
23
  removeDefaultItem: function () {
24
  nodes.$itemsWrapper.find('.item.default').remove();
25
  },
26
+ toogleNodes : function(){
27
+ utils.toogleItemsWrapper();
28
+ utils.toogleAddButton();
29
+ },
30
  toogleItemsWrapper: function () {
31
 
32
  if (utils.countItems() === 0) {
35
  nodes.$itemsWrapper.show();
36
  }
37
  },
38
+ toogleAddButton: function(){
39
+ if(data.limit !== 0 ){
40
+ (utils.countItems() >= data.limit ) ?
41
+ nodes.$addButton.hide() :
42
+ nodes.$addButton.show();
43
+ }
44
+ },
45
  init: function () {
46
  utils.initItemsTemplates();
47
+ utils.toogleNodes();
48
  utils.removeDefaultItem();
49
  utils.initSortable();
50
  },
112
  e.stopPropagation();
113
  e.preventDefault();
114
  $(this).closest('.item').remove();
115
+ utils.toogleNodes();
116
  });
117
 
118
  nodes.$itemsWrapper.on('click', '.item', function (e) {
140
  utils.modal.on('change:values', function (modal, values) {
141
  if (!modal.get('edit')) {
142
  utils.addNewItem(values);
143
+ utils.toogleNodes();
144
  } else {
145
  utils.editItem(utils.modal.get('itemRef'), values);
146
  }
framework/includes/option-types/icon/class-fw-option-type-icon.php CHANGED
@@ -92,6 +92,10 @@ class FW_Option_Type_Icon extends FW_Option_Type
92
  */
93
  protected function _get_value_from_input($option, $input_value)
94
  {
 
 
 
 
95
  $sets = $this->get_sets();
96
 
97
  if (isset($sets[ $option['set'] ])) {
@@ -102,7 +106,7 @@ class FW_Option_Type_Icon extends FW_Option_Type
102
 
103
  unset($sets);
104
 
105
- if (is_null($input_value) || !isset($set['icons'][ $input_value ])) {
106
  $input_value = $option['value'];
107
  }
108
 
@@ -141,7 +145,7 @@ class FW_Option_Type_Icon extends FW_Option_Type
141
  private function generate_unknown_set($icon)
142
  {
143
  return array(
144
- 'font-style-src' => 'data:text/css;charset=utf-8;base64,LyoqLw==',
145
  'container-class' => '',
146
  'groups' => array(
147
  'unknown' => __('Unknown Set', 'fw'),
92
  */
93
  protected function _get_value_from_input($option, $input_value)
94
  {
95
+ if (is_null($input_value)) {
96
+ return $option['value'];
97
+ }
98
+
99
  $sets = $this->get_sets();
100
 
101
  if (isset($sets[ $option['set'] ])) {
106
 
107
  unset($sets);
108
 
109
+ if (!isset($set['icons'][ $input_value ])) {
110
  $input_value = $option['value'];
111
  }
112
 
145
  private function generate_unknown_set($icon)
146
  {
147
  return array(
148
+ 'font-style-src' => 'data:text/css;charset=utf-8;base64,LyoqLw==', // fixme: WP will transform this to `http://domain.com/data:text/css;...`
149
  'container-class' => '',
150
  'groups' => array(
151
  'unknown' => __('Unknown Set', 'fw'),
framework/includes/option-types/image-picker/class-fw-option-type-image-picker.php CHANGED
@@ -171,6 +171,10 @@ class Fw_Option_Type_Image_Picker extends FW_Option_Type
171
  */
172
  protected function _get_value_from_input($option, $input_value)
173
  {
 
 
 
 
174
  if (!isset($option['choices'][$input_value])) {
175
  if (
176
  empty($option['choices']) ||
171
  */
172
  protected function _get_value_from_input($option, $input_value)
173
  {
174
+ if (is_null($input_value)) {
175
+ return $option['value'];
176
+ }
177
+
178
  if (!isset($option['choices'][$input_value])) {
179
  if (
180
  empty($option['choices']) ||
framework/includes/option-types/multi-picker/class-fw-option-type-multi-picker.php CHANGED
@@ -74,6 +74,45 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
74
  $option['attr']['class'] .= ' fw-option-type-multi-picker-without-borders';
75
  }
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  return '<div ' . fw_attr_to_html($option['attr']) . '>' .
78
  fw()->backend->render_options($options_array, $data['value'], array(
79
  'id_prefix' => $data['id_prefix'] . $id . '-',
@@ -170,7 +209,7 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
170
  'type' => 'group',
171
  'attr' => array('class' => 'choice-group choice-' . $key),
172
  'options' => array(
173
- $key => array(
174
  'type' => 'multi',
175
  'attr' => array('class' => $show_borders),
176
  'label' => false,
@@ -209,10 +248,14 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
209
 
210
  $value = array();
211
 
212
- $value[$picker_key] = fw()->backend->option_type($picker_type)->get_value_from_input(
213
- $picker,
214
- isset($input_value[$picker_key]) ? $input_value[$picker_key] : null
215
- );
 
 
 
 
216
 
217
  // choices
218
  switch($picker_type) {
@@ -241,14 +284,16 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
241
  }
242
 
243
  foreach ($choices as $choice_id => $options) {
244
-
245
- foreach (fw_extract_only_options($options) as $option_id => $option) {
246
- $value[$choice_id][$option_id] = fw()->backend->option_type($option['type'])->get_value_from_input(
247
- $option,
248
- isset($input_value[$choice_id][$option_id]) ? $input_value[$choice_id][$option_id] : null
249
- );
 
 
 
250
  }
251
-
252
  }
253
 
254
  return $value;
74
  $option['attr']['class'] .= ' fw-option-type-multi-picker-without-borders';
75
  }
76
 
77
+ /**
78
+ * Leave only select choice options to be rendered in the browser
79
+ * the rest move to attr[data-options-template] to be rendered on choice change.
80
+ * This should improve page loading speed.
81
+ */
82
+ {
83
+ {
84
+ reset($option['picker']);
85
+ $picker_key = key($option['picker']);
86
+ $picker_type = $option['picker'][$picker_key]['type'];
87
+ $picker = $option['picker'][$picker_key];
88
+ $picker_value = fw()->backend->option_type($picker_type)->get_value_from_input(
89
+ $picker,
90
+ isset($data['value'][$picker_key]) ? $data['value'][$picker_key] : null
91
+ );
92
+ }
93
+
94
+ $skip_first = true;
95
+ foreach ($options_array as $group_id => &$group) {
96
+ if ($skip_first) {
97
+ // first is picker
98
+ $skip_first = false;
99
+ continue;
100
+ }
101
+
102
+ if ($group_id === $id .'-'. $picker_value) {
103
+ // skip selected choice options
104
+ continue;
105
+ }
106
+
107
+ $options_array[$group_id]['attr']['data-options-template'] = fw()->backend->render_options(
108
+ $options_array[$group_id]['options'], $data['value'], array(
109
+ 'id_prefix' => $data['id_prefix'] . $id . '-',
110
+ 'name_prefix' => $data['name_prefix'] . '[' . $id . ']',
111
+ ));
112
+ $options_array[$group_id]['options'] = array();
113
+ }
114
+ }
115
+
116
  return '<div ' . fw_attr_to_html($option['attr']) . '>' .
117
  fw()->backend->render_options($options_array, $data['value'], array(
118
  'id_prefix' => $data['id_prefix'] . $id . '-',
209
  'type' => 'group',
210
  'attr' => array('class' => 'choice-group choice-' . $key),
211
  'options' => array(
212
+ $key => array(
213
  'type' => 'multi',
214
  'attr' => array('class' => $show_borders),
215
  'label' => false,
248
 
249
  $value = array();
250
 
251
+ if (is_null($input_value) && isset($option['value'][$picker_key])) {
252
+ $value[$picker_key] = $option['value'][$picker_key];
253
+ } else {
254
+ $value[$picker_key] = fw()->backend->option_type($picker_type)->get_value_from_input(
255
+ $picker,
256
+ isset($input_value[$picker_key]) ? $input_value[$picker_key] : null
257
+ );
258
+ }
259
 
260
  // choices
261
  switch($picker_type) {
284
  }
285
 
286
  foreach ($choices as $choice_id => $options) {
287
+ if (is_null($input_value) && isset($option['value'][$choice_id])) {
288
+ $value[$choice_id] = $option['value'][$choice_id];
289
+ } else {
290
+ foreach (fw_extract_only_options($options) as $option_id => $option) {
291
+ $value[$choice_id][$option_id] = fw()->backend->option_type($option['type'])->get_value_from_input(
292
+ $option,
293
+ isset($input_value[$choice_id][$option_id]) ? $input_value[$choice_id][$option_id] : null
294
+ );
295
+ }
296
  }
 
297
  }
298
 
299
  return $value;
framework/includes/option-types/multi-picker/static/js/multi-picker.js CHANGED
@@ -7,6 +7,23 @@
7
  },
8
  chooseGroup = function(groupId) {
9
  var $choicesToReveal = elements.$choicesGroups.filter('.choice-' + groupId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  elements.$choicesGroups.removeClass('chosen');
11
  $choicesToReveal.addClass('chosen');
12
 
7
  },
8
  chooseGroup = function(groupId) {
9
  var $choicesToReveal = elements.$choicesGroups.filter('.choice-' + groupId);
10
+
11
+ /**
12
+ * The group options html was rendered in an attribute to make page load faster.
13
+ * Move the html from attribute in group and init options with js.
14
+ */
15
+ if ($choicesToReveal.attr('data-options-template')) {
16
+ $choicesToReveal.html(
17
+ $choicesToReveal.attr('data-options-template')
18
+ );
19
+
20
+ $choicesToReveal.removeAttr('data-options-template');
21
+
22
+ fwEvents.trigger('fw:options:init', {
23
+ $elements: $choicesToReveal
24
+ });
25
+ }
26
+
27
  elements.$choicesGroups.removeClass('chosen');
28
  $choicesToReveal.addClass('chosen');
29
 
framework/includes/option-types/multi-select/class-fw-option-type-multi-select.php CHANGED
@@ -2,169 +2,131 @@
2
  die( 'Forbidden' );
3
  }
4
 
5
- if (!class_exists('FW_Option_Type_Multi_Select')):
6
 
7
- /**
8
- * Select multiple choices from different sources: posts, taxonomies, users or custom array
9
- */
10
- class FW_Option_Type_Multi_Select extends FW_Option_Type
11
- {
12
  /**
13
- * @internal
14
  */
15
- protected function _get_defaults() {
16
- return array(
17
- 'value' => array(),
18
- /**
19
- * Available options: array, posts, taxonomy, users
20
- */
21
- 'population' => 'array',
22
- /**
23
- * Set post types, taxonomies, user roles to search for
24
- *
25
- * 'population' => 'posts'
26
- * 'source' => 'page',
27
- *
28
- * 'population' => 'taxonomy'
29
- * 'source' => 'category',
30
- *
31
- * 'population' => 'users'
32
- * 'source' => array( 'editor', 'subscriber', 'author' ),
33
- *
34
- * 'population' => 'array'
35
- * 'source' => '' // will populate with 'choices' array
36
- */
37
- 'source' => '',
38
- /**
39
- * An array with the available choices
40
- * Used only when 'population' => 'array'
41
- */
42
- 'choices' => array( /* 'value' => 'Title' */ ),
43
- /**
44
- * Set maximum items number that can be selected
45
- */
46
- 'limit' => 100,
47
- );
48
- }
 
 
 
 
 
 
 
 
 
49
 
50
- private $internal_options = array();
51
 
52
- public function get_type() {
53
- return 'multi-select';
54
- }
55
 
56
- /**
57
- * @internal
58
- */
59
- public function _init() {
60
- $this->internal_options = array(
61
- 'label' => false,
62
- 'type' => 'text',
63
- 'value' => '',
64
- );
65
- }
66
 
67
- /**
68
- * @internal
69
- */
70
- public static function _admin_action_get_ajax_response() {
71
  /**
72
- * @var WPDB $wpdb
73
  */
74
- global $wpdb;
75
-
76
- $type = FW_Request::POST('data/type');
77
- $names = json_decode( FW_Request::POST('data/names'), true );
78
- $title = FW_Request::POST('data/string');
79
-
80
- $items = array();
81
-
82
- switch ($type) {
83
- case 'posts':
84
- $items = $wpdb->get_results(
85
- call_user_func_array(
86
- array($wpdb, 'prepare'),
87
- array_merge(
88
- array(
89
- "SELECT ID val, post_title title " .
90
- "FROM $wpdb->posts " .
91
- "WHERE post_title LIKE %s " .
92
- "AND post_status = 'publish' " .
93
- "AND NULLIF(post_password, '') IS NULL " .
94
- "AND post_type IN ( ".
95
- implode(', ', array_fill(1, count($names), '%s')) .
96
- " ) " .
97
- "LIMIT 100",
98
- '%'. $wpdb->esc_like($title) .'%'
99
- ),
100
- /**
101
- * These strings may contain '%abc'
102
- * so we cannot use them directly in sql
103
- * because $wpdb->prepare() will return false
104
- * also you can test that with sprintf()
105
- */
106
- $names
107
- )
108
- )
109
- );
110
- break;
111
- case 'taxonomy':
112
- $items = $wpdb->get_results(
113
- call_user_func_array(
114
- array($wpdb, 'prepare'),
115
- array_merge(
116
- array(
117
- "SELECT terms.term_id val, terms.name title " .
118
- "FROM $wpdb->terms as terms, $wpdb->term_taxonomy as taxonomies " .
119
- "WHERE terms.name LIKE %s AND taxonomies.taxonomy IN ( ".
120
- implode(', ', array_fill(1, count($names), '%s')) .
121
- " ) " .
122
- "AND terms.term_id = taxonomies.term_id " .
123
- "AND taxonomies.term_id = taxonomies.term_taxonomy_id " .
124
- "LIMIT 100",
125
- '%'. $wpdb->esc_like($title) .'%'
126
- ),
127
- /**
128
- * These strings may contain '%abc'
129
- * so we cannot use them directly in sql
130
- * because $wpdb->prepare() will return false
131
- * also you can test that with sprintf()
132
- */
133
- $names
134
- )
135
- )
136
- );
137
- break;
138
- case 'users':
139
- if ( empty( $names ) ) {
140
  $items = $wpdb->get_results(
141
- $wpdb->prepare(
142
- "SELECT users.id val, users.user_nicename title " .
143
- "FROM $wpdb->users as users " .
144
- "WHERE users.user_nicename LIKE %s " .
145
- "LIMIT 100",
146
- '%'. $wpdb->esc_like($title) .'%'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  )
148
  );
149
- } else {
150
- $like_user_meta = array();
151
- foreach ($names as $name) {
152
- $like_user_meta[] = '%'. $wpdb->esc_like($name) .'%';
153
- }
154
-
155
  $items = $wpdb->get_results(
156
  call_user_func_array(
157
- array($wpdb, 'prepare'),
158
  array_merge(
159
  array(
160
- "SELECT users.id val, users.user_nicename title " .
161
- "FROM $wpdb->users as users, $wpdb->usermeta as usermeta " .
162
- "WHERE users.user_nicename LIKE %s AND usermeta.meta_key = 'wp_capabilities' " .
163
- "AND ( ".
164
- implode(' OR ', array_fill(1, count($like_user_meta), 'usermeta.meta_value LIKE %s')) .
165
  " ) " .
166
- "AND usermeta.user_id = users.ID",
167
- '%'. $wpdb->esc_like($title) .'%'
 
 
168
  ),
169
  /**
170
  * These strings may contain '%abc'
@@ -172,111 +134,276 @@ class FW_Option_Type_Multi_Select extends FW_Option_Type
172
  * because $wpdb->prepare() will return false
173
  * also you can test that with sprintf()
174
  */
175
- $like_user_meta
176
  )
177
  )
178
  );
179
- }
180
- break;
181
- }
182
-
183
- wp_send_json_success( $items );
184
- }
185
-
186
- /**
187
- * @internal
188
- */
189
- public function _get_backend_width_type() {
190
- return 'fixed';
191
- }
 
 
 
 
192
 
193
- /**
194
- * @internal
195
- */
196
- protected function _render( $id, $option, $data ) {
197
- $items = '';
198
- $population = 'array';
199
- $source = array();
200
-
201
- if ( isset( $option['population'] ) ) {
202
- switch ( $option['population'] ) {
203
- case 'array' :
204
- if ( isset( $option['choices'] ) && is_array( $option['choices'] ) ) {
205
- $items = $option['choices'];
 
 
 
 
 
 
 
 
 
 
 
 
206
  }
207
  break;
208
- case 'posts' :
209
- if ( isset( $option['source'] ) ) {
210
- /**
211
- * @var WPDB $wpdb
212
- */
213
- global $wpdb;
214
 
215
- $source = is_array($option['source']) ? $option['source'] : array($option['source']);
216
- $population = 'posts';
217
 
218
- if ( empty( $data['value'] ) ) {
219
- break;
220
- }
 
 
 
221
 
222
- $ids = $data['value'];
223
- foreach ( $ids as $post_id ) {
224
- $ids[] = intval( $post_id );
 
 
 
 
 
 
 
 
 
 
225
  }
226
- $ids = implode( ', ', array_unique( $ids ) );
227
-
228
- //$query = new WP_Query( array( 'post__in' => $ids ) );
229
-
230
- $query = $wpdb->get_results(
231
- "SELECT posts.ID, posts.post_title " .
232
- "FROM $wpdb->posts as posts " .
233
- "WHERE posts.ID IN ( $ids )"
234
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
 
236
- if ( is_wp_error( $query ) ) {
237
- break;
238
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
- $items = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
- foreach ( $query as $post ) {
243
- $items[ $post->ID ] = $post->post_title;
244
- }
245
- }
246
- break;
247
- case 'taxonomy' :
248
- if ( isset( $option['source'] ) ) {
249
- $population = 'taxonomy';
250
- $source = is_array($option['source']) ? $option['source'] : array($option['source']);
251
 
252
- if ( empty( $data['value'] ) ) {
253
- break;
254
- }
255
 
 
 
 
 
 
 
256
  /**
257
  * @var WPDB $wpdb
258
  */
259
  global $wpdb;
260
-
261
- $ids = $data['value'];
262
- foreach ( $ids as $post_id ) {
263
- $ids[] = intval( $post_id );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  }
265
- $ids = implode( ', ', array_unique( $ids ) );
266
 
267
- $in_sources = array();
268
- foreach ($source as $_source) {
269
- $in_sources[] = $wpdb->prepare('%s', $_source);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  }
271
- $in_sources = implode(', ', $in_sources);
272
-
273
- $query = $wpdb->get_results(
274
- "SELECT terms.term_id id, terms.name title " .
275
- "FROM $wpdb->terms as terms, $wpdb->term_taxonomy as taxonomies " .
276
- "WHERE terms.term_id IN ( $ids ) AND taxonomies.taxonomy IN ( $in_sources ) " .
277
- "AND terms.term_id = taxonomies.term_id " .
278
- "AND taxonomies.term_id = taxonomies.term_taxonomy_id"
279
- );
280
 
281
  if ( is_wp_error( $query ) || empty( $query ) ) {
282
  break;
@@ -287,145 +414,86 @@ class FW_Option_Type_Multi_Select extends FW_Option_Type
287
  foreach ( $query as $term ) {
288
  $items[ $term->id ] = $term->title;
289
  }
290
- }
291
- break;
292
- case 'users' :
293
- /**
294
- * @var WPDB $wpdb
295
- */
296
- global $wpdb;
297
- $population = 'users';
298
-
299
- if ( isset( $option['source'] ) && ! empty( $option['source'] ) ) {
300
- $source = is_array($option['source']) ? $option['source'] : array($option['source']);
301
-
302
- if ( empty( $data['value'] ) ) {
303
- break;
304
- }
305
-
306
- $ids = $data['value'];
307
- foreach ( $ids as $post_id ) {
308
- $ids[] = intval( $post_id );
309
- }
310
- $ids = implode( ', ', array_unique( $ids ) );
311
-
312
- $in_sources = array();
313
- foreach ($source as $_source) {
314
- $in_sources[] = $wpdb->prepare('usermeta.meta_value LIKE %s', '%'. $wpdb->esc_like($_source) .'%');
315
- }
316
- $in_sources = implode(' OR ', $in_sources);
317
-
318
- $query = $wpdb->get_results(
319
- "SELECT users.id, users.display_name title " .
320
- "FROM $wpdb->users as users, $wpdb->usermeta usermeta " .
321
- "WHERE users.ID IN ($ids) AND usermeta.meta_key = 'wp_capabilities' AND ( $in_sources ) " .
322
- "AND usermeta.user_id = users.ID"
323
- );
324
-
325
- } else {
326
- $source = array();
327
-
328
- if ( empty( $data['value'] ) ) {
329
- break;
330
- }
331
-
332
- $ids = $data['value'];
333
- foreach ( $ids as $post_id ) {
334
- $ids[] = intval( $post_id );
335
- }
336
- $ids = implode( ', ', array_unique( $ids ) );
337
 
338
- $query = $wpdb->get_results(
339
- "SELECT users.id, users.user_nicename title " .
340
- "FROM $wpdb->users as users " .
341
- "WHERE users.ID IN ($ids)"
342
- );
343
- }
344
-
345
- if ( is_wp_error( $query ) || empty( $query ) ) {
346
  break;
347
- }
348
-
349
- $items = array();
350
-
351
- foreach ( $query as $term ) {
352
- $items[ $term->id ] = $term->title;
353
- }
354
 
355
- break;
356
- default :
357
- $items = '';
 
 
 
 
 
 
 
 
358
  }
359
 
360
- $option['attr']['data-options'] = json_encode( $this->convert_array( $items ) );
361
- $option['attr']['data-population'] = $population;
362
- $option['attr']['data-source'] = json_encode( $source );
363
- $option['attr']['data-limit'] = ( intval( $option['limit'] ) > 0 ) ? $option['limit'] : 0;
364
- } else {
365
- return '';
366
  }
367
- if ( ! empty( $data['value'] ) ) {
368
- $data['value'] = implode( '/*/', $data['value'] );
369
- } else {
370
- $data['value'] = '';
371
- }
372
-
373
- return fw()->backend->option_type( 'text' )->render( $id, $option, $data );
374
- }
375
 
376
- /**
377
- * @internal
378
- * {@inheritdoc}
379
- */
380
- protected function _enqueue_static($id, $option, $data) {
381
- wp_enqueue_style(
382
- $this->get_type() . '-styles',
383
- fw_get_framework_directory_uri('/includes/option-types/' . $this->get_type() . '/static/css/style.css'),
384
- array('fw-selectize'),
385
- fw()->manifest->get_version()
386
- );
387
- wp_enqueue_script(
388
- $this->get_type() . '-styles',
389
- fw_get_framework_directory_uri('/includes/option-types/' . $this->get_type() . '/static/js/scripts.js'),
390
- array( 'jquery', 'fw-events', 'fw-selectize' ),
391
- fw()->manifest->get_version(),
392
- true
393
- );
394
-
395
- fw()->backend->option_type( 'text' )->enqueue_static();
396
- }
397
 
398
- /**
399
- * @internal
400
- */
401
- private function convert_array( $array = array() ) {
402
- if ( ! is_array( $array ) || empty( $array ) ) {
403
- return array();
404
  }
405
 
406
- $return = array();
407
- foreach ( $array as $key => $item ) {
408
- $return[] = array(
409
- 'val' => $key,
410
- 'title' => $item,
411
- );
 
 
 
 
 
 
 
 
 
 
 
412
  }
413
 
414
- return $return;
415
- }
 
 
 
 
 
416
 
417
- /**
418
- * @internal
419
- */
420
- protected function _get_value_from_input( $option, $input_value ) {
421
- $value = explode( '/*/', $input_value );
422
 
423
- return empty( $input_value ) ? array() : $value;
 
424
  }
425
- }
426
 
427
- FW_Option_Type::register( 'FW_Option_Type_Multi_Select' );
428
 
429
- add_action( 'wp_ajax_admin_action_get_ajax_response', array( "FW_Option_Type_Multi_Select", '_admin_action_get_ajax_response' ) );
 
430
 
431
  endif;
2
  die( 'Forbidden' );
3
  }
4
 
5
+ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
6
 
 
 
 
 
 
7
  /**
8
+ * Select multiple choices from different sources: posts, taxonomies, users or custom array
9
  */
10
+ class FW_Option_Type_Multi_Select extends FW_Option_Type {
11
+ /**
12
+ * @internal
13
+ */
14
+ protected function _get_defaults() {
15
+ return array(
16
+ 'value' => array(),
17
+ /**
18
+ * Available options: array, posts, taxonomy, users
19
+ */
20
+ 'population' => 'array',
21
+ /**
22
+ * Set post types, taxonomies, user roles to search for
23
+ *
24
+ * 'population' => 'posts'
25
+ * 'source' => 'page',
26
+ *
27
+ * 'population' => 'taxonomy'
28
+ * 'source' => 'category',
29
+ *
30
+ * 'population' => 'users'
31
+ * 'source' => array( 'editor', 'subscriber', 'author' ),
32
+ *
33
+ * 'population' => 'array'
34
+ * 'source' => '' // will populate with 'choices' array
35
+ */
36
+ 'source' => '',
37
+ /**
38
+ * Set the number of posts/users/taxonomies that multi-select will be prepopulated
39
+ * Or set the value to false in order to disable this functionality.
40
+ */
41
+ 'prepopulate' => 10,
42
+ /**
43
+ * An array with the available choices
44
+ * Used only when 'population' => 'array'
45
+ */
46
+ 'choices' => array( /* 'value' => 'Title' */ ),
47
+ /**
48
+ * Set maximum items number that can be selected
49
+ */
50
+ 'limit' => 100,
51
+ );
52
+ }
53
 
54
+ private $internal_options = array();
55
 
56
+ public function get_type() {
57
+ return 'multi-select';
58
+ }
59
 
60
+ /**
61
+ * @internal
62
+ */
63
+ public function _init() {
64
+ $this->internal_options = array(
65
+ 'label' => false,
66
+ 'type' => 'text',
67
+ 'value' => '',
68
+ );
69
+ }
70
 
 
 
 
 
71
  /**
72
+ * @internal
73
  */
74
+ public static function _admin_action_get_ajax_response() {
75
+ /**
76
+ * @var WPDB $wpdb
77
+ */
78
+ global $wpdb;
79
+
80
+ $type = FW_Request::POST( 'data/type' );
81
+ $names = json_decode( FW_Request::POST( 'data/names' ), true );
82
+ $title = FW_Request::POST( 'data/string' );
83
+
84
+ $items = array();
85
+
86
+ switch ( $type ) {
87
+ case 'posts':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  $items = $wpdb->get_results(
89
+ call_user_func_array(
90
+ array( $wpdb, 'prepare' ),
91
+ array_merge(
92
+ array(
93
+ "SELECT ID val, post_title title " .
94
+ "FROM $wpdb->posts " .
95
+ "WHERE post_title LIKE %s " .
96
+ "AND post_status = 'publish' " .
97
+ "AND NULLIF(post_password, '') IS NULL " .
98
+ "AND post_type IN ( " .
99
+ implode( ', ', array_fill( 1, count( $names ), '%s' ) ) .
100
+ " ) " .
101
+ "LIMIT 100",
102
+ '%' . $wpdb->esc_like( $title ) . '%'
103
+ ),
104
+ /**
105
+ * These strings may contain '%abc'
106
+ * so we cannot use them directly in sql
107
+ * because $wpdb->prepare() will return false
108
+ * also you can test that with sprintf()
109
+ */
110
+ $names
111
+ )
112
  )
113
  );
114
+ break;
115
+ case 'taxonomy':
 
 
 
 
116
  $items = $wpdb->get_results(
117
  call_user_func_array(
118
+ array( $wpdb, 'prepare' ),
119
  array_merge(
120
  array(
121
+ "SELECT terms.term_id val, terms.name title " .
122
+ "FROM $wpdb->terms as terms, $wpdb->term_taxonomy as taxonomies " .
123
+ "WHERE terms.name LIKE %s AND taxonomies.taxonomy IN ( " .
124
+ implode( ', ', array_fill( 1, count( $names ), '%s' ) ) .
 
125
  " ) " .
126
+ "AND terms.term_id = taxonomies.term_id " .
127
+ "AND taxonomies.term_id = taxonomies.term_taxonomy_id " .
128
+ "LIMIT 100",
129
+ '%' . $wpdb->esc_like( $title ) . '%'
130
  ),
131
  /**
132
  * These strings may contain '%abc'
134
  * because $wpdb->prepare() will return false
135
  * also you can test that with sprintf()
136
  */
137
+ $names
138
  )
139
  )
140
  );
141
+ break;
142
+ case 'users':
143
+ if ( empty( $names ) ) {
144
+ $items = $wpdb->get_results(
145
+ $wpdb->prepare(
146
+ "SELECT users.id val, users.user_nicename title " .
147
+ "FROM $wpdb->users as users " .
148
+ "WHERE users.user_nicename LIKE %s " .
149
+ "LIMIT 100",
150
+ '%' . $wpdb->esc_like( $title ) . '%'
151
+ )
152
+ );
153
+ } else {
154
+ $like_user_meta = array();
155
+ foreach ( $names as $name ) {
156
+ $like_user_meta[] = '%' . $wpdb->esc_like( $name ) . '%';
157
+ }
158
 
159
+ $items = $wpdb->get_results(
160
+ call_user_func_array(
161
+ array( $wpdb, 'prepare' ),
162
+ array_merge(
163
+ array(
164
+ "SELECT users.id val, users.user_nicename title " .
165
+ "FROM $wpdb->users as users, $wpdb->usermeta as usermeta " .
166
+ "WHERE users.user_nicename LIKE %s AND usermeta.meta_key = 'wp_capabilities' " .
167
+ "AND ( " .
168
+ implode( ' OR ',
169
+ array_fill( 1, count( $like_user_meta ), 'usermeta.meta_value LIKE %s' ) ) .
170
+ " ) " .
171
+ "AND usermeta.user_id = users.ID",
172
+ '%' . $wpdb->esc_like( $title ) . '%'
173
+ ),
174
+ /**
175
+ * These strings may contain '%abc'
176
+ * so we cannot use them directly in sql
177
+ * because $wpdb->prepare() will return false
178
+ * also you can test that with sprintf()
179
+ */
180
+ $like_user_meta
181
+ )
182
+ )
183
+ );
184
  }
185
  break;
186
+ }
 
 
 
 
 
187
 
188
+ wp_send_json_success( $items );
189
+ }
190
 
191
+ /**
192
+ * @internal
193
+ */
194
+ public function _get_backend_width_type() {
195
+ return 'fixed';
196
+ }
197
 
198
+ /**
199
+ * @internal
200
+ */
201
+ protected function _render( $id, $option, $data ) {
202
+ $items = '';
203
+ $population = 'array';
204
+ $source = array();
205
+
206
+ if ( isset( $option['population'] ) ) {
207
+ switch ( $option['population'] ) {
208
+ case 'array' :
209
+ if ( isset( $option['choices'] ) && is_array( $option['choices'] ) ) {
210
+ $items = $option['choices'];
211
  }
212
+ break;
213
+ case 'posts' :
214
+ if ( isset( $option['source'] ) ) {
215
+ /**
216
+ * @var WPDB $wpdb
217
+ */
218
+ global $wpdb;
219
+
220
+ $source = is_array( $option['source'] ) ? $option['source'] : array( $option['source'] );
221
+ $population = 'posts';
222
+
223
+ if ( isset( $option['prepopulate'] )
224
+ && ( $number = (int) ( $option['prepopulate'] ) ) > 0
225
+ && ! empty( $source )
226
+ ) {
227
+
228
+ $posts = $wpdb->get_results(
229
+ "SELECT posts.ID, posts.post_title " .
230
+ "FROM $wpdb->posts as posts " .
231
+ "WHERE post_type IN ('" . implode( "', ", $source ) . "') " .
232
+ "ORDER BY post_date DESC LIMIT $number"
233
+ );
234
+
235
+ if ( ! empty( $posts ) || ! is_wp_error( $posts ) ) {
236
+ $items = wp_list_pluck( $posts, 'post_title', 'ID' );
237
+ }
238
+ unset( $posts );
239
+ }
240
+
241
+ if ( empty( $data['value'] ) ) {
242
+ break;
243
+ }
244
+
245
+ $ids = $data['value'];
246
+ foreach ( $ids as $post_id ) {
247
+ $ids[] = intval( $post_id );
248
+ }
249
+ $ids = implode( ', ', array_unique( $ids ) );
250
+
251
+ //$query = new WP_Query( array( 'post__in' => $ids ) );
252
+
253
+ $query = $wpdb->get_results(
254
+ "SELECT posts.ID, posts.post_title " .
255
+ "FROM $wpdb->posts as posts " .
256
+ "WHERE posts.ID IN ( $ids )"
257
+ );
258
+
259
+ if ( is_wp_error( $query ) ) {
260
+ break;
261
+ }
262
+
263
+ foreach ( $query as $post ) {
264
+ $items[ $post->ID ] = $post->post_title;
265
+ }
266
 
 
 
267
  }
268
+ break;
269
+ case 'taxonomy' :
270
+ if ( isset( $option['source'] ) ) {
271
+ /**
272
+ * @var WPDB $wpdb
273
+ */
274
+ global $wpdb;
275
+ $population = 'taxonomy';
276
+ $source = is_array( $option['source'] ) ? $option['source'] : array( $option['source'] );
277
+
278
+ if ( isset( $option['prepopulate'] ) && ! empty( $source )
279
+ && ( $number = (int) ( $option['prepopulate'] ) ) > 0
280
+ ) {
281
+ $terms = $wpdb->get_results(
282
+ "SELECT terms.term_id, terms.name " .
283
+ "FROM $wpdb->terms as terms, $wpdb->term_taxonomy as taxonomies " .
284
+ "WHERE taxonomies.taxonomy IN ('" . implode( "', ", $source ) . "') " .
285
+ "AND terms.term_id = taxonomies.term_id " .
286
+ "AND taxonomies.term_id = taxonomies.term_taxonomy_id"
287
+ );
288
+
289
+ if ( ! empty( $terms ) || ! is_wp_error( $terms ) ) {
290
+ $items = wp_list_pluck( $terms, 'name', 'term_id' );
291
+ }
292
+ unset( $terms );
293
+ }
294
+
295
+ if ( empty( $data['value'] ) ) {
296
+ break;
297
+ }
298
 
299
+ /**
300
+ * @var WPDB $wpdb
301
+ */
302
+ global $wpdb;
303
+
304
+ $ids = $data['value'];
305
+ foreach ( $ids as $post_id ) {
306
+ $ids[] = intval( $post_id );
307
+ }
308
+ $ids = implode( ', ', array_unique( $ids ) );
309
+
310
+ $in_sources = array();
311
+ foreach ( $source as $_source ) {
312
+ $in_sources[] = $wpdb->prepare( '%s', $_source );
313
+ }
314
+ $in_sources = implode( ', ', $in_sources );
315
+
316
+ $query = $wpdb->get_results(
317
+ "SELECT terms.term_id id, terms.name title " .
318
+ "FROM $wpdb->terms as terms, $wpdb->term_taxonomy as taxonomies " .
319
+ "WHERE terms.term_id IN ( $ids ) AND taxonomies.taxonomy IN ( $in_sources ) " .
320
+ "AND terms.term_id = taxonomies.term_id " .
321
+ "AND taxonomies.term_id = taxonomies.term_taxonomy_id"
322
+ );
323
 
324
+ if ( is_wp_error( $query ) || empty( $query ) ) {
325
+ break;
326
+ }
 
 
 
 
 
 
327
 
328
+ $items = array();
 
 
329
 
330
+ foreach ( $query as $term ) {
331
+ $items[ $term->id ] = $term->title;
332
+ }
333
+ }
334
+ break;
335
+ case 'users' :
336
  /**
337
  * @var WPDB $wpdb
338
  */
339
  global $wpdb;
340
+ $population = 'users';
341
+
342
+ if ( isset( $option['prepopulate'] )
343
+ && ( $number = (int) ( $option['prepopulate'] ) ) > 0
344
+ ) {
345
+ $users = $wpdb->get_results(
346
+ "SELECT DISTINCT users.ID, users.user_nicename " .
347
+ "FROM $wpdb->users as users, $wpdb->usermeta usermeta " .
348
+ ( ! empty( $source )
349
+ ? "WHERE usermeta.meta_key = 'wp_capabilities'" .
350
+ " AND usermeta.meta_value IN ('" . implode( "', ", $source ) . "')" .
351
+ " AND usermeta.user_id = users.ID"
352
+ : '' ) . " LIMIT $number"
353
+ );
354
+
355
+ if ( ! empty( $users ) || ! is_wp_error( $users ) ) {
356
+ $items = wp_list_pluck( $users, 'user_nicename', 'ID' );
357
+ }
358
+ unset( $users );
359
  }
 
360
 
361
+ if ( isset( $option['source'] ) && ! empty( $option['source'] ) ) {
362
+ $source = is_array( $option['source'] ) ? $option['source'] : array( $option['source'] );
363
+
364
+ if ( empty( $data['value'] ) ) {
365
+ break;
366
+ }
367
+
368
+ $ids = $data['value'];
369
+ foreach ( $ids as $post_id ) {
370
+ $ids[] = intval( $post_id );
371
+ }
372
+ $ids = implode( ', ', array_unique( $ids ) );
373
+
374
+ $in_sources = array();
375
+ foreach ( $source as $_source ) {
376
+ $in_sources[] = $wpdb->prepare( 'usermeta.meta_value LIKE %s',
377
+ '%' . $wpdb->esc_like( $_source ) . '%' );
378
+ }
379
+ $in_sources = implode( ' OR ', $in_sources );
380
+
381
+ $query = $wpdb->get_results(
382
+ "SELECT users.id, users.user_nicename title " .
383
+ "FROM $wpdb->users as users, $wpdb->usermeta usermeta " .
384
+ "WHERE users.ID IN ($ids) AND usermeta.meta_key = 'wp_capabilities' AND ( $in_sources ) " .
385
+ "AND usermeta.user_id = users.ID"
386
+ );
387
+
388
+ } else {
389
+ $source = array();
390
+
391
+ if ( empty( $data['value'] ) ) {
392
+ break;
393
+ }
394
+
395
+ $ids = $data['value'];
396
+ foreach ( $ids as $post_id ) {
397
+ $ids[] = intval( $post_id );
398
+ }
399
+ $ids = implode( ', ', array_unique( $ids ) );
400
+
401
+ $query = $wpdb->get_results(
402
+ "SELECT users.id, users.user_nicename title " .
403
+ "FROM $wpdb->users as users " .
404
+ "WHERE users.ID IN ($ids)"
405
+ );
406
  }
 
 
 
 
 
 
 
 
 
407
 
408
  if ( is_wp_error( $query ) || empty( $query ) ) {
409
  break;
414
  foreach ( $query as $term ) {
415
  $items[ $term->id ] = $term->title;
416
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
 
 
 
 
 
 
 
 
 
418
  break;
419
+ default :
420
+ $items = '';
421
+ }
 
 
 
 
422
 
423
+ $option['attr']['data-options'] = json_encode( $this->convert_array( $items ) );
424
+ $option['attr']['data-population'] = $population;
425
+ $option['attr']['data-source'] = json_encode( $source );
426
+ $option['attr']['data-limit'] = ( intval( $option['limit'] ) > 0 ) ? $option['limit'] : 0;
427
+ } else {
428
+ return '';
429
+ }
430
+ if ( ! empty( $data['value'] ) ) {
431
+ $data['value'] = implode( '/*/', $data['value'] );
432
+ } else {
433
+ $data['value'] = '';
434
  }
435
 
436
+ return fw()->backend->option_type( 'text' )->render( $id, $option, $data );
 
 
 
 
 
437
  }
 
 
 
 
 
 
 
 
438
 
439
+ /**
440
+ * @internal
441
+ * {@inheritdoc}
442
+ */
443
+ protected function _enqueue_static( $id, $option, $data ) {
444
+ wp_enqueue_style(
445
+ $this->get_type() . '-styles',
446
+ fw_get_framework_directory_uri( '/includes/option-types/' . $this->get_type() . '/static/css/style.css' ),
447
+ array( 'fw-selectize' ),
448
+ fw()->manifest->get_version()
449
+ );
450
+ wp_enqueue_script(
451
+ $this->get_type() . '-styles',
452
+ fw_get_framework_directory_uri( '/includes/option-types/' . $this->get_type() . '/static/js/scripts.js' ),
453
+ array( 'jquery', 'fw-events', 'fw-selectize' ),
454
+ fw()->manifest->get_version(),
455
+ true
456
+ );
 
 
 
457
 
458
+ fw()->backend->option_type( 'text' )->enqueue_static();
 
 
 
 
 
459
  }
460
 
461
+ /**
462
+ * @internal
463
+ */
464
+ private function convert_array( $array = array() ) {
465
+ if ( ! is_array( $array ) || empty( $array ) ) {
466
+ return array();
467
+ }
468
+
469
+ $return = array();
470
+ foreach ( $array as $key => $item ) {
471
+ $return[] = array(
472
+ 'val' => $key,
473
+ 'title' => $item,
474
+ );
475
+ }
476
+
477
+ return $return;
478
  }
479
 
480
+ /**
481
+ * @internal
482
+ */
483
+ protected function _get_value_from_input( $option, $input_value ) {
484
+ if ( is_null( $input_value ) ) {
485
+ return $option['value'];
486
+ }
487
 
488
+ $value = explode( '/*/', $input_value );
 
 
 
 
489
 
490
+ return empty( $input_value ) ? array() : $value;
491
+ }
492
  }
 
493
 
494
+ FW_Option_Type::register( 'FW_Option_Type_Multi_Select' );
495
 
496
+ add_action( 'wp_ajax_admin_action_get_ajax_response',
497
+ array( "FW_Option_Type_Multi_Select", '_admin_action_get_ajax_response' ) );
498
 
499
  endif;
framework/includes/option-types/multi-upload/views/images-only.php CHANGED
@@ -30,15 +30,15 @@
30
  <?php endif; ?>
31
  </div>
32
  <p><a href="#"><?php echo $is_empty ? $l10n['button_add'] : $l10n['button_edit']; ?></a></p>
33
- <br class="thumb-template-empty fw-hidden" data-template="<?php ob_start(); ?>
34
- <div class="thumb no-image">
35
- <img src="<?php echo fw_get_framework_directory_uri('/static/img/no-image.png'); ?>" class="no-image-img" alt="<?php esc_attr_e('No image', 'fw') ?>"/>
36
- </div>
37
- <?php echo fw_htmlspecialchars(ob_get_clean()) ?>">
38
- <br class="thumb-template-not-empty fw-hidden" data-template="<?php ob_start(); ?>
39
- <div class="thumb" data-attid="<%= data.id %>" data-origsrc="<%= data.originalSrc %>">
40
- <img src="<%= data.src %>" alt="<%= data.alt %>"/>
41
- <a href="#" class="dashicons fw-x clear-uploads-thumb"></a>
42
- </div>
43
- <?php echo fw_htmlspecialchars(ob_get_clean()) ?>">
44
  </div>
30
  <?php endif; ?>
31
  </div>
32
  <p><a href="#"><?php echo $is_empty ? $l10n['button_add'] : $l10n['button_edit']; ?></a></p>
33
+ <br class="thumb-template-empty fw-hidden" data-template="<?php echo fw_htmlspecialchars(
34
+ '<div class="thumb no-image">'.
35
+ '<img src="'. fw_get_framework_directory_uri('/static/img/no-image.png') .'" class="no-image-img" alt="'. esc_attr__('No image', 'fw') .'"/>'.
36
+ '</div>'
37
+ ); ?>">
38
+ <br class="thumb-template-not-empty fw-hidden" data-template="<?php echo fw_htmlspecialchars(
39
+ '<div class="thumb" data-attid="<%= data.id %>" data-origsrc="<%- data.originalSrc %>">'.
40
+ '<img src="<%- data.src %>" alt="<%- data.alt %>"/>'.
41
+ '<a href="#" class="dashicons fw-x clear-uploads-thumb"></a>'.
42
+ '</div>'
43
+ ); ?>">
44
  </div>
framework/includes/option-types/popup/class-fw-option-type-popup.php CHANGED
@@ -117,10 +117,7 @@ class FW_Option_Type_Popup extends FW_Option_Type {
117
  return array();
118
  }
119
 
120
- foreach ( $option['popup-options'] as $key => $op ) {
121
- $values[ $key ] = isset( $op['value'] ) ? $op['value'] : null;
122
- }
123
-
124
  } else {
125
  $values = json_decode( $input_value, true );
126
  }
@@ -156,6 +153,12 @@ class FW_Option_Type_Popup extends FW_Option_Type {
156
  * Array of options that you need to add in the popup
157
  */
158
  'popup-options' => array(),
 
 
 
 
 
 
159
  /*
160
  * Array of default values for the popup options
161
  */
117
  return array();
118
  }
119
 
120
+ $values = fw_get_options_values_from_input($option['popup-options'], array());
 
 
 
121
  } else {
122
  $values = json_decode( $input_value, true );
123
  }
153
  * Array of options that you need to add in the popup
154
  */
155
  'popup-options' => array(),
156
+
157
+ /*
158
+ * Popup size
159
+ */
160
+ 'size' => 'medium',
161
+
162
  /*
163
  * Array of default values for the popup options
164
  */
framework/includes/option-types/radio-text/class-fw-option-type-radio-text.php CHANGED
@@ -85,6 +85,10 @@ class FW_Option_Type_Radio_Text extends FW_Option_Type
85
  */
86
  protected function _get_value_from_input($option, $input_value)
87
  {
 
 
 
 
88
  $option['choices'][ $this->custom_choice_key ] = '';
89
 
90
  $selected = fw()->backend->option_type( 'radio' )->get_value_from_input( array(
85
  */
86
  protected function _get_value_from_input($option, $input_value)
87
  {
88
+ if (is_null($input_value)) {
89
+ return $option['value'];
90
+ }
91
+
92
  $option['choices'][ $this->custom_choice_key ] = '';
93
 
94
  $selected = fw()->backend->option_type( 'radio' )->get_value_from_input( array(
framework/includes/option-types/rgba-color-picker/class-fw-option-type-rgba-color-picker.php CHANGED
@@ -51,16 +51,18 @@ class FW_Option_Type_Rgba_Color_Picker extends FW_Option_Type {
51
  * @internal
52
  */
53
  protected function _get_value_from_input( $option, $input_value ) {
54
- if ( ! isset( $input_value ) ) {
 
 
55
  $input_value = trim($input_value);
56
  $input_value = (
57
  preg_match( '/^#[a-f0-9]{3}([a-f0-9]{3})?$/i', $input_value )
58
  ||
59
  preg_match( '/^rgba\( *([01]?\d\d?|2[0-4]\d|25[0-5]) *\, *([01]?\d\d?|2[0-4]\d|25[0-5]) *\, *([01]?\d\d?|2[0-4]\d|25[0-5]) *\, *(1|0|0?.\d+) *\)$/', $input_value )
60
  ) ? $input_value : $option['value'];
61
- }
62
 
63
- return (string) $input_value;
 
64
  }
65
 
66
  /**
51
  * @internal
52
  */
53
  protected function _get_value_from_input( $option, $input_value ) {
54
+ if (is_null($input_value)) {
55
+ return $option['value'];
56
+ } else {
57
  $input_value = trim($input_value);
58
  $input_value = (
59
  preg_match( '/^#[a-f0-9]{3}([a-f0-9]{3})?$/i', $input_value )
60
  ||
61
  preg_match( '/^rgba\( *([01]?\d\d?|2[0-4]\d|25[0-5]) *\, *([01]?\d\d?|2[0-4]\d|25[0-5]) *\, *([01]?\d\d?|2[0-4]\d|25[0-5]) *\, *(1|0|0?.\d+) *\)$/', $input_value )
62
  ) ? $input_value : $option['value'];
 
63
 
64
+ return (string) $input_value;
65
+ }
66
  }
67
 
68
  /**
framework/includes/option-types/rgba-color-picker/static/js/scripts.js CHANGED
@@ -1,3 +1,4 @@
 
1
  (function ($) {
2
  $(document.body).click(function (e) {
3
  if (!$(e.target).is('.fw-option-type-rgba-color-picker, .iris-picker, .iris-picker-inner, .iris-palette, .fw-alpha-container')) {
@@ -33,110 +34,145 @@
33
  return '#' + hex;
34
  };
35
 
36
- fwEvents.on('fw:options:init', function (data) {
37
- data.$elements.find('input.fw-option-type-rgba-color-picker:not(.initialized)').each(function () {
38
-
39
- var $input = $(this);
40
-
41
- $input.iris({
42
- palettes: true,
43
- defaultColor: false,
44
- change: function (event, ui) {
45
- var $transparency = $input.next('.iris-picker').find('.transparency');
46
- $transparency.css('backgroundColor', ui.color.toString('no-alpha'));
47
-
48
- $alpha_slider.slider( "option", "value", ui.color._alpha * 100 );
49
-
50
- $input.css('background-color', ui.color.toCSS());
51
- $input.css('color', ($alpha_slider.slider("value") > 40) ? ui.color.getMaxContrastColor().toCSS() : '#000000');
52
-
53
- $input.trigger('fw:rgba:color:picker:changed', {
54
- $element: $input,
55
- iris: $input.data('a8cIris'),
56
- alphaSlider: $alpha_slider.data('uiSlider')
57
- });
58
- }
59
- });
60
-
61
- $input.on('change keyup blur', function () {
62
- /**
63
- * iris::change is not triggered when the input is empty or color is wrong
64
- */
65
- if (Color($input.val()).error) {
66
- $input.css('background-color', '');
67
- $input.css('color', '');
68
- }
69
- });
70
 
71
  $('<div class="fw-alpha-container"><div class="slider-alpha"></div><div class="transparency"></div></div>').appendTo($input.next('.iris-picker'));
72
 
73
- var $alpha_slider = $input.next('.iris-picker:first').find('.slider-alpha');
74
-
75
- $alpha_slider.slider({
76
- value: Color($input.val())._alpha * 100,
77
- range: "max",
78
- step: 1,
79
- min: 0,
80
- max: 100,
81
- slide: function (event, ui) {
82
- $(this).find('.ui-slider-handle').text(ui.value);
83
-
84
- var color = $input.iris('color', true);
85
- var cssColor = (ui.value < 100) ? color.toCSS('rgba', ui.value / 100) : color.toHex();
86
-
87
- $input.css('background-color', cssColor).val(cssColor);
88
- $input.css('color', (ui.value > 40) ? color.getMaxContrastColor().toCSS() : '#000000');
89
-
90
- var new_alpha_val = parseFloat(ui.value),
91
- iris = $input.data('a8cIris');
92
- iris._color._alpha = new_alpha_val / 100.0;
93
- },
94
- create: function (event, ui) {
95
- var v = $(this).slider('value');
96
- $(this).find('.ui-slider-handle').text(v);
97
- var $transparency = $input.next('.iris-picker:first').find('.transparency');
98
- $transparency.css('backgroundColor', Color($input.val()).toCSS('rgb', 1));
99
- },
100
- change: function (event, ui) {
101
- $(this).find('.ui-slider-handle').text(ui.value);
102
-
103
- var color = $input.iris('color', true);
104
- var cssColor = (ui.value < 100) ? color.toCSS('rgba', ui.value / 100) : color.toHex();
105
-
106
- $input.css('background-color', cssColor).val(cssColor);
107
- $input.css('color', (ui.value > 40) ? color.getMaxContrastColor().toCSS() : '#000000');
108
-
109
- var new_alpha_val = parseFloat(ui.value),
110
- iris = $input.data('a8cIris');
111
- iris._color._alpha = new_alpha_val / 100.0;
112
-
113
- $input.trigger('fw:rgba:color:picker:changed', {
114
- $element: $input,
115
- iris: $input.data('a8cIris'),
116
- alphaSlider: $alpha_slider.data('uiSlider')
117
- });
118
- }
119
- });
120
-
121
- $input.iris('hide');
122
-
123
- if (!Color($input.val()).error) {
124
- $input.iris('color', $input.val());
125
  }
 
126
 
127
- $input.addClass('initialized');
128
 
129
- $input.show();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
- $('.fw-inner').on('click', '.fw-option-type-rgba-color-picker', function () {
132
- $('.fw-option-type-rgba-color-picker.initialized').iris('hide');
133
 
134
- $(this).iris('show');
135
 
136
- return false;
137
- });
138
  });
139
  });
140
 
141
  })(jQuery);
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* globals jQuery */
2
  (function ($) {
3
  $(document.body).click(function (e) {
4
  if (!$(e.target).is('.fw-option-type-rgba-color-picker, .iris-picker, .iris-picker-inner, .iris-palette, .fw-alpha-container')) {
34
  return '#' + hex;
35
  };
36
 
37
+ function activateIris(input) {
38
+
39
+ var $input = input;
40
+
41
+ $input.iris({
42
+ palettes: true,
43
+ defaultColor: false,
44
+ change: function (event, ui) {
45
+ var $transparency = $input.next('.iris-picker').find('.transparency');
46
+ $transparency.css('backgroundColor', ui.color.toString('no-alpha'));
47
+
48
+ $alpha_slider.slider("option", "value", ui.color._alpha * 100);
49
+
50
+ $input.css('background-color', ui.color.toCSS());
51
+ $input.css('color', ($alpha_slider.slider("value") > 40) ? ui.color.getMaxContrastColor().toCSS() : '#000000');
52
+
53
+ $input.trigger('fw:rgba:color:picker:changed', {
54
+ $element: $input,
55
+ iris: $input.data('a8cIris'),
56
+ alphaSlider: $alpha_slider.data('uiSlider')
57
+ });
58
+ }
59
+ });
60
+
61
+ $input.on('change keyup blur', function () {
62
+
63
+ // * iris::change is not triggered when the input is empty or color is wrong
64
+ if (Color($input.val()).error) {
65
+ $input.css('background-color', '');
66
+ $input.css('color', '');
67
+ }
68
+ });
69
+
70
+ if (!$input.hasClass('initialized')) {
71
 
72
  $('<div class="fw-alpha-container"><div class="slider-alpha"></div><div class="transparency"></div></div>').appendTo($input.next('.iris-picker'));
73
 
74
+ }
75
+
76
+ var $alpha_slider = $input.next('.iris-picker:first').find('.slider-alpha');
77
+
78
+ $alpha_slider.slider({
79
+ value: Color($input.val())._alpha * 100,
80
+ range: "max",
81
+ step: 1,
82
+ min: 0,
83
+ max: 100,
84
+ slide: function (event, ui) {
85
+ $(this).find('.ui-slider-handle').text(ui.value);
86
+
87
+ var color = $input.iris('color', true);
88
+ var cssColor = (ui.value < 100) ? color.toCSS('rgba', ui.value / 100) : color.toHex();
89
+
90
+ $input.css('background-color', cssColor).val(cssColor);
91
+ $input.css('color', (ui.value > 40) ? color.getMaxContrastColor().toCSS() : '#000000');
92
+
93
+ var new_alpha_val = parseFloat(ui.value),
94
+ iris = $input.data('a8cIris');
95
+ iris._color._alpha = new_alpha_val / 100.0;
96
+ },
97
+ create: function (event, ui) {
98
+ var v = $(this).slider('value');
99
+ $(this).find('.ui-slider-handle').text(v);
100
+ var $transparency = $input.next('.iris-picker:first').find('.transparency');
101
+ $transparency.css('backgroundColor', Color($input.val()).toCSS('rgb', 1));
102
+ },
103
+ change: function (event, ui) {
104
+ $(this).find('.ui-slider-handle').text(ui.value);
105
+
106
+ var color = $input.iris('color', true);
107
+ var cssColor = (ui.value < 100) ? color.toCSS('rgba', ui.value / 100) : color.toHex();
108
+
109
+ $input.css('background-color', cssColor).val(cssColor);
110
+ $input.css('color', (ui.value > 40) ? color.getMaxContrastColor().toCSS() : '#000000');
111
+
112
+ var new_alpha_val = parseFloat(ui.value),
113
+ iris = $input.data('a8cIris');
114
+ iris._color._alpha = new_alpha_val / 100.0;
115
+
116
+ $input.trigger('fw:rgba:color:picker:changed', {
117
+ $element: $input,
118
+ iris: $input.data('a8cIris'),
119
+ alphaSlider: $alpha_slider.data('uiSlider')
120
+ });
 
 
 
 
 
121
  }
122
+ });
123
 
124
+ $input.iris('show');
125
 
126
+ if (!Color($input.val()).error) {
127
+ $input.iris('color', $input.val());
128
+ }
129
+
130
+ $input.addClass('initialized');
131
+
132
+ }
133
+
134
+ fwEvents.on('fw:options:init', function (data) {
135
+
136
+ data.$elements.find('input.fw-option-type-rgba-color-picker').each(function () {
137
+
138
+ var $this = $(this);
139
+
140
+ if ($this.val() != '') {
141
+ $this.css('background-color', $(this).val());
142
+ $this.contrastColor();
143
+ }
144
+
145
+ });
146
 
147
+ $('.fw-inner').on('click', '.fw-option-type-rgba-color-picker', function () {
 
148
 
149
+ activateIris($(this));
150
 
151
+ return false;
 
152
  });
153
  });
154
 
155
  })(jQuery);
156
 
157
+ (function ($) {
158
+ $.fn.contrastColor = function () {
159
+ return this.each(function () {
160
+ var bg = $(this).css('background-color');
161
+ //use first opaque parent bg if element is transparent
162
+ if (bg == 'transparent' || bg == 'rgba(0, 0, 0, 0)') {
163
+ $(this).parents().each(function () {
164
+ bg = $(this).css('background-color');
165
+ if (bg != 'transparent' && bg != 'rgba(0, 0, 0, 0)') return false;
166
+ });
167
+ //exit if all parents are transparent
168
+ if (bg == 'transparent' || bg == 'rgba(0, 0, 0, 0)') return false;
169
+ }
170
+ //get r,g,b and decide
171
+ var rgb = bg.replace(/^(rgb|rgba)\(/, '').replace(/\)$/, '').replace(/\s/g, '').split(',');
172
+ var yiq = ((rgb[0] * 299) + (rgb[1] * 587) + (rgb[2] * 114)) / 1000;
173
+ if (yiq >= 150) $(this).css('color', '#000000');
174
+ else $(this).css('color', '#ffffff');
175
+ });
176
+ };
177
+
178
+ })(jQuery);
framework/includes/option-types/switch/class-fw-option-type-switch.php CHANGED
@@ -103,7 +103,6 @@ class FW_Option_Type_Switch extends FW_Option_Type
103
  protected function _get_value_from_input($option, $input_value)
104
  {
105
  if (is_null($input_value)) {
106
- // input value is not present
107
  return $option['value'];
108
  } else {
109
  if ($input_value) {
103
  protected function _get_value_from_input($option, $input_value)
104
  {
105
  if (is_null($input_value)) {
 
106
  return $option['value'];
107
  } else {
108
  if ($input_value) {
framework/includes/option-types/upload/class-fw-option-type-upload.php CHANGED
@@ -183,8 +183,7 @@ class FW_Option_Type_Upload extends FW_Option_Type
183
  protected function _get_value_from_input($option, $input_value)
184
  {
185
  if (empty($input_value)) {
186
- $defaults = $this->get_defaults();
187
- return $defaults['value'];
188
  } else {
189
  return $this->get_attachment_info($input_value);
190
  }
183
  protected function _get_value_from_input($option, $input_value)
184
  {
185
  if (empty($input_value)) {
186
+ return $option['value'];
 
187
  } else {
188
  return $this->get_attachment_info($input_value);
189
  }
framework/includes/option-types/upload/views/images-only.php CHANGED
@@ -25,11 +25,12 @@
25
  </div>
26
  <?php endif; ?>
27
  <p><a href="#"><?php echo $is_empty ? $l10n['button_add'] : $l10n['button_edit']; ?></a></p>
28
- <br class="thumb-template-empty fw-hidden" data-template="<?php ob_start(); ?>
29
- <img src="<?php echo fw_get_framework_directory_uri('/static/img/no-image.png'); ?>" class="no-image-img" alt="<?php esc_attr_e('No image', 'fw') ?>"/>
30
- <?php echo fw_htmlspecialchars(ob_get_clean()) ?>">
31
- <br class="thumb-template-not-empty fw-hidden" data-template="<?php ob_start(); ?>
32
- <img src="<%= data.src %>" alt="<%= data.alt %>"/>
33
- <a href="#" class="dashicons fw-x clear-uploads-thumb"></a>
34
- <?php echo fw_htmlspecialchars(ob_get_clean()) ?>">
 
35
  </div>
25
  </div>
26
  <?php endif; ?>
27
  <p><a href="#"><?php echo $is_empty ? $l10n['button_add'] : $l10n['button_edit']; ?></a></p>
28
+
29
+ <br class="thumb-template-empty fw-hidden" data-template="<?php echo fw_htmlspecialchars(
30
+ '<img src="'. fw_get_framework_directory_uri('/static/img/no-image.png') .'" class="no-image-img" alt="'. esc_attr__('No image', 'fw') .'"/>'
31
+ ); ?>">
32
+ <br class="thumb-template-not-empty fw-hidden" data-template="<?php echo fw_htmlspecialchars(
33
+ '<img src="<%- data.src %>" alt="<%- data.alt %>"/>'.
34
+ '<a href="#" class="dashicons fw-x clear-uploads-thumb"></a>'
35
+ ); ?>">
36
  </div>
framework/manifest.php CHANGED
@@ -4,4 +4,4 @@ $manifest = array();
4
 
5
  $manifest['name'] = __('Unyson', 'fw');
6
 
7
- $manifest['version'] = '2.2.7';
4
 
5
  $manifest['name'] = __('Unyson', 'fw');
6
 
7
+ $manifest['version'] = '2.2.8';
framework/static/css/fw.css CHANGED
@@ -2894,6 +2894,7 @@ a.fw-wp-link:hover {
2894
  -webkit-box-sizing: border-box;
2895
  -moz-box-sizing: border-box;
2896
  box-sizing: border-box;
 
2897
  }
2898
 
2899
  .fw-options-modal.fw-options-modal-small > .media-modal {
2894
  -webkit-box-sizing: border-box;
2895
  -moz-box-sizing: border-box;
2896
  box-sizing: border-box;
2897
+ width: 100%;
2898
  }
2899
 
2900
  .fw-options-modal.fw-options-modal-small > .media-modal {
framework/static/js/backend-options.js CHANGED
@@ -105,7 +105,7 @@ jQuery(document).ready(function($){
105
  /**
106
  * leave open only first boxes
107
  */
108
- $boxes.filter('.fw-backend-postboxes > .fw-postbox:not(:first-child)').addClass('closed');
109
 
110
  $boxes.addClass('fw-postbox-initialized');
111
 
105
  /**
106
  * leave open only first boxes
107
  */
108
+ $boxes.filter('.fw-backend-postboxes > .fw-postbox:not(:first-child):not(.prevent-auto-close)').addClass('closed');
109
 
110
  $boxes.addClass('fw-postbox-initialized');
111
 
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: unyson, themefusecom
3
  Tags: page builder, cms, grid, layout, responsive, back up, backup, db backup, dump, migrate, schedule, search engine optimization, seo, media, slideshow, shortcode, slide, slideshare, slideshow, google sitemaps, sitemaps, analytics, google analytics, calendar, event, events, google maps, learning, lessons, sidebars, breadcrumbs, review, portfolio, framework
4
  Requires at least: 4.0.0
5
- Tested up to: 4.1
6
- Stable tag: 2.2.7
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -86,6 +86,19 @@ Yes; Unyson will work with any theme.
86
 
87
  == Changelog ==
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  = 2.2.7 =
90
  * Option type `popup` fixes
91
  * Added "Show/Hide other extensions" button [#307](https://github.com/ThemeFuse/Unyson/issues/307)
2
  Contributors: unyson, themefusecom
3
  Tags: page builder, cms, grid, layout, responsive, back up, backup, db backup, dump, migrate, schedule, search engine optimization, seo, media, slideshow, shortcode, slide, slideshare, slideshow, google sitemaps, sitemaps, analytics, google analytics, calendar, event, events, google maps, learning, lessons, sidebars, breadcrumbs, review, portfolio, framework
4
  Requires at least: 4.0.0
5
+ Tested up to: 4.2
6
+ Stable tag: 2.2.8
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
86
 
87
  == Changelog ==
88
 
89
+ = 2.2.8 =
90
+ * Fixed [#453](https://github.com/ThemeFuse/Unyson/issues/453)
91
+ * Improved option type `multi-picker` html render [#442](https://github.com/ThemeFuse/Unyson/issues/442)
92
+ * Option type `rgba-color-picker` optimizations [#442](https://github.com/ThemeFuse/Unyson/issues/442)
93
+ * `fw_resize()` improvements [#447](https://github.com/ThemeFuse/Unyson/issues/447)
94
+ * Fixed [#445](https://github.com/ThemeFuse/Unyson/issues/445), [#161](https://github.com/ThemeFuse/Unyson/issues/161), [#484](https://github.com/ThemeFuse/Unyson/issues/484), [#456](https://github.com/ThemeFuse/Unyson/issues/456)
95
+ * Added the possibility to prevent box auto-close [#466](https://github.com/ThemeFuse/Unyson/issues/466)
96
+ * Fixed the `_get_value_from_input()` method in some option types [#275](https://github.com/ThemeFuse/Unyson/issues/275#issuecomment-94084590)
97
+ * Added the `limit` parameter for option type `addable-popup` [#478](https://github.com/ThemeFuse/Unyson/issues/478)
98
+ * Fixed popup position in IE [#483](https://github.com/ThemeFuse/Unyson/issues/483)
99
+ * Created `fw_post_options_update` action
100
+ * Improved post save: Options are saved in revision and autosave. Restore from revision works.
101
+
102
  = 2.2.7 =
103
  * Option type `popup` fixes
104
  * Added "Show/Hide other extensions" button [#307](https://github.com/ThemeFuse/Unyson/issues/307)
unyson.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: Unyson
4
  * Plugin URI: http://unyson.themefuse.com/
5
  * Description: A free drag & drop framework that comes with a bunch of built in extensions that will help you develop premium themes fast & easy.
6
- * Version: 2.2.7
7
  * Author: ThemeFuse
8
  * Author URI: http://themefuse.com
9
  * License: GPL2+
3
  * Plugin Name: Unyson
4
  * Plugin URI: http://unyson.themefuse.com/
5
  * Description: A free drag & drop framework that comes with a bunch of built in extensions that will help you develop premium themes fast & easy.
6
+ * Version: 2.2.8
7
  * Author: ThemeFuse
8
  * Author URI: http://themefuse.com
9
  * License: GPL2+