Unyson - Version 2.2.0

Version Description

  • Added the possibility to load extensions from any directory

    function _filter_my_plugin_extensions($locations) {
        $locations['/path/to/plugin/extensions']
    
Download this release

Release Info

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

Code changes from version 2.1.25 to 2.2.0

Files changed (34) hide show
  1. README.md +4 -1
  2. framework/bootstrap-helpers.php +61 -10
  3. framework/core/components/backend.php +150 -73
  4. framework/core/components/extensions.php +144 -210
  5. framework/core/components/extensions/manager/available-extensions.php +12 -0
  6. framework/core/components/extensions/manager/class--fw-extensions-manager.php +1055 -625
  7. framework/core/components/extensions/manager/static/extensions-page.css +44 -34
  8. framework/core/components/extensions/manager/static/img/thumbnails/translation.jpg +0 -0
  9. framework/core/components/extensions/manager/views/delete-form.php +2 -1
  10. framework/core/components/extensions/manager/views/extension.php +10 -12
  11. framework/core/components/theme.php +1 -1
  12. framework/core/extends/class-fw-extension.php +72 -148
  13. framework/helpers/general.php +36 -12
  14. framework/includes/option-types/addable-box/class-fw-option-type-addable-box.php +13 -1
  15. framework/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php +16 -2
  16. framework/includes/option-types/addable-popup/static/js/addable-popup.js +2 -1
  17. framework/includes/option-types/multi-picker/class-fw-option-type-multi-picker.php +30 -29
  18. framework/includes/option-types/multi-picker/static/css/multi-picker.css +40 -14
  19. framework/includes/option-types/multi/static/css/styles.css +5 -2
  20. framework/includes/option-types/popup/class-fw-option-type-popup.php +13 -1
  21. framework/includes/option-types/radio-text/class-fw-option-type-radio-text.php +1 -1
  22. framework/includes/option-types/rgba-color-picker/static/js/scripts.js +1 -1
  23. framework/includes/option-types/typography/class-fw-option-type-typography.php +0 -1
  24. framework/includes/option-types/wp-editor/class-fw-option-type-wp-editor.php +164 -121
  25. framework/includes/option-types/wp-editor/static/js/scripts.js +15 -5
  26. framework/manifest.php +1 -1
  27. framework/static/css/backend-options.css +100 -70
  28. framework/static/css/fw.css +191 -25
  29. framework/static/js/backend-options.js +13 -29
  30. framework/static/js/fw.js +36 -34
  31. framework/static/js/option-types.js +1 -1
  32. framework/views/backend-tabs.php +1 -1
  33. readme.txt +20 -1
  34. unyson.php +1 -1
README.md CHANGED
@@ -44,7 +44,9 @@ A good bug report includes full details to easily understand the issue you are h
44
 
45
  ## Documentation
46
 
47
- Unyson's documentation is available on http://manual.unyson.io/
 
 
48
 
49
  ## Copyright and license
50
 
@@ -78,6 +80,7 @@ If you have a bug report or feature request related to a specific extension, fol
78
  * [Breadcrumbs](https://github.com/ThemeFuse/Unyson-Breadcrumbs-Extension)
79
  * [Learning](https://github.com/ThemeFuse/Unyson-Learning-Extension)
80
  * [Events](https://github.com/ThemeFuse/Unyson-Events-Extension)
 
81
  * [Update](https://github.com/ThemeFuse/Unyson-Update-Extension)
82
  * [Analytics](https://github.com/ThemeFuse/Unyson-Analytics-Extension)
83
  * [Builder](https://github.com/ThemeFuse/Unyson-Builder-Extension)
44
 
45
  ## Documentation
46
 
47
+ Unyson's documentation is available on http://manual.unyson.io/.
48
+
49
+ You can help us improve the documentation by contributing to this [Github repository](https://github.com/ThemeFuse/Unyson-Documentation).
50
 
51
  ## Copyright and license
52
 
80
  * [Breadcrumbs](https://github.com/ThemeFuse/Unyson-Breadcrumbs-Extension)
81
  * [Learning](https://github.com/ThemeFuse/Unyson-Learning-Extension)
82
  * [Events](https://github.com/ThemeFuse/Unyson-Events-Extension)
83
+ * [Translation](https://github.com/ThemeFuse/Unyson-Translation-Extension)
84
  * [Update](https://github.com/ThemeFuse/Unyson-Update-Extension)
85
  * [Analytics](https://github.com/ThemeFuse/Unyson-Analytics-Extension)
86
  * [Builder](https://github.com/ThemeFuse/Unyson-Builder-Extension)
framework/bootstrap-helpers.php CHANGED
@@ -11,14 +11,31 @@ function fw_fix_path($path) {
11
  return untrailingslashit( str_replace(array('//', '\\'), array('/', '/'), $path) );
12
  }
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  /** Child theme related functions */
15
  {
16
  /**
17
- * Full path to the child-theme/framework-customizations directory
 
 
18
  */
19
  function fw_get_stylesheet_customizations_directory($rel_path = '') {
20
  if (is_child_theme()) {
21
- return get_stylesheet_directory() . '/framework-customizations' . $rel_path;
22
  } else {
23
  // check is_child_theme() before using this function
24
  return null;
@@ -26,11 +43,13 @@ function fw_fix_path($path) {
26
  }
27
 
28
  /**
29
- * URI to the child-theme/framework-customizations directory
 
 
30
  */
31
  function fw_get_stylesheet_customizations_directory_uri($rel_path = '') {
32
  if (is_child_theme()) {
33
- return get_stylesheet_directory_uri() . '/framework-customizations' . $rel_path;
34
  } else {
35
  // check is_child_theme() before using this function
36
  return null;
@@ -41,17 +60,33 @@ function fw_fix_path($path) {
41
  /** Parent theme related functions */
42
  {
43
  /**
44
- * Full path to the parent-theme/framework-customizations directory
 
 
45
  */
46
  function fw_get_template_customizations_directory($rel_path = '') {
47
- return get_template_directory() .'/framework-customizations'. $rel_path;
 
 
 
 
 
 
48
  }
49
 
50
  /**
51
- * URI to the parent-theme/framework-customizations directory
 
 
52
  */
53
  function fw_get_template_customizations_directory_uri($rel_path = '') {
54
- return get_template_directory_uri() .'/framework-customizations'. $rel_path;
 
 
 
 
 
 
55
  }
56
  }
57
 
@@ -59,15 +94,31 @@ function fw_fix_path($path) {
59
  {
60
  /**
61
  * Full path to the parent-theme/framework directory
 
 
62
  */
63
  function fw_get_framework_directory($rel_path = '') {
64
- return apply_filters('fw_framework_directory', dirname(__FILE__)) . $rel_path;
 
 
 
 
 
 
65
  }
66
 
67
  /**
68
  * URI to the parent-theme/framework directory
 
 
69
  */
70
  function fw_get_framework_directory_uri($rel_path = '') {
71
- return apply_filters('fw_framework_directory_uri', get_template_directory_uri() .'/framework') . $rel_path;
 
 
 
 
 
 
72
  }
73
  }
11
  return untrailingslashit( str_replace(array('//', '\\'), array('/', '/'), $path) );
12
  }
13
 
14
+ /**
15
+ * Relative path of the framework customizations directory
16
+ * @param string $append
17
+ * @return string
18
+ */
19
+ function fw_get_framework_customizations_dir_rel_path($append = '') {
20
+ static $cache = null;
21
+
22
+ if ($cache === null) {
23
+ $cache = apply_filters('fw_framework_customizations_dir_rel_path', '/framework-customizations');
24
+ }
25
+
26
+ return $cache . $append;
27
+ }
28
+
29
  /** Child theme related functions */
30
  {
31
  /**
32
+ * Full path to the child-theme framework customizations directory
33
+ * @param string $rel_path
34
+ * @return null|string
35
  */
36
  function fw_get_stylesheet_customizations_directory($rel_path = '') {
37
  if (is_child_theme()) {
38
+ return get_stylesheet_directory() . fw_get_framework_customizations_dir_rel_path($rel_path);
39
  } else {
40
  // check is_child_theme() before using this function
41
  return null;
43
  }
44
 
45
  /**
46
+ * URI to the child-theme framework customizations directory
47
+ * @param string $rel_path
48
+ * @return null|string
49
  */
50
  function fw_get_stylesheet_customizations_directory_uri($rel_path = '') {
51
  if (is_child_theme()) {
52
+ return get_stylesheet_directory_uri() . fw_get_framework_customizations_dir_rel_path($rel_path);
53
  } else {
54
  // check is_child_theme() before using this function
55
  return null;
60
  /** Parent theme related functions */
61
  {
62
  /**
63
+ * Full path to the parent-theme framework customizations directory
64
+ * @param string $rel_path
65
+ * @return string
66
  */
67
  function fw_get_template_customizations_directory($rel_path = '') {
68
+ static $cache = null;
69
+
70
+ if ($cache === null) {
71
+ $cache = get_template_directory() . fw_get_framework_customizations_dir_rel_path();
72
+ }
73
+
74
+ return $cache . $rel_path;
75
  }
76
 
77
  /**
78
+ * URI to the parent-theme framework customizations directory
79
+ * @param string $rel_path
80
+ * @return string
81
  */
82
  function fw_get_template_customizations_directory_uri($rel_path = '') {
83
+ static $cache = null;
84
+
85
+ if ($cache === null) {
86
+ $cache = get_template_directory_uri() . fw_get_framework_customizations_dir_rel_path();
87
+ }
88
+
89
+ return $cache . $rel_path;
90
  }
91
  }
92
 
94
  {
95
  /**
96
  * Full path to the parent-theme/framework directory
97
+ * @param string $rel_path
98
+ * @return string
99
  */
100
  function fw_get_framework_directory($rel_path = '') {
101
+ static $cache = null;
102
+
103
+ if ($cache === null) {
104
+ $cache = apply_filters('fw_framework_directory', dirname(__FILE__));
105
+ }
106
+
107
+ return $cache . $rel_path;
108
  }
109
 
110
  /**
111
  * URI to the parent-theme/framework directory
112
+ * @param string $rel_path
113
+ * @return string
114
  */
115
  function fw_get_framework_directory_uri($rel_path = '') {
116
+ static $cache = null;
117
+
118
+ if ($cache === null) {
119
+ $cache = apply_filters('fw_framework_directory_uri', get_template_directory_uri() . '/framework');
120
+ }
121
+
122
+ return $cache . $rel_path;
123
  }
124
  }
framework/core/components/backend.php CHANGED
@@ -253,6 +253,7 @@ final class _FW_Component_Backend
253
  'l10n' => array(
254
  'done' => __('Done', 'fw'),
255
  'ah_sorry' => __('Ah, Sorry', 'fw'),
 
256
  ),
257
  ));
258
  }
@@ -602,15 +603,14 @@ final class _FW_Component_Backend
602
 
603
  $old_values = (array)fw_get_db_post_option($post_id);
604
 
605
- $options_values = array_merge(
606
- $old_values,
 
607
  fw_get_options_values_from_input(
608
  fw()->theme->get_post_options($post->post_type)
609
  )
610
  );
611
 
612
- fw_set_db_post_option($post_id, null, $options_values);
613
-
614
  do_action('fw_save_post_options', $post_id, $post, $old_values);
615
  }
616
 
@@ -702,12 +702,12 @@ final class _FW_Component_Backend
702
 
703
  $old_values = (array)fw_get_db_term_option($term_id, $taxonomy);
704
 
705
- fw_set_db_term_option($term_id, $taxonomy, null,
706
- array_merge(
707
- $old_values,
708
- fw_get_options_values_from_input(
709
- fw()->theme->get_taxonomy_options($taxonomy)
710
- )
711
  )
712
  );
713
 
@@ -716,6 +716,53 @@ final class _FW_Component_Backend
716
 
717
  public function _action_admin_enqueue_scripts()
718
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
719
  $this->register_static();
720
  }
721
 
@@ -898,7 +945,7 @@ final class _FW_Component_Backend
898
  public function _settings_form_validate($errors)
899
  {
900
  if (!current_user_can('manage_options')) {
901
- $errors['_no_permission'] = __('You have no permissions to change settings options', 'fw');;
902
  }
903
 
904
  return $errors;
@@ -918,11 +965,8 @@ final class _FW_Component_Backend
918
  } else { // The "Save" button was pressed
919
  fw_set_db_settings_option(
920
  null,
921
- array_merge(
922
- $old_values,
923
- fw_get_options_values_from_input(
924
- fw()->theme->get_settings_options()
925
- )
926
  )
927
  );
928
 
@@ -978,78 +1022,111 @@ final class _FW_Component_Backend
978
 
979
  fw_collect_first_level_options($collected, $options);
980
 
981
- ob_start();
982
-
983
- if (!empty($collected['tabs'])) {
984
- fw_render_view(fw_get_framework_directory('/views/backend-tabs.php'), array(
985
- 'tabs' => &$collected['tabs'],
986
- 'values' => &$values,
987
- 'options_data' => $options_data,
988
- ), false);
989
  }
990
- unset($collected['tabs']);
991
 
992
- if (!empty($collected['boxes'])) {
993
- echo '<div class="fw-backend-postboxes metabox-holder">';
994
 
995
- foreach ($collected['boxes'] as $id => &$box) {
996
- // prepare attributes
997
- {
998
- $attr = isset($box['attr']) ? $box['attr'] : array();
999
 
1000
- unset($attr['id']); // do not allow id overwrite, it is sent in first argument of render_box()
1001
- }
 
 
1002
 
1003
- echo $this->render_box(
1004
- 'fw-options-box-'. $id,
1005
- empty($box['title']) ? ' ' : $box['title'],
1006
- $this->render_options($box['options'], $values, $options_data),
1007
- array(
1008
- 'attr' => $attr
1009
- )
1010
- );
1011
- }
1012
 
1013
- echo '</div>';
1014
- }
1015
- unset($collected['boxes']);
 
 
 
1016
 
1017
- if (!empty($collected['groups_and_options'])) {
1018
- foreach ($collected['groups_and_options'] as $id => &$option) {
1019
- if (isset($option['options'])) { // group
1020
- // prepare attributes
1021
- {
1022
- $attr = isset($option['attr']) ? $option['attr'] : array();
 
 
 
 
 
 
 
 
 
 
 
 
1023
 
1024
- $attr['id'] = 'fw-backend-options-group-'. esc_attr($id);
 
 
 
 
 
 
 
 
1025
 
1026
- if (!isset($attr['class'])) {
1027
- $attr['class'] = 'fw-backend-options-group';
1028
- } else {
1029
- $attr['class'] = 'fw-backend-options-group '. $attr['class'];
 
 
 
 
 
 
 
 
 
 
 
1030
  }
1031
- }
1032
 
1033
- echo '<div '. fw_attr_to_html($attr) .'>';
1034
- echo $this->render_options($option['options'], $values, $options_data);
1035
- echo '</div>';
1036
- } else { // option
1037
- $data = $options_data;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1038
 
1039
- $data['value'] = isset($values[$id]) ? $values[$id] : null;
1040
 
1041
- echo $this->render_option(
1042
- $id,
1043
- $option,
1044
- $data,
1045
- $design
1046
- );
1047
- }
1048
  }
1049
  }
1050
- unset($collected['options']);
1051
 
1052
- return ob_get_clean();
1053
  }
1054
 
1055
  /**
@@ -1358,4 +1435,4 @@ final class FW_Option_Type_Undefined extends FW_Option_Type
1358
  'value' => array()
1359
  );
1360
  }
1361
- }
253
  'l10n' => array(
254
  'done' => __('Done', 'fw'),
255
  'ah_sorry' => __('Ah, Sorry', 'fw'),
256
+ 'save' => __('Save', 'fw'),
257
  ),
258
  ));
259
  }
603
 
604
  $old_values = (array)fw_get_db_post_option($post_id);
605
 
606
+ fw_set_db_post_option(
607
+ $post_id,
608
+ null,
609
  fw_get_options_values_from_input(
610
  fw()->theme->get_post_options($post->post_type)
611
  )
612
  );
613
 
 
 
614
  do_action('fw_save_post_options', $post_id, $post, $old_values);
615
  }
616
 
702
 
703
  $old_values = (array)fw_get_db_term_option($term_id, $taxonomy);
704
 
705
+ fw_set_db_term_option(
706
+ $term_id,
707
+ $taxonomy,
708
+ null,
709
+ fw_get_options_values_from_input(
710
+ fw()->theme->get_taxonomy_options($taxonomy)
711
  )
712
  );
713
 
716
 
717
  public function _action_admin_enqueue_scripts()
718
  {
719
+ global $current_screen, $plugin_page, $post;
720
+
721
+ /**
722
+ * Enqueue settings options static in <head>
723
+ */
724
+ {
725
+ if ($this->_get_settings_page_slug() === $plugin_page) {
726
+ fw()->backend->enqueue_options_static(
727
+ fw()->theme->get_settings_options()
728
+ );
729
+
730
+ do_action('fw_admin_enqueue_scripts:settings');
731
+ }
732
+ }
733
+
734
+ /**
735
+ * Enqueue post options static in <head>
736
+ */
737
+ {
738
+ if ('post' === $current_screen->base && $post) {
739
+ fw()->backend->enqueue_options_static(
740
+ fw()->theme->get_post_options($post->post_type)
741
+ );
742
+
743
+ do_action('fw_admin_enqueue_scripts:post', $post);
744
+ }
745
+ }
746
+
747
+ /**
748
+ * Enqueue term options static in <head>
749
+ */
750
+ {
751
+ if (
752
+ 'edit-tags' === $current_screen->base
753
+ &&
754
+ $current_screen->taxonomy
755
+ &&
756
+ !empty($_GET['tag_ID'])
757
+ ) {
758
+ fw()->backend->enqueue_options_static(
759
+ fw()->theme->get_taxonomy_options($current_screen->taxonomy)
760
+ );
761
+
762
+ do_action('fw_admin_enqueue_scripts:term', $current_screen->taxonomy);
763
+ }
764
+ }
765
+
766
  $this->register_static();
767
  }
768
 
945
  public function _settings_form_validate($errors)
946
  {
947
  if (!current_user_can('manage_options')) {
948
+ $errors['_no_permission'] = __('You have no permissions to change settings options', 'fw');
949
  }
950
 
951
  return $errors;
965
  } else { // The "Save" button was pressed
966
  fw_set_db_settings_option(
967
  null,
968
+ fw_get_options_values_from_input(
969
+ fw()->theme->get_settings_options()
 
 
 
970
  )
971
  );
972
 
1022
 
1023
  fw_collect_first_level_options($collected, $options);
1024
 
1025
+ if (empty($collected['all'])) {
1026
+ return false;
 
 
 
 
 
 
1027
  }
 
1028
 
1029
+ $html = '';
 
1030
 
1031
+ $option = reset($collected['all']);
 
 
 
1032
 
1033
+ $collected_type = $option['type'];
1034
+ $collected_type_options = array(
1035
+ $option['id'] => &$option['option']
1036
+ );
1037
 
1038
+ while ($collected_type_options) {
1039
+ $option = next($collected['all']);
 
 
 
 
 
 
 
1040
 
1041
+ if ($option) {
1042
+ if ($option['type'] === $collected_type) {
1043
+ $collected_type_options[$option['id']] = &$option['option'];
1044
+ continue;
1045
+ }
1046
+ }
1047
 
1048
+ switch ($collected_type) {
1049
+ case 'tab':
1050
+ $html .= fw_render_view(fw_get_framework_directory('/views/backend-tabs.php'), array(
1051
+ 'tabs' => &$collected_type_options,
1052
+ 'values' => &$values,
1053
+ 'options_data' => $options_data,
1054
+ ));
1055
+ break;
1056
+ case 'box':
1057
+ $html .= '<div class="fw-backend-postboxes metabox-holder">';
1058
+
1059
+ foreach ($collected_type_options as $id => &$box) {
1060
+ // prepare attributes
1061
+ {
1062
+ $attr = isset($box['attr']) ? $box['attr'] : array();
1063
+
1064
+ unset($attr['id']); // do not allow id overwrite, it is sent in first argument of render_box()
1065
+ }
1066
 
1067
+ $html .= $this->render_box(
1068
+ 'fw-options-box-'. $id,
1069
+ empty($box['title']) ? ' ' : $box['title'],
1070
+ $this->render_options($box['options'], $values, $options_data),
1071
+ array(
1072
+ 'attr' => $attr
1073
+ )
1074
+ );
1075
+ }
1076
 
1077
+ $html .= '</div>';
1078
+ break;
1079
+ case 'group':
1080
+ foreach ($collected_type_options as $id => &$group) {
1081
+ // prepare attributes
1082
+ {
1083
+ $attr = isset($group['attr']) ? $group['attr'] : array();
1084
+
1085
+ $attr['id'] = 'fw-backend-options-group-' . $id;
1086
+
1087
+ if (!isset($attr['class'])) {
1088
+ $attr['class'] = 'fw-backend-options-group';
1089
+ } else {
1090
+ $attr['class'] = 'fw-backend-options-group ' . $attr['class'];
1091
+ }
1092
  }
 
1093
 
1094
+ $html .= '<div ' . fw_attr_to_html($attr) . '>';
1095
+ $html .= $this->render_options($group['options'], $values, $options_data);
1096
+ $html .= '</div>';
1097
+ }
1098
+ break;
1099
+ case 'option':
1100
+ foreach ($collected_type_options as $id => &$_option) {
1101
+ $data = $options_data;
1102
+
1103
+ $data['value'] = isset($values[$id]) ? $values[$id] : null;
1104
+
1105
+ $html .= $this->render_option(
1106
+ $id,
1107
+ $_option,
1108
+ $data,
1109
+ $design
1110
+ );
1111
+ }
1112
+ break;
1113
+ default:
1114
+ $html .= '<p><em>'. __('Unknown collected type', 'fw') .': '. $collected_type .'</em></p>';
1115
+ }
1116
 
1117
+ unset($collected_type, $collected_type_options);
1118
 
1119
+ if ($option) {
1120
+ $collected_type = $option['type'];
1121
+ $collected_type_options = array(
1122
+ $option['id'] => &$option['option']
1123
+ );
1124
+ } else {
1125
+ $collected_type_options = array();
1126
  }
1127
  }
 
1128
 
1129
+ return $html;
1130
  }
1131
 
1132
  /**
1435
  'value' => array()
1436
  );
1437
  }
1438
+ }
framework/core/components/extensions.php CHANGED
@@ -38,13 +38,6 @@ final class _FW_Component_Extensions
38
  */
39
  private static $active_extensions_tree = array();
40
 
41
- /**
42
- * Used on extensions load
43
- * Indicates from where are currently extensions loaded
44
- * @var string|null framework|parent|child
45
- */
46
- private static $current_declaring_source;
47
-
48
  /**
49
  * @var array { 'extension_name' => array('required_by', 'required_by') }
50
  */
@@ -104,30 +97,70 @@ final class _FW_Component_Extensions
104
  /**
105
  * Load extension from directory
106
  *
107
- * @param null|FW_Extension $parent
108
- * @param array $all_extensions_tree
109
- * @param FW_Extension[] $all_extensions
110
- * @param string $dir
111
- * @param string $URI
112
- * @var int $current_depth
113
  */
114
- private static function load_extensions($dir, &$parent, &$all_extensions_tree, &$all_extensions, $URI, $current_depth)
115
  {
116
- $dirs = glob($dir .'/*', GLOB_ONLYDIR);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  if (empty($dirs)) {
119
  return;
120
  }
121
 
 
 
 
 
 
 
 
 
 
 
122
  foreach ($dirs as $extension_dir) {
123
  $extension_name = basename($extension_dir);
124
 
125
- if (isset($all_extensions[$extension_name])) {
126
- if ($all_extensions[$extension_name]->get_parent() !== $parent) {
 
 
 
 
 
 
 
 
127
  // extension with the same name exists in another tree
128
  trigger_error(
129
  'Extension "'. $extension_name .'" is already defined '.
130
- 'in "'. $all_extensions[$extension_name]->get_declared_path() .'" '.
131
  'found again in "'. $extension_dir .'"',
132
  E_USER_ERROR
133
  );
@@ -135,28 +168,28 @@ final class _FW_Component_Extensions
135
 
136
  // this is a directory with customizations for an extension
137
 
138
- self::load_extensions(
139
- $extension_dir .'/extensions',
140
- $all_extensions[$extension_name],
141
- $all_extensions_tree[$extension_name],
142
- $all_extensions,
143
- $URI,
144
- $current_depth + 1
145
- );
 
 
146
  } else {
147
  $class_file_name = 'class-fw-extension-'. $extension_name .'.php';
148
 
149
  if (file_exists($extension_dir .'/manifest.php')) {
150
- $all_extensions_tree[$extension_name] = array();
151
 
152
- self::$extension_to_all_tree[$extension_name] =& $all_extensions_tree[$extension_name];
153
 
154
- if (file_exists($extension_dir .'/'. $class_file_name)) {
155
  $class_name = 'FW_Extension_'. fw_dirname_to_classname($extension_name);
156
-
157
- require $extension_dir .'/'. $class_file_name;
158
  } else {
159
- $parent_class_name = get_class($parent);
160
  // check if parent extension has been defined custom Default class for its child extensions
161
  if (class_exists($parent_class_name .'_Default')) {
162
  $class_name = $parent_class_name .'_Default';
@@ -169,13 +202,13 @@ final class _FW_Component_Extensions
169
  trigger_error('Extension "'. $extension_name .'" must extend FW_Extension class', E_USER_ERROR);
170
  }
171
 
172
- $all_extensions[$extension_name] = new $class_name(
173
- $parent,
174
- $extension_dir,
175
- self::$current_declaring_source,
176
- $URI,
177
- $current_depth
178
- );
179
  } else {
180
  /**
181
  * The manifest file does not exist, do not load this extension.
@@ -184,14 +217,16 @@ final class _FW_Component_Extensions
184
  continue;
185
  }
186
 
187
- self::load_extensions(
188
- $all_extensions[$extension_name]->get_declared_path() .'/extensions',
189
- $all_extensions[$extension_name],
190
- $all_extensions_tree[$extension_name],
191
- $all_extensions,
192
- $URI,
193
- $current_depth + 1
194
- );
 
 
195
  }
196
  }
197
  }
@@ -211,63 +246,18 @@ final class _FW_Component_Extensions
211
  $extension = fw()->extensions->get($extension);
212
  }
213
 
214
- list(
215
- $search_in_framework,
216
- $search_in_parent_theme,
217
- $search_in_child_theme
218
- ) = $extension->correct_search_in_locations(
219
- true,
220
- true,
221
- true
222
- );
223
-
224
- $rel_path = $extension->get_rel_path() . $file_rel_path;
225
-
226
- {
227
- $paths = array();
228
 
229
- if ($search_in_framework) {
230
- if (file_exists($path = fw_get_framework_directory('/extensions'. $rel_path))) {
231
- $paths['framework'] = $path;
232
- }
233
- }
234
- if ($search_in_parent_theme) {
235
- if (file_exists($path = fw_get_template_customizations_directory( '/extensions' . $rel_path ))) {
236
- $paths['parent'] = $path;
237
- }
238
- }
239
- if ($search_in_child_theme) {
240
- if (file_exists($path = fw_get_stylesheet_customizations_directory('/extensions'. $rel_path))) {
241
- $paths['child'] = $path;
242
- }
243
- }
244
-
245
- if (empty($paths)) {
246
- return;
247
- }
248
-
249
- if ($themeFirst) {
250
- $paths = array_reverse($paths);
251
- }
252
  }
253
 
254
- if ($onlyFirstFound) {
255
- $path = array_shift($paths);
256
-
257
- /**
258
- * This is not a view render, just used this function to include file isolated and send it some variables
259
- */
260
- fw_render_view($path, array(
261
- /**
262
- * For e.g. you overwrite in the child theme a file located in the framework,
263
- * but still want to include the original file from the framework.
264
- * You can accomplish that by including it manually using the $other_existing_paths variable.
265
- */
266
- 'other_existing_paths' => $paths
267
- ), false);
268
- } else {
269
- foreach ($paths as $location_name => $path) {
270
- fw_include_file_isolated($path);
271
  }
272
  }
273
  }
@@ -286,34 +276,15 @@ final class _FW_Component_Extensions
286
  $extension = fw()->extensions->get($extension);
287
  }
288
 
289
- list(
290
- $search_in_framework,
291
- $search_in_parent_theme,
292
- $search_in_child_theme
293
- ) = $extension->correct_search_in_locations(
294
- true,
295
- true,
296
- true
297
- );
298
-
299
- $rel_path = $extension->get_rel_path() . $dir_rel_path;
300
 
301
- {
302
- $paths = array();
303
-
304
- if ($search_in_framework) {
305
- $paths[] = fw_get_framework_directory('/extensions'. $rel_path);
306
- }
307
- if ($search_in_parent_theme) {
308
- $paths[] = fw_get_template_customizations_directory('/extensions'. $rel_path);
309
- }
310
- if ($search_in_child_theme) {
311
- $paths[] = fw_get_stylesheet_customizations_directory('/extensions'. $rel_path);
312
- }
313
  }
314
 
315
- foreach ($paths as $path) {
316
- if ($files = glob($path .'/*.php')) {
317
  foreach ($files as $dir_file_path) {
318
  fw_include_file_isolated($dir_file_path);
319
  }
@@ -323,41 +294,50 @@ final class _FW_Component_Extensions
323
 
324
  private function load_all_extensions()
325
  {
326
- $parent = null;
327
-
328
- self::$current_declaring_source = 'framework';
329
- self::load_extensions(
330
- fw_get_framework_directory('/extensions'),
331
- $parent,
332
- self::$all_extensions_tree,
333
- self::$all_extensions,
334
- fw_get_framework_directory_uri('/extensions'),
335
- 1
336
- );
337
-
338
- self::$current_declaring_source = 'parent';
339
- self::load_extensions(
340
- fw_get_template_customizations_directory('/extensions'),
341
- $parent,
342
- self::$all_extensions_tree,
343
- self::$all_extensions,
344
- fw_get_template_customizations_directory_uri('/extensions'),
345
- 1
346
- );
347
 
348
- if (is_child_theme()) {
349
- self::$current_declaring_source = 'child';
350
- self::load_extensions(
351
- fw_get_stylesheet_customizations_directory('/extensions'),
352
- $parent,
353
- self::$all_extensions_tree,
354
- self::$all_extensions,
355
- fw_get_stylesheet_customizations_directory_uri('/extensions'),
356
- 1
357
- );
358
  }
359
 
360
- self::$current_declaring_source = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  }
362
 
363
  /**
@@ -583,66 +563,20 @@ final class _FW_Component_Extensions
583
  }
584
 
585
  /**
586
- * Search relative path in: child theme -> parent theme -> framework extensions directory and return full path
587
- *
588
- * @param string $rel_path '/<extension>/path_to_dir' or '/<extension>/extensions/<another_extension>/path_to_file.php'
589
- * @param bool $search_in_framework
590
- * @param bool $search_in_parent_theme
591
- * @param bool $search_in_child_theme
592
- * @return false|string Full path or false if not found
593
  */
594
- public function locate_path($rel_path, $search_in_framework = true, $search_in_parent_theme = true, $search_in_child_theme = true)
595
  {
596
- if ($search_in_child_theme && is_child_theme()) {
597
- if (file_exists(fw_get_stylesheet_customizations_directory('/extensions'. $rel_path))) {
598
- return fw_get_stylesheet_customizations_directory('/extensions'. $rel_path);
599
- }
600
- }
601
-
602
- if ($search_in_parent_theme) {
603
- if (file_exists(fw_get_template_customizations_directory('/extensions'. $rel_path))) {
604
- return fw_get_template_customizations_directory('/extensions'. $rel_path);
605
- }
606
- }
607
-
608
- if ($search_in_framework) {
609
- if (file_exists(fw_get_framework_directory('/extensions'. $rel_path))) {
610
- return fw_get_framework_directory('/extensions'. $rel_path);
611
- }
612
- }
613
-
614
  return false;
615
  }
616
 
617
  /**
618
- * Search relative path in: child theme -> parent theme -> framework extensions directory and return URI
619
- *
620
- * @param string $rel_path '/<extension>/path_to_dir' or '/<extension>/extensions/<another_extension>/path_to_file.php'
621
- * @param bool $search_in_framework
622
- * @param bool $search_in_parent_theme
623
- * @param bool $search_in_child_theme
624
- * @return false|string URI or false if not found
625
  */
626
- public function locate_path_URI($rel_path, $search_in_framework = true, $search_in_parent_theme = true, $search_in_child_theme = true)
627
  {
628
- if ($search_in_child_theme && is_child_theme()) {
629
- if (file_exists(fw_get_stylesheet_customizations_directory('/extensions'. $rel_path))) {
630
- return fw_get_stylesheet_customizations_directory_uri('/extensions' . $rel_path);
631
- }
632
- }
633
-
634
- if ($search_in_parent_theme) {
635
- if (file_exists(fw_get_template_customizations_directory('/extensions'. $rel_path))) {
636
- return fw_get_template_customizations_directory_uri('/extensions'. $rel_path);
637
- }
638
- }
639
-
640
- if ($search_in_framework) {
641
- if (file_exists(fw_get_framework_directory('/extensions'. $rel_path))) {
642
- return fw_get_framework_directory_uri('/extensions'. $rel_path);
643
- }
644
- }
645
-
646
  return false;
647
  }
648
  }
38
  */
39
  private static $active_extensions_tree = array();
40
 
 
 
 
 
 
 
 
41
  /**
42
  * @var array { 'extension_name' => array('required_by', 'required_by') }
43
  */
97
  /**
98
  * Load extension from directory
99
  *
100
+ * @param array $data
 
 
 
 
 
101
  */
102
+ private static function load_extensions($data)
103
  {
104
+ if (false) {
105
+ $data = array(
106
+ 'path' => '/path/to/extension',
107
+ 'uri' => 'https://uri.to/extension',
108
+ 'customizations_locations' => array(
109
+ '/path/to/parent/theme/customizations/extensions/ext/rel/path' => 'https://uri.to/customization/path',
110
+ '/path/to/child/theme/customizations/extensions/ext/rel/path' => 'https://uri.to/customization/path',
111
+ ),
112
+
113
+ 'all_extensions_tree' => array(),
114
+ 'all_extensions' => array(),
115
+ 'current_depth' => 1,
116
+ 'parent' => '&$parent_extension_instance',
117
+ );
118
+ }
119
+
120
+ /**
121
+ * Do not check all keys
122
+ * if one not set, then sure others are not set (this is a private method)
123
+ */
124
+ if (!isset($data['all_extensions_tree'])) {
125
+ $data['all_extensions_tree'] = &self::$all_extensions_tree;
126
+ $data['all_extensions'] = &self::$all_extensions;
127
+ $data['current_depth'] = 1;
128
+ $data['parent'] = null;
129
+ }
130
+
131
+ $dirs = glob($data['path'] .'/*', GLOB_ONLYDIR);
132
 
133
  if (empty($dirs)) {
134
  return;
135
  }
136
 
137
+ if ($data['current_depth'] > 1) {
138
+ $customizations_locations = array();
139
+
140
+ foreach ($data['customizations_locations'] as $customization_path => $customization_uri) {
141
+ $customizations_locations[ $customization_path .'/extensions' ] = $customization_uri .'/extensions';
142
+ }
143
+
144
+ $data['customizations_locations'] = $customizations_locations;
145
+ }
146
+
147
  foreach ($dirs as $extension_dir) {
148
  $extension_name = basename($extension_dir);
149
 
150
+ {
151
+ $customizations_locations = array();
152
+
153
+ foreach ($data['customizations_locations'] as $customization_path => $customization_uri) {
154
+ $customizations_locations[ $customization_path .'/'. $extension_name ] = $customization_uri .'/'. $extension_name;
155
+ }
156
+ }
157
+
158
+ if (isset($data['all_extensions'][$extension_name])) {
159
+ if ($data['all_extensions'][$extension_name]->get_parent() !== $data['parent']) {
160
  // extension with the same name exists in another tree
161
  trigger_error(
162
  'Extension "'. $extension_name .'" is already defined '.
163
+ 'in "'. $data['all_extensions'][$extension_name]->get_declared_path() .'" '.
164
  'found again in "'. $extension_dir .'"',
165
  E_USER_ERROR
166
  );
168
 
169
  // this is a directory with customizations for an extension
170
 
171
+ self::load_extensions(array(
172
+ 'path' => $data['path'] .'/extensions',
173
+ 'uri' => $data['uri'] .'/extensions',
174
+ 'customizations_locations' => $customizations_locations,
175
+
176
+ 'all_extensions_tree' => &$data['all_extensions_tree'][$extension_name],
177
+ 'all_extensions' => &$data['all_extensions'],
178
+ 'current_depth' => $data['current_depth'] + 1,
179
+ 'parent' => &$data['all_extensions'][$extension_name],
180
+ ));
181
  } else {
182
  $class_file_name = 'class-fw-extension-'. $extension_name .'.php';
183
 
184
  if (file_exists($extension_dir .'/manifest.php')) {
185
+ $data['all_extensions_tree'][$extension_name] = array();
186
 
187
+ self::$extension_to_all_tree[$extension_name] = &$data['all_extensions_tree'][$extension_name];
188
 
189
+ if (fw_include_file_isolated($extension_dir .'/'. $class_file_name)) {
190
  $class_name = 'FW_Extension_'. fw_dirname_to_classname($extension_name);
 
 
191
  } else {
192
+ $parent_class_name = get_class($data['parent']);
193
  // check if parent extension has been defined custom Default class for its child extensions
194
  if (class_exists($parent_class_name .'_Default')) {
195
  $class_name = $parent_class_name .'_Default';
202
  trigger_error('Extension "'. $extension_name .'" must extend FW_Extension class', E_USER_ERROR);
203
  }
204
 
205
+ $data['all_extensions'][$extension_name] = new $class_name(array(
206
+ 'path' => $data['path'] .'/'. $extension_name,
207
+ 'uri' => $data['uri'] .'/'. $extension_name,
208
+ 'parent' => $data['parent'],
209
+ 'depth' => $data['current_depth'],
210
+ 'customizations_locations' => $customizations_locations,
211
+ ));
212
  } else {
213
  /**
214
  * The manifest file does not exist, do not load this extension.
217
  continue;
218
  }
219
 
220
+ self::load_extensions(array(
221
+ 'path' => $data['all_extensions'][$extension_name]->get_path() .'/extensions',
222
+ 'uri' => $data['all_extensions'][$extension_name]->get_uri() .'/extensions',
223
+ 'customizations_locations' => $customizations_locations,
224
+
225
+ 'parent' => &$data['all_extensions'][$extension_name],
226
+ 'all_extensions_tree' => &$data['all_extensions_tree'][$extension_name],
227
+ 'all_extensions' => &$data['all_extensions'],
228
+ 'current_depth' => $data['current_depth'] + 1,
229
+ ));
230
  }
231
  }
232
  }
246
  $extension = fw()->extensions->get($extension);
247
  }
248
 
249
+ $paths = $extension->get_customizations_locations();
250
+ $paths[$extension->get_path()] = $extension->get_uri();
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
+ if (!$themeFirst) {
253
+ $paths = array_reverse($paths);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  }
255
 
256
+ foreach ($paths as $path => $uri) {
257
+ if (fw_include_file_isolated($path . $file_rel_path)) {
258
+ if ($onlyFirstFound) {
259
+ return;
260
+ }
 
 
 
 
 
 
 
 
 
 
 
 
261
  }
262
  }
263
  }
276
  $extension = fw()->extensions->get($extension);
277
  }
278
 
279
+ $paths = $extension->get_customizations_locations();
280
+ $paths[$extension->get_path()] = $extension->get_uri();
 
 
 
 
 
 
 
 
 
281
 
282
+ if (!$themeFirst) {
283
+ $paths = array_reverse($paths);
 
 
 
 
 
 
 
 
 
 
284
  }
285
 
286
+ foreach ($paths as $path => $uri) {
287
+ if ($files = glob($path . $dir_rel_path .'/*.php')) {
288
  foreach ($files as $dir_file_path) {
289
  fw_include_file_isolated($dir_file_path);
290
  }
294
 
295
  private function load_all_extensions()
296
  {
297
+ {
298
+ $customizations_locations = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
+ if (is_child_theme()) {
301
+ $customizations_locations[fw_get_stylesheet_customizations_directory('/extensions')]
302
+ = fw_get_stylesheet_customizations_directory_uri('/extensions');
303
+ }
304
+
305
+ $customizations_locations[fw_get_template_customizations_directory('/extensions')]
306
+ = fw_get_template_customizations_directory_uri('/extensions');
 
 
 
307
  }
308
 
309
+ self::load_extensions(array(
310
+ 'path' => fw_get_framework_directory('/extensions'),
311
+ 'uri' => fw_get_framework_directory_uri('/extensions'),
312
+ 'customizations_locations' => $customizations_locations,
313
+ ));
314
+
315
+ /**
316
+ * { '/hello/world/extensions' => 'https://hello.com/world/extensions' }
317
+ */
318
+ foreach (apply_filters('fw_extensions_locations', array()) as $path => $uri) {
319
+ self::load_extensions(array(
320
+ 'path' => $path,
321
+ 'uri' => $uri,
322
+ 'customizations_locations' => $customizations_locations,
323
+ ));
324
+ }
325
+
326
+ array_pop($customizations_locations);
327
+ self::load_extensions(array(
328
+ 'path' => fw_get_template_customizations_directory('/extensions'),
329
+ 'uri' => fw_get_template_customizations_directory_uri('/extensions'),
330
+ 'customizations_locations' => $customizations_locations,
331
+ ));
332
+
333
+ if (is_child_theme()) {
334
+ array_pop($customizations_locations);
335
+ self::load_extensions(array(
336
+ 'path' => fw_get_stylesheet_customizations_directory('/extensions'),
337
+ 'uri' => fw_get_stylesheet_customizations_directory_uri('/extensions'),
338
+ 'customizations_locations' => $customizations_locations,
339
+ ));
340
+ }
341
  }
342
 
343
  /**
563
  }
564
 
565
  /**
566
+ * @return false
567
+ * @deprecated Use $extension->locate_path()
 
 
 
 
 
568
  */
569
+ public function locate_path()
570
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
  return false;
572
  }
573
 
574
  /**
575
+ * @return false
576
+ * @deprecated Use $extension->locate_URI()
 
 
 
 
 
577
  */
578
+ public function locate_path_URI()
579
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  return false;
581
  }
582
  }
framework/core/components/extensions/manager/available-extensions.php CHANGED
@@ -246,4 +246,16 @@ $extensions = array(
246
  ),
247
  ),
248
  ),
 
 
 
 
 
 
 
 
 
 
 
 
249
  );
246
  ),
247
  ),
248
  ),
249
+ /*'translation' => array(
250
+ 'display' => true,
251
+ 'parent' => null,
252
+ 'name' => __( 'Translations', 'fw' ),
253
+ 'description' => __( 'This extension lets you translate your website in any language or even add multiple languages for your users to change at their will from the front-end.', 'fw' ),
254
+ 'thumbnail' => $thumbnails_uri . '/translation.jpg',
255
+ 'download' => array(
256
+ 'github' => array(
257
+ 'user_repo' => $github_account . '/Unyson-Translation-Extension',
258
+ ),
259
+ ),
260
+ ),*/
261
  );
framework/core/components/extensions/manager/class--fw-extensions-manager.php CHANGED
@@ -54,7 +54,6 @@ final class _FW_Extensions_Manager
54
  if ($this->can_install()) {
55
  add_action('wp_ajax_fw_extensions_check_direct_fs_access', array($this, '_action_ajax_check_direct_fs_access'));
56
  }
57
-
58
  }
59
 
60
  /** Filters */
@@ -70,7 +69,7 @@ final class _FW_Extensions_Manager
70
  * - save extension settings options
71
  * @return bool
72
  */
73
- private function can_activate()
74
  {
75
  static $can_activate = null;
76
 
@@ -97,7 +96,7 @@ final class _FW_Extensions_Manager
97
  * - delete extensions
98
  * @return bool
99
  */
100
- private function can_install()
101
  {
102
  static $can_install = null;
103
 
@@ -118,7 +117,7 @@ final class _FW_Extensions_Manager
118
  return $can_install;
119
  }
120
 
121
- private function get_page_slug()
122
  {
123
  return 'fw-extensions';
124
  }
@@ -220,7 +219,7 @@ final class _FW_Extensions_Manager
220
  public function _action_after_plugin_activate()
221
  {
222
  $this->activate_theme_extensions();
223
- $this->activate_extensions_if_exists(
224
  array_fill_keys(
225
  array_keys(fw()->theme->manifest->get('supported_extensions', array())),
226
  array()
@@ -451,6 +450,63 @@ final class _FW_Extensions_Manager
451
  'validate' => array($this, '_extension_settings_form_validate'),
452
  'save' => array($this, '_extension_settings_form_save'),
453
  ));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  }
455
 
456
  /**
@@ -502,12 +558,21 @@ final class _FW_Extensions_Manager
502
 
503
  if (is_wp_error($collected)) {
504
  if (defined('WP_DEBUG') && WP_DEBUG) {
505
- FW_Flash_Messages::add('fw_ext_auto_activate_hidden_standalone',
506
- sprintf(__('Cannot activate hidden standalone extension %s', 'fw'),
507
- fw_akg('name', $ext_data['manifest'], fw_id_to_title($ext_name))
508
- ),
509
- 'error'
510
- );
 
 
 
 
 
 
 
 
 
511
  }
512
  return;
513
  }
@@ -538,10 +603,10 @@ final class _FW_Extensions_Manager
538
  }
539
 
540
  $data = array(
541
- 'title' => fw()->manifest->get_name(),
542
- 'capability' => $capability,
543
- 'slug' => $this->get_page_slug(),
544
- 'content_callback' => array($this, '_display_page'),
545
  );
546
 
547
  /**
@@ -755,44 +820,32 @@ final class _FW_Extensions_Manager
755
  return;
756
  }
757
 
758
- $installed_extensions = $this->get_installed_extensions();
759
-
760
  if (array_key_exists('supported', $_GET)) {
761
  $supported = true;
762
-
763
- $this->activate_extensions_if_exists(
764
- array_fill_keys(
765
- array_keys(fw()->theme->manifest->get('supported_extensions', array())),
766
- array()
767
- )
768
  );
769
 
770
- $install_data = $this->get_install_data(
771
- array_keys($this->get_supported_extensions_for_install())
772
- );
 
 
 
 
 
 
773
  } else {
774
  $supported = false;
775
 
776
- $extension_names = array_map( 'trim', explode( ',', FW_Request::GET( 'extension', '' ) ));
777
-
778
- $this->activate_extensions_if_exists(
779
- array_fill_keys(
780
- $extension_names,
781
- array()
782
- )
783
- );
784
-
785
- $install_data = $this->get_install_data(
786
- $extension_names
787
  );
788
 
789
- unset($extension_names);
790
- }
791
-
792
- if (is_wp_error($install_data)) {
793
- FW_Flash_Messages::add($flash_id, $install_data->get_error_message(), 'error');
794
- $this->js_redirect();
795
- return;
796
  }
797
 
798
  {
@@ -804,8 +857,8 @@ final class _FW_Extensions_Manager
804
 
805
  $skin = new _FW_Extensions_Install_Upgrader_Skin(array(
806
  'title' => $supported
807
- ? _n('Install Compatible Extension', 'Install Compatible Extensions', count($install_data['all']), 'fw')
808
- : _n('Install Extension', 'Install Extensions', count($install_data['all']), 'fw'),
809
  ));
810
  }
811
 
@@ -825,188 +878,25 @@ final class _FW_Extensions_Manager
825
  break;
826
  }
827
 
828
- // increase timeout
829
- if (
830
- function_exists('set_time_limit')
831
- &&
832
- function_exists('ini_get')
833
- &&
834
- ($timeout = intval(ini_get('max_execution_time')))
835
- ) {
836
- $extensions_count = 0;
837
- foreach ($install_data['parents'] as $extension_name => $parent_extensions) {
838
- $extensions_count += count($parent_extensions);
839
- }
840
-
841
- set_time_limit($timeout + $extensions_count * 10);
842
- }
843
-
844
- $available_extensions = $this->get_available_extensions();
845
-
846
- $extensions_before_install = array_keys($installed_extensions);
847
-
848
- $activate_extensions = array();
849
-
850
- do {
851
- /**
852
- * Install parent extensions and the extension
853
- */
854
- foreach ($install_data['parents'] as $extension_name => $parent_extensions) {
855
- $current_extension_path = fw_get_framework_directory();
856
-
857
- foreach ($parent_extensions as $parent_extension_name) {
858
- $current_extension_path .= '/extensions/'. $parent_extension_name;
859
-
860
- if (isset($installed_extensions[$parent_extension_name])) {
861
- // skip already installed extensions
862
- $activate_extensions[$parent_extension_name] = array();
863
- continue;
864
- }
865
-
866
- $skin->feedback(
867
- sprintf(__('Downloading the "%s" extension...', 'fw'),
868
- $install_data['all'][$parent_extension_name]
869
- )
870
- );
871
-
872
- $wp_fw_downloaded_dir = $this->download(
873
- $parent_extension_name,
874
- $available_extensions[$parent_extension_name]
875
- );
876
-
877
- if (is_wp_error($wp_fw_downloaded_dir)) {
878
- $skin->error($wp_fw_downloaded_dir);
879
- break 3;
880
- }
881
-
882
- $skin->feedback(
883
- sprintf(__('Installing the "%s" extension...', 'fw'),
884
- $install_data['all'][$parent_extension_name]
885
- )
886
- );
887
-
888
- $merge_result = $this->merge_extension(
889
- $wp_fw_downloaded_dir,
890
- FW_WP_Filesystem::real_path_to_filesystem_path($current_extension_path)
891
- );
892
-
893
- if (is_wp_error($merge_result)) {
894
- $skin->error($merge_result);
895
- break 3;
896
- }
897
-
898
- $skin->feedback(
899
- sprintf(__('%s extension successfully installed.', 'fw'),
900
- $install_data['all'][$parent_extension_name]
901
- )
902
- );
903
 
904
- $activate_extensions[$parent_extension_name] = array();
 
 
 
905
 
906
- /**
907
- * Read again all extensions
908
- * The downloaded extension may contain more sub extensions
909
- */
910
- {
911
- unset($installed_extensions);
912
- $installed_extensions = $this->get_installed_extensions( true );
913
- }
914
  }
915
  }
916
 
917
- $install_data = false;
918
-
919
- /**
920
- * Collect newly installed extensions required extensions
921
- */
922
- do {
923
- $new_extensions = array_diff(
924
- array_keys($installed_extensions),
925
- $extensions_before_install
926
- );
927
-
928
- if (empty($new_extensions)) {
929
- break;
930
- }
931
-
932
- $activate_extensions += array_fill_keys($new_extensions, array());
933
-
934
- $extensions_before_install = array_keys($installed_extensions);
935
-
936
- $required_extensions = array();
937
-
938
- foreach ($new_extensions as $extension_name) {
939
- $extension_required_extensions = fw_akg(
940
- 'requirements/extensions',
941
- $installed_extensions[$extension_name]['manifest'],
942
- array()
943
- );
944
-
945
- if (empty($extension_required_extensions)) {
946
- continue;
947
- }
948
-
949
- foreach ($extension_required_extensions as $required_extension_name => $required_extension_data) {
950
- $activate_extensions[$required_extension_name] = array();
951
-
952
- if (isset($installed_extensions[$required_extension_name])) {
953
- continue;
954
- }
955
-
956
- $required_extensions[$required_extension_name] = true;
957
- }
958
- }
959
-
960
- if (empty($required_extensions)) {
961
- break;
962
- }
963
-
964
- $required_extensions = array_keys($required_extensions);
965
-
966
- $install_data = $this->get_install_data($required_extensions);
967
-
968
- if (is_wp_error($install_data)) {
969
- $skin->feedback(
970
- sprintf(
971
- _n(
972
- 'Installed extensions has required extension: %s.',
973
- 'Installed extensions has required extensions: %s.',
974
- count($required_extensions), 'fw'
975
- ),
976
- implode(', ', $required_extensions)
977
- )
978
- );
979
- $skin->error($install_data);
980
- $install_data = false;
981
- break 2;
982
- } else {
983
- $required_extensions_titles = array();
984
- foreach ($required_extensions as $required_extension_name) {
985
- $required_extensions_titles[$required_extension_name]
986
- = $available_extensions[$required_extension_name]['name'];
987
- }
988
-
989
- $skin->feedback(
990
- sprintf(
991
- _n(
992
- 'Installed extensions has required extension: %s. Installing...',
993
- 'Installed extensions has required extensions: %s. Installing...',
994
- count($required_extensions_titles), 'fw'
995
- ),
996
- implode(', ', $required_extensions_titles)
997
- )
998
- );
999
- }
1000
- } while(false);
1001
 
1002
- if (empty($install_data)) {
1003
- /**
1004
- * All extensions were installed successfully and there is nothing else to install
1005
- * (the "while" will exit below)
1006
- */
1007
- $skin->set_result(true);
1008
- }
1009
- } while(!empty($install_data));
1010
 
1011
  /** @var WP_Filesystem_Base $wp_filesystem */
1012
  global $wp_filesystem;
@@ -1018,7 +908,6 @@ final class _FW_Extensions_Manager
1018
  $skin->error(
1019
  sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
1020
  );
1021
- break;
1022
  }
1023
  }
1024
 
@@ -1030,8 +919,13 @@ final class _FW_Extensions_Manager
1030
 
1031
  wp_nonce_field($nonce['action'], $nonce['name']);
1032
 
 
 
 
 
 
1033
  fw_render_view(dirname(__FILE__) .'/views/install-form.php', array(
1034
- 'extension_titles' => $install_data['all'],
1035
  'list_page_link' => $this->get_link(),
1036
  'supported' => $supported
1037
  ), false);
@@ -1040,179 +934,465 @@ final class _FW_Extensions_Manager
1040
  }
1041
  } while(false);
1042
 
1043
- if ($skin->result && !empty($activate_extensions)) {
1044
- $db_active_extensions = fw()->extensions->_get_db_active_extensions();
1045
- $db_active_extensions += $activate_extensions;
1046
 
1047
- // make sure to activate parents
1048
- foreach ($activate_extensions as $extension_name => $x) {
1049
- $current_parent = $extension_name;
1050
- while ($current_parent = $installed_extensions[$current_parent]['parent']) {
1051
- $db_active_extensions[ $current_parent ] = array();
1052
- }
1053
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1054
 
1055
- update_option(
1056
- fw()->extensions->_get_active_extensions_db_option_name(),
1057
- $db_active_extensions
 
 
 
 
 
 
 
 
1058
  );
1059
  }
1060
 
1061
- $skin->footer();
1062
- }
 
 
 
 
1063
 
1064
- private function display_delete_page()
1065
- {
1066
- $flash_id = 'fw_extensions_delete';
1067
 
1068
- if (!$this->can_install()) {
1069
- FW_Flash_Messages::add(
1070
- $flash_id,
1071
- __('You are not allowed to delete extensions.', 'fw'),
1072
- 'error'
1073
  );
1074
- $this->js_redirect();
1075
- return;
1076
  }
1077
 
 
 
 
 
 
 
 
1078
  $installed_extensions = $this->get_installed_extensions();
1079
 
1080
- $extensions = array_fill_keys(array_map('trim', explode(',', FW_Request::GET('extension', ''))), array());
 
1081
 
1082
- {
1083
- $error = '';
 
 
1084
 
1085
- do {
1086
- foreach ($extensions as $extension_name => $x) {
1087
- if (empty($extension_name)) {
1088
- unset($extensions[$extension_name]);
1089
- continue;
1090
- }
1091
 
1092
- if (!isset($installed_extensions[ $extension_name ])) {
1093
- $error = sprintf(__('Extension "%s" is not installed.', 'fw'), $this->get_extension_title($extension_name));
1094
- break 2;
1095
- }
1096
- }
 
1097
 
1098
- if (empty($extensions)) {
1099
- $error = __('No extensions to delete.', 'fw');
1100
  break;
 
 
1101
  }
1102
- } while(false);
1103
-
1104
- if ($error) {
1105
- FW_Flash_Messages::add($flash_id, $error, 'error');
1106
- $this->js_redirect();
1107
- return;
1108
  }
1109
- }
1110
 
1111
- {
1112
- if (!class_exists('_FW_Extensions_Delete_Upgrader_Skin')) {
1113
- fw_include_file_isolated(
1114
- dirname(__FILE__) .'/includes/class--fw-extensions-delete-upgrader-skin.php'
 
 
 
1115
  );
 
 
 
 
 
 
 
1116
  }
1117
 
1118
- $skin = new _FW_Extensions_Delete_Upgrader_Skin(array(
1119
- 'title' => _n('Delete Extension', 'Delete Extensions', count($extensions), 'fw'),
1120
- ));
1121
- }
 
 
1122
 
1123
- $skin->header();
 
 
 
 
 
 
 
 
 
 
 
 
1124
 
1125
- do {
1126
- $nonce = $this->get_nonce('delete');
 
 
 
 
1127
 
1128
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1129
- if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1130
- $skin->error(__('Invalid nonce.', 'fw'));
1131
  }
1132
 
1133
- if (!FW_WP_Filesystem::request_access(
1134
- fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
1135
- )) {
1136
- break;
1137
- }
1138
 
1139
- {
1140
- // add sub-extensions for deletion
1141
- foreach (array_keys($extensions) as $extension_name) {
1142
- foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
1143
- $extensions[ $sub_extension_name ] = array();
1144
- }
1145
- }
1146
 
1147
- // add not used extensions for deletion
1148
- {
1149
- $not_used_extensions = array_fill_keys(array_keys(array_diff_key(
1150
- $installed_extensions,
1151
- $this->get_used_extensions($extensions, array_keys($installed_extensions))
1152
- )), array());
1153
 
1154
- $extensions = array_merge($extensions, $not_used_extensions);
 
 
1155
  }
1156
- }
1157
 
1158
- /** @var WP_Filesystem_Base $wp_filesystem */
1159
- global $wp_filesystem;
 
 
1160
 
1161
- foreach ($extensions as $extension_name => $x) {
1162
- if (!isset($installed_extensions[ $extension_name ])) {
1163
- $skin->error(sprintf(__('Extension "%s" is not installed.', 'fw'), $this->get_extension_title($extension_name)));
1164
- continue;
 
1165
  }
1166
 
1167
- if (
1168
- !isset($installed_extensions[ $extension_name ]['path'])
1169
- ||
1170
- empty($installed_extensions[ $extension_name ]['path'])
1171
- ) {
1172
- // this happens sometimes, but I don't know why
1173
- fw_print($extension_name, $installed_extensions);
1174
- die;
1175
  }
1176
 
1177
- $extension_title = $this->get_extension_title($extension_name);
1178
-
1179
- $wp_fs_extension_path = FW_WP_Filesystem::real_path_to_filesystem_path(
1180
- $installed_extensions[ $extension_name ]['path']
1181
  );
1182
 
1183
- if (!$wp_filesystem->exists($wp_fs_extension_path)) {
1184
- // already deleted, maybe because it was a sub-extension of an deleted extension
1185
- continue;
1186
- }
1187
 
1188
- $skin->feedback(
1189
- sprintf(__('Deleting the "%s" extension...', 'fw'), $extension_title)
1190
- );
 
 
 
1191
 
1192
- if (!$wp_filesystem->delete($wp_fs_extension_path, true, 'd')) {
1193
- $skin->error(
1194
- sprintf(__('Cannot delete the "%s" extension.', 'fw'), $extension_title)
 
 
 
 
 
 
 
 
 
 
1195
  );
1196
- } else {
1197
- $skin->feedback(
1198
- sprintf(__('%s extension successfully delete.', 'fw'), $extension_title)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1199
  );
1200
 
1201
- $skin->set_result(true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1202
  }
1203
  }
 
1204
 
1205
- // remove from active list the deleted extensions
1206
- {
1207
- $db_active_extensions = fw()->extensions->_get_db_active_extensions();
1208
- $db_active_extensions = array_diff_key($db_active_extensions, $extensions);
1209
 
1210
- update_option(
1211
- fw()->extensions->_get_active_extensions_db_option_name(),
1212
- $db_active_extensions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1213
  );
1214
 
1215
- unset($db_active_extensions);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1216
  }
1217
 
1218
  $skin->after(array(
@@ -1225,7 +1405,7 @@ final class _FW_Extensions_Manager
1225
 
1226
  fw_render_view(dirname(__FILE__) .'/views/delete-form.php', array(
1227
  'extension_names' => array_keys($extensions),
1228
- 'installed_extensions' => $installed_extensions,
1229
  'list_page_link' => $this->get_link(),
1230
  ), false);
1231
 
@@ -1236,6 +1416,217 @@ final class _FW_Extensions_Manager
1236
  $skin->footer();
1237
  }
1238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1239
  private function display_extension_page()
1240
  {
1241
  $extension_name = trim(FW_Request::GET('extension', ''));
@@ -1380,7 +1771,7 @@ final class _FW_Extensions_Manager
1380
  'type' => 'html-full',
1381
  'html' => $this->get_markdown_parser()->text(
1382
  fw_render_view($docs_path, array())
1383
- )
1384
  ),
1385
  ))
1386
  );
@@ -1388,9 +1779,6 @@ final class _FW_Extensions_Manager
1388
 
1389
  private function display_activate_page()
1390
  {
1391
- $installed_extensions = $this->get_installed_extensions();
1392
- $db_active_extensions = fw()->extensions->_get_db_active_extensions();
1393
-
1394
  $error = '';
1395
 
1396
  do {
@@ -1411,41 +1799,166 @@ final class _FW_Extensions_Manager
1411
  break;
1412
  }
1413
 
1414
- $extensions = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1415
 
1416
- foreach (explode(',', $_GET['extension']) as $extension_name) {
1417
- if (!isset($installed_extensions[$extension_name])) {
1418
- $error = sprintf(__('Extension "%s" does not exist.', 'fw'), $this->get_extension_title($extension_name));
1419
- break 2;
 
 
 
 
 
 
 
 
1420
  }
 
1421
 
1422
- $collected = $this->get_extensions_for_activation($extension_name);
1423
 
1424
- if (is_wp_error($collected)) {
1425
- $error = $collected->get_error_message();
1426
- break 2;
1427
- }
1428
 
1429
- $extensions = array_merge($extensions, $collected);
 
 
 
 
1430
  }
1431
 
1432
- $db_active_extensions = array_merge($db_active_extensions, $extensions);
1433
- } while(false);
1434
 
1435
- $flash_id = 'fw_extensions_activate_page';
 
1436
 
1437
- if ($error) {
1438
- FW_Flash_Messages::add($flash_id, $error, 'error');
1439
- $this->js_redirect();
1440
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1441
  }
1442
 
1443
  update_option(
1444
  fw()->extensions->_get_active_extensions_db_option_name(),
1445
- $db_active_extensions
1446
  );
1447
 
1448
- $this->js_redirect();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1449
  }
1450
 
1451
  private function collect_sub_extensions($ext_name, &$installed_extensions)
@@ -1503,82 +2016,193 @@ final class _FW_Extensions_Manager
1503
  break;
1504
  }
1505
 
1506
- /**
1507
- * {extension_name => [parent_name, sub_parent_name, ..., extension_name]}
1508
- */
1509
- $extensions_parents = array();
1510
-
1511
- foreach (explode(',', $_GET['extension']) as $extension_name) {
1512
- if (!isset($installed_extensions[$extension_name])) {
1513
- $error = sprintf(__( 'Extension "%s" does not exist.' , 'fw' ), $this->get_extension_title($extension_name));
1514
- break 2;
1515
- }
1516
 
1517
- {
1518
- $extensions_parents[$extension_name] = array($extension_name);
 
 
1519
 
1520
- $current_parent = $extension_name;
1521
- while ($current_parent = $installed_extensions[$current_parent]['parent']) {
1522
- $extensions_parents[$extension_name][] = $current_parent;
1523
  }
1524
-
1525
- $extensions_parents[$extension_name] = array_reverse($extensions_parents[$extension_name]);
1526
  }
 
 
1527
  }
1528
  } while(false);
1529
 
1530
- $flash_id = 'fw_extensions_activate_page';
1531
-
1532
  if ($error) {
1533
- FW_Flash_Messages::add($flash_id, $error, 'error');
1534
- $this->js_redirect();
1535
- return;
 
 
1536
  }
1537
 
1538
- $db_active_extensions = fw()->extensions->_get_db_active_extensions();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1539
 
1540
- $deactivated_extensions = array();
 
 
 
 
 
1541
 
1542
- {
1543
- // add sub-extensions for deactivation
1544
- foreach ($extensions_parents as $extension_name => $extension_parents) {
1545
- unset($db_active_extensions[$extension_name]);
1546
- $deactivated_extensions[$extension_name] = array();
 
 
 
 
 
 
 
 
 
 
1547
 
1548
- foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
1549
- unset($db_active_extensions[ $sub_extension_name ]);
1550
- $deactivated_extensions[ $sub_extension_name ] = array();
 
1551
  }
1552
  }
1553
 
1554
- // add extensions that requires deactivated extensions
1555
- {
1556
- $this->collect_extensions_that_requires($deactivated_extensions, $deactivated_extensions);
 
 
 
 
1557
  }
1558
 
1559
- // add not used extensions for deactivation
1560
- {
1561
- $not_used_extensions = array_fill_keys(array_keys(array_diff_key(
1562
- $installed_extensions,
1563
- $this->get_used_extensions($deactivated_extensions, array_keys(fw()->extensions->get_all()))
1564
- )), array());
 
 
 
 
 
 
1565
 
1566
- $deactivated_extensions = array_merge($deactivated_extensions, $not_used_extensions);
1567
- $db_active_extensions = array_diff_key($db_active_extensions, $not_used_extensions);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1568
  }
1569
  }
1570
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1571
  update_option(
1572
  fw()->extensions->_get_active_extensions_db_option_name(),
1573
- $db_active_extensions
 
 
 
1574
  );
1575
 
1576
- $this->js_redirect();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1577
  }
1578
 
1579
  /**
1580
  * @param array $data
1581
  * @return array
 
1582
  */
1583
  public function _extension_settings_form_render($data)
1584
  {
@@ -1620,6 +2244,7 @@ final class _FW_Extensions_Manager
1620
  /**
1621
  * @param array $errors
1622
  * @return array
 
1623
  */
1624
  public function _extension_settings_form_validate($errors)
1625
  {
@@ -1648,6 +2273,7 @@ final class _FW_Extensions_Manager
1648
  /**
1649
  * @param array $data
1650
  * @return array
 
1651
  */
1652
  public function _extension_settings_form_save($data)
1653
  {
@@ -2046,92 +2672,6 @@ final class _FW_Extensions_Manager
2046
  }
2047
  }
2048
 
2049
- /**
2050
- * @param array $extension_names
2051
- * @return array|WP_Error
2052
- */
2053
- private function get_install_data($extension_names)
2054
- {
2055
- $installed_extensions = $this->get_installed_extensions();
2056
- $available_extensions = $this->get_available_extensions();
2057
-
2058
- $error = '';
2059
-
2060
- do {
2061
- foreach ($extension_names as $i => $extension_name) {
2062
- if (empty($extension_name)) {
2063
- unset($extension_names[$i]);
2064
- continue;
2065
- }
2066
-
2067
- if (!isset($available_extensions[ $extension_name ])) {
2068
- $error = sprintf(__('Extension "%s" is not available for install.', 'fw'), $this->get_extension_title($extension_name));
2069
- break 2;
2070
- }
2071
-
2072
- if (isset($installed_extensions[ $extension_name ])) {
2073
- $error = sprintf(__('Extension "%s" is already installed.', 'fw'), $this->get_extension_title($extension_name));
2074
- break 2;
2075
- }
2076
- }
2077
-
2078
- if (empty($extension_names)) {
2079
- $error = __('No extensions to install.', 'fw');
2080
- break;
2081
- }
2082
-
2083
- /**
2084
- * Find extensions parents that will be installed if does not exist
2085
- */
2086
- {
2087
- $extensions_and_parents = array();
2088
- $extensions_for_install = array();
2089
-
2090
- foreach ($extension_names as $extension_name) {
2091
- $current_parent = $extension_name;
2092
- $extensions_and_parents[$extension_name] = array($extension_name);
2093
-
2094
- $extensions_for_install[$current_parent] = $available_extensions[$current_parent]['name'];
2095
-
2096
- while (!empty($available_extensions[$current_parent]['parent'])) {
2097
- $current_parent = $available_extensions[$current_parent]['parent'];
2098
-
2099
- if (!isset($available_extensions[$current_parent])) {
2100
- $error = sprintf(
2101
- __('Extension "%s" has parent extension "%s" that is not available.', 'fw'),
2102
- $this->get_extension_title($extension_name), $current_parent
2103
- );
2104
- break 3;
2105
- }
2106
-
2107
- $extensions_and_parents[$extension_name][] = $current_parent;
2108
-
2109
- if (!isset($installed_extensions[$current_parent])) {
2110
- $extensions_for_install[ $current_parent ] = $available_extensions[ $current_parent ]['name'];
2111
- }
2112
- }
2113
-
2114
- $extensions_and_parents[$extension_name] = array_reverse($extensions_and_parents[$extension_name]);
2115
- }
2116
- }
2117
- } while(false);
2118
-
2119
- if ($error) {
2120
- return new WP_Error('fw_extensions_install_data', $error);
2121
- }
2122
-
2123
- return array(
2124
- /**
2125
- * {extension_name: [parent_name, sub_parent_name, ..., extension_name]}
2126
- */
2127
- 'parents' => $extensions_and_parents,
2128
- /**
2129
- * {extension_name: Title}
2130
- */
2131
- 'all' => $extensions_for_install,
2132
- );
2133
- }
2134
-
2135
  private function get_supported_extensions_for_install()
2136
  {
2137
  $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
@@ -2157,6 +2697,11 @@ final class _FW_Extensions_Manager
2157
  return $supported_extensions;
2158
  }
2159
 
 
 
 
 
 
2160
  public function _filter_plugin_action_list($actions)
2161
  {
2162
  return array_merge(
@@ -2266,8 +2811,10 @@ final class _FW_Extensions_Manager
2266
  foreach (array_keys($skip_extensions) as $skip_extension_name) {
2267
  unset($used_extensions[$skip_extension_name]);
2268
 
2269
- foreach ($this->collect_sub_extensions($skip_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2270
- unset($used_extensions[$sub_extension_name]);
 
 
2271
  }
2272
  }
2273
 
@@ -2282,7 +2829,7 @@ final class _FW_Extensions_Manager
2282
  $this->activate_hidden_standalone_extensions();
2283
  }
2284
 
2285
- private function get_extension_title($extension_name)
2286
  {
2287
  $installed_extensions = $this->get_installed_extensions();
2288
 
@@ -2336,7 +2883,7 @@ final class _FW_Extensions_Manager
2336
  public function _action_theme_switch()
2337
  {
2338
  $this->activate_theme_extensions();
2339
- $this->activate_extensions_if_exists(
2340
  array_fill_keys(
2341
  array_keys(fw()->theme->manifest->get('supported_extensions', array())),
2342
  array()
@@ -2344,123 +2891,6 @@ final class _FW_Extensions_Manager
2344
  );
2345
  }
2346
 
2347
- /**
2348
- * @param $activate_extensions {'extension_name' => array()}
2349
- * @return array Errors {'extension_name' => array()}
2350
- */
2351
- private function activate_extensions_if_exists($activate_extensions)
2352
- {
2353
- $errors = array();
2354
-
2355
- $installed_extensions = $this->get_installed_extensions();
2356
- $db_active_extensions = fw()->extensions->_get_db_active_extensions();
2357
-
2358
- foreach (
2359
- array_keys(array_intersect_key($activate_extensions, $installed_extensions))
2360
- as $extension_name
2361
- ) {
2362
- $current_extension_activation = array();
2363
-
2364
- // add extension to activation only if all required extensions are installed
2365
- {
2366
- $required_extensions = array();
2367
- $this->collect_required_extensions($extension_name, $installed_extensions, $required_extensions);
2368
-
2369
- if ($not_installed_required_extensions = array_diff_key($required_extensions, $installed_extensions)) {
2370
- // extension requires extensions that are not installed
2371
- $errors[$extension_name] = array(
2372
- 'not_installed_required_extensions' => $not_installed_required_extensions
2373
- );
2374
- continue;
2375
- } else {
2376
- // all required extensions are installed
2377
- $current_extension_activation[ $extension_name ] = array();
2378
-
2379
- if (!empty($required_extensions)) {
2380
- $current_extension_activation = array_merge(
2381
- $current_extension_activation,
2382
- array_fill_keys(array_keys($required_extensions), array())
2383
- );
2384
- }
2385
- }
2386
- }
2387
-
2388
- // activate parents
2389
- {
2390
- $current_parent = $extension_name;
2391
- while ($current_parent = $installed_extensions[$current_parent]['parent']) {
2392
- // add extension to activation only if all required extensions are installed
2393
- {
2394
- $required_extensions = array();
2395
- $this->collect_required_extensions($current_parent, $installed_extensions, $required_extensions);
2396
-
2397
- if ($not_installed_required_extensions = array_diff_key($required_extensions, $installed_extensions)) {
2398
- // extension requires extensions that are not installed
2399
- $errors[$current_parent] = array(
2400
- 'not_installed_required_extensions' => $not_installed_required_extensions
2401
- );
2402
- continue 2;
2403
- } else {
2404
- // all required extensions are installed
2405
- $current_extension_activation[ $current_parent ] = array();
2406
-
2407
- if (!empty($required_extensions)) {
2408
- $current_extension_activation = array_merge(
2409
- $current_extension_activation,
2410
- array_fill_keys(array_keys($required_extensions), array())
2411
- );
2412
- }
2413
- }
2414
- }
2415
- }
2416
- }
2417
-
2418
- // activate children
2419
- {
2420
- foreach (
2421
- array_keys($this->collect_sub_extensions($extension_name, $installed_extensions))
2422
- as $sub_extension_name
2423
- ) {
2424
- // add extension to activation only if all required extensions are installed
2425
- {
2426
- $required_extensions = array();
2427
- $this->collect_required_extensions($sub_extension_name, $installed_extensions, $required_extensions);
2428
-
2429
- if ($not_installed_required_extensions = array_diff_key($required_extensions, $installed_extensions)) {
2430
- // extension requires extensions that are not installed
2431
- $errors[$sub_extension_name] = array(
2432
- 'not_installed_required_extensions' => $not_installed_required_extensions
2433
- );
2434
- continue 2;
2435
- } else {
2436
- // all required extensions are installed
2437
- $current_extension_activation[ $sub_extension_name ] = array();
2438
-
2439
- if (!empty($required_extensions)) {
2440
- $current_extension_activation = array_merge(
2441
- $current_extension_activation,
2442
- array_fill_keys(array_keys($required_extensions), array())
2443
- );
2444
- }
2445
- }
2446
- }
2447
- }
2448
-
2449
- $db_active_extensions = array_merge(
2450
- $db_active_extensions,
2451
- $current_extension_activation
2452
- );
2453
- }
2454
- }
2455
-
2456
- update_option(
2457
- fw()->extensions->_get_active_extensions_db_option_name(),
2458
- $db_active_extensions
2459
- );
2460
-
2461
- return $errors;
2462
- }
2463
-
2464
  /**
2465
  * @param array $collected The found extensions {'extension_name' => array()}
2466
  * @param array $extensions {'extension_name' => array()}
@@ -2526,7 +2956,7 @@ final class _FW_Extensions_Manager
2526
  return new WP_Error($wp_error_id,
2527
  sprintf(
2528
  __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
2529
- fw_id_to_title($extension_name),
2530
  fw_html_tag('a', array(
2531
  'href' => $this->get_link() .'&sub-page=install&extension='. $extension_name
2532
  ), __('Install', 'fw'))
@@ -2573,7 +3003,7 @@ final class _FW_Extensions_Manager
2573
  return new WP_Error($wp_error_id,
2574
  sprintf(
2575
  __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
2576
- fw_id_to_title($required_extension_name),
2577
  fw_html_tag('a', array(
2578
  'href' => $this->get_link() .'&sub-page=install&extension='. $required_extension_name
2579
  ), __('Install', 'fw'))
54
  if ($this->can_install()) {
55
  add_action('wp_ajax_fw_extensions_check_direct_fs_access', array($this, '_action_ajax_check_direct_fs_access'));
56
  }
 
57
  }
58
 
59
  /** Filters */
69
  * - save extension settings options
70
  * @return bool
71
  */
72
+ public function can_activate()
73
  {
74
  static $can_activate = null;
75
 
96
  * - delete extensions
97
  * @return bool
98
  */
99
+ public function can_install()
100
  {
101
  static $can_install = null;
102
 
117
  return $can_install;
118
  }
119
 
120
+ public function get_page_slug()
121
  {
122
  return 'fw-extensions';
123
  }
219
  public function _action_after_plugin_activate()
220
  {
221
  $this->activate_theme_extensions();
222
+ $this->activate_extensions(
223
  array_fill_keys(
224
  array_keys(fw()->theme->manifest->get('supported_extensions', array())),
225
  array()
450
  'validate' => array($this, '_extension_settings_form_validate'),
451
  'save' => array($this, '_extension_settings_form_save'),
452
  ));
453
+
454
+ if (is_admin() && $this->can_activate()) {
455
+ $db_wp_option_name = 'fw_extensions_activation';
456
+
457
+ if ($db_wp_option_value = get_option($db_wp_option_name, array())) {
458
+ $db_wp_option_value = array_merge(array(
459
+ 'activated' => array(),
460
+ 'deactivated' => array(),
461
+ ), $db_wp_option_value);
462
+
463
+ /**
464
+ * Fire the 'fw_extensions_after_activation' action
465
+ */
466
+ if ($db_wp_option_value['activated']) {
467
+ $succeeded_extensions = $failed_extensions = array();
468
+
469
+ foreach ($db_wp_option_value['activated'] as $extension_name => $not_used_var) {
470
+ if (fw_ext($extension_name)) {
471
+ $succeeded_extensions[$extension_name] = array();
472
+ } else {
473
+ $failed_extensions[$extension_name] = array();
474
+ }
475
+ }
476
+
477
+ if (!empty($succeeded_extensions)) {
478
+ do_action('fw_extensions_after_activation', $succeeded_extensions);
479
+ }
480
+ if (!empty($failed_extensions)) {
481
+ do_action('fw_extensions_activation_failed', $failed_extensions);
482
+ }
483
+ }
484
+
485
+ /**
486
+ * Fire the 'fw_extensions_after_deactivation' action
487
+ */
488
+ if ($db_wp_option_value['deactivated']) {
489
+ $succeeded_extensions = $failed_extensions = array();
490
+
491
+ foreach ($db_wp_option_value['deactivated'] as $extension_name => $not_used_var) {
492
+ if (!fw_ext($extension_name)) {
493
+ $succeeded_extensions[$extension_name] = array();
494
+ } else {
495
+ $failed_extensions[$extension_name] = array();
496
+ }
497
+ }
498
+
499
+ if (!empty($succeeded_extensions)) {
500
+ do_action('fw_extensions_after_deactivation', $succeeded_extensions);
501
+ }
502
+ if (!empty($failed_extensions)) {
503
+ do_action('fw_extensions_deactivation_failed', $failed_extensions);
504
+ }
505
+ }
506
+
507
+ delete_option($db_wp_option_name);
508
+ }
509
+ }
510
  }
511
 
512
  /**
558
 
559
  if (is_wp_error($collected)) {
560
  if (defined('WP_DEBUG') && WP_DEBUG) {
561
+ if (
562
+ fw_current_screen_match(array(
563
+ 'only' => array(
564
+ array('parent_base' => $this->get_page_slug())
565
+ )
566
+ ))
567
+ ) {
568
+ // display this warning only on Unyson extensions page
569
+ FW_Flash_Messages::add('fw_ext_auto_activate_hidden_standalone',
570
+ sprintf(__('Cannot activate hidden standalone extension %s', 'fw'),
571
+ fw_akg('name', $ext_data['manifest'], fw_id_to_title($ext_name))
572
+ ),
573
+ 'error'
574
+ );
575
+ }
576
  }
577
  return;
578
  }
603
  }
604
 
605
  $data = array(
606
+ 'title' => fw()->manifest->get_name(),
607
+ 'capability' => $capability,
608
+ 'slug' => $this->get_page_slug(),
609
+ 'content_callback' => array($this, '_display_page'),
610
  );
611
 
612
  /**
820
  return;
821
  }
822
 
 
 
823
  if (array_key_exists('supported', $_GET)) {
824
  $supported = true;
825
+ $extensions = array_fill_keys(
826
+ array_keys($this->get_supported_extensions_for_install()),
827
+ array()
 
 
 
828
  );
829
 
830
+ if (empty($extensions)) {
831
+ FW_Flash_Messages::add(
832
+ $flash_id,
833
+ __('All supported extensions are already installed.', 'fw'),
834
+ 'info'
835
+ );
836
+ $this->js_redirect();
837
+ return;
838
+ }
839
  } else {
840
  $supported = false;
841
 
842
+ $extensions = array_fill_keys(
843
+ array_map( 'trim', explode( ',', FW_Request::GET( 'extension', '' ) )),
844
+ array()
 
 
 
 
 
 
 
 
845
  );
846
 
847
+ // activate already installed extensions
848
+ $this->activate_extensions($extensions);
 
 
 
 
 
849
  }
850
 
851
  {
857
 
858
  $skin = new _FW_Extensions_Install_Upgrader_Skin(array(
859
  'title' => $supported
860
+ ? _n('Install Compatible Extension', 'Install Compatible Extensions', count($extensions), 'fw')
861
+ : _n('Install Extension', 'Install Extensions', count($extensions), 'fw'),
862
  ));
863
  }
864
 
878
  break;
879
  }
880
 
881
+ $install_result = $this->install_extensions($extensions, array('verbose' => $skin));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
882
 
883
+ if (is_wp_error($install_result)) {
884
+ $skin->error($install_result);
885
+ } elseif (is_array($install_result)) {
886
+ $error = array();
887
 
888
+ foreach ($install_result as $extension_name => $extension_result) {
889
+ if (is_wp_error($extension_result)) {
890
+ $error[] = $extension_result->get_error_message();
 
 
 
 
 
891
  }
892
  }
893
 
894
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
895
 
896
+ $skin->error($error);
897
+ } elseif ($install_result === true) {
898
+ $skin->set_result(true);
899
+ }
 
 
 
 
900
 
901
  /** @var WP_Filesystem_Base $wp_filesystem */
902
  global $wp_filesystem;
908
  $skin->error(
909
  sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
910
  );
 
911
  }
912
  }
913
 
919
 
920
  wp_nonce_field($nonce['action'], $nonce['name']);
921
 
922
+ $extension_titles = array();
923
+ foreach ($extensions as $extension_name => $not_used_var) {
924
+ $extension_titles[$extension_name] = $this->get_extension_title($extension_name);
925
+ }
926
+
927
  fw_render_view(dirname(__FILE__) .'/views/install-form.php', array(
928
+ 'extension_titles' => $extension_titles,
929
  'list_page_link' => $this->get_link(),
930
  'supported' => $supported
931
  ), false);
934
  }
935
  } while(false);
936
 
937
+ $skin->footer();
938
+ }
 
939
 
940
+ /**
941
+ * Download (and activate) extensions
942
+ * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
943
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
944
+ * @param array $opts
945
+ * @return WP_Error|bool|array
946
+ * true: when all extensions succeeded
947
+ * array: when some/all failed
948
+ */
949
+ public function install_extensions(array $extensions, $opts = array())
950
+ {
951
+ {
952
+ $opts = array_merge(array(
953
+ /**
954
+ * @type bool
955
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
956
+ * true: return first WP_Error or true on success
957
+ */
958
+ 'cancel_on_error' => false,
959
+ /**
960
+ * @type bool Activate installed extensions
961
+ */
962
+ 'activate' => true,
963
+ /**
964
+ * @type bool|WP_Upgrader_Skin
965
+ */
966
+ 'verbose' => false,
967
+ ), $opts);
968
 
969
+ $cancel_on_error = $opts['cancel_on_error']; // fixme: remove successfully installed extensions before error?
970
+ $activate = $opts['activate'];
971
+ $verbose = $opts['verbose'];
972
+
973
+ unset($opts);
974
+ }
975
+
976
+ if (!$this->can_install()) {
977
+ return new WP_Error(
978
+ 'access_denied',
979
+ __('You have no permissions to install extensions', 'fw')
980
  );
981
  }
982
 
983
+ if (empty($extensions)) {
984
+ return new WP_Error(
985
+ 'no_extensions',
986
+ __('No extensions provided', 'fw')
987
+ );
988
+ }
989
 
990
+ global $wp_filesystem;
 
 
991
 
992
+ if (!$wp_filesystem) {
993
+ return new WP_Error(
994
+ 'fs_not_initialized',
995
+ __('WP Filesystem is not initialized', 'fw')
 
996
  );
 
 
997
  }
998
 
999
+ if (function_exists('ini_get')) {
1000
+ $timeout = intval(ini_get('max_execution_time'));
1001
+ } else {
1002
+ $timeout = false;
1003
+ }
1004
+
1005
+ $available_extensions = $this->get_available_extensions();
1006
  $installed_extensions = $this->get_installed_extensions();
1007
 
1008
+ $result = $downloaded_extensions = array();
1009
+ $has_errors = false;
1010
 
1011
+ while (!empty($extensions)) {
1012
+ $not_used_var = reset($extensions);
1013
+ $extension_name = key($extensions);
1014
+ unset($extensions[$extension_name]);
1015
 
1016
+ $extensions_before_install = array_keys($installed_extensions);
 
 
 
 
 
1017
 
1018
+ if (isset($installed_extensions[$extension_name])) {
1019
+ $result[$extension_name] = new WP_Error(
1020
+ 'extension_installed',
1021
+ sprintf(__('Extension "%s" is already installed.', 'fw'), $this->get_extension_title($extension_name))
1022
+ );
1023
+ $has_errors = true;
1024
 
1025
+ if ($cancel_on_error) {
 
1026
  break;
1027
+ } else {
1028
+ continue;
1029
  }
 
 
 
 
 
 
1030
  }
 
1031
 
1032
+ if (!isset($available_extensions[ $extension_name ])) {
1033
+ $result[$extension_name] = new WP_Error(
1034
+ 'extension_not_available',
1035
+ sprintf(
1036
+ __('Extension "%s" is not available for install.', 'fw'),
1037
+ $this->get_extension_title($extension_name)
1038
+ )
1039
  );
1040
+ $has_errors = true;
1041
+
1042
+ if ($cancel_on_error) {
1043
+ break;
1044
+ } else {
1045
+ continue;
1046
+ }
1047
  }
1048
 
1049
+ /**
1050
+ * Find parent extensions
1051
+ * they will be installed if does not exist
1052
+ */
1053
+ {
1054
+ $parents = array($extension_name);
1055
 
1056
+ $current_parent = $extension_name;
1057
+ while (!empty($available_extensions[$current_parent]['parent'])) {
1058
+ $current_parent = $available_extensions[$current_parent]['parent'];
1059
+
1060
+ if (!isset($available_extensions[$current_parent])) {
1061
+ $result[$extension_name] = new WP_Error(
1062
+ 'parent_extension_not_available',
1063
+ sprintf(
1064
+ __('Parent extension "%s" not available.', 'fw'),
1065
+ $this->get_extension_title($current_parent)
1066
+ )
1067
+ );
1068
+ $has_errors = true;
1069
 
1070
+ if ($cancel_on_error) {
1071
+ break 2;
1072
+ } else {
1073
+ continue 2;
1074
+ }
1075
+ }
1076
 
1077
+ $parents[] = $current_parent;
 
 
1078
  }
1079
 
1080
+ $parents = array_reverse($parents);
1081
+ }
 
 
 
1082
 
1083
+ /**
1084
+ * Install parent extensions and the extension
1085
+ */
1086
+ {
1087
+ $current_extension_path = fw_get_framework_directory();
 
 
1088
 
1089
+ foreach ($parents as $parent_extension_name) {
1090
+ $current_extension_path .= '/extensions/'. $parent_extension_name;
 
 
 
 
1091
 
1092
+ if (isset($installed_extensions[$parent_extension_name])) {
1093
+ // skip already installed extensions
1094
+ continue;
1095
  }
 
1096
 
1097
+ if ($verbose) {
1098
+ $verbose_message = sprintf(__('Downloading the "%s" extension...', 'fw'),
1099
+ $this->get_extension_title($parent_extension_name)
1100
+ );
1101
 
1102
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1103
+ $verbose->feedback($verbose_message);
1104
+ } else {
1105
+ echo fw_html_tag('p', array(), $verbose_message);
1106
+ }
1107
  }
1108
 
1109
+ // increase timeout
1110
+ if ($timeout !== false && function_exists('set_time_limit')) {
1111
+ $timeout += 30;
1112
+ set_time_limit($timeout);
 
 
 
 
1113
  }
1114
 
1115
+ $wp_fw_downloaded_dir = $this->download(
1116
+ $parent_extension_name,
1117
+ $available_extensions[$parent_extension_name]
 
1118
  );
1119
 
1120
+ if (is_wp_error($wp_fw_downloaded_dir)) {
1121
+ if ($verbose) {
1122
+ $verbose_message = $wp_fw_downloaded_dir->get_error_message();
 
1123
 
1124
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1125
+ $verbose->error($verbose_message);
1126
+ } else {
1127
+ echo fw_html_tag('p', array(), $verbose_message);
1128
+ }
1129
+ }
1130
 
1131
+ $result[$extension_name] = $wp_fw_downloaded_dir;
1132
+ $has_errors = true;
1133
+
1134
+ if ($cancel_on_error) {
1135
+ break 2;
1136
+ } else {
1137
+ continue 2;
1138
+ }
1139
+ }
1140
+
1141
+ if ($verbose) {
1142
+ $verbose_message = sprintf(__('Installing the "%s" extension...', 'fw'),
1143
+ $this->get_extension_title($parent_extension_name)
1144
  );
1145
+
1146
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1147
+ $verbose->feedback($verbose_message);
1148
+ } else {
1149
+ echo fw_html_tag('p', array(), $verbose_message);
1150
+ }
1151
+ }
1152
+
1153
+ $merge_result = $this->merge_extension(
1154
+ $wp_fw_downloaded_dir,
1155
+ FW_WP_Filesystem::real_path_to_filesystem_path($current_extension_path)
1156
+ );
1157
+
1158
+ if (is_wp_error($merge_result)) {
1159
+ if ($verbose) {
1160
+ $verbose_message = $merge_result->get_error_message();
1161
+
1162
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1163
+ $verbose->error($verbose_message);
1164
+ } else {
1165
+ echo fw_html_tag('p', array(), $verbose_message);
1166
+ }
1167
+ }
1168
+
1169
+ $result[$extension_name] = $merge_result;
1170
+ $has_errors = true;
1171
+
1172
+ if ($cancel_on_error) {
1173
+ break 2;
1174
+ } else {
1175
+ continue 2;
1176
+ }
1177
+ }
1178
+
1179
+ if ($verbose) {
1180
+ $verbose_message = sprintf(__('The %s extension has been successfully installed.', 'fw'),
1181
+ $this->get_extension_title($parent_extension_name)
1182
  );
1183
 
1184
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1185
+ $verbose->feedback($verbose_message);
1186
+ } else {
1187
+ echo fw_html_tag('p', array(), $verbose_message);
1188
+ }
1189
+ }
1190
+
1191
+ $downloaded_extensions[$parent_extension_name] = array();
1192
+
1193
+ /**
1194
+ * Read again all extensions
1195
+ * The downloaded extension may contain more sub extensions
1196
+ */
1197
+ {
1198
+ unset($installed_extensions);
1199
+ $installed_extensions = $this->get_installed_extensions(true);
1200
  }
1201
  }
1202
+ }
1203
 
1204
+ $result[$extension_name] = true;
 
 
 
1205
 
1206
+ /**
1207
+ * Collect required extensions of the newly installed extensions
1208
+ */
1209
+ foreach (
1210
+ // new extensions
1211
+ array_diff(
1212
+ array_keys($installed_extensions),
1213
+ $extensions_before_install
1214
+ )
1215
+ as $new_extension_name
1216
+ ) {
1217
+ foreach (
1218
+ array_keys(
1219
+ fw_akg(
1220
+ 'requirements/extensions',
1221
+ $installed_extensions[$new_extension_name]['manifest'],
1222
+ array()
1223
+ )
1224
+ )
1225
+ as $required_extension_name
1226
+ ) {
1227
+ if (isset($installed_extensions[$required_extension_name])) {
1228
+ // already installed
1229
+ continue;
1230
+ }
1231
+
1232
+ $extensions[$required_extension_name] = array();
1233
+ }
1234
+ }
1235
+ }
1236
+
1237
+ if ($activate) {
1238
+ $activate_extensions = array();
1239
+
1240
+ foreach ($result as $extension_name => $extension_result) {
1241
+ if (!is_wp_error($extension_result)) {
1242
+ $activate_extensions[$extension_name] = array();
1243
+ }
1244
+ }
1245
+
1246
+ if (!empty($activate_extensions)) {
1247
+ if ($verbose) {
1248
+ $verbose_message = _n(
1249
+ 'Activating extension...',
1250
+ 'Activating extensions...',
1251
+ count($activate_extensions),
1252
+ 'fw'
1253
  );
1254
 
1255
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1256
+ $verbose->feedback($verbose_message);
1257
+ } else {
1258
+ echo fw_html_tag('p', array(), $verbose_message);
1259
+ }
1260
+ }
1261
+
1262
+ $activation_result = $this->activate_extensions($activate_extensions);
1263
+
1264
+ if ($verbose) {
1265
+ if (is_wp_error($activation_result)) {
1266
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1267
+ $verbose->error($activation_result->get_error_message());
1268
+ } else {
1269
+ echo fw_html_tag('p', array(), $activation_result->get_error_message());
1270
+ }
1271
+ } elseif (is_array($activation_result)) {
1272
+ $verbose_message = array();
1273
+
1274
+ foreach ($activation_result as $extension_name => $extension_result) {
1275
+ if (is_wp_error($extension_result)) {
1276
+ $verbose_message[] = $extension_result->get_error_message();
1277
+ }
1278
+ }
1279
+
1280
+ $verbose_message = '<ul><li>' . implode('</li><li>', $verbose_message) . '</li></ul>';
1281
+
1282
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1283
+ $verbose->error($verbose_message);
1284
+ } else {
1285
+ echo fw_html_tag('p', array(), $verbose_message);
1286
+ }
1287
+ } elseif ($activation_result === true) {
1288
+ $verbose_message = _n(
1289
+ 'Extension has been successfully activated.',
1290
+ 'Extensions has been successfully activated.',
1291
+ count($activate_extensions),
1292
+ 'fw'
1293
+ );
1294
+
1295
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1296
+ $verbose->feedback($verbose_message);
1297
+ } else {
1298
+ echo fw_html_tag('p', array(), $verbose_message);
1299
+ }
1300
+ }
1301
+ }
1302
+ }
1303
+ }
1304
+
1305
+ do_action('fw_extensions_install', $result);
1306
+
1307
+ if (
1308
+ $cancel_on_error
1309
+ &&
1310
+ $has_errors
1311
+ ) {
1312
+ if (
1313
+ ($last_result = end($result))
1314
+ &&
1315
+ is_wp_error($last_result)
1316
+ ) {
1317
+ return $last_result;
1318
+ } else {
1319
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
1320
+ return new WP_Error(
1321
+ 'installation_failed',
1322
+ _n('Cannot install extension', 'Cannot install extensions', count($extensions), 'fw')
1323
+ );
1324
+ }
1325
+ }
1326
+
1327
+ if ($has_errors) {
1328
+ return $result;
1329
+ } else {
1330
+ return true;
1331
+ }
1332
+ }
1333
+
1334
+ private function display_delete_page()
1335
+ {
1336
+ $flash_id = 'fw_extensions_delete';
1337
+
1338
+ if (!$this->can_install()) {
1339
+ FW_Flash_Messages::add(
1340
+ $flash_id,
1341
+ __('You are not allowed to delete extensions.', 'fw'),
1342
+ 'error'
1343
+ );
1344
+ $this->js_redirect();
1345
+ return;
1346
+ }
1347
+
1348
+ $extensions = array_fill_keys(array_map('trim', explode(',', FW_Request::GET('extension', ''))), array());
1349
+
1350
+ {
1351
+ if (!class_exists('_FW_Extensions_Delete_Upgrader_Skin')) {
1352
+ fw_include_file_isolated(
1353
+ dirname(__FILE__) .'/includes/class--fw-extensions-delete-upgrader-skin.php'
1354
+ );
1355
+ }
1356
+
1357
+ $skin = new _FW_Extensions_Delete_Upgrader_Skin(array(
1358
+ 'title' => _n('Delete Extension', 'Delete Extensions', count($extensions), 'fw'),
1359
+ ));
1360
+ }
1361
+
1362
+ $skin->header();
1363
+
1364
+ do {
1365
+ $nonce = $this->get_nonce('delete');
1366
+
1367
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1368
+ if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1369
+ $skin->error(__('Invalid nonce.', 'fw'));
1370
+ }
1371
+
1372
+ if (!FW_WP_Filesystem::request_access(
1373
+ fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
1374
+ )) {
1375
+ break;
1376
+ }
1377
+
1378
+ $uninstall_result = $this->uninstall_extensions($extensions, array('verbose' => $skin));
1379
+
1380
+ if (is_wp_error($uninstall_result)) {
1381
+ $skin->error($uninstall_result);
1382
+ } elseif (is_array($uninstall_result)) {
1383
+ $error = array();
1384
+
1385
+ foreach ($uninstall_result as $extension_name => $extension_result) {
1386
+ if (is_wp_error($extension_result)) {
1387
+ $error[] = $extension_result->get_error_message();
1388
+ }
1389
+ }
1390
+
1391
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1392
+
1393
+ $skin->error($error);
1394
+ } elseif ($uninstall_result === true) {
1395
+ $skin->set_result(true);
1396
  }
1397
 
1398
  $skin->after(array(
1405
 
1406
  fw_render_view(dirname(__FILE__) .'/views/delete-form.php', array(
1407
  'extension_names' => array_keys($extensions),
1408
+ 'installed_extensions' => $this->get_installed_extensions(),
1409
  'list_page_link' => $this->get_link(),
1410
  ), false);
1411
 
1416
  $skin->footer();
1417
  }
1418
 
1419
+ /**
1420
+ * Remove extensions
1421
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1422
+ * @param array $opts
1423
+ * @return WP_Error|bool|array
1424
+ * true: when all extensions succeeded
1425
+ * array: when some/all failed
1426
+ */
1427
+ public function uninstall_extensions(array $extensions, $opts = array())
1428
+ {
1429
+ {
1430
+ $opts = array_merge(array(
1431
+ /**
1432
+ * @type bool
1433
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1434
+ * true: return first WP_Error or true on success
1435
+ */
1436
+ 'cancel_on_error' => false,
1437
+ /**
1438
+ * @type bool|WP_Upgrader_Skin
1439
+ */
1440
+ 'verbose' => false,
1441
+ ), $opts);
1442
+
1443
+ $cancel_on_error = $opts['cancel_on_error']; // fixme: install back successfully removed extensions before error?
1444
+ $verbose = $opts['verbose'];
1445
+
1446
+ unset($opts);
1447
+ }
1448
+
1449
+ if (!$this->can_install()) {
1450
+ return new WP_Error(
1451
+ 'access_denied',
1452
+ __('You have no permissions to uninstall extensions', 'fw')
1453
+ );
1454
+ }
1455
+
1456
+ if (empty($extensions)) {
1457
+ return new WP_Error(
1458
+ 'no_extensions',
1459
+ __('No extensions provided', 'fw')
1460
+ );
1461
+ }
1462
+
1463
+ /** @var WP_Filesystem_Base $wp_filesystem */
1464
+ global $wp_filesystem;
1465
+
1466
+ if (!$wp_filesystem) {
1467
+ return new WP_Error(
1468
+ 'fs_not_initialized',
1469
+ __('WP Filesystem is not initialized', 'fw')
1470
+ );
1471
+ }
1472
+
1473
+ $installed_extensions = $this->get_installed_extensions();
1474
+ $extensions_before_uninstall = array_fill_keys(array_keys($installed_extensions), array());
1475
+
1476
+ $result = $uninstalled_extensions = array();
1477
+ $has_errors = false;
1478
+
1479
+ while (!empty($extensions)) {
1480
+ $not_used_var = reset($extensions);
1481
+ $extension_name = key($extensions);
1482
+ unset($extensions[$extension_name]);
1483
+
1484
+ $extension_title = $this->get_extension_title($extension_name);
1485
+
1486
+ if (!isset($installed_extensions[ $extension_name ])) {
1487
+ // already deleted
1488
+ $result[$extension_name] = true;
1489
+ continue;
1490
+ }
1491
+
1492
+ if (
1493
+ !isset($installed_extensions[ $extension_name ]['path'])
1494
+ ||
1495
+ empty($installed_extensions[ $extension_name ]['path'])
1496
+ ) {
1497
+ /**
1498
+ * This happens sometimes, but I don't know why
1499
+ * If the script will continue, it will delete the root folder
1500
+ */
1501
+ fw_print(
1502
+ 'Please report this to https://github.com/ThemeFuse/Unyson/issues',
1503
+ $extension_name,
1504
+ $installed_extensions
1505
+ );
1506
+ die;
1507
+ }
1508
+
1509
+ $wp_fs_extension_path = FW_WP_Filesystem::real_path_to_filesystem_path(
1510
+ $installed_extensions[ $extension_name ]['path']
1511
+ );
1512
+
1513
+ if (!$wp_filesystem->exists($wp_fs_extension_path)) {
1514
+ // already deleted, maybe because it was a sub-extension of an deleted extension
1515
+ $result[$extension_name] = true;
1516
+ continue;
1517
+ }
1518
+
1519
+ if ($verbose) {
1520
+ $verbose_message = sprintf(__('Deleting the "%s" extension...', 'fw'), $extension_title);
1521
+
1522
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1523
+ $verbose->feedback($verbose_message);
1524
+ } else {
1525
+ echo fw_html_tag('p', array(), $verbose_message);
1526
+ }
1527
+ }
1528
+
1529
+ if (!$wp_filesystem->delete($wp_fs_extension_path, true, 'd')) {
1530
+ $result[$extension_name] = new WP_Error(
1531
+ 'cannot_delete_directory',
1532
+ sprintf(__('Cannot delete the "%s" extension.', 'fw'), $extension_title)
1533
+ );
1534
+ $has_errors = true;
1535
+
1536
+ if ($cancel_on_error) {
1537
+ break;
1538
+ } else {
1539
+ continue;
1540
+ }
1541
+ } else {
1542
+ if ($verbose) {
1543
+ $verbose_message = sprintf(
1544
+ __('The %s extension has been successfully deleted.', 'fw'),
1545
+ $extension_title
1546
+ );
1547
+
1548
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1549
+ $verbose->feedback($verbose_message);
1550
+ } else {
1551
+ echo fw_html_tag('p', array(), $verbose_message);
1552
+ }
1553
+ }
1554
+
1555
+ $result[$extension_name] = true;
1556
+ }
1557
+
1558
+ /**
1559
+ * Read again all extensions
1560
+ * The delete extension may contain more sub extensions
1561
+ */
1562
+ {
1563
+ unset($installed_extensions);
1564
+ $installed_extensions = $this->get_installed_extensions(true);
1565
+ }
1566
+
1567
+ /**
1568
+ * Add for deletion not used extensions
1569
+ * For e.g. standalone=false extension that were required by the deleted extension
1570
+ * and now are not required by any other extension
1571
+ */
1572
+ {
1573
+ $not_used_extensions = array_fill_keys(
1574
+ array_keys(
1575
+ array_diff_key(
1576
+ $installed_extensions,
1577
+ $this->get_used_extensions($extensions, array_keys($installed_extensions))
1578
+ )
1579
+ ),
1580
+ array()
1581
+ );
1582
+
1583
+ $extensions = array_merge($extensions, $not_used_extensions);
1584
+ }
1585
+ }
1586
+
1587
+ do_action('fw_extensions_uninstall', $result);
1588
+
1589
+ if (
1590
+ $cancel_on_error
1591
+ &&
1592
+ $has_errors
1593
+ ) {
1594
+ if (
1595
+ ($last_result = end($result))
1596
+ &&
1597
+ is_wp_error($last_result)
1598
+ ) {
1599
+ return $last_result;
1600
+ } else {
1601
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
1602
+ return new WP_Error(
1603
+ 'uninstall_failed',
1604
+ _n('Cannot uninstall extension', 'Cannot uninstall extensions', count($extensions), 'fw')
1605
+ );
1606
+ }
1607
+ }
1608
+
1609
+ // remove from active list the deleted extensions
1610
+ {
1611
+ update_option(
1612
+ fw()->extensions->_get_active_extensions_db_option_name(),
1613
+ array_diff_key(
1614
+ fw()->extensions->_get_db_active_extensions(),
1615
+ array_diff_key(
1616
+ $extensions_before_uninstall,
1617
+ $installed_extensions
1618
+ )
1619
+ )
1620
+ );
1621
+ }
1622
+
1623
+ if ($has_errors) {
1624
+ return $result;
1625
+ } else {
1626
+ return true;
1627
+ }
1628
+ }
1629
+
1630
  private function display_extension_page()
1631
  {
1632
  $extension_name = trim(FW_Request::GET('extension', ''));
1771
  'type' => 'html-full',
1772
  'html' => $this->get_markdown_parser()->text(
1773
  fw_render_view($docs_path, array())
1774
+ ),
1775
  ),
1776
  ))
1777
  );
1779
 
1780
  private function display_activate_page()
1781
  {
 
 
 
1782
  $error = '';
1783
 
1784
  do {
1799
  break;
1800
  }
1801
 
1802
+ $activation_result = $this->activate_extensions(
1803
+ array_fill_keys(explode(',', $_GET['extension']), array())
1804
+ );
1805
+
1806
+ if (is_wp_error($activation_result)) {
1807
+ $error = $activation_result->get_error_message();
1808
+ } elseif (is_array($activation_result)) {
1809
+ $error = array();
1810
+
1811
+ foreach ($activation_result as $extension_name => $extension_result) {
1812
+ if (is_wp_error($extension_result)) {
1813
+ $error[] = $extension_result->get_error_message();
1814
+ }
1815
+ }
1816
+
1817
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1818
+ }
1819
+ } while(false);
1820
+
1821
+ if ($error) {
1822
+ FW_Flash_Messages::add(
1823
+ 'fw_extensions_activate_page',
1824
+ $error,
1825
+ 'error'
1826
+ );
1827
+ $this->js_redirect();
1828
+ return;
1829
+ }
1830
+
1831
+ $this->js_redirect();
1832
+ }
1833
+
1834
+ /**
1835
+ * Add extensions to active extensions list in database
1836
+ * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1837
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1838
+ * @param bool $cancel_on_error
1839
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1840
+ * true: return first WP_Error or true on success
1841
+ * @return WP_Error|bool|array
1842
+ * true: when all extensions succeeded
1843
+ * array: when some/all failed
1844
+ */
1845
+ public function activate_extensions(array $extensions, $cancel_on_error = false)
1846
+ {
1847
+ if (!$this->can_activate()) {
1848
+ return new WP_Error(
1849
+ 'access_denied',
1850
+ __('You have no permissions to activate extensions', 'fw')
1851
+ );
1852
+ }
1853
+
1854
+ if (empty($extensions)) {
1855
+ return new WP_Error(
1856
+ 'no_extensions',
1857
+ __('No extensions provided', 'fw')
1858
+ );
1859
+ }
1860
+
1861
+ $installed_extensions = $this->get_installed_extensions();
1862
+
1863
+ $result = $extensions_for_activation = array();
1864
+ $has_errors = false;
1865
 
1866
+ foreach ($extensions as $extension_name => $not_used_var) {
1867
+ if (!isset($installed_extensions[$extension_name])) {
1868
+ $result[$extension_name] = new WP_Error(
1869
+ 'extension_not_installed',
1870
+ sprintf(__('Extension "%s" does not exist.', 'fw'), $this->get_extension_title($extension_name))
1871
+ );
1872
+ $has_errors = true;
1873
+
1874
+ if ($cancel_on_error) {
1875
+ break;
1876
+ } else {
1877
+ continue;
1878
  }
1879
+ }
1880
 
1881
+ $collected = $this->get_extensions_for_activation($extension_name);
1882
 
1883
+ if (is_wp_error($collected)) {
1884
+ $result[$extension_name] = $collected;
1885
+ $has_errors = true;
 
1886
 
1887
+ if ($cancel_on_error) {
1888
+ break;
1889
+ } else {
1890
+ continue;
1891
+ }
1892
  }
1893
 
1894
+ $extensions_for_activation = array_merge($extensions_for_activation, $collected);
 
1895
 
1896
+ $result[$extension_name] = true;
1897
+ }
1898
 
1899
+ if (
1900
+ $cancel_on_error
1901
+ &&
1902
+ $has_errors
1903
+ ) {
1904
+ if (
1905
+ ($last_result = end($result))
1906
+ &&
1907
+ is_wp_error($last_result)
1908
+ ) {
1909
+ return $last_result;
1910
+ } else {
1911
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
1912
+ return new WP_Error(
1913
+ 'activation_failed',
1914
+ _n('Cannot activate extension', 'Cannot activate extensions', count($extensions), 'fw')
1915
+ );
1916
+ }
1917
  }
1918
 
1919
  update_option(
1920
  fw()->extensions->_get_active_extensions_db_option_name(),
1921
+ array_merge(fw()->extensions->_get_db_active_extensions(), $extensions_for_activation)
1922
  );
1923
 
1924
+ // remove already active extensions
1925
+ foreach ($extensions_for_activation as $extension_name => $not_used_var) {
1926
+ if (fw_ext($extension_name)) {
1927
+ unset($extensions_for_activation[$extension_name]);
1928
+ }
1929
+ }
1930
+
1931
+ /**
1932
+ * Prepare db wp option used to fire the 'fw_extensions_after_activation' action on next refresh
1933
+ */
1934
+ {
1935
+ $db_wp_option_name = 'fw_extensions_activation';
1936
+ $db_wp_option_value = get_option($db_wp_option_name, array(
1937
+ 'activated' => array(),
1938
+ 'deactivated' => array(),
1939
+ ));
1940
+
1941
+ /**
1942
+ * Keep adding to the existing value instead of resetting it on each method call
1943
+ * in case the method will be called multiple times
1944
+ */
1945
+ $db_wp_option_value['activated'] = array_merge($db_wp_option_value['activated'], $extensions_for_activation);
1946
+
1947
+ /**
1948
+ * Remove activated extensions from deactivated
1949
+ */
1950
+ $db_wp_option_value['deactivated'] = array_diff_key($db_wp_option_value['deactivated'], $db_wp_option_value['activated']);
1951
+
1952
+ update_option($db_wp_option_name, $db_wp_option_value);
1953
+ }
1954
+
1955
+ do_action('fw_extensions_before_activation', $extensions_for_activation);
1956
+
1957
+ if ($has_errors) {
1958
+ return $result;
1959
+ } else {
1960
+ return true;
1961
+ }
1962
  }
1963
 
1964
  private function collect_sub_extensions($ext_name, &$installed_extensions)
2016
  break;
2017
  }
2018
 
2019
+ $deactivation_result = $this->deactivate_extensions(
2020
+ array_fill_keys(explode(',', $_GET['extension']), array())
2021
+ );
 
 
 
 
 
 
 
2022
 
2023
+ if (is_wp_error($deactivation_result)) {
2024
+ $error = $deactivation_result->get_error_message();
2025
+ } elseif (is_array($deactivation_result)) {
2026
+ $error = array();
2027
 
2028
+ foreach ($deactivation_result as $extension_name => $extension_result) {
2029
+ if (is_wp_error($extension_result)) {
2030
+ $error[] = $extension_result->get_error_message();
2031
  }
 
 
2032
  }
2033
+
2034
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
2035
  }
2036
  } while(false);
2037
 
 
 
2038
  if ($error) {
2039
+ FW_Flash_Messages::add(
2040
+ 'fw_extensions_activate_page',
2041
+ $error,
2042
+ 'error'
2043
+ );
2044
  }
2045
 
2046
+ $this->js_redirect();
2047
+ }
2048
+
2049
+ /**
2050
+ * Remove extensions from active extensions list in database
2051
+ * After refresh they will be inactive
2052
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
2053
+ * @param bool $cancel_on_error
2054
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
2055
+ * true: return first WP_Error or true on success
2056
+ * @return WP_Error|bool|array
2057
+ * true: when all extensions succeeded
2058
+ * array: when some/all failed
2059
+ */
2060
+ public function deactivate_extensions(array $extensions, $cancel_on_error = false)
2061
+ {
2062
+ if (!$this->can_activate()) {
2063
+ return new WP_Error(
2064
+ 'access_denied',
2065
+ __('You have no permissions to deactivate extensions', 'fw')
2066
+ );
2067
+ }
2068
 
2069
+ if (empty($extensions)) {
2070
+ return new WP_Error(
2071
+ 'no_extensions',
2072
+ __('No extensions provided', 'fw')
2073
+ );
2074
+ }
2075
 
2076
+ $installed_extensions = $this->get_installed_extensions();
2077
+
2078
+ $result = $extensions_for_deactivation = array();
2079
+ $has_errors = false;
2080
+
2081
+ foreach ($extensions as $extension_name => $not_used_var) {
2082
+ if (!isset($installed_extensions[$extension_name])) {
2083
+ // anyway remove from the active list
2084
+ $extensions_for_deactivation[$extension_name] = array();
2085
+
2086
+ $result[$extension_name] = new WP_Error(
2087
+ 'extension_not_installed',
2088
+ sprintf(__( 'Extension "%s" does not exist.' , 'fw' ), $this->get_extension_title($extension_name))
2089
+ );
2090
+ $has_errors = true;
2091
 
2092
+ if ($cancel_on_error) {
2093
+ break;
2094
+ } else {
2095
+ continue;
2096
  }
2097
  }
2098
 
2099
+ $current_deactivating_extensions = array(
2100
+ $extension_name => array()
2101
+ );
2102
+
2103
+ // add sub-extensions for deactivation
2104
+ foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2105
+ $current_deactivating_extensions[ $sub_extension_name ] = array();
2106
  }
2107
 
2108
+ // add extensions that requires deactivated extensions
2109
+ $this->collect_extensions_that_requires($current_deactivating_extensions, $current_deactivating_extensions);
2110
+
2111
+ $extensions_for_deactivation = array_merge(
2112
+ $extensions_for_deactivation,
2113
+ $current_deactivating_extensions
2114
+ );
2115
+
2116
+ unset($current_deactivating_extensions);
2117
+
2118
+ $result[$extension_name] = true;
2119
+ }
2120
 
2121
+ if (
2122
+ $cancel_on_error
2123
+ &&
2124
+ $has_errors
2125
+ ) {
2126
+ if (
2127
+ ($last_result = end($result))
2128
+ &&
2129
+ is_wp_error($last_result)
2130
+ ) {
2131
+ return $last_result;
2132
+ } else {
2133
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
2134
+ return new WP_Error(
2135
+ 'deactivation_failed',
2136
+ _n('Cannot deactivate extension', 'Cannot activate extensions', count($extensions), 'fw')
2137
+ );
2138
  }
2139
  }
2140
 
2141
+ // add not used extensions for deactivation
2142
+ $extensions_for_deactivation = array_merge($extensions_for_deactivation,
2143
+ array_fill_keys(
2144
+ array_keys(
2145
+ array_diff_key(
2146
+ $installed_extensions,
2147
+ $this->get_used_extensions($extensions_for_deactivation, array_keys(fw()->extensions->get_all()))
2148
+ )
2149
+ ),
2150
+ array()
2151
+ )
2152
+ );
2153
+
2154
  update_option(
2155
  fw()->extensions->_get_active_extensions_db_option_name(),
2156
+ array_diff_key(
2157
+ fw()->extensions->_get_db_active_extensions(),
2158
+ $extensions_for_deactivation
2159
+ )
2160
  );
2161
 
2162
+ // remove already inactive extensions
2163
+ foreach ($extensions_for_deactivation as $extension_name => $not_used_var) {
2164
+ if (!fw_ext($extension_name)) {
2165
+ unset($extensions_for_deactivation[$extension_name]);
2166
+ }
2167
+ }
2168
+
2169
+ /**
2170
+ * Prepare db wp option used to fire the 'fw_extensions_after_deactivation' action on next refresh
2171
+ */
2172
+ {
2173
+ $db_wp_option_name = 'fw_extensions_activation';
2174
+ $db_wp_option_value = get_option($db_wp_option_name, array(
2175
+ 'activated' => array(),
2176
+ 'deactivated' => array(),
2177
+ ));
2178
+
2179
+ /**
2180
+ * Keep adding to the existing value instead of resetting it on each method call
2181
+ * in case the method will be called multiple times
2182
+ */
2183
+ $db_wp_option_value['deactivated'] = array_merge($db_wp_option_value['deactivated'], $extensions_for_deactivation);
2184
+
2185
+ /**
2186
+ * Remove deactivated extensions from activated
2187
+ */
2188
+ $db_wp_option_value['activated'] = array_diff_key($db_wp_option_value['activated'], $db_wp_option_value['deactivated']);
2189
+
2190
+ update_option($db_wp_option_name, $db_wp_option_value);
2191
+ }
2192
+
2193
+ do_action('fw_extensions_before_deactivation', $extensions_for_deactivation);
2194
+
2195
+ if ($has_errors) {
2196
+ return $result;
2197
+ } else {
2198
+ return true;
2199
+ }
2200
  }
2201
 
2202
  /**
2203
  * @param array $data
2204
  * @return array
2205
+ * @internal
2206
  */
2207
  public function _extension_settings_form_render($data)
2208
  {
2244
  /**
2245
  * @param array $errors
2246
  * @return array
2247
+ * @internal
2248
  */
2249
  public function _extension_settings_form_validate($errors)
2250
  {
2273
  /**
2274
  * @param array $data
2275
  * @return array
2276
+ * @internal
2277
  */
2278
  public function _extension_settings_form_save($data)
2279
  {
2672
  }
2673
  }
2674
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2675
  private function get_supported_extensions_for_install()
2676
  {
2677
  $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
2697
  return $supported_extensions;
2698
  }
2699
 
2700
+ /**
2701
+ * @param $actions
2702
+ * @return array
2703
+ * @internal
2704
+ */
2705
  public function _filter_plugin_action_list($actions)
2706
  {
2707
  return array_merge(
2811
  foreach (array_keys($skip_extensions) as $skip_extension_name) {
2812
  unset($used_extensions[$skip_extension_name]);
2813
 
2814
+ if (isset($installed_extensions[$skip_extension_name])) {
2815
+ foreach ($this->collect_sub_extensions($skip_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2816
+ unset($used_extensions[$sub_extension_name]);
2817
+ }
2818
  }
2819
  }
2820
 
2829
  $this->activate_hidden_standalone_extensions();
2830
  }
2831
 
2832
+ public function get_extension_title($extension_name)
2833
  {
2834
  $installed_extensions = $this->get_installed_extensions();
2835
 
2883
  public function _action_theme_switch()
2884
  {
2885
  $this->activate_theme_extensions();
2886
+ $this->activate_extensions(
2887
  array_fill_keys(
2888
  array_keys(fw()->theme->manifest->get('supported_extensions', array())),
2889
  array()
2891
  );
2892
  }
2893
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2894
  /**
2895
  * @param array $collected The found extensions {'extension_name' => array()}
2896
  * @param array $extensions {'extension_name' => array()}
2956
  return new WP_Error($wp_error_id,
2957
  sprintf(
2958
  __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
2959
+ $this->get_extension_title($extension_name),
2960
  fw_html_tag('a', array(
2961
  'href' => $this->get_link() .'&sub-page=install&extension='. $extension_name
2962
  ), __('Install', 'fw'))
3003
  return new WP_Error($wp_error_id,
3004
  sprintf(
3005
  __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3006
+ $this->get_extension_title($required_extension_name),
3007
  fw_html_tag('a', array(
3008
  'href' => $this->get_link() .'&sub-page=install&extension='. $required_extension_name
3009
  ), __('Install', 'fw'))
framework/core/components/extensions/manager/static/extensions-page.css CHANGED
@@ -45,13 +45,28 @@
45
  margin-top: 0;
46
  }
47
 
48
- .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-details p:last-child,
49
  .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title:last-child {
50
  margin-bottom: 0;
51
  }
52
 
53
- .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-details {
54
- padding-right: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
56
 
57
  @media (max-width: 782px) {
@@ -59,53 +74,48 @@
59
  padding-right: 0;
60
  }
61
 
62
- .fw-extensions-list .fw-extensions-list-item table,
63
- .fw-extensions-list .fw-extensions-list-item table tbody,
64
- .fw-extensions-list .fw-extensions-list-item table tr,
65
- .fw-extensions-list .fw-extensions-list-item table td {
66
- display: block;
67
- }
68
-
69
- .fw-extensions-list .fw-extensions-list-item table tr:before,
70
- .fw-extensions-list .fw-extensions-list-item table tr:after {
71
- display: table;
72
- content: " ";
73
- }
74
-
75
- .fw-extensions-list .fw-extensions-list-item table tr:after {
76
- clear: both;
77
  }
78
 
79
- .fw-extensions-list .fw-extensions-list-item td {
80
- float: left;
81
- clear: both;
82
- width: 100%;
83
- padding-bottom: 20px;
84
-
85
- -webkit-box-sizing: border-box;
86
- -moz-box-sizing: border-box;
87
- box-sizing: border-box;
88
  }
89
 
90
- .fw-extensions-list .fw-extensions-list-item td:last-child {
91
- padding-bottom: 0;
92
  }
93
 
94
- .fw-extensions-list .fw-extensions-list-item .fw-text-center {
95
- text-align: left;
 
 
96
  }
97
 
98
- body.rtl .fw-extensions-list .fw-extensions-list-item .fw-text-center {
99
- text-align: right;
 
100
  }
101
  }
102
 
103
-
104
  @media (min-width: 783px) {
105
  hr.fw-extensions-lists-separator {
106
  margin: 22px 0 30px;
107
  margin-right: 15px; /* same as .fw-extensions-list .fw-extensions-list-item padding-right */
108
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  }
110
 
111
 
45
  margin-top: 0;
46
  }
47
 
 
48
  .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title:last-child {
49
  margin-bottom: 0;
50
  }
51
 
52
+
53
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table {
54
+ display: table;
55
+ width: 100%;
56
+ }
57
+
58
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row {
59
+ display: table-row;
60
+ }
61
+
62
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell {
63
+ display: table-cell;
64
+ vertical-align: top;
65
+ }
66
+
67
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-2,
68
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 {
69
+ vertical-align: middle;
70
  }
71
 
72
  @media (max-width: 782px) {
74
  padding-right: 0;
75
  }
76
 
77
+ .fw-extensions-list .fw-extensions-list-item .fw-text-center {
78
+ text-align: left;
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  }
80
 
81
+ body.rtl .fw-extensions-list .fw-extensions-list-item .fw-text-center {
82
+ text-align: right;
 
 
 
 
 
 
 
83
  }
84
 
85
+ .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title {
86
+ margin-top: 20px;
87
  }
88
 
89
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table,
90
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row,
91
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell {
92
+ display: block;
93
  }
94
 
95
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form,
96
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form .button {
97
+ margin-bottom: 0;
98
  }
99
  }
100
 
 
101
  @media (min-width: 783px) {
102
  hr.fw-extensions-lists-separator {
103
  margin: 22px 0 30px;
104
  margin-right: 15px; /* same as .fw-extensions-list .fw-extensions-list-item padding-right */
105
  }
106
+
107
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell:first-child,
108
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell:last-child {
109
+ width: 10px;
110
+ }
111
+
112
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell:not(:first-child) {
113
+ padding-left: 20px;
114
+ }
115
+
116
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell > p:last-child {
117
+ margin-bottom: 0;
118
+ }
119
  }
120
 
121
 
framework/core/components/extensions/manager/static/img/thumbnails/translation.jpg ADDED
Binary file
framework/core/components/extensions/manager/views/delete-form.php CHANGED
@@ -17,7 +17,7 @@ $count = count($extension_names);
17
 
18
  <ul class="ul-disc">
19
  <?php foreach ($extension_names as $extension_name): ?>
20
- <li><strong><?php echo fw_akg('name', $installed_extensions[ $extension_name ]['manifest'], fw_id_to_title($extension_name)); ?></strong></li>
21
  <?php endforeach; ?>
22
  </ul>
23
 
@@ -48,6 +48,7 @@ $count = count($extension_names);
48
  <ul class="code">
49
  <?php $replace_regex = '/^'. preg_quote(fw_get_framework_directory('/extensions'), '/') .'/'; ?>
50
  <?php foreach ($extension_names as $extension_name): ?>
 
51
  <li><?php echo preg_replace($replace_regex, '', $installed_extensions[$extension_name]['path']) ?>/</li>
52
  <?php endforeach; ?>
53
  </ul>
17
 
18
  <ul class="ul-disc">
19
  <?php foreach ($extension_names as $extension_name): ?>
20
+ <li><strong><?php echo fw()->extensions->manager->get_extension_title($extension_name); ?></strong></li>
21
  <?php endforeach; ?>
22
  </ul>
23
 
48
  <ul class="code">
49
  <?php $replace_regex = '/^'. preg_quote(fw_get_framework_directory('/extensions'), '/') .'/'; ?>
50
  <?php foreach ($extension_names as $extension_name): ?>
51
+ <?php if (!isset($installed_extensions[$extension_name])) continue; ?>
52
  <li><?php echo preg_replace($replace_regex, '', $installed_extensions[$extension_name]['path']) ?>/</li>
53
  <?php endforeach; ?>
54
  </ul>
framework/core/components/extensions/manager/views/extension.php CHANGED
@@ -39,13 +39,12 @@ if (isset($lists['available'][$name])) {
39
  ?>
40
  <div class="fw-col-xs-12 fw-col-lg-6 fw-extensions-list-item<?php if ($installed_data && !$is_active): ?> disabled<?php endif; ?>" id="fw-ext-<?php echo esc_attr($name) ?>">
41
  <div class="inner">
42
- <table width="100%">
43
- <tbody>
44
- <tr>
45
- <td valign="top" align="left" width="140">
46
  <img height="128" src="<?php echo esc_attr($thumbnail) ?>" class="fw-extensions-list-item-thumbnail" alt="Thumbnail"/>
47
- </td>
48
- <td valign="middle" align="left" class="fw-extensions-list-item-details">
49
  <h3 class="fw-extensions-list-item-title"><?php
50
  if ($is_active && ($extension_link = fw()->extensions->get($name)->_get_link())) {
51
  echo fw_html_tag('a', array('href' => $extension_link), $title);
@@ -90,8 +89,8 @@ if (isset($lists['available'][$name])) {
90
  ): ?>
91
  <p><em><strong><span class="dashicons dashicons-yes"></span> <?php _e('Compatible', 'fw') ?></strong> <?php _e('with your current theme', 'fw') ?></em></p>
92
  <?php endif; ?>
93
- </td>
94
- <td valign="middle" align="left" width="10">
95
  <?php if ($is_active): ?>
96
  <form action="<?php echo esc_attr($link) ?>&sub-page=deactivate&extension=<?php echo esc_attr($name) ?>" method="post">
97
  <?php wp_nonce_field($nonces['deactivate']['action'], $nonces['deactivate']['name']); ?>
@@ -128,10 +127,9 @@ if (isset($lists['available'][$name])) {
128
  <input type="submit" class="button" value="<?php esc_attr_e('Download', 'fw') ?>">
129
  </form>
130
  <?php endif; ?>
131
- </td>
132
- </tr>
133
- </tbody>
134
- </table>
135
  <?php if ($installed_data): ?>
136
  <?php if (!$is_active): ?>
137
  <?php if (!fw()->extensions->_get_db_active_extensions($name)): ?>
39
  ?>
40
  <div class="fw-col-xs-12 fw-col-lg-6 fw-extensions-list-item<?php if ($installed_data && !$is_active): ?> disabled<?php endif; ?>" id="fw-ext-<?php echo esc_attr($name) ?>">
41
  <div class="inner">
42
+ <div class="fw-extension-list-item-table">
43
+ <div class="fw-extension-list-item-table-row">
44
+ <div class="fw-extension-list-item-table-cell cell-1">
 
45
  <img height="128" src="<?php echo esc_attr($thumbnail) ?>" class="fw-extensions-list-item-thumbnail" alt="Thumbnail"/>
46
+ </div>
47
+ <div class="fw-extension-list-item-table-cell cell-2">
48
  <h3 class="fw-extensions-list-item-title"><?php
49
  if ($is_active && ($extension_link = fw()->extensions->get($name)->_get_link())) {
50
  echo fw_html_tag('a', array('href' => $extension_link), $title);
89
  ): ?>
90
  <p><em><strong><span class="dashicons dashicons-yes"></span> <?php _e('Compatible', 'fw') ?></strong> <?php _e('with your current theme', 'fw') ?></em></p>
91
  <?php endif; ?>
92
+ </div>
93
+ <div class="fw-extension-list-item-table-cell cell-3">
94
  <?php if ($is_active): ?>
95
  <form action="<?php echo esc_attr($link) ?>&sub-page=deactivate&extension=<?php echo esc_attr($name) ?>" method="post">
96
  <?php wp_nonce_field($nonces['deactivate']['action'], $nonces['deactivate']['name']); ?>
127
  <input type="submit" class="button" value="<?php esc_attr_e('Download', 'fw') ?>">
128
  </form>
129
  <?php endif; ?>
130
+ </div>
131
+ </div>
132
+ </div>
 
133
  <?php if ($installed_data): ?>
134
  <?php if (!$is_active): ?>
135
  <?php if (!fw()->extensions->_get_db_active_extensions($name)): ?>
framework/core/components/theme.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  /**
4
  * Theme Component
5
- * Works with framework-customizations/theme directory
6
  */
7
  final class _FW_Component_Theme
8
  {
2
 
3
  /**
4
  * Theme Component
5
+ * Works with framework customizations / theme directory
6
  */
7
  final class _FW_Component_Theme
8
  {
framework/core/extends/class-fw-extension.php CHANGED
@@ -33,23 +33,14 @@ abstract class FW_Extension
33
  private $parent;
34
 
35
  /**
36
- * Where this extension was declared: 'framework', 'parent' or 'child'
37
  * @var string
38
  */
39
- private $declared_source;
40
 
41
  /**
42
- * Full path to directory where the extension was first found/declared
43
- * (Search direction: framework -> parent theme -> child theme)
44
  * @var string
45
  */
46
- private $declared_dir;
47
-
48
- /**
49
- * URI to directory where extension was first found/declared
50
- * @var string
51
- */
52
- private $declared_URI;
53
 
54
  /**
55
  * On what directory depth is the extension
@@ -64,31 +55,27 @@ abstract class FW_Extension
64
  private $depth;
65
 
66
  /**
67
- * If _init() was called
68
- * @var bool
69
  */
70
- private $initialized = false;
71
 
72
- final public function __construct(&$parent, $declared_dir, $declared_source, $URI, $depth)
73
  {
74
  if (!self::$access_key) {
75
  self::$access_key = new FW_Access_Key('extension');
76
  }
77
 
78
- $this->parent =& $parent;
79
- $this->declared_source = $declared_source;
80
- $this->declared_dir = $declared_dir;
81
- $this->declared_URI = $URI . $this->get_rel_path(); // ! set $this->parent before calling get_rel_path()
82
- $this->depth = $depth;
83
 
84
  {
85
- $manifest = array();
86
-
87
- if (file_exists($this->declared_dir .'/manifest.php')) {
88
- $variables = fw_get_variables_from_file($this->declared_dir .'/manifest.php', array('manifest' => array()));
89
- $manifest = $variables['manifest'];
90
- unset($variables);
91
- }
92
 
93
  if (empty($manifest['name'])) {
94
  $manifest['name'] = fw_id_to_title($this->get_name());
@@ -132,15 +119,10 @@ abstract class FW_Extension
132
 
133
  /**
134
  * @internal
 
135
  */
136
  final public function _call_init()
137
  {
138
- if ($this->initialized) { // todo: use private key
139
- trigger_error('Extension already initialized: '. $this->get_name(), E_USER_ERROR);
140
- } else {
141
- $this->initialized = true;
142
- }
143
-
144
  return $this->_init();
145
  }
146
 
@@ -155,52 +137,38 @@ abstract class FW_Extension
155
 
156
  /**
157
  * @param string $rel_path '/views/test.php'
158
- * @return false|string '/var/www/wordpress/wp-content/themes/theme-name/framework/extensions/<extension>/views/test.php'
159
  */
160
  final public function locate_path($rel_path)
161
  {
162
- list(
163
- $search_in_framework,
164
- $search_in_parent_theme,
165
- $search_in_child_theme
166
- ) = $this->correct_search_in_locations(
167
- true,
168
- true,
169
- true
170
- );
171
 
172
- $rel_path = $this->get_rel_path() . $rel_path;
 
 
 
 
173
 
174
- return fw()->extensions->locate_path($rel_path,
175
- $search_in_framework,
176
- $search_in_parent_theme,
177
- $search_in_child_theme
178
- );
179
  }
180
 
181
  /**
182
  * @param string $rel_path E.g. '/static/js/scripts.js'
183
- * @return string URI E.g. 'http: //wordpress.com/wp-content/themes/theme-name/framework/extensions/<extension>/static/js/scripts.js'
184
  */
185
  final public function locate_URI($rel_path)
186
  {
187
- list(
188
- $search_in_framework,
189
- $search_in_parent_theme,
190
- $search_in_child_theme
191
- ) = $this->correct_search_in_locations(
192
- true,
193
- true,
194
- true
195
- );
196
 
197
- $rel_path = $this->get_rel_path() . $rel_path;
 
 
 
 
198
 
199
- return fw()->extensions->locate_path_URI($rel_path,
200
- $search_in_framework,
201
- $search_in_parent_theme,
202
- $search_in_child_theme
203
- );
204
  }
205
 
206
  /**
@@ -217,8 +185,7 @@ abstract class FW_Extension
217
  final public function get_name()
218
  {
219
  if ($this->name === null) {
220
- $this->name = explode('/', $this->get_declared_path());
221
- $this->name = array_pop($this->name);
222
  }
223
 
224
  return $this->name;
@@ -226,53 +193,45 @@ abstract class FW_Extension
226
 
227
  /**
228
  * @return string
 
229
  */
230
  final public function get_declared_source()
231
  {
232
- return $this->declared_source;
233
  }
234
 
235
  /**
236
  * @param string $append_rel_path E.g. '/includes/something.php'
237
  * @return string
 
238
  */
239
  final public function get_declared_path($append_rel_path = '')
240
  {
241
- return $this->declared_dir . $append_rel_path;
 
 
 
 
 
242
  }
243
 
244
  /**
245
  * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
246
  * @return string
 
247
  */
248
  final public function get_declared_URI($append_rel_path = '')
249
  {
250
- return $this->declared_URI . $append_rel_path;
251
  }
252
 
253
  /**
254
- * Used to determine where to search files (options, views, static files)
255
- * Because there is no sense to search (one level up) in framework, if extension is declared in parent theme
256
- * @param bool $search_in_framework
257
- * @param bool $search_in_parent_theme
258
- * @param bool $search_in_child_theme
259
- * @return array
260
  */
261
- final public function correct_search_in_locations($search_in_framework, $search_in_parent_theme, $search_in_child_theme)
262
  {
263
- $source = $this->get_declared_source();
264
-
265
- if ($source == 'parent') {
266
- $search_in_framework = false;
267
- } elseif ($source == 'child') {
268
- $search_in_framework = $search_in_parent_theme = false;
269
- }
270
-
271
- if (!is_child_theme()) {
272
- $search_in_child_theme = false;
273
- }
274
-
275
- return array($search_in_framework, $search_in_parent_theme, $search_in_child_theme);
276
  }
277
 
278
  /**
@@ -308,35 +267,6 @@ abstract class FW_Extension
308
  return $result;
309
  }
310
 
311
- /**
312
- * Get path relative to parent "extensions" directory where all extensions are located
313
- * E.g.: /foo/extensions/bar/extensions/hello
314
- * @return string
315
- */
316
- final public function get_rel_path()
317
- {
318
- $cache_key = $this->get_cache_key() .'/rel_path';
319
-
320
- try {
321
- return FW_Cache::get($cache_key);
322
- } catch (FW_Cache_Not_Found_Exception $e) {
323
- $rel_path = array($this->get_name());
324
-
325
- // balk back in parents and generate array(child, ..., parent)
326
- $parent_walker = $this;
327
- while ($parent_walker = $parent_walker->get_parent()) {
328
- $rel_path[] = $parent_walker->get_name();
329
- }
330
- unset($parent_walker);
331
-
332
- $rel_path = '/'. implode('/extensions/', array_reverse($rel_path));
333
-
334
- FW_Cache::set($cache_key, $rel_path);
335
-
336
- return $rel_path;
337
- }
338
- }
339
-
340
  /**
341
  * Return config key value, or entire config array
342
  * Config array is merged from child configs
@@ -350,33 +280,16 @@ abstract class FW_Extension
350
  try {
351
  $config = FW_Cache::get($cache_key);
352
  } catch (FW_Cache_Not_Found_Exception $e) {
353
- list(
354
- $search_in_framework,
355
- $search_in_parent_theme,
356
- $search_in_child_theme
357
- ) = $this->correct_search_in_locations(
358
- true,
359
- true,
360
- true
361
- );
362
-
363
- $rel_path = $this->get_rel_path() .'/config.php';
364
- $config = array();
365
- $paths = array();
366
-
367
- if ($search_in_framework) {
368
- $paths[] = fw_get_framework_directory('/extensions'. $rel_path);
369
- }
370
- if ($search_in_parent_theme) {
371
- $paths[] = fw_get_template_customizations_directory('/extensions'. $rel_path);
372
- }
373
- if ($search_in_child_theme) {
374
- $paths[] = fw_get_stylesheet_customizations_directory('/extensions'. $rel_path);
375
- }
376
 
377
- foreach ($paths as $path) {
378
- if (file_exists($path)) {
379
- $variables = fw_get_variables_from_file($path, array('cfg' => null));
380
 
381
  if (!empty($variables['cfg'])) {
382
  $config = array_merge($config, $variables['cfg']);
@@ -385,8 +298,6 @@ abstract class FW_Extension
385
  }
386
  }
387
 
388
- unset($path);
389
-
390
  FW_Cache::set($cache_key, $config);
391
  }
392
 
@@ -418,7 +329,7 @@ abstract class FW_Extension
418
  try {
419
  return FW_Cache::get($cache_key);
420
  } catch (FW_Cache_Not_Found_Exception $e) {
421
- $path = $this->get_declared_path('/settings-options.php');
422
 
423
  if (!file_exists($path)) {
424
  FW_Cache::set($cache_key, array());
@@ -521,6 +432,19 @@ abstract class FW_Extension
521
  return $this->depth;
522
  }
523
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
  /**
525
  * Check if child extension is valid
526
  *
33
  private $parent;
34
 
35
  /**
 
36
  * @var string
37
  */
38
+ private $path;
39
 
40
  /**
 
 
41
  * @var string
42
  */
43
+ private $uri;
 
 
 
 
 
 
44
 
45
  /**
46
  * On what directory depth is the extension
55
  private $depth;
56
 
57
  /**
58
+ * Locations where the extension can look for customizations (overwrite views, options; extend config)
59
+ * @var array {'/path' => 'https://uri.to/path'}
60
  */
61
+ private $customizations_locations;
62
 
63
+ final public function __construct($data)
64
  {
65
  if (!self::$access_key) {
66
  self::$access_key = new FW_Access_Key('extension');
67
  }
68
 
69
+ $this->path = $data['path'];
70
+ $this->uri = $data['uri'];
71
+ $this->parent = $data['parent'];
72
+ $this->depth = $data['depth'];
73
+ $this->customizations_locations = $data['customizations_locations'];
74
 
75
  {
76
+ $variables = fw_get_variables_from_file($this->path .'/manifest.php', array('manifest' => array()));
77
+ $manifest = $variables['manifest'];
78
+ unset($variables);
 
 
 
 
79
 
80
  if (empty($manifest['name'])) {
81
  $manifest['name'] = fw_id_to_title($this->get_name());
119
 
120
  /**
121
  * @internal
122
+ * fixme: ask access key from caller
123
  */
124
  final public function _call_init()
125
  {
 
 
 
 
 
 
126
  return $this->_init();
127
  }
128
 
137
 
138
  /**
139
  * @param string $rel_path '/views/test.php'
140
+ * @return false|string '/var/www/.../extensions/<extension>/views/test.php'
141
  */
142
  final public function locate_path($rel_path)
143
  {
144
+ $locations = $this->customizations_locations;
145
+ $locations[$this->get_path()] = $this->get_uri();
 
 
 
 
 
 
 
146
 
147
+ foreach ($locations as $path => $uri) {
148
+ if (file_exists($path . $rel_path)) {
149
+ return $path . $rel_path;
150
+ }
151
+ }
152
 
153
+ return false;
 
 
 
 
154
  }
155
 
156
  /**
157
  * @param string $rel_path E.g. '/static/js/scripts.js'
158
+ * @return string URI E.g. 'http: //wordpress.com/.../extensions/<extension>/static/js/scripts.js'
159
  */
160
  final public function locate_URI($rel_path)
161
  {
162
+ $locations = $this->customizations_locations;
163
+ $locations[$this->get_path()] = $this->get_uri();
 
 
 
 
 
 
 
164
 
165
+ foreach ($this->customizations_locations as $path => $uri) {
166
+ if (file_exists($path . $rel_path)) {
167
+ return $uri . $rel_path;
168
+ }
169
+ }
170
 
171
+ return false;
 
 
 
 
172
  }
173
 
174
  /**
185
  final public function get_name()
186
  {
187
  if ($this->name === null) {
188
+ $this->name = basename($this->path);
 
189
  }
190
 
191
  return $this->name;
193
 
194
  /**
195
  * @return string
196
+ * @deprecated
197
  */
198
  final public function get_declared_source()
199
  {
200
+ return 'deprecated';
201
  }
202
 
203
  /**
204
  * @param string $append_rel_path E.g. '/includes/something.php'
205
  * @return string
206
+ * @deprecated
207
  */
208
  final public function get_declared_path($append_rel_path = '')
209
  {
210
+ return $this->get_path($append_rel_path);
211
+ }
212
+
213
+ final public function get_path($append_rel_path = '')
214
+ {
215
+ return $this->path . $append_rel_path;
216
  }
217
 
218
  /**
219
  * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
220
  * @return string
221
+ * @deprecated
222
  */
223
  final public function get_declared_URI($append_rel_path = '')
224
  {
225
+ return $this->get_uri($append_rel_path);
226
  }
227
 
228
  /**
229
+ * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
230
+ * @return string
 
 
 
 
231
  */
232
+ final public function get_uri($append_rel_path = '')
233
  {
234
+ return $this->uri . $append_rel_path;
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
 
237
  /**
267
  return $result;
268
  }
269
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  /**
271
  * Return config key value, or entire config array
272
  * Config array is merged from child configs
280
  try {
281
  $config = FW_Cache::get($cache_key);
282
  } catch (FW_Cache_Not_Found_Exception $e) {
283
+ $config = array();
284
+
285
+ $locations = $this->customizations_locations;
286
+ $locations[$this->get_path()] = $this->get_uri();
287
+
288
+ foreach (array_reverse($locations) as $path => $uri) {
289
+ $config_path = $path .'/config.php';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
+ if (file_exists($config_path)) {
292
+ $variables = fw_get_variables_from_file($config_path, array('cfg' => null));
 
293
 
294
  if (!empty($variables['cfg'])) {
295
  $config = array_merge($config, $variables['cfg']);
298
  }
299
  }
300
 
 
 
301
  FW_Cache::set($cache_key, $config);
302
  }
303
 
329
  try {
330
  return FW_Cache::get($cache_key);
331
  } catch (FW_Cache_Not_Found_Exception $e) {
332
+ $path = $this->get_path('/settings-options.php');
333
 
334
  if (!file_exists($path)) {
335
  FW_Cache::set($cache_key, array());
432
  return $this->depth;
433
  }
434
 
435
+ final public function get_customizations_locations()
436
+ {
437
+ return $this->customizations_locations;
438
+ }
439
+
440
+ /**
441
+ * @deprecated
442
+ */
443
+ final public function get_rel_path()
444
+ {
445
+ return preg_replace('/^'. preg_quote(fw_get_framework_directory('/extensions'), '/') .'/', '', $this->get_path());
446
+ }
447
+
448
  /**
449
  * Check if child extension is valid
450
  *
framework/helpers/general.php CHANGED
@@ -627,17 +627,17 @@ function fw_extract_only_options(array $options, &$_recursion_options = array())
627
  * @param array $options
628
  */
629
  function fw_collect_first_level_options(&$collected, &$options) {
630
- if (empty($options))
631
  return;
 
632
 
633
  if (empty($collected)) {
634
- $collected['tabs'] = array();
635
- $collected['boxes'] = array();
636
- $collected['groups'] = array();
637
-
638
- $collected['options'] = array();
639
-
640
- $collected['groups_and_options'] = array();
641
  }
642
 
643
  foreach ($options as $option_id => &$option) {
@@ -653,12 +653,18 @@ function fw_collect_first_level_options(&$collected, &$options) {
653
  break;
654
  case 'group':
655
  $collected['groups'][$option_id] =& $option;
656
-
657
  $collected['groups_and_options'][$option_id] =& $option;
658
  break;
659
  default:
660
  trigger_error('Invalid option container type: '. $option['type'], E_USER_WARNING);
 
661
  }
 
 
 
 
 
 
662
  } elseif (
663
  is_int($option_id)
664
  &&
@@ -698,8 +704,13 @@ function fw_collect_first_level_options(&$collected, &$options) {
698
  } elseif (isset($option['type'])) {
699
  // simple option, last possible level in options array
700
  $collected['options'][$option_id] =& $option;
701
-
702
  $collected['groups_and_options'][$option_id] =& $option;
 
 
 
 
 
 
703
  } else {
704
  trigger_error('Invalid option: '. $option_id, E_USER_WARNING);
705
  }
@@ -725,6 +736,11 @@ function fw_get_options_values_from_input(array $options, $input_array = null) {
725
  $option,
726
  isset($input_array[$id]) ? $input_array[$id] : null
727
  );
 
 
 
 
 
728
  }
729
 
730
  return $values;
@@ -923,7 +939,11 @@ function fw_human_time($seconds)
923
  }
924
 
925
  function fw_strlen($string) {
926
- return mb_strlen($string, 'UTF-8');
 
 
 
 
927
  }
928
 
929
  /**
@@ -1052,7 +1072,11 @@ function fw_secure_rand($length)
1052
  */
1053
  function fw_id_to_title($id) {
1054
  // mb_ucfirst()
1055
- $id = mb_strtoupper(mb_substr($id, 0, 1, 'UTF-8'), 'UTF-8') . mb_substr($id, 1, mb_strlen($id, 'UTF-8'), 'UTF-8');
 
 
 
 
1056
 
1057
  return str_replace(array('_', '-'), ' ', $id);
1058
  }
627
  * @param array $options
628
  */
629
  function fw_collect_first_level_options(&$collected, &$options) {
630
+ if (empty($options)) {
631
  return;
632
+ }
633
 
634
  if (empty($collected)) {
635
+ $collected['tabs'] =
636
+ $collected['boxes'] =
637
+ $collected['groups'] =
638
+ $collected['options'] =
639
+ $collected['groups_and_options'] =
640
+ $collected['all'] = array();
 
641
  }
642
 
643
  foreach ($options as $option_id => &$option) {
653
  break;
654
  case 'group':
655
  $collected['groups'][$option_id] =& $option;
 
656
  $collected['groups_and_options'][$option_id] =& $option;
657
  break;
658
  default:
659
  trigger_error('Invalid option container type: '. $option['type'], E_USER_WARNING);
660
+ continue 2;
661
  }
662
+
663
+ $collected['all'][ $option['type'] .':~:'. $option_id ] = array(
664
+ 'type' => $option['type'],
665
+ 'id' => $option_id,
666
+ 'option' => &$option,
667
+ );
668
  } elseif (
669
  is_int($option_id)
670
  &&
704
  } elseif (isset($option['type'])) {
705
  // simple option, last possible level in options array
706
  $collected['options'][$option_id] =& $option;
 
707
  $collected['groups_and_options'][$option_id] =& $option;
708
+
709
+ $collected['all'][ 'option' .':~:'. $option_id ] = array(
710
+ 'type' => 'option',
711
+ 'id' => $option_id,
712
+ 'option' => &$option,
713
+ );
714
  } else {
715
  trigger_error('Invalid option: '. $option_id, E_USER_WARNING);
716
  }
736
  $option,
737
  isset($input_array[$id]) ? $input_array[$id] : null
738
  );
739
+
740
+ if (is_null($values[$id])) {
741
+ // do not save null values
742
+ unset($values[$id]);
743
+ }
744
  }
745
 
746
  return $values;
939
  }
940
 
941
  function fw_strlen($string) {
942
+ if (function_exists('mb_strlen')) {
943
+ return mb_strlen($string, 'UTF-8');
944
+ } else {
945
+ return strlen($string);
946
+ }
947
  }
948
 
949
  /**
1072
  */
1073
  function fw_id_to_title($id) {
1074
  // mb_ucfirst()
1075
+ if (function_exists('mb_strtoupper') && function_exists('mb_substr') && function_exists('mb_strlen')) {
1076
+ $id = mb_strtoupper(mb_substr($id, 0, 1, 'UTF-8'), 'UTF-8') . mb_substr($id, 1, mb_strlen($id, 'UTF-8'), 'UTF-8');
1077
+ } else {
1078
+ $id = strtoupper(substr($id, 0, 1)) . substr($id, 1, strlen($id));
1079
+ }
1080
 
1081
  return str_replace(array('_', '-'), ' ', $id);
1082
  }
framework/includes/option-types/addable-box/class-fw-option-type-addable-box.php CHANGED
@@ -44,7 +44,19 @@ class FW_Option_Type_Addable_Box extends FW_Option_Type
44
  {
45
  $new_options = array();
46
  foreach ($options as $id => $option) {
47
- $new_options[] = array($id => $option);
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
  return $new_options;
50
  }
44
  {
45
  $new_options = array();
46
  foreach ($options as $id => $option) {
47
+ if (is_int($id)) {
48
+ /**
49
+ * this happens when in options array are loaded external options using fw()->theme->get_options()
50
+ * and the array looks like this
51
+ * array(
52
+ * 'hello' => array('type' => 'text'), // this has string key
53
+ * array('hi' => array('type' => 'text')) // this has int key
54
+ * )
55
+ */
56
+ $new_options[] = $option;
57
+ } else {
58
+ $new_options[] = array($id => $option);
59
+ }
60
  }
61
  return $new_options;
62
  }
framework/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php CHANGED
@@ -48,7 +48,8 @@ class FW_Option_Type_Addable_Popup extends FW_Option_Type
48
  $option['attr']['data-for-js'] = json_encode(array(
49
  'title' => empty($option['popup-title']) ? $option['label'] : $option['popup-title'],
50
  'options' => $this->transform_options($option['popup-options']),
51
- 'template' => $option['template']
 
52
  ));
53
 
54
  $sortable_image = fw_get_framework_directory_uri('/static/img/sort-vertically.png');
@@ -64,7 +65,19 @@ class FW_Option_Type_Addable_Popup extends FW_Option_Type
64
  {
65
  $new_options = array();
66
  foreach ($options as $id => $option) {
67
- $new_options[] = array($id => $option);
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
  return $new_options;
70
  }
@@ -120,6 +133,7 @@ class FW_Option_Type_Addable_Popup extends FW_Option_Type
120
  ),
121
  'template' => '',
122
  'popup-title' => null,
 
123
  );
124
  }
125
 
48
  $option['attr']['data-for-js'] = json_encode(array(
49
  'title' => empty($option['popup-title']) ? $option['label'] : $option['popup-title'],
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');
65
  {
66
  $new_options = array();
67
  foreach ($options as $id => $option) {
68
+ if (is_int($id)) {
69
+ /**
70
+ * this happens when in options array are loaded external options using fw()->theme->get_options()
71
+ * and the array looks like this
72
+ * array(
73
+ * 'hello' => array('type' => 'text'), // this has string key
74
+ * array('hi' => array('type' => 'text')) // this has int key
75
+ * )
76
+ */
77
+ $new_options[] = $option;
78
+ } else {
79
+ $new_options[] = array($id => $option);
80
+ }
81
  }
82
  return $new_options;
83
  }
133
  ),
134
  'template' => '',
135
  'popup-title' => null,
136
+ 'size' => 'small' // small, medium, large
137
  );
138
  }
139
 
framework/includes/option-types/addable-popup/static/js/addable-popup.js CHANGED
@@ -14,7 +14,8 @@
14
  utils = {
15
  modal: new fw.OptionsModal({
16
  title: data.title,
17
- options: data.options
 
18
  }),
19
  countItems: function () {
20
  return nodes.$itemsWrapper.find('.item:not(.default)').length;
14
  utils = {
15
  modal: new fw.OptionsModal({
16
  title: data.title,
17
+ options: data.options,
18
+ size : data.size
19
  }),
20
  countItems: function () {
21
  return nodes.$itemsWrapper.find('.item:not(.default)').length;
framework/includes/option-types/multi-picker/class-fw-option-type-multi-picker.php CHANGED
@@ -38,18 +38,17 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
38
  */
39
  protected function _enqueue_static($id, $option, $data)
40
  {
41
- $css_path = fw_get_framework_directory_uri('/includes/option-types/' . $this->get_type() . '/static/css/');
42
- $js_path = fw_get_framework_directory_uri('/includes/option-types/' . $this->get_type() . '/static/js/');
43
 
44
  wp_enqueue_style(
45
  'fw-option-type' . $this->get_type(),
46
- $css_path . 'multi-picker.css',
47
  array(),
48
  fw()->manifest->get_version()
49
  );
50
  wp_enqueue_script(
51
  'fw-option-type' . $this->get_type(),
52
- $js_path . 'multi-picker.js',
53
  array('jquery', 'fw-events'),
54
  fw()->manifest->get_version(),
55
  true
@@ -91,20 +90,23 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
91
  private function prepare_option($id, $option)
92
  {
93
  if (empty($option['picker'])) {
94
- // TODO: think of text for error when no picker is set
95
  trigger_error(
96
  sprintf(__('No \'picker\' key set for multi-picker option: %s', 'fw'), $id),
97
  E_USER_ERROR
98
  );
99
  }
100
 
101
- reset($option['picker']);
102
- $picker_key = key($option['picker']);
103
- $picker = $option['picker'][$picker_key];
104
- $picker_type = $picker['type'];
105
- $supported_picker_types = array('select', 'short-select', 'radio', 'image-picker', 'switch', 'color-palette');
 
 
 
 
 
106
  if (!in_array($picker_type, $supported_picker_types)) {
107
- // TODO: think of text for error when incorrect picker type is used
108
  trigger_error(
109
  sprintf(
110
  __('Invalid picker type for multi-picker option %s, only pickers of types %s are supported', 'fw'),
@@ -141,24 +143,21 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
141
  }
142
 
143
  $hide_picker = '';
144
- $show_borders = '';
145
-
146
- //set default value if nothing isset
147
- // if (empty($option['controls']['value'])) {
148
- // reset($groups);
149
- // $option['controls']['value'] = key($groups);
150
- // }
151
 
152
  if (
153
- 1 === count($picker_choices) &&
154
- isset($option['hide_picker']) &&
 
 
155
  true === $option['hide_picker']
156
  ) {
157
- $hide_picker = 'fw-hidden ';
158
  }
159
 
160
  if (
161
- isset($option['show_borders']) &&
 
162
  true === $option['show_borders']
163
  ) {
164
  $show_borders = 'fw-show-borders';
@@ -188,7 +187,7 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
188
  'type' => 'group',
189
  'desc' => false,
190
  'label' => false,
191
- 'attr' => array('class' => $show_borders.' '.$hide_picker . ' picker-group picker-type-' . $picker_type),
192
  'options' => array($picker_key => $picker)
193
  )
194
  );
@@ -201,13 +200,15 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
201
  */
202
  protected function _get_value_from_input($option, $input_value)
203
  {
 
 
 
 
 
 
 
204
  $value = array();
205
 
206
- // picker
207
- reset($option['picker']);
208
- $picker_key = key($option['picker']);
209
- $picker_type = $option['picker'][$picker_key]['type'];
210
- $picker = $option['picker'][$picker_key];
211
  $value[$picker_key] = fw()->backend->option_type($picker_type)->get_value_from_input(
212
  $picker,
213
  isset($input_value[$picker_key]) ? $input_value[$picker_key] : null
@@ -241,7 +242,7 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
241
 
242
  foreach ($choices as $choice_id => $options) {
243
 
244
- foreach ($options as $option_id => $option) {
245
  $value[$choice_id][$option_id] = fw()->backend->option_type($option['type'])->get_value_from_input(
246
  $option,
247
  isset($input_value[$choice_id][$option_id]) ? $input_value[$choice_id][$option_id] : null
38
  */
39
  protected function _enqueue_static($id, $option, $data)
40
  {
41
+ $uri = fw_get_framework_directory_uri('/includes/option-types/' . $this->get_type());
 
42
 
43
  wp_enqueue_style(
44
  'fw-option-type' . $this->get_type(),
45
+ $uri . '/static/css/multi-picker.css',
46
  array(),
47
  fw()->manifest->get_version()
48
  );
49
  wp_enqueue_script(
50
  'fw-option-type' . $this->get_type(),
51
+ $uri . '/static/js/multi-picker.js',
52
  array('jquery', 'fw-events'),
53
  fw()->manifest->get_version(),
54
  true
90
  private function prepare_option($id, $option)
91
  {
92
  if (empty($option['picker'])) {
 
93
  trigger_error(
94
  sprintf(__('No \'picker\' key set for multi-picker option: %s', 'fw'), $id),
95
  E_USER_ERROR
96
  );
97
  }
98
 
99
+ {
100
+ reset($option['picker']);
101
+ $picker_key = key($option['picker']);
102
+ $picker = $option['picker'][$picker_key];
103
+ $picker_type = $picker['type'];
104
+ }
105
+
106
+ $supported_picker_types = array('select', 'short-select', 'radio', 'image-picker', 'switch',
107
+ 'color-palette' // fixme: this is a temporary hardcode for a ThemeFuse theme option-type, think a way to allow other option-types here
108
+ );
109
  if (!in_array($picker_type, $supported_picker_types)) {
 
110
  trigger_error(
111
  sprintf(
112
  __('Invalid picker type for multi-picker option %s, only pickers of types %s are supported', 'fw'),
143
  }
144
 
145
  $hide_picker = '';
146
+ $show_borders = '';
 
 
 
 
 
 
147
 
148
  if (
149
+ 1 === count($picker_choices)
150
+ &&
151
+ isset($option['hide_picker'])
152
+ &&
153
  true === $option['hide_picker']
154
  ) {
155
+ $hide_picker = 'fw-hidden';
156
  }
157
 
158
  if (
159
+ isset($option['show_borders'])
160
+ &&
161
  true === $option['show_borders']
162
  ) {
163
  $show_borders = 'fw-show-borders';
187
  'type' => 'group',
188
  'desc' => false,
189
  'label' => false,
190
+ 'attr' => array('class' => $show_borders .' '. $hide_picker .' picker-group picker-type-'. $picker_type),
191
  'options' => array($picker_key => $picker)
192
  )
193
  );
200
  */
201
  protected function _get_value_from_input($option, $input_value)
202
  {
203
+ {
204
+ reset($option['picker']);
205
+ $picker_key = key($option['picker']);
206
+ $picker_type = $option['picker'][$picker_key]['type'];
207
+ $picker = $option['picker'][$picker_key];
208
+ }
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
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
framework/includes/option-types/multi-picker/static/css/multi-picker.css CHANGED
@@ -1,6 +1,19 @@
1
  .fw-backend-option-type-multi-picker {
2
  padding-left: 0;
3
  padding-right: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
6
  .fw-option-type-multi-picker > .choice-group {
@@ -15,33 +28,46 @@
15
  -webkit-animation-name: fwFadeIn;
16
  animation-name: fwFadeIn;
17
 
18
- -webkit-animation-duration: 1s;
19
- animation-duration: 1s;
20
  }
21
 
22
- .fw-option-type-multi-picker > .choice-group .fw-show-borders .fw-backend-option {
 
23
  border-bottom-width: 1px;
24
  }
25
 
26
- .fw-option-type-multi-picker > .choice-group .fw-show-borders .fw-backend-option:last-child {
27
- border-bottom-width: 0px !important;
28
- }
29
-
30
- .fw-option-type-multi-picker:not(.fw-option-type-multi-picker-with-borders) > .picker-group,
31
  .fw-option-type-multi-picker.fw-option-type-multi-picker-with-borders:not(.has-choice) > .picker-group {
32
- border-bottom-width: 0px;
33
- padding-bottom: 0;
34
  }
35
 
36
  .fw-option-type-multi-picker > .picker-group > .fw-backend-option {
37
  padding-top: 0;
38
  }
39
 
40
- .fw-option-type-multi-picker .fw-option-type-multi > .fw-backend-option,
41
- .fw-option-type-multi-picker.has-choice:not(.fw-option-type-multi-picker-with-borders) > .picker-group {
42
  padding-bottom: 0;
43
  }
44
 
45
- .fw-option-type-multi-picker > .picker-group.fw-show-borders {
46
- border-bottom-width: 1px;
 
 
 
47
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  .fw-backend-option-type-multi-picker {
2
  padding-left: 0;
3
  padding-right: 0;
4
+ padding-bottom: 0;
5
+ }
6
+
7
+ /* hide last inner option border */
8
+ .fw-postbox .fw-option-type-multi-picker > .fw-backend-options-group.choice-group:after,
9
+ .postbox-with-fw-options .fw-option-type-multi-picker > .fw-backend-options-group.choice-group:after,
10
+ .fw-option-type-multi-picker.fw-option-type-multi-picker-with-borders > .choice-group > .fw-backend-option:after,
11
+ .fw-option-type-multi-picker.fw-option-type-multi-picker-with-borders > .choice-group .fw-options-tabs-contents > .fw-inner > .fw-options-tab:after {
12
+ content: ' ';
13
+ display: block;
14
+ border-top: 1px solid #fff;
15
+ margin-top: -1px;
16
+ position: relative;
17
  }
18
 
19
  .fw-option-type-multi-picker > .choice-group {
28
  -webkit-animation-name: fwFadeIn;
29
  animation-name: fwFadeIn;
30
 
31
+ -webkit-animation-duration: .5s;
32
+ animation-duration: .5s;
33
  }
34
 
35
+ .fw-option-type-multi-picker.fw-option-type-multi-picker-with-borders.has-choice > .picker-group,
36
+ .fw-option-type-multi-picker.fw-option-type-multi-picker-with-borders > .choice-group > .fw-backend-option {
37
  border-bottom-width: 1px;
38
  }
39
 
40
+ .fw-option-type-multi-picker.fw-option-type-multi-picker-without-borders > .picker-group,
 
 
 
 
41
  .fw-option-type-multi-picker.fw-option-type-multi-picker-with-borders:not(.has-choice) > .picker-group {
42
+ border-bottom-width: 0;
 
43
  }
44
 
45
  .fw-option-type-multi-picker > .picker-group > .fw-backend-option {
46
  padding-top: 0;
47
  }
48
 
49
+ .fw-option-type-multi-picker.fw-option-type-multi-picker-without-borders.has-choice > .picker-group > .fw-backend-option {
 
50
  padding-bottom: 0;
51
  }
52
 
53
+ /* tabs fixes */
54
+
55
+ .fw-option-type-multi-picker > .choice-group .fw-options-tabs-wrapper > .fw-options-tabs-list,
56
+ .fw-option-type-multi-picker > .choice-group .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list {
57
+ padding-left: 27px; /* same as .fw-backend-option-design-default */
58
  }
59
+
60
+ .fw-option-type-multi-picker > .choice-group .fw-options-tabs-wrapper > .fw-options-tabs-list {
61
+ padding-top: 24px; /* same as .fw-backend-option-design-default */
62
+ }
63
+
64
+ .fw-option-type-multi-picker > .choice-group .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list {
65
+ padding-top: 20px;
66
+ }
67
+
68
+ .fw-option-type-multi-picker > .choice-group .fw-options-tabs-wrapper > .fw-options-tabs-contents {
69
+ margin-top: 0;
70
+ }
71
+
72
+ /* end: tabs fixes */
73
+
framework/includes/option-types/multi/static/css/styles.css CHANGED
@@ -6,7 +6,6 @@
6
  border-bottom-width: 0;
7
  }
8
 
9
-
10
  /* show options separator borders */
11
 
12
  .fw-option-type-multi.fw-option-type-multi-show-borders > .fw-backend-option {
@@ -17,4 +16,8 @@
17
  border-bottom-width: 0;
18
  }
19
 
20
- /* end: show options separator borders */
 
 
 
 
6
  border-bottom-width: 0;
7
  }
8
 
 
9
  /* show options separator borders */
10
 
11
  .fw-option-type-multi.fw-option-type-multi-show-borders > .fw-backend-option {
16
  border-bottom-width: 0;
17
  }
18
 
19
+ /* end: show options separator borders */
20
+
21
+ .fw-option-type-multi:not(.fw-option-type-multi-show-borders) > .fw-backend-option:not(:last-child) {
22
+ padding-bottom: 0;
23
+ }
framework/includes/option-types/popup/class-fw-option-type-popup.php CHANGED
@@ -80,7 +80,19 @@ class FW_Option_Type_Popup extends FW_Option_Type {
80
  private function transform_options( $options ) {
81
  $new_options = array();
82
  foreach ( $options as $id => $option ) {
83
- $new_options[] = array( $id => $option );
 
 
 
 
 
 
 
 
 
 
 
 
84
  }
85
 
86
  return $new_options;
80
  private function transform_options( $options ) {
81
  $new_options = array();
82
  foreach ( $options as $id => $option ) {
83
+ if (is_int($id)) {
84
+ /**
85
+ * this happens when in options array are loaded external options using fw()->theme->get_options()
86
+ * and the array looks like this
87
+ * array(
88
+ * 'hello' => array('type' => 'text'), // this has string key
89
+ * array('hi' => array('type' => 'text')) // this has int key
90
+ * )
91
+ */
92
+ $new_options[] = $option;
93
+ } else {
94
+ $new_options[] = array($id => $option);
95
+ }
96
  }
97
 
98
  return $new_options;
framework/includes/option-types/radio-text/class-fw-option-type-radio-text.php CHANGED
@@ -89,7 +89,7 @@ class FW_Option_Type_Radio_Text extends FW_Option_Type
89
 
90
  $selected = fw()->backend->option_type( 'radio' )->get_value_from_input( array(
91
  'value' => $option['value'],
92
- 'choices' => $option['choices']
93
  ),
94
  $input_value['predefined']
95
  );
89
 
90
  $selected = fw()->backend->option_type( 'radio' )->get_value_from_input( array(
91
  'value' => $option['value'],
92
+ 'choices' => $option['choices']
93
  ),
94
  $input_value['predefined']
95
  );
framework/includes/option-types/rgba-color-picker/static/js/scripts.js CHANGED
@@ -70,7 +70,7 @@
70
  value: Color($input.val())._alpha * 100,
71
  range: "max",
72
  step: 1,
73
- min: 1,
74
  max: 100,
75
  slide: function (event, ui) {
76
  $(this).find('.ui-slider-handle').text(ui.value);
70
  value: Color($input.val())._alpha * 100,
71
  range: "max",
72
  step: 1,
73
+ min: 0,
74
  max: 100,
75
  slide: function (event, ui) {
76
  $(this).find('.ui-slider-handle').text(ui.value);
framework/includes/option-types/typography/class-fw-option-type-typography.php CHANGED
@@ -68,7 +68,6 @@ class FW_Option_Type_Typography extends FW_Option_Type
68
  );
69
  $fw_typography_fonts = $this->get_fonts();
70
  wp_localize_script('fw-option-' . $this->get_type(), 'fw_typography_fonts', $fw_typography_fonts);
71
- wp_localize_script('fw-option-' . $this->get_type(), 'googleFonts', $fw_typography_fonts['google']);
72
  }
73
 
74
  /**
68
  );
69
  $fw_typography_fonts = $this->get_fonts();
70
  wp_localize_script('fw-option-' . $this->get_type(), 'fw_typography_fonts', $fw_typography_fonts);
 
71
  }
72
 
73
  /**
framework/includes/option-types/wp-editor/class-fw-option-type-wp-editor.php CHANGED
@@ -1,25 +1,26 @@
1
- <?php if (!defined('FW')) die('Forbidden');
 
 
2
 
3
- class FW_Option_Type_Wp_Editor extends FW_Option_Type
4
- {
5
  private $js_uri;
6
  private $css_uri;
7
 
8
- public function get_type()
9
- {
 
10
  return 'wp-editor';
11
  }
12
 
13
  /**
14
  * @internal
15
  */
16
- protected function _get_defaults()
17
- {
18
  return array(
19
  /**
20
  * boolean | array
21
  */
22
- 'tinymce' => true,
23
  /**
24
  * boolean
25
  */
@@ -27,134 +28,136 @@ class FW_Option_Type_Wp_Editor extends FW_Option_Type
27
  /**
28
  * boolean
29
  */
30
- 'teeny' => false,
31
  /**
32
  * boolean
33
  */
34
- 'wpautop' => true,
35
  /**
36
  * string
37
  * Additional CSS styling applied for both visual and HTML editors buttons, needs to include <style> tags, can use "scoped"
38
  */
39
- 'editor_css' => '',
40
  /**
41
  * boolean
42
  * if smth wrong try change true
43
  */
44
- 'reinit' => false,
 
 
 
 
45
  /**
46
  * string
47
  */
48
- 'value' => '',
49
  );
50
  }
51
 
52
  /**
53
  * @internal
54
  */
55
- protected function _init()
56
- {
57
- $static_uri = fw_get_framework_directory_uri('/includes/option-types/' . $this->get_type() . '/static');
58
  $this->js_uri = $static_uri . '/js';
59
  $this->css_uri = $static_uri . '/css';
60
  }
61
 
62
- private function get_teeny_preset($option){
63
  return array(
64
- 'menubar' => false,
65
- 'wpautop' => $option['wpautop'],
66
- 'tabfocus_elements' => ":prev,:next",
67
- 'toolbar1' => "bold,italic,strikethrough,bullist,numlist,blockquote,hr,alignleft,aligncenter,alignright,link,unlink,wp_more,spellchecker,fullscreen,wp_adv",
68
- 'toolbar2' =>"underline,alignjustify,forecolor,pastetext,removeformat,charmap,outdent,indent,undo,redo",
69
- 'plugins' => "hr,tabfocus,fullscreen,wordpress,wpeditimage",
70
- 'preview_styles' => 'font-family font-size font-weight font-style text-decoration text-transform',
71
- 'content_css' => $this->_get_tmce_content_css(),
72
- 'language' => $this->_get_tmce_locale(),
73
- 'relative_urls' => false,
74
  'remove_script_host' => false,
75
  );
76
  }
77
 
78
- private function get_extended_preset($option) {
79
- return array(
80
- 'theme' => 'modern',
81
- 'skin' => 'lightgray',
82
- 'formats' => array(
83
- 'alignleft' => array (
84
- array(
85
- 'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li',
86
- 'styles' => array( 'textAlign' => 'left' ),
87
- ),
88
- array(
89
- 'selector' => 'img,table,dl.wp-caption',
90
- 'classes' => 'alignleft'
91
- ),
 
 
 
 
 
92
  ),
93
- 'aligncenter' => array (
94
- array(
95
- 'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li',
96
- 'styles' => array('textAlign' => 'center' )
97
- ),
98
- array(
99
- 'selector' => 'img,table,dl.wp-caption',
100
- 'classes' => 'aligncenter'
101
- ),
102
  ),
103
- 'alignright' => array (
104
- array(
105
- 'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li',
106
- 'styles' => array( 'textAlign' => 'right' )
107
- ),
108
- array(
109
- 'selector' => 'img,table,dl.wp-caption',
110
- 'classes' => 'alignright'
111
- )
112
  ),
113
- 'strikethrough' => array( 'inline' => 'del'),
 
 
 
 
 
114
  ),
115
- 'relative_urls' => false,
116
- 'remove_script_host' => false,
117
- 'convert_urls' => false,
118
- 'browser_spellcheck' => true,
119
- 'fix_list_elements' => true,
120
- 'entities' => '38,amp,60,lt,62,gt',
121
- 'entity_encoding' => 'raw',
122
- 'keep_styles' => false,
123
- 'paste_webkit_styles' => 'font-weight font-style color',
124
- 'preview_styles' => 'font-family font-size font-weight font-style text-decoration text-transform',
125
  'wpeditimage_disable_captions' => false,
126
- 'wpeditimage_html5_captions' => true,
127
- 'plugins' => 'charmap,hr,media,paste,tabfocus,textcolor,fullscreen,wordpress,wpeditimage,wpgallery,wplink,wpdialogs,wpview',
128
- 'resize' => 'vertical',
129
- 'menubar' => false,
130
- 'indent' => false,
131
- 'toolbar1' => 'bold,italic,strikethrough,bullist,numlist,blockquote,hr,alignleft,aligncenter,alignright,link,unlink,wp_more,spellchecker,fullscreen,wp_adv',
132
- 'toolbar2' => 'formatselect,underline,alignjustify,forecolor,pastetext,removeformat,charmap,outdent,indent,undo,redo,wp_help',
133
- 'toolbar3' => '',
134
- 'toolbar4' => '',
135
- 'tabfocus_elements' => ':prev,:next',
136
- 'body_class' => 'post-type-page post-status-publish',
137
- 'content_css' => $this->_get_tmce_content_css(),
138
- 'language' => $this->_get_tmce_locale(),
139
- 'wpautop' => $option['wpautop'],
140
  );
141
  }
142
 
143
  /**
144
  * @internal
145
  */
146
- protected function _render($id, $option, $data)
147
- {
148
  //replace \u00a0 char to &nbsp;
149
- $value = str_replace( chr( 194 ) . chr( 160 ), '&nbsp;', (string)$data['value'] );
150
 
151
  $name = $option['attr']['name'];
152
- unset($option['attr']['name'], $option['attr']['value']);
153
 
154
  $textarea_id = 'textarea_';
155
  if (
156
  // check if id contains characters that can produce errors
157
- preg_match('/[^a-z0-9_\-]/i', $option['attr']['id'])
158
  ||
159
  $option['reinit']
160
  ) {
@@ -163,23 +166,24 @@ class FW_Option_Type_Wp_Editor extends FW_Option_Type
163
  $textarea_id .= $option['attr']['id'];
164
  }
165
 
166
- $wrapper_attr = array_merge($option['attr'], array(
167
- 'data-name' => $name,
168
- 'data-config' => $option['teeny'] ? 'teeny' : (is_array($option['tinymce']) ? 'custom' : 'extended'),
169
- 'data-tinymce' => is_array($option['tinymce']) ? json_encode($option['tinymce']) : $option['tinymce'],
170
- 'data-tmce-teeny' => json_encode($this->get_teeny_preset($option)),
171
- 'data-tmce-extended' => json_encode($this->get_extended_preset($option)),
172
- ));
 
173
 
174
- echo '<div ' . fw_attr_to_html($wrapper_attr) . ' >';
175
 
176
- $option['editor_css'] .= '<style>#wp-link-wrap{z-index: 160105} #wp-link-backdrop{z-index: 160100} .mce-container.mce-panel.mce-floatpanel.mce-menu, .mce-container.mce-panel.mce-floatpanel.mce-popover, .mce-container.mce-panel.mce-floatpanel.mce-window {z-index: 160105 !important;}</style>';
177
 
178
  wp_editor( $value, $textarea_id, array(
179
- 'teeny' => $option['teeny'],
180
  'media_buttons' => $option['media_buttons'],
181
- 'tinymce' => $option['tinymce'],
182
- 'editor_css' => $option['editor_css']
183
  ) );
184
 
185
  echo '</div>';
@@ -189,44 +193,73 @@ class FW_Option_Type_Wp_Editor extends FW_Option_Type
189
  * @internal
190
  * {@inheritdoc}
191
  */
192
- protected function _enqueue_static($id, $option, $data)
193
- {
 
 
 
 
194
  wp_enqueue_script(
195
- 'fw-option-type-'. $this->get_type() ,
196
  $this->js_uri . '/scripts.js',
197
- array('jquery', 'fw-events', 'editor', 'fw'),
198
  fw()->manifest->get_version(),
199
  true
200
  );
201
 
202
  wp_enqueue_style(
203
  'editor-buttons-css',
204
- includes_url("/css/editor.min.css"),
205
  array(),
206
  fw()->manifest->get_version()
207
  );
208
  wp_enqueue_style(
209
  'dashicons-css',
210
- includes_url("css/dashicons.min.css"),
211
  array(),
212
  fw()->manifest->get_version()
213
  );
214
  wp_enqueue_style(
215
- 'fw-option-type-'. $this->get_type() ,
216
  $this->css_uri . '/styles.css',
217
  array(),
218
  fw()->manifest->get_version()
219
  );
220
  }
221
 
222
- private function _get_tmce_locale(){
223
  $mce_locale = get_locale();
 
224
  return empty( $mce_locale ) ? 'en' : strtolower( substr( $mce_locale, 0, 2 ) );
225
  }
226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  //styles for wp-editor content
228
  private function _get_tmce_content_css() {
229
- $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
230
  $version = 'ver=' . $GLOBALS['wp_version'];
231
 
232
  $mce_css = array(
@@ -241,8 +274,8 @@ class FW_Option_Type_Wp_Editor extends FW_Option_Type
241
  $editor_styles = $GLOBALS['editor_styles'];
242
 
243
  $editor_styles = array_unique( array_filter( $editor_styles ) );
244
- $style_uri = get_stylesheet_directory_uri();
245
- $style_dir = get_stylesheet_directory();
246
 
247
  // Support externally referenced styles (like, say, fonts).
248
  foreach ( $editor_styles as $key => $file ) {
@@ -258,35 +291,45 @@ class FW_Option_Type_Wp_Editor extends FW_Option_Type
258
  $template_dir = get_template_directory();
259
 
260
  foreach ( $editor_styles as $key => $file ) {
261
- if ( $file && file_exists( "$template_dir/$file" ) )
262
  $mce_css[] = "$template_uri/$file";
 
263
  }
264
  }
265
 
266
  foreach ( $editor_styles as $file ) {
267
- if ( $file && file_exists( "$style_dir/$file" ) )
268
  $mce_css[] = "$style_uri/$file";
 
269
  }
270
  }
271
 
272
- return $mce_css_urls = trim(implode(',', $mce_css));
273
  }
 
274
  /**
275
  * @internal
276
  */
277
- protected function _get_value_from_input($option, $input_value)
278
- {
279
- if (is_null($input_value)) {
280
  return $option['value'];
281
  }
282
 
283
- $value = (string)$input_value;
284
 
285
  if ( $option['wpautop'] === true ) {
286
- $value = wpautop( $value );
287
  }
288
 
289
  return $value;
290
  }
 
 
 
 
 
 
 
291
  }
292
- FW_Option_Type::register('FW_Option_Type_Wp_Editor');
 
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
 
5
+ class FW_Option_Type_Wp_Editor extends FW_Option_Type {
 
6
  private $js_uri;
7
  private $css_uri;
8
 
9
+ private $flag = false;
10
+
11
+ public function get_type() {
12
  return 'wp-editor';
13
  }
14
 
15
  /**
16
  * @internal
17
  */
18
+ protected function _get_defaults() {
 
19
  return array(
20
  /**
21
  * boolean | array
22
  */
23
+ 'tinymce' => true,
24
  /**
25
  * boolean
26
  */
28
  /**
29
  * boolean
30
  */
31
+ 'teeny' => false,
32
  /**
33
  * boolean
34
  */
35
+ 'wpautop' => true,
36
  /**
37
  * string
38
  * Additional CSS styling applied for both visual and HTML editors buttons, needs to include <style> tags, can use "scoped"
39
  */
40
+ 'editor_css' => '',
41
  /**
42
  * boolean
43
  * if smth wrong try change true
44
  */
45
+ 'reinit' => false,
46
+ /**
47
+ * string
48
+ */
49
+ 'value' => '',
50
  /**
51
  * string
52
  */
53
+ 'size' => 'small' // small, large
54
  );
55
  }
56
 
57
  /**
58
  * @internal
59
  */
60
+ protected function _init() {
61
+ $static_uri = fw_get_framework_directory_uri( '/includes/option-types/' . $this->get_type() . '/static' );
 
62
  $this->js_uri = $static_uri . '/js';
63
  $this->css_uri = $static_uri . '/css';
64
  }
65
 
66
+ private function get_teeny_preset( $option ) {
67
  return array(
68
+ 'menubar' => false,
69
+ 'wpautop' => $option['wpautop'],
70
+ 'tabfocus_elements' => ":prev,:next",
71
+ 'toolbar1' => "bold,italic,strikethrough,bullist,numlist,blockquote,hr,alignleft,aligncenter,alignright,link,unlink,wp_more,spellchecker,fullscreen,wp_adv",
72
+ 'toolbar2' => "underline,alignjustify,forecolor,pastetext,removeformat,charmap,outdent,indent,undo,redo",
73
+ 'plugins' => "hr,tabfocus,fullscreen,wordpress,wpeditimage",
74
+ 'preview_styles' => 'font-family font-size font-weight font-style text-decoration text-transform',
75
+ 'content_css' => $this->_get_tmce_content_css(),
76
+ 'language' => $this->_get_tmce_locale(),
77
+ 'relative_urls' => false,
78
  'remove_script_host' => false,
79
  );
80
  }
81
 
82
+ private function get_extended_preset( $option ) {
83
+ return array(
84
+ 'theme' => 'modern',
85
+ 'skin' => 'lightgray',
86
+ 'formats' => array(
87
+ 'alignleft' => array(
88
+ array(
89
+ 'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li',
90
+ 'styles' => array( 'textAlign' => 'left' ),
91
+ ),
92
+ array(
93
+ 'selector' => 'img,table,dl.wp-caption',
94
+ 'classes' => 'alignleft'
95
+ ),
96
+ ),
97
+ 'aligncenter' => array(
98
+ array(
99
+ 'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li',
100
+ 'styles' => array( 'textAlign' => 'center' )
101
  ),
102
+ array(
103
+ 'selector' => 'img,table,dl.wp-caption',
104
+ 'classes' => 'aligncenter'
 
 
 
 
 
 
105
  ),
106
+ ),
107
+ 'alignright' => array(
108
+ array(
109
+ 'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li',
110
+ 'styles' => array( 'textAlign' => 'right' )
 
 
 
 
111
  ),
112
+ array(
113
+ 'selector' => 'img,table,dl.wp-caption',
114
+ 'classes' => 'alignright'
115
+ )
116
+ ),
117
+ 'strikethrough' => array( 'inline' => 'del' ),
118
  ),
119
+ 'relative_urls' => false,
120
+ 'remove_script_host' => false,
121
+ 'convert_urls' => false,
122
+ 'browser_spellcheck' => true,
123
+ 'fix_list_elements' => true,
124
+ 'entities' => '38,amp,60,lt,62,gt',
125
+ 'entity_encoding' => 'raw',
126
+ 'keep_styles' => false,
127
+ 'paste_webkit_styles' => 'font-weight font-style color',
128
+ 'preview_styles' => 'font-family font-size font-weight font-style text-decoration text-transform',
129
  'wpeditimage_disable_captions' => false,
130
+ 'wpeditimage_html5_captions' => true,
131
+ 'plugins' => 'charmap,hr,media,paste,tabfocus,textcolor,fullscreen,wordpress,wpeditimage,wpgallery,wplink,wpdialogs,wpview',
132
+ 'resize' => 'vertical',
133
+ 'menubar' => false,
134
+ 'indent' => false,
135
+ 'toolbar1' => 'bold,italic,strikethrough,bullist,numlist,blockquote,hr,alignleft,aligncenter,alignright,link,unlink,wp_more,spellchecker,fullscreen,wp_adv',
136
+ 'toolbar2' => 'formatselect,underline,alignjustify,forecolor,pastetext,removeformat,charmap,outdent,indent,undo,redo,wp_help',
137
+ 'toolbar3' => '',
138
+ 'toolbar4' => '',
139
+ 'tabfocus_elements' => ':prev,:next',
140
+ 'body_class' => 'wp-admin',
141
+ 'content_css' => $this->_get_tmce_content_css(),
142
+ 'language' => $this->_get_tmce_locale(),
143
+ 'wpautop' => $option['wpautop'],
144
  );
145
  }
146
 
147
  /**
148
  * @internal
149
  */
150
+ protected function _render( $id, $option, $data ) {
 
151
  //replace \u00a0 char to &nbsp;
152
+ $value = str_replace( chr( 194 ) . chr( 160 ), '&nbsp;', (string) $data['value'] );
153
 
154
  $name = $option['attr']['name'];
155
+ unset( $option['attr']['name'], $option['attr']['value'] );
156
 
157
  $textarea_id = 'textarea_';
158
  if (
159
  // check if id contains characters that can produce errors
160
+ preg_match( '/[^a-z0-9_\-]/i', $option['attr']['id'] )
161
  ||
162
  $option['reinit']
163
  ) {
166
  $textarea_id .= $option['attr']['id'];
167
  }
168
 
169
+ $wrapper_attr = array_merge( $option['attr'], array(
170
+ 'data-name' => $name,
171
+ 'data-config' => $option['teeny'] ? 'teeny' : ( is_array( $option['tinymce'] ) ? 'custom' : 'extended' ),
172
+ 'data-tinymce' => is_array( $option['tinymce'] ) ? json_encode( $option['tinymce'] ) : $option['tinymce'],
173
+ 'data-tmce-teeny' => json_encode( $this->get_teeny_preset( $option ) ),
174
+ 'data-tmce-extended' => json_encode( $this->get_extended_preset( $option ) ),
175
+ 'data-width-type' => $option['size'],
176
+ ) );
177
 
178
+ echo '<div ' . fw_attr_to_html( $wrapper_attr ) . '>';
179
 
180
+ $option['editor_css'] .= '<style>#wp-link-wrap{z-index: 160105} #wp-link-backdrop{z-index: 160100} .mce-container.mce-panel.mce-floatpanel.mce-menu, .mce-container.mce-panel.mce-floatpanel.mce-popover, .mce-container.mce-panel.mce-floatpanel.mce-window {z-index: 160105 !important;}</style>';
181
 
182
  wp_editor( $value, $textarea_id, array(
183
+ 'teeny' => $option['teeny'],
184
  'media_buttons' => $option['media_buttons'],
185
+ 'tinymce' => $option['tinymce'],
186
+ 'editor_css' => $option['editor_css']
187
  ) );
188
 
189
  echo '</div>';
193
  * @internal
194
  * {@inheritdoc}
195
  */
196
+ protected function _enqueue_static( $id, $option, $data ) {
197
+ add_action( 'admin_print_footer_scripts', array( $this, '_action_print_wp_editor' ), 9999 );
198
+
199
+ wp_enqueue_script( 'quicktags' );
200
+ wp_enqueue_style( 'buttons' );
201
+
202
  wp_enqueue_script(
203
+ 'fw-option-type-' . $this->get_type(),
204
  $this->js_uri . '/scripts.js',
205
+ array( 'jquery', 'fw-events', 'editor', 'fw' ),
206
  fw()->manifest->get_version(),
207
  true
208
  );
209
 
210
  wp_enqueue_style(
211
  'editor-buttons-css',
212
+ includes_url( "/css/editor.min.css" ),
213
  array(),
214
  fw()->manifest->get_version()
215
  );
216
  wp_enqueue_style(
217
  'dashicons-css',
218
+ includes_url( "css/dashicons.min.css" ),
219
  array(),
220
  fw()->manifest->get_version()
221
  );
222
  wp_enqueue_style(
223
+ 'fw-option-type-' . $this->get_type(),
224
  $this->css_uri . '/styles.css',
225
  array(),
226
  fw()->manifest->get_version()
227
  );
228
  }
229
 
230
+ private function _get_tmce_locale() {
231
  $mce_locale = get_locale();
232
+
233
  return empty( $mce_locale ) ? 'en' : strtolower( substr( $mce_locale, 0, 2 ) );
234
  }
235
 
236
+ /**
237
+ * @internal
238
+ **/
239
+ public function _action_print_wp_editor() {
240
+ if ( ! class_exists( '_WP_Editors' ) ) {
241
+ require( ABSPATH . WPINC . '/class-wp-editor.php' );
242
+
243
+ $id = 'fw-wp-editor-option-type';
244
+
245
+ $set = _WP_Editors::parse_settings( $id, array(
246
+ 'teeny' => true,
247
+ 'media_buttons' => true,
248
+ 'tinymce' => true,
249
+ 'editor_css' => true,
250
+ 'quicktags' => true
251
+ ) );
252
+
253
+ _WP_Editors::editor_settings( $id, $set );
254
+
255
+ _WP_Editors::enqueue_scripts();
256
+ _WP_Editors::editor_js();
257
+ }
258
+ }
259
+
260
  //styles for wp-editor content
261
  private function _get_tmce_content_css() {
262
+ $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
263
  $version = 'ver=' . $GLOBALS['wp_version'];
264
 
265
  $mce_css = array(
274
  $editor_styles = $GLOBALS['editor_styles'];
275
 
276
  $editor_styles = array_unique( array_filter( $editor_styles ) );
277
+ $style_uri = get_stylesheet_directory_uri();
278
+ $style_dir = get_stylesheet_directory();
279
 
280
  // Support externally referenced styles (like, say, fonts).
281
  foreach ( $editor_styles as $key => $file ) {
291
  $template_dir = get_template_directory();
292
 
293
  foreach ( $editor_styles as $key => $file ) {
294
+ if ( $file && file_exists( "$template_dir/$file" ) ) {
295
  $mce_css[] = "$template_uri/$file";
296
+ }
297
  }
298
  }
299
 
300
  foreach ( $editor_styles as $file ) {
301
+ if ( $file && file_exists( "$style_dir/$file" ) ) {
302
  $mce_css[] = "$style_uri/$file";
303
+ }
304
  }
305
  }
306
 
307
+ return $mce_css_urls = trim( implode( ',', $mce_css ) );
308
  }
309
+
310
  /**
311
  * @internal
312
  */
313
+ protected function _get_value_from_input( $option, $input_value ) {
314
+ if ( is_null( $input_value ) ) {
 
315
  return $option['value'];
316
  }
317
 
318
+ $value = (string) $input_value;
319
 
320
  if ( $option['wpautop'] === true ) {
321
+ $value = preg_replace("/\n/i","", wpautop( $value ));
322
  }
323
 
324
  return $value;
325
  }
326
+
327
+ /**
328
+ * {@inheritdoc}
329
+ */
330
+ public function _get_backend_width_type() {
331
+ return 'auto';
332
+ }
333
  }
334
+
335
+ FW_Option_Type::register( 'FW_Option_Type_Wp_Editor' );
framework/includes/option-types/wp-editor/static/js/scripts.js CHANGED
@@ -2,6 +2,18 @@
2
 
3
  var init = function() {
4
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  var $textareaWrapper = $(this),
6
  $textarea = $textareaWrapper.find('textarea');
7
 
@@ -34,10 +46,6 @@
34
  id = $textarea.attr('id'),
35
  settings = {id: id , buttons: 'strong,em,link,block,del,ins,img,ul,ol,li,code,more,close'};
36
 
37
- /**
38
- * set tinymce settings for modal use teeny setting
39
- */
40
-
41
 
42
  var tmceCustomSettings = $textarea.parents('.fw-option-type-wp-editor').data('tinymce'),
43
  tmce_teeny = $textarea.parents('.fw-option-type-wp-editor').data('tmce-teeny'),
@@ -84,7 +92,9 @@
84
  initTinyMCESettings.selector = '#' + id;
85
  tinymce.init(initTinyMCESettings);
86
  parent.removeClass('html-active').addClass('tmce-active');
87
- QTags._buttonsInit();
 
 
88
  }
89
  else
90
  {
2
 
3
  var init = function() {
4
 
5
+ var width = jQuery(this).data('width-type');
6
+
7
+ if (width == 'large') {
8
+ jQuery(this).parents('.fw-backend-option-input-type-wp-editor')
9
+ .removeClass('width-type-auto')
10
+ .addClass('width-type-full');
11
+ } else {
12
+ jQuery(this).parents('.fw-backend-option-input-type-wp-editor')
13
+ .removeClass('width-type-auto')
14
+ .addClass('width-type-fixed');
15
+ }
16
+
17
  var $textareaWrapper = $(this),
18
  $textarea = $textareaWrapper.find('textarea');
19
 
46
  id = $textarea.attr('id'),
47
  settings = {id: id , buttons: 'strong,em,link,block,del,ins,img,ul,ol,li,code,more,close'};
48
 
 
 
 
 
49
 
50
  var tmceCustomSettings = $textarea.parents('.fw-option-type-wp-editor').data('tinymce'),
51
  tmce_teeny = $textarea.parents('.fw-option-type-wp-editor').data('tmce-teeny'),
92
  initTinyMCESettings.selector = '#' + id;
93
  tinymce.init(initTinyMCESettings);
94
  parent.removeClass('html-active').addClass('tmce-active');
95
+ if (QTags != undefined) {
96
+ QTags._buttonsInit();
97
+ }
98
  }
99
  else
100
  {
framework/manifest.php CHANGED
@@ -4,4 +4,4 @@ $manifest = array();
4
 
5
  $manifest['name'] = __('Unyson', 'fw');
6
 
7
- $manifest['version'] = '2.1.25';
4
 
5
  $manifest['name'] = __('Unyson', 'fw');
6
 
7
+ $manifest['version'] = '2.2.0';
framework/static/css/backend-options.css CHANGED
@@ -19,6 +19,12 @@ Included on pages where backend options are rendered
19
  max-width: 100%;
20
  }
21
 
 
 
 
 
 
 
22
  /* Tabs */
23
 
24
  .fw-options-tabs-list ul,
@@ -27,28 +33,39 @@ Included on pages where backend options are rendered
27
  padding: 0;
28
  }
29
 
30
- .fw-options-tabs-first-level > .fw-options-tabs-list {
 
 
 
 
 
 
 
 
 
 
 
31
  border-bottom: 1px solid #CCC;
32
  padding: 0 10px;
33
  }
34
 
35
- .fw-options-tabs-first-level > .fw-options-tabs-list ul,
36
- .fw-options-tabs-first-level > .fw-options-tabs-list ul li {
37
  padding: 0;
38
  margin: 0;
39
  }
40
 
41
- .fw-options-tabs-first-level > .fw-options-tabs-list ul li {
42
  float: left;
43
  margin-right: 6px;
44
  }
45
- body.rtl .fw-options-tabs-first-level > .fw-options-tabs-list ul li {
46
  float: right;
47
  margin-right: 0px;
48
  margin-left: 6px;
49
  }
50
 
51
- .fw-options-tabs-first-level > .fw-options-tabs-list ul li a.nav-tab {
52
  padding: 6px 12px;
53
  font-weight: 700;
54
  font-size: 15px;
@@ -59,30 +76,34 @@ body.rtl .fw-options-tabs-first-level > .fw-options-tabs-list ul li {
59
  margin-top: 0;
60
  }
61
 
62
- .fw-options-tabs-first-level > .fw-options-tabs-list ul li a.nav-tab:hover {
63
  color: #000;
64
  background-color: #f1f1f1;
65
  }
66
 
67
- .postbox .fw-options-tabs-first-level > .fw-options-tabs-list {
68
  padding-top: 10px;
69
  }
70
 
71
- .postbox .fw-options-tabs-first-level > .fw-options-tabs-list ul li a.nav-tab {
72
  background-color: #f1f1f1;
73
  }
74
 
75
- .postbox .fw-options-tabs-first-level > .fw-options-tabs-list ul li a.nav-tab:hover {
76
  background-color: #f8f8f8;
77
  }
78
 
79
- .fw-options-tabs-first-level > .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
 
 
 
 
80
  color: #222;
81
  background: 0 0;
82
  border-bottom-color: #f1f1f1;
83
  }
84
 
85
- .fw-options-tabs-first-level > .fw-options-tabs-list ul li.ui-state-active a.nav-tab:hover {
86
  background: 0 0;
87
  }
88
 
@@ -92,85 +113,93 @@ body.rtl .fw-options-tabs-first-level > .fw-options-tabs-list ul li {
92
  box-shadow: none;
93
  }
94
 
95
- .fw-options-tabs-wrapper .metabox-holder,
96
- .fw-backend-postboxes.metabox-holder {
97
- padding-top: 0 !important; /* to rewrite: #wpbody-content .metabox-holder */
98
- margin: 0 !important; /* to rewrite: #wpbody-content .metabox-holder */
99
  }
100
 
101
- .fw-options-tabs-wrapper .fw-options-tabs-contents {
102
- margin-top: 20px !important; /* to rewrite: .fw-options-tabs-wrapper .metabox-holder */
103
  }
104
 
105
- .fw-options-tabs-contents .fw-options-tabs-list {
106
  padding: 0 0 0 5px;
 
107
  }
108
- body.rtl .fw-options-tabs-contents .fw-options-tabs-list {
109
  padding: 0 5px 0 0;
110
  }
111
 
112
- .postbox .fw-options-tabs-contents .fw-options-tabs-list {
113
  padding: 0 0 0 25px;
114
  }
115
- body.rtl .postbox .fw-options-tabs-contents .fw-options-tabs-list {
116
  padding: 0 25px 0 0;
117
  }
118
 
119
- .fw-options-tabs-contents .fw-options-tabs-list ul li:after {
120
  content: '|';
121
  }
122
 
123
- .fw-options-tabs-contents .fw-options-tabs-list ul li:last-child:after {
124
  content: none;
125
  }
126
 
127
- .fw-options-tabs-contents .fw-options-tabs-list ul > li {
128
  float: left;
129
  color: #666;
 
130
  }
131
- body.rtl .fw-options-tabs-contents .fw-options-tabs-list ul > li {
132
  float: right;
 
133
  }
134
 
135
- .fw-options-tabs-contents .fw-options-tabs-list ul li a.nav-tab {
136
  background: none;
137
  border: none;
138
  padding: 0;
139
  margin: 0 5px;
140
  font-size: 13px;
 
 
141
  }
142
 
143
- .fw-options-tabs-contents .fw-options-tabs-list ul li:first-child a.nav-tab {
 
 
 
 
144
  margin-left: 0;
145
  }
146
- body.rtl .fw-options-tabs-contents .fw-options-tabs-list ul li:first-child a.nav-tab {
147
  margin-left: 5px;
148
  margin-right: 0;
149
  }
150
 
151
- .fw-options-tabs-contents .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
152
  background: none;
153
  padding: 0;
154
  border: none;
155
  color: #000;
156
- font-size: 13px;
157
  }
158
 
159
- .fw-options-tabs-contents > .fw-inner > .fw-options-tab {
160
  position: relative;
161
  }
162
 
163
  @media (max-width: 782px) {
164
- .fw-options-tabs-first-level > .fw-options-tabs-list ul li {
165
  float: none;
166
  }
167
 
168
- .fw-options-tabs-first-level > .fw-options-tabs-list ul li a {
169
  display: block;
 
170
  }
171
 
172
- .fw-options-tabs-contents .fw-options-tabs-list ul li a.nav-tab {
173
- margin: 0 5px;
174
  }
175
  }
176
 
@@ -262,47 +291,51 @@ form.fw-settings-form.fw-backend-side-tabs {
262
  color: #fff;
263
  }
264
 
 
 
 
 
265
  .fw-backend-side-tabs .fw-options-tabs-list ul li:focus {
266
  outline: none;
267
  }
268
 
269
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list {
270
  padding: 0;
271
  }
272
 
273
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li:after {
274
  content: none;
275
  }
276
 
277
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul {
278
  padding: 0;
279
  background: #f7f7f7;
280
  border-width: 1px 0;
281
  clear: both;
282
  }
283
 
284
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul:before,
285
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul:after {
286
  display: table;
287
  content: " ";
288
  }
289
 
290
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul:after {
291
  clear: both;
292
  margin-bottom: -1px;
293
  }
294
 
295
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul,
296
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li {
297
  border-bottom: 1px solid #e5e5e5;
298
  }
299
 
300
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li {
301
  position: relative;
302
  border-right: 1px solid #e5e5e5;
303
  }
304
 
305
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li.ui-state-active:after {
306
  height: 1px;
307
  background: #fff;
308
  width: 100%;
@@ -311,8 +344,8 @@ form.fw-settings-form.fw-backend-side-tabs {
311
  position: absolute;
312
  }
313
 
314
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li a.nav-tab,
315
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
316
  margin: 0;
317
  padding: 0 24px;
318
  border: none;
@@ -324,25 +357,26 @@ form.fw-settings-form.fw-backend-side-tabs {
324
  outline: none;
325
  }
326
 
327
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li a.nav-tab {
328
  background: transparent;
329
  color: #000;
330
  }
331
 
332
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
333
  background: #fff;
334
  }
335
 
336
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul > li a.nav-tab:hover {
337
  color: #0074a2;
338
  }
339
 
340
- .fw-backend-side-tabs .fw-options-tabs-wrapper .fw-options-tabs-contents {
341
- margin-top: 0 !important;
 
342
  }
343
 
344
  /* hide last option border */
345
- .fw-backend-side-tabs .fw-options-tabs-wrapper .fw-options-tabs-contents > .fw-inner:after {
346
  content: ' ';
347
  display: block;
348
  width: 100%;
@@ -420,32 +454,34 @@ form.fw-settings-form.fw-backend-side-tabs {
420
  padding-bottom: 0;
421
  }
422
 
423
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul {
424
  height: auto;
425
  border-bottom-width: 0;
426
  }
427
 
428
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li {
429
  float: none;
430
  width: 100%;
431
  text-align: center;
432
  border-right-width: 0;
433
  }
434
 
435
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li.ui-state-active:after {
436
  display: none;
437
  }
438
 
439
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
440
  margin-bottom: 0;
441
  border-bottom-width: 0;
 
442
  }
443
 
444
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li a.nav-tab,
445
- .fw-backend-side-tabs .fw-options-tabs-contents .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
446
  width: 100%;
447
  height: auto;
448
  padding: 0;
 
449
  }
450
 
451
  .fw-backend-side-tabs .fw-backend-postboxes > .fw-postbox > .handlediv:before {
@@ -617,11 +653,7 @@ form.fw-settings-form.fw-backend-side-tabs {
617
 
618
  /* Fixes for edit post page */
619
 
620
- form#post .fw-options-tabs-first-level > .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
621
- border-bottom-color: #FFFFFF;
622
- }
623
-
624
- form#post .fw-options-tabs-contents .fw-backend-postboxes {
625
  padding: 0 10px;
626
  }
627
 
@@ -775,15 +807,13 @@ body.rtl .fw-backend-option-design-default > .fw-backend-option-desc > .fw-inner
775
 
776
  .fw-backend-options-group {
777
  border-bottom: 1px solid #eeeeee;
778
- padding-bottom: 25px;
779
- }
780
-
781
- .fw-force-xs .fw-backend-options-group {
782
- padding-bottom: 15px;
783
  }
784
 
785
  .fw-backend-options-group > .fw-backend-option {
786
  border-bottom-width: 0;
 
 
 
787
  padding-bottom: 0;
788
  }
789
 
19
  max-width: 100%;
20
  }
21
 
22
+ .fw-backend-postboxes.metabox-holder,
23
+ #wpbody-content .fw-backend-postboxes.metabox-holder {
24
+ padding-top: 0;
25
+ }
26
+
27
+
28
  /* Tabs */
29
 
30
  .fw-options-tabs-list ul,
33
  padding: 0;
34
  }
35
 
36
+ .fw-options-tabs-wrapper {
37
+ opacity: 0;
38
+ }
39
+
40
+ .fw-options-tabs-wrapper.ui-tabs {
41
+ opacity: 1;
42
+
43
+ transition: opacity ease 0.3s;
44
+ -webkit-transition: opacity ease 0.3s;
45
+ }
46
+
47
+ .fw-options-tabs-wrapper > .fw-options-tabs-list {
48
  border-bottom: 1px solid #CCC;
49
  padding: 0 10px;
50
  }
51
 
52
+ .fw-options-tabs-wrapper > .fw-options-tabs-list ul,
53
+ .fw-options-tabs-wrapper > .fw-options-tabs-list ul li {
54
  padding: 0;
55
  margin: 0;
56
  }
57
 
58
+ .fw-options-tabs-wrapper > .fw-options-tabs-list ul li {
59
  float: left;
60
  margin-right: 6px;
61
  }
62
+ body.rtl .fw-options-tabs-wrapper > .fw-options-tabs-list ul li {
63
  float: right;
64
  margin-right: 0px;
65
  margin-left: 6px;
66
  }
67
 
68
+ .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab {
69
  padding: 6px 12px;
70
  font-weight: 700;
71
  font-size: 15px;
76
  margin-top: 0;
77
  }
78
 
79
+ .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab:hover {
80
  color: #000;
81
  background-color: #f1f1f1;
82
  }
83
 
84
+ .postbox .fw-options-tabs-wrapper > .fw-options-tabs-list {
85
  padding-top: 10px;
86
  }
87
 
88
+ .postbox .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab {
89
  background-color: #f1f1f1;
90
  }
91
 
92
+ .postbox .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab:hover {
93
  background-color: #f8f8f8;
94
  }
95
 
96
+ .postbox .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
97
+ border-bottom-color: #fff;
98
+ }
99
+
100
+ .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
101
  color: #222;
102
  background: 0 0;
103
  border-bottom-color: #f1f1f1;
104
  }
105
 
106
+ .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active a.nav-tab:hover {
107
  background: 0 0;
108
  }
109
 
113
  box-shadow: none;
114
  }
115
 
116
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents,
117
+ #wpbody-content .metabox-holder.fw-options-tabs-contents {
118
+ padding-top: 0;
 
119
  }
120
 
121
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents {
122
+ margin-top: 20px;
123
  }
124
 
125
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list {
126
  padding: 0 0 0 5px;
127
+ border-bottom-width: 0;
128
  }
129
+ body.rtl .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list {
130
  padding: 0 5px 0 0;
131
  }
132
 
133
+ .postbox .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list {
134
  padding: 0 0 0 25px;
135
  }
136
+ body.rtl .postbox .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list {
137
  padding: 0 25px 0 0;
138
  }
139
 
140
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li:after {
141
  content: '|';
142
  }
143
 
144
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li:last-child:after {
145
  content: none;
146
  }
147
 
148
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul > li {
149
  float: left;
150
  color: #666;
151
+ margin-right: 0;
152
  }
153
+ body.rtl .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul > li {
154
  float: right;
155
+ margin-left: 0;
156
  }
157
 
158
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab {
159
  background: none;
160
  border: none;
161
  padding: 0;
162
  margin: 0 5px;
163
  font-size: 13px;
164
+ line-height: 16px;
165
+ font-weight: normal;
166
  }
167
 
168
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab.fw-wp-link {
169
+ color: #0074a2;
170
+ }
171
+
172
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li:first-child a.nav-tab {
173
  margin-left: 0;
174
  }
175
+ body.rtl .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li:first-child a.nav-tab {
176
  margin-left: 5px;
177
  margin-right: 0;
178
  }
179
 
180
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
181
  background: none;
182
  padding: 0;
183
  border: none;
184
  color: #000;
 
185
  }
186
 
187
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab {
188
  position: relative;
189
  }
190
 
191
  @media (max-width: 782px) {
192
+ .fw-options-tabs-wrapper > .fw-options-tabs-list ul li {
193
  float: none;
194
  }
195
 
196
+ .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab {
197
  display: block;
198
+ text-align: center;
199
  }
200
 
201
+ .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab {
202
+ display: inline;
203
  }
204
  }
205
 
291
  color: #fff;
292
  }
293
 
294
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
295
+ border-bottom-color: #fff;
296
+ }
297
+
298
  .fw-backend-side-tabs .fw-options-tabs-list ul li:focus {
299
  outline: none;
300
  }
301
 
302
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list {
303
  padding: 0;
304
  }
305
 
306
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li:after {
307
  content: none;
308
  }
309
 
310
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul {
311
  padding: 0;
312
  background: #f7f7f7;
313
  border-width: 1px 0;
314
  clear: both;
315
  }
316
 
317
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul:before,
318
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul:after {
319
  display: table;
320
  content: " ";
321
  }
322
 
323
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul:after {
324
  clear: both;
325
  margin-bottom: -1px;
326
  }
327
 
328
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul,
329
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li {
330
  border-bottom: 1px solid #e5e5e5;
331
  }
332
 
333
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li {
334
  position: relative;
335
  border-right: 1px solid #e5e5e5;
336
  }
337
 
338
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active:after {
339
  height: 1px;
340
  background: #fff;
341
  width: 100%;
344
  position: absolute;
345
  }
346
 
347
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab,
348
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
349
  margin: 0;
350
  padding: 0 24px;
351
  border: none;
357
  outline: none;
358
  }
359
 
360
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab {
361
  background: transparent;
362
  color: #000;
363
  }
364
 
365
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
366
  background: #fff;
367
  }
368
 
369
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul > li a.nav-tab:hover {
370
  color: #0074a2;
371
  }
372
 
373
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents,
374
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-contents {
375
+ margin-top: 0;
376
  }
377
 
378
  /* hide last option border */
379
+ .fw-backend-side-tabs .fw-options-tabs-wrapper > .fw-options-tabs-contents > .fw-inner:after {
380
  content: ' ';
381
  display: block;
382
  width: 100%;
454
  padding-bottom: 0;
455
  }
456
 
457
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul {
458
  height: auto;
459
  border-bottom-width: 0;
460
  }
461
 
462
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li {
463
  float: none;
464
  width: 100%;
465
  text-align: center;
466
  border-right-width: 0;
467
  }
468
 
469
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active:after {
470
  display: none;
471
  }
472
 
473
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
474
  margin-bottom: 0;
475
  border-bottom-width: 0;
476
+ display: block;
477
  }
478
 
479
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li a.nav-tab,
480
+ .fw-backend-side-tabs .fw-options-tabs-first-level > .fw-options-tabs-contents > .fw-inner > .fw-options-tab > .fw-options-tabs-wrapper > .fw-options-tabs-list ul li.ui-state-active a.nav-tab {
481
  width: 100%;
482
  height: auto;
483
  padding: 0;
484
+ display: block;
485
  }
486
 
487
  .fw-backend-side-tabs .fw-backend-postboxes > .fw-postbox > .handlediv:before {
653
 
654
  /* Fixes for edit post page */
655
 
656
+ form#post .fw-options-tabs-wrapper > .fw-options-tabs-contents .fw-backend-postboxes {
 
 
 
 
657
  padding: 0 10px;
658
  }
659
 
807
 
808
  .fw-backend-options-group {
809
  border-bottom: 1px solid #eeeeee;
 
 
 
 
 
810
  }
811
 
812
  .fw-backend-options-group > .fw-backend-option {
813
  border-bottom-width: 0;
814
+ }
815
+
816
+ .fw-backend-options-group > .fw-backend-option:not(:last-child) {
817
  padding-bottom: 0;
818
  }
819
 
framework/static/css/fw.css CHANGED
@@ -388,7 +388,11 @@ min-width: 1200px
388
  .fw-col-xs-12,
389
  .fw-col-sm-12,
390
  .fw-col-md-12,
391
- .fw-col-lg-12 {
 
 
 
 
392
  position: relative;
393
  min-height: 1px;
394
 
@@ -408,7 +412,8 @@ min-width: 1200px
408
  .fw-col-xs-9,
409
  .fw-col-xs-10,
410
  .fw-col-xs-11,
411
- .fw-col-xs-12 {
 
412
  float: left;
413
  }
414
  body.rtl .fw-col-xs-1,
@@ -422,9 +427,14 @@ body.rtl .fw-col-xs-8,
422
  body.rtl .fw-col-xs-9,
423
  body.rtl .fw-col-xs-10,
424
  body.rtl .fw-col-xs-11,
425
- body.rtl .fw-col-xs-12 {
 
426
  float: right;
427
  }
 
 
 
 
428
  .fw-col-xs-12 {
429
  width: 100%;
430
  }
@@ -461,6 +471,10 @@ body.rtl .fw-col-xs-12 {
461
  .fw-col-xs-1 {
462
  width: 8.33333333%;
463
  }
 
 
 
 
464
  .fw-col-xs-pull-12 {
465
  right: 100%;
466
  }
@@ -500,6 +514,7 @@ body.rtl .fw-col-xs-12 {
500
  .fw-col-xs-pull-0 {
501
  right: auto;
502
  }
 
503
  body.rtl .fw-col-xs-pull-12,
504
  body.rtl .fw-col-xs-pull-11,
505
  body.rtl .fw-col-xs-pull-10,
@@ -515,6 +530,10 @@ body.rtl .fw-col-xs-pull-1,
515
  body.rtl .fw-col-xs-pull-0 {
516
  right: auto;
517
  }
 
 
 
 
518
  body.rtl .fw-col-xs-pull-12 {
519
  left: 100%;
520
  }
@@ -554,6 +573,10 @@ body.rtl .fw-col-xs-pull-1 {
554
  body.rtl .fw-col-xs-pull-0 {
555
  left: auto;
556
  }
 
 
 
 
557
  .fw-col-xs-push-12 {
558
  left: 100%;
559
  }
@@ -593,6 +616,8 @@ body.rtl .fw-col-xs-pull-0 {
593
  .fw-col-xs-push-0 {
594
  left: auto;
595
  }
 
 
596
  body.rtl .fw-col-xs-push-12,
597
  body.rtl .fw-col-xs-push-11,
598
  body.rtl .fw-col-xs-push-10,
@@ -607,6 +632,10 @@ body.rtl .fw-col-xs-push-1,
607
  body.rtl .fw-col-xs-push-0 {
608
  left: auto;
609
  }
 
 
 
 
610
  body.rtl .fw-col-xs-push-12 {
611
  right: 100%;
612
  }
@@ -646,6 +675,10 @@ body.rtl .fw-col-xs-push-1 {
646
  body.rtl .fw-col-xs-push-0 {
647
  right: auto;
648
  }
 
 
 
 
649
  .fw-col-xs-offset-12 {
650
  margin-left: 100%;
651
  }
@@ -685,6 +718,8 @@ body.rtl .fw-col-xs-push-0 {
685
  .fw-col-xs-offset-0 {
686
  margin-left: 0;
687
  }
 
 
688
  body.rtl .fw-col-xs-offset-12,
689
  body.rtl .fw-col-xs-offset-11,
690
  body.rtl .fw-col-xs-offset-10,
@@ -700,6 +735,10 @@ body.rtl .fw-col-xs-offset-1,
700
  body.rtl .fw-col-xs-offset-0 {
701
  margin-left: 0;
702
  }
 
 
 
 
703
  body.rtl .fw-col-xs-offset-12 {
704
  margin-right: 100%;
705
  }
@@ -752,7 +791,8 @@ body.rtl .fw-col-xs-offset-0 {
752
  .fw-col-sm-9,
753
  .fw-col-sm-10,
754
  .fw-col-sm-11,
755
- .fw-col-sm-12 {
 
756
  float: left;
757
  }
758
  body.rtl .fw-col-sm-1,
@@ -766,9 +806,14 @@ body.rtl .fw-col-xs-offset-0 {
766
  body.rtl .fw-col-sm-9,
767
  body.rtl .fw-col-sm-10,
768
  body.rtl .fw-col-sm-11,
769
- body.rtl .fw-col-sm-12 {
 
770
  float: right;
771
  }
 
 
 
 
772
  .fw-col-sm-12 {
773
  width: 100%;
774
  }
@@ -805,6 +850,10 @@ body.rtl .fw-col-xs-offset-0 {
805
  .fw-col-sm-1 {
806
  width: 8.33333333%;
807
  }
 
 
 
 
808
  .fw-col-sm-pull-12 {
809
  right: 100%;
810
  }
@@ -844,6 +893,7 @@ body.rtl .fw-col-xs-offset-0 {
844
  .fw-col-sm-pull-0 {
845
  right: auto;
846
  }
 
847
  body.rtl .fw-col-sm-pull-12,
848
  body.rtl .fw-col-sm-pull-11,
849
  body.rtl .fw-col-sm-pull-10,
@@ -859,6 +909,10 @@ body.rtl .fw-col-xs-offset-0 {
859
  body.rtl .fw-col-sm-pull-0 {
860
  right: auto;
861
  }
 
 
 
 
862
  body.rtl .fw-col-sm-pull-12 {
863
  left: 100%;
864
  }
@@ -898,6 +952,9 @@ body.rtl .fw-col-xs-offset-0 {
898
  body.rtl .fw-col-sm-pull-0 {
899
  left: auto;
900
  }
 
 
 
901
  .fw-col-sm-push-12 {
902
  left: 100%;
903
  }
@@ -937,6 +994,7 @@ body.rtl .fw-col-xs-offset-0 {
937
  .fw-col-sm-push-0 {
938
  left: auto;
939
  }
 
940
  body.rtl .fw-col-sm-push-12,
941
  body.rtl .fw-col-sm-push-11,
942
  body.rtl .fw-col-sm-push-10,
@@ -951,6 +1009,9 @@ body.rtl .fw-col-xs-offset-0 {
951
  body.rtl .fw-col-sm-push-0 {
952
  left: auto;
953
  }
 
 
 
954
  body.rtl .fw-col-sm-push-12 {
955
  right: 100%;
956
  }
@@ -990,6 +1051,9 @@ body.rtl .fw-col-xs-offset-0 {
990
  body.rtl .fw-col-sm-push-0 {
991
  right: auto;
992
  }
 
 
 
993
  .fw-col-sm-offset-12 {
994
  margin-left: 100%;
995
  }
@@ -1029,6 +1093,7 @@ body.rtl .fw-col-xs-offset-0 {
1029
  .fw-col-sm-offset-0 {
1030
  margin-left: 0;
1031
  }
 
1032
  body.rtl .fw-col-sm-offset-12,
1033
  body.rtl .fw-col-sm-offset-11,
1034
  body.rtl .fw-col-sm-offset-10,
@@ -1044,6 +1109,9 @@ body.rtl .fw-col-xs-offset-0 {
1044
  body.rtl .fw-col-sm-offset-0 {
1045
  margin-left: 0;
1046
  }
 
 
 
1047
  body.rtl .fw-col-sm-offset-12 {
1048
  margin-right: 100%;
1049
  }
@@ -1097,7 +1165,8 @@ body.rtl .fw-col-xs-offset-0 {
1097
  .fw-col-md-9,
1098
  .fw-col-md-10,
1099
  .fw-col-md-11,
1100
- .fw-col-md-12 {
 
1101
  float: left;
1102
  }
1103
  body.rtl .fw-col-md-1,
@@ -1111,9 +1180,13 @@ body.rtl .fw-col-xs-offset-0 {
1111
  body.rtl .fw-col-md-9,
1112
  body.rtl .fw-col-md-10,
1113
  body.rtl .fw-col-md-11,
1114
- body.rtl .fw-col-md-12 {
 
1115
  float: right;
1116
  }
 
 
 
1117
  .fw-col-md-12 {
1118
  width: 100%;
1119
  }
@@ -1150,6 +1223,10 @@ body.rtl .fw-col-xs-offset-0 {
1150
  .fw-col-md-1 {
1151
  width: 8.33333333%;
1152
  }
 
 
 
 
1153
  .fw-col-md-pull-12 {
1154
  right: 100%;
1155
  }
@@ -1189,6 +1266,7 @@ body.rtl .fw-col-xs-offset-0 {
1189
  .fw-col-md-pull-0 {
1190
  right: auto;
1191
  }
 
1192
  body.rtl .fw-col-md-pull-12,
1193
  body.rtl .fw-col-md-pull-11,
1194
  body.rtl .fw-col-md-pull-10,
@@ -1204,6 +1282,12 @@ body.rtl .fw-col-xs-offset-0 {
1204
  body.rtl .fw-col-md-pull-0 {
1205
  right: auto;
1206
  }
 
 
 
 
 
 
1207
  body.rtl .fw-col-md-pull-12 {
1208
  left: 100%;
1209
  }
@@ -1243,6 +1327,10 @@ body.rtl .fw-col-xs-offset-0 {
1243
  body.rtl .fw-col-md-pull-0 {
1244
  left: auto;
1245
  }
 
 
 
 
1246
  .fw-col-md-push-12 {
1247
  left: 100%;
1248
  }
@@ -1282,6 +1370,8 @@ body.rtl .fw-col-xs-offset-0 {
1282
  .fw-col-md-push-0 {
1283
  left: auto;
1284
  }
 
 
1285
  body.rtl .fw-col-md-push-12,
1286
  body.rtl .fw-col-md-push-11,
1287
  body.rtl .fw-col-md-push-10,
@@ -1296,6 +1386,10 @@ body.rtl .fw-col-xs-offset-0 {
1296
  body.rtl .fw-col-md-push-0 {
1297
  left: auto;
1298
  }
 
 
 
 
1299
  body.rtl .fw-col-md-push-12 {
1300
  right: 100%;
1301
  }
@@ -1335,6 +1429,9 @@ body.rtl .fw-col-xs-offset-0 {
1335
  body.rtl .fw-col-md-push-0 {
1336
  right: auto;
1337
  }
 
 
 
1338
  .fw-col-md-offset-12 {
1339
  margin-left: 100%;
1340
  }
@@ -1374,6 +1471,7 @@ body.rtl .fw-col-xs-offset-0 {
1374
  .fw-col-md-offset-0 {
1375
  margin-left: 0;
1376
  }
 
1377
  body.rtl .fw-col-md-offset-12,
1378
  body.rtl .fw-col-md-offset-11,
1379
  body.rtl .fw-col-md-offset-10,
@@ -1389,6 +1487,9 @@ body.rtl .fw-col-xs-offset-0 {
1389
  body.rtl .fw-col-md-offset-0 {
1390
  margin-left: 0;
1391
  }
 
 
 
1392
  body.rtl .fw-col-md-offset-12 {
1393
  margin-right: 100%;
1394
  }
@@ -1442,9 +1543,11 @@ body.rtl .fw-col-xs-offset-0 {
1442
  .fw-col-lg-9,
1443
  .fw-col-lg-10,
1444
  .fw-col-lg-11,
1445
- .fw-col-lg-12 {
 
1446
  float: left;
1447
  }
 
1448
  body.rtl .fw-col-lg-1,
1449
  body.rtl .fw-col-lg-2,
1450
  body.rtl .fw-col-lg-3,
@@ -1456,9 +1559,14 @@ body.rtl .fw-col-xs-offset-0 {
1456
  body.rtl .fw-col-lg-9,
1457
  body.rtl .fw-col-lg-10,
1458
  body.rtl .fw-col-lg-11,
1459
- body.rtl .fw-col-lg-12 {
 
1460
  float: right;
1461
  }
 
 
 
 
1462
  .fw-col-lg-12 {
1463
  width: 100%;
1464
  }
@@ -1495,6 +1603,10 @@ body.rtl .fw-col-xs-offset-0 {
1495
  .fw-col-lg-1 {
1496
  width: 8.33333333%;
1497
  }
 
 
 
 
1498
  .fw-col-lg-pull-12 {
1499
  right: 100%;
1500
  }
@@ -1534,6 +1646,8 @@ body.rtl .fw-col-xs-offset-0 {
1534
  .fw-col-lg-pull-0 {
1535
  right: auto;
1536
  }
 
 
1537
  body.rtl .fw-col-lg-pull-12,
1538
  body.rtl .fw-col-lg-pull-11,
1539
  body.rtl .fw-col-lg-pull-10,
@@ -1549,6 +1663,10 @@ body.rtl .fw-col-xs-offset-0 {
1549
  body.rtl .fw-col-lg-pull-0 {
1550
  right: auto;
1551
  }
 
 
 
 
1552
  body.rtl .fw-col-lg-pull-12 {
1553
  left: 100%;
1554
  }
@@ -1588,6 +1706,10 @@ body.rtl .fw-col-xs-offset-0 {
1588
  body.rtl .fw-col-lg-pull-0 {
1589
  left: auto;
1590
  }
 
 
 
 
1591
  .fw-col-lg-push-12 {
1592
  left: 100%;
1593
  }
@@ -1627,6 +1749,8 @@ body.rtl .fw-col-xs-offset-0 {
1627
  .fw-col-lg-push-0 {
1628
  left: auto;
1629
  }
 
 
1630
  body.rtl .fw-col-lg-push-12,
1631
  body.rtl .fw-col-lg-push-11,
1632
  body.rtl .fw-col-lg-push-10,
@@ -1641,6 +1765,10 @@ body.rtl .fw-col-xs-offset-0 {
1641
  body.rtl .fw-col-lg-push-0 {
1642
  left: auto;
1643
  }
 
 
 
 
1644
  body.rtl .fw-col-lg-push-12 {
1645
  right: 100%;
1646
  }
@@ -1680,6 +1808,10 @@ body.rtl .fw-col-xs-offset-0 {
1680
  body.rtl .fw-col-lg-push-0 {
1681
  right: auto;
1682
  }
 
 
 
 
1683
  .fw-col-lg-offset-12 {
1684
  margin-left: 100%;
1685
  }
@@ -1719,6 +1851,8 @@ body.rtl .fw-col-xs-offset-0 {
1719
  .fw-col-lg-offset-0 {
1720
  margin-left: 0;
1721
  }
 
 
1722
  body.rtl .fw-col-lg-offset-12,
1723
  body.rtl .fw-col-lg-offset-11,
1724
  body.rtl .fw-col-lg-offset-10,
@@ -1734,6 +1868,10 @@ body.rtl .fw-col-xs-offset-0 {
1734
  body.rtl .fw-col-lg-offset-0{
1735
  margin-left: 0;
1736
  }
 
 
 
 
1737
  body.rtl .fw-col-lg-offset-12 {
1738
  margin-right: 100%;
1739
  }
@@ -1804,6 +1942,7 @@ body.rtl .fw-col-xs-offset-0 {
1804
  display: none !important;
1805
  }
1806
 
 
1807
  .fw-force-xs .fw-col-sm-12,
1808
  .fw-force-xs .fw-col-sm-11,
1809
  .fw-force-xs .fw-col-sm-10,
@@ -1842,6 +1981,8 @@ body.rtl .fw-col-xs-offset-0 {
1842
  .fw-force-xs .fw-col-lg-1 {
1843
  width: 100%;
1844
  }
 
 
1845
  .fw-force-xs .fw-col-sm-pull-12,
1846
  .fw-force-xs .fw-col-sm-pull-11,
1847
  .fw-force-xs .fw-col-sm-pull-10,
@@ -1883,6 +2024,8 @@ body.rtl .fw-col-xs-offset-0 {
1883
  .fw-force-xs .fw-col-lg-pull-0 {
1884
  right: auto;
1885
  }
 
 
1886
  body.rtl .fw-force-xs .fw-col-sm-pull-12,
1887
  body.rtl .fw-force-xs .fw-col-sm-pull-11,
1888
  body.rtl .fw-force-xs .fw-col-sm-pull-10,
@@ -1924,6 +2067,8 @@ body.rtl .fw-force-xs .fw-col-lg-pull-1,
1924
  body.rtl .fw-force-xs .fw-col-lg-pull-0 {
1925
  left: auto;
1926
  }
 
 
1927
  .fw-force-xs .fw-col-sm-push-12,
1928
  .fw-force-xs .fw-col-sm-push-11,
1929
  .fw-force-xs .fw-col-sm-push-10,
@@ -1965,6 +2110,8 @@ body.rtl .fw-force-xs .fw-col-lg-pull-0 {
1965
  .fw-force-xs .fw-col-lg-push-0 {
1966
  left: auto;
1967
  }
 
 
1968
  body.rtl .fw-force-xs .fw-col-sm-push-12,
1969
  body.rtl .fw-force-xs .fw-col-sm-push-11,
1970
  body.rtl .fw-force-xs .fw-col-sm-push-10,
@@ -2006,6 +2153,8 @@ body.rtl .fw-force-xs .fw-col-lg-push-1,
2006
  body.rtl .fw-force-xs .fw-col-lg-push-0 {
2007
  right: auto;
2008
  }
 
 
2009
  .fw-force-xs .fw-col-sm-offset-12,
2010
  .fw-force-xs .fw-col-sm-offset-11,
2011
  .fw-force-xs .fw-col-sm-offset-10,
@@ -2047,6 +2196,8 @@ body.rtl .fw-force-xs .fw-col-lg-push-0 {
2047
  .fw-force-xs .fw-col-lg-offset-0 {
2048
  margin-left: 0;
2049
  }
 
 
2050
  body.rtl .fw-force-xs .fw-col-sm-offset-12,
2051
  body.rtl .fw-force-xs .fw-col-sm-offset-11,
2052
  body.rtl .fw-force-xs .fw-col-sm-offset-10,
@@ -2113,6 +2264,9 @@ body.rtl .fw-force-xs .fw-col-lg-offset-0 {
2113
  .fw-force-xs .fw-col-xs-12 {
2114
  width: 100%;
2115
  }
 
 
 
2116
  .fw-force-xs .fw-col-xs-11 {
2117
  width: 91.66666667%;
2118
  }
@@ -2146,6 +2300,10 @@ body.rtl .fw-force-xs .fw-col-lg-offset-0 {
2146
  .fw-force-xs .fw-col-xs-1 {
2147
  width: 8.33333333%;
2148
  }
 
 
 
 
2149
  .fw-force-xs .fw-col-xs-pull-12 {
2150
  right: 100%;
2151
  }
@@ -2185,6 +2343,8 @@ body.rtl .fw-force-xs .fw-col-lg-offset-0 {
2185
  .fw-force-xs .fw-col-xs-pull-0 {
2186
  right: auto;
2187
  }
 
 
2188
  body.rtl .fw-force-xs .fw-col-xs-pull-12,
2189
  body.rtl .fw-force-xs .fw-col-xs-pull-11,
2190
  body.rtl .fw-force-xs .fw-col-xs-pull-10,
@@ -2200,6 +2360,10 @@ body.rtl .fw-force-xs .fw-col-xs-pull-1,
2200
  body.rtl .fw-force-xs .fw-col-xs-pull-0 {
2201
  right: auto;
2202
  }
 
 
 
 
2203
  body.rtl .fw-force-xs .fw-col-xs-pull-12 {
2204
  left: 100%;
2205
  }
@@ -2239,6 +2403,9 @@ body.rtl .fw-force-xs .fw-col-xs-pull-1 {
2239
  body.rtl .fw-force-xs .fw-col-xs-pull-0 {
2240
  left: auto;
2241
  }
 
 
 
2242
  .fw-force-xs .fw-col-xs-push-12 {
2243
  left: 100%;
2244
  }
@@ -2278,6 +2445,7 @@ body.rtl .fw-force-xs .fw-col-xs-pull-0 {
2278
  .fw-force-xs .fw-col-xs-push-0 {
2279
  left: auto;
2280
  }
 
2281
  body.rtl .fw-force-xs .fw-col-xs-push-12,
2282
  body.rtl .fw-force-xs .fw-col-xs-push-11,
2283
  body.rtl .fw-force-xs .fw-col-xs-push-10,
@@ -2293,6 +2461,10 @@ body.rtl .fw-force-xs .fw-col-xs-push-1,
2293
  body.rtl .fw-force-xs .fw-col-xs-push-0 {
2294
  left: auto;
2295
  }
 
 
 
 
2296
  body.rtl .fw-force-xs .fw-col-xs-push-12 {
2297
  right: 100%;
2298
  }
@@ -2332,6 +2504,9 @@ body.rtl .fw-force-xs .fw-col-xs-push-1 {
2332
  body.rtl .fw-force-xs .fw-col-xs-push-0 {
2333
  right: auto;
2334
  }
 
 
 
2335
  .fw-force-xs .fw-col-xs-offset-12 {
2336
  margin-left: 100%;
2337
  }
@@ -2371,6 +2546,7 @@ body.rtl .fw-force-xs .fw-col-xs-push-0 {
2371
  .fw-force-xs .fw-col-xs-offset-0 {
2372
  margin-left: 0;
2373
  }
 
2374
  body.rtl .fw-force-xs .fw-col-xs-offset-12,
2375
  body.rtl .fw-force-xs .fw-col-xs-offset-11,
2376
  body.rtl .fw-force-xs .fw-col-xs-offset-10,
@@ -2386,6 +2562,10 @@ body.rtl .fw-force-xs .fw-col-xs-offset-1,
2386
  body.rtl .fw-force-xs .fw-col-xs-offset-0 {
2387
  margin-left: 0;
2388
  }
 
 
 
 
2389
  body.rtl .fw-force-xs .fw-col-xs-offset-12 {
2390
  margin-right: 100%;
2391
  }
@@ -2590,17 +2770,6 @@ a.fw-wp-link:hover {
2590
  100% {opacity: 1;}
2591
  }
2592
 
2593
-
2594
- @-webkit-keyframes fwFadeIn07 {
2595
- 0% {opacity: 0;}
2596
- 100% {opacity: 0.7;}
2597
- }
2598
-
2599
- @keyframes fwFadeIn07 {
2600
- 0% {opacity: 0;}
2601
- 100% {opacity: 0.7;}
2602
- }
2603
-
2604
  /* end: fadeIn */
2605
 
2606
  /* end: Animations */
@@ -2638,11 +2807,8 @@ a.fw-wp-link:hover {
2638
  .fw-modal.fw-modal-open > .media-modal-backdrop {
2639
  opacity: .7; /* must be the same as in .media-modal-backdrop {} wp style */
2640
 
2641
- -webkit-animation-name: fwFadeIn07;
2642
- animation-name: fwFadeIn07;
2643
-
2644
- -webkit-animation-duration: .3s;
2645
- animation-duration: .3s;
2646
  }
2647
 
2648
  .fw-modal.fw-modal-open > .media-modal > .media-modal-content {
388
  .fw-col-xs-12,
389
  .fw-col-sm-12,
390
  .fw-col-md-12,
391
+ .fw-col-lg-12,
392
+ .fw-col-xs-15,
393
+ .fw-col-sm-15,
394
+ .fw-col-md-15,
395
+ .fw-col-lg-15 {
396
  position: relative;
397
  min-height: 1px;
398
 
412
  .fw-col-xs-9,
413
  .fw-col-xs-10,
414
  .fw-col-xs-11,
415
+ .fw-col-xs-12,
416
+ .fw-col-xs-15 {
417
  float: left;
418
  }
419
  body.rtl .fw-col-xs-1,
427
  body.rtl .fw-col-xs-9,
428
  body.rtl .fw-col-xs-10,
429
  body.rtl .fw-col-xs-11,
430
+ body.rtl .fw-col-xs-12,
431
+ body.rtl .fw-col-xs-15 {
432
  float: right;
433
  }
434
+
435
+ .fw-col-lg-15 {
436
+ width: 20%;
437
+ }
438
  .fw-col-xs-12 {
439
  width: 100%;
440
  }
471
  .fw-col-xs-1 {
472
  width: 8.33333333%;
473
  }
474
+
475
+ .fw-col-xs-pull-15 {
476
+ right: 20%;
477
+ }
478
  .fw-col-xs-pull-12 {
479
  right: 100%;
480
  }
514
  .fw-col-xs-pull-0 {
515
  right: auto;
516
  }
517
+
518
  body.rtl .fw-col-xs-pull-12,
519
  body.rtl .fw-col-xs-pull-11,
520
  body.rtl .fw-col-xs-pull-10,
530
  body.rtl .fw-col-xs-pull-0 {
531
  right: auto;
532
  }
533
+
534
+ body.rtl .fw-col-xs-pull-15 {
535
+ left: 20%;
536
+ }
537
  body.rtl .fw-col-xs-pull-12 {
538
  left: 100%;
539
  }
573
  body.rtl .fw-col-xs-pull-0 {
574
  left: auto;
575
  }
576
+
577
+ .fw-col-xs-push-15 {
578
+ left: 20%;
579
+ }
580
  .fw-col-xs-push-12 {
581
  left: 100%;
582
  }
616
  .fw-col-xs-push-0 {
617
  left: auto;
618
  }
619
+
620
+ body.rtl .fw-col-xs-push-15,
621
  body.rtl .fw-col-xs-push-12,
622
  body.rtl .fw-col-xs-push-11,
623
  body.rtl .fw-col-xs-push-10,
632
  body.rtl .fw-col-xs-push-0 {
633
  left: auto;
634
  }
635
+
636
+ body.rtl .fw-col-xs-push-12 {
637
+ right: 20%;
638
+ }
639
  body.rtl .fw-col-xs-push-12 {
640
  right: 100%;
641
  }
675
  body.rtl .fw-col-xs-push-0 {
676
  right: auto;
677
  }
678
+
679
+ .fw-col-xs-offset-15 {
680
+ margin-left: 20%;
681
+ }
682
  .fw-col-xs-offset-12 {
683
  margin-left: 100%;
684
  }
718
  .fw-col-xs-offset-0 {
719
  margin-left: 0;
720
  }
721
+
722
+ body.rtl .fw-col-xs-offset-15,
723
  body.rtl .fw-col-xs-offset-12,
724
  body.rtl .fw-col-xs-offset-11,
725
  body.rtl .fw-col-xs-offset-10,
735
  body.rtl .fw-col-xs-offset-0 {
736
  margin-left: 0;
737
  }
738
+
739
+ body.rtl .fw-col-xs-offset-15 {
740
+ margin-right: 20%;
741
+ }
742
  body.rtl .fw-col-xs-offset-12 {
743
  margin-right: 100%;
744
  }
791
  .fw-col-sm-9,
792
  .fw-col-sm-10,
793
  .fw-col-sm-11,
794
+ .fw-col-sm-12,
795
+ .fw-col-sm-15 {
796
  float: left;
797
  }
798
  body.rtl .fw-col-sm-1,
806
  body.rtl .fw-col-sm-9,
807
  body.rtl .fw-col-sm-10,
808
  body.rtl .fw-col-sm-11,
809
+ body.rtl .fw-col-sm-12,
810
+ body.rtl .fw-col-sm-15 {
811
  float: right;
812
  }
813
+
814
+ .fw-col-sm-15 {
815
+ width: 20%;
816
+ }
817
  .fw-col-sm-12 {
818
  width: 100%;
819
  }
850
  .fw-col-sm-1 {
851
  width: 8.33333333%;
852
  }
853
+
854
+ .fw-col-sm-pull-15 {
855
+ right: 20%;
856
+ }
857
  .fw-col-sm-pull-12 {
858
  right: 100%;
859
  }
893
  .fw-col-sm-pull-0 {
894
  right: auto;
895
  }
896
+ body.rtl .fw-col-sm-pull-15,
897
  body.rtl .fw-col-sm-pull-12,
898
  body.rtl .fw-col-sm-pull-11,
899
  body.rtl .fw-col-sm-pull-10,
909
  body.rtl .fw-col-sm-pull-0 {
910
  right: auto;
911
  }
912
+
913
+ body.rtl .fw-col-sm-pull-15 {
914
+ left: 20%;
915
+ }
916
  body.rtl .fw-col-sm-pull-12 {
917
  left: 100%;
918
  }
952
  body.rtl .fw-col-sm-pull-0 {
953
  left: auto;
954
  }
955
+ .fw-col-sm-push-15 {
956
+ left: 20%;
957
+ }
958
  .fw-col-sm-push-12 {
959
  left: 100%;
960
  }
994
  .fw-col-sm-push-0 {
995
  left: auto;
996
  }
997
+ body.rtl .fw-col-sm-push-15,
998
  body.rtl .fw-col-sm-push-12,
999
  body.rtl .fw-col-sm-push-11,
1000
  body.rtl .fw-col-sm-push-10,
1009
  body.rtl .fw-col-sm-push-0 {
1010
  left: auto;
1011
  }
1012
+ body.rtl .fw-col-sm-push-15 {
1013
+ right: 20%;
1014
+ }
1015
  body.rtl .fw-col-sm-push-12 {
1016
  right: 100%;
1017
  }
1051
  body.rtl .fw-col-sm-push-0 {
1052
  right: auto;
1053
  }
1054
+ .fw-col-sm-offset-15 {
1055
+ margin-left: 20%;
1056
+ }
1057
  .fw-col-sm-offset-12 {
1058
  margin-left: 100%;
1059
  }
1093
  .fw-col-sm-offset-0 {
1094
  margin-left: 0;
1095
  }
1096
+ body.rtl .fw-col-sm-offset-15,
1097
  body.rtl .fw-col-sm-offset-12,
1098
  body.rtl .fw-col-sm-offset-11,
1099
  body.rtl .fw-col-sm-offset-10,
1109
  body.rtl .fw-col-sm-offset-0 {
1110
  margin-left: 0;
1111
  }
1112
+ body.rtl .fw-col-sm-offset-15 {
1113
+ margin-right: 20%;
1114
+ }
1115
  body.rtl .fw-col-sm-offset-12 {
1116
  margin-right: 100%;
1117
  }
1165
  .fw-col-md-9,
1166
  .fw-col-md-10,
1167
  .fw-col-md-11,
1168
+ .fw-col-md-12,
1169
+ .fw-col-md-15 {
1170
  float: left;
1171
  }
1172
  body.rtl .fw-col-md-1,
1180
  body.rtl .fw-col-md-9,
1181
  body.rtl .fw-col-md-10,
1182
  body.rtl .fw-col-md-11,
1183
+ body.rtl .fw-col-md-12,
1184
+ body.rtl .fw-col-md-15 {
1185
  float: right;
1186
  }
1187
+ .fw-col-md-15 {
1188
+ width: 20%;
1189
+ }
1190
  .fw-col-md-12 {
1191
  width: 100%;
1192
  }
1223
  .fw-col-md-1 {
1224
  width: 8.33333333%;
1225
  }
1226
+
1227
+ .fw-col-md-pull-15 {
1228
+ right: 20%;
1229
+ }
1230
  .fw-col-md-pull-12 {
1231
  right: 100%;
1232
  }
1266
  .fw-col-md-pull-0 {
1267
  right: auto;
1268
  }
1269
+ body.rtl .fw-col-md-pull-15,
1270
  body.rtl .fw-col-md-pull-12,
1271
  body.rtl .fw-col-md-pull-11,
1272
  body.rtl .fw-col-md-pull-10,
1282
  body.rtl .fw-col-md-pull-0 {
1283
  right: auto;
1284
  }
1285
+ body.rtl .fw-col-md-pull-15 {
1286
+ left: 20%;
1287
+ }
1288
+ body.rtl .fw-col-md-pull-15 {
1289
+ left: 100%;
1290
+ }
1291
  body.rtl .fw-col-md-pull-12 {
1292
  left: 100%;
1293
  }
1327
  body.rtl .fw-col-md-pull-0 {
1328
  left: auto;
1329
  }
1330
+
1331
+ .fw-col-md-push-15 {
1332
+ left: 20%;
1333
+ }
1334
  .fw-col-md-push-12 {
1335
  left: 100%;
1336
  }
1370
  .fw-col-md-push-0 {
1371
  left: auto;
1372
  }
1373
+
1374
+ body.rtl .fw-col-md-push-15,
1375
  body.rtl .fw-col-md-push-12,
1376
  body.rtl .fw-col-md-push-11,
1377
  body.rtl .fw-col-md-push-10,
1386
  body.rtl .fw-col-md-push-0 {
1387
  left: auto;
1388
  }
1389
+
1390
+ body.rtl .fw-col-md-push-15 {
1391
+ right: 20%;
1392
+ }
1393
  body.rtl .fw-col-md-push-12 {
1394
  right: 100%;
1395
  }
1429
  body.rtl .fw-col-md-push-0 {
1430
  right: auto;
1431
  }
1432
+ .fw-col-md-offset-15 {
1433
+ margin-left: 20%;
1434
+ }
1435
  .fw-col-md-offset-12 {
1436
  margin-left: 100%;
1437
  }
1471
  .fw-col-md-offset-0 {
1472
  margin-left: 0;
1473
  }
1474
+ body.rtl .fw-col-md-offset-15,
1475
  body.rtl .fw-col-md-offset-12,
1476
  body.rtl .fw-col-md-offset-11,
1477
  body.rtl .fw-col-md-offset-10,
1487
  body.rtl .fw-col-md-offset-0 {
1488
  margin-left: 0;
1489
  }
1490
+ body.rtl .fw-col-md-offset-15 {
1491
+ margin-right: 20%;
1492
+ }
1493
  body.rtl .fw-col-md-offset-12 {
1494
  margin-right: 100%;
1495
  }
1543
  .fw-col-lg-9,
1544
  .fw-col-lg-10,
1545
  .fw-col-lg-11,
1546
+ .fw-col-lg-12,
1547
+ .fw-col-lg-15 {
1548
  float: left;
1549
  }
1550
+
1551
  body.rtl .fw-col-lg-1,
1552
  body.rtl .fw-col-lg-2,
1553
  body.rtl .fw-col-lg-3,
1559
  body.rtl .fw-col-lg-9,
1560
  body.rtl .fw-col-lg-10,
1561
  body.rtl .fw-col-lg-11,
1562
+ body.rtl .fw-col-lg-12,
1563
+ body.rtl .fw-col-lg-15 {
1564
  float: right;
1565
  }
1566
+
1567
+ .fw-col-lg-15 {
1568
+ width: 20%;
1569
+ }
1570
  .fw-col-lg-12 {
1571
  width: 100%;
1572
  }
1603
  .fw-col-lg-1 {
1604
  width: 8.33333333%;
1605
  }
1606
+
1607
+ .fw-col-lg-pull-15 {
1608
+ right: 20%;
1609
+ }
1610
  .fw-col-lg-pull-12 {
1611
  right: 100%;
1612
  }
1646
  .fw-col-lg-pull-0 {
1647
  right: auto;
1648
  }
1649
+
1650
+ body.rtl .fw-col-lg-pull-15,
1651
  body.rtl .fw-col-lg-pull-12,
1652
  body.rtl .fw-col-lg-pull-11,
1653
  body.rtl .fw-col-lg-pull-10,
1663
  body.rtl .fw-col-lg-pull-0 {
1664
  right: auto;
1665
  }
1666
+
1667
+ body.rtl .fw-col-lg-pull-15 {
1668
+ left: 20%;
1669
+ }
1670
  body.rtl .fw-col-lg-pull-12 {
1671
  left: 100%;
1672
  }
1706
  body.rtl .fw-col-lg-pull-0 {
1707
  left: auto;
1708
  }
1709
+
1710
+ .fw-col-lg-push-15 {
1711
+ left: 20%;
1712
+ }
1713
  .fw-col-lg-push-12 {
1714
  left: 100%;
1715
  }
1749
  .fw-col-lg-push-0 {
1750
  left: auto;
1751
  }
1752
+
1753
+ body.rtl .fw-col-lg-push-15,
1754
  body.rtl .fw-col-lg-push-12,
1755
  body.rtl .fw-col-lg-push-11,
1756
  body.rtl .fw-col-lg-push-10,
1765
  body.rtl .fw-col-lg-push-0 {
1766
  left: auto;
1767
  }
1768
+
1769
+ body.rtl .fw-col-lg-push-15 {
1770
+ right: 20%;
1771
+ }
1772
  body.rtl .fw-col-lg-push-12 {
1773
  right: 100%;
1774
  }
1808
  body.rtl .fw-col-lg-push-0 {
1809
  right: auto;
1810
  }
1811
+
1812
+ .fw-col-lg-offset-15 {
1813
+ margin-left: 20%;
1814
+ }
1815
  .fw-col-lg-offset-12 {
1816
  margin-left: 100%;
1817
  }
1851
  .fw-col-lg-offset-0 {
1852
  margin-left: 0;
1853
  }
1854
+
1855
+ body.rtl .fw-col-lg-offset-15,
1856
  body.rtl .fw-col-lg-offset-12,
1857
  body.rtl .fw-col-lg-offset-11,
1858
  body.rtl .fw-col-lg-offset-10,
1868
  body.rtl .fw-col-lg-offset-0{
1869
  margin-left: 0;
1870
  }
1871
+
1872
+ body.rtl .fw-col-lg-offset-15 {
1873
+ margin-right: 20%;
1874
+ }
1875
  body.rtl .fw-col-lg-offset-12 {
1876
  margin-right: 100%;
1877
  }
1942
  display: none !important;
1943
  }
1944
 
1945
+ .fw-force-xs .fw-col-sm-15,
1946
  .fw-force-xs .fw-col-sm-12,
1947
  .fw-force-xs .fw-col-sm-11,
1948
  .fw-force-xs .fw-col-sm-10,
1981
  .fw-force-xs .fw-col-lg-1 {
1982
  width: 100%;
1983
  }
1984
+
1985
+ .fw-force-xs .fw-col-sm-pull-15,
1986
  .fw-force-xs .fw-col-sm-pull-12,
1987
  .fw-force-xs .fw-col-sm-pull-11,
1988
  .fw-force-xs .fw-col-sm-pull-10,
2024
  .fw-force-xs .fw-col-lg-pull-0 {
2025
  right: auto;
2026
  }
2027
+
2028
+ body.rtl .fw-force-xs .fw-col-sm-pull-15,
2029
  body.rtl .fw-force-xs .fw-col-sm-pull-12,
2030
  body.rtl .fw-force-xs .fw-col-sm-pull-11,
2031
  body.rtl .fw-force-xs .fw-col-sm-pull-10,
2067
  body.rtl .fw-force-xs .fw-col-lg-pull-0 {
2068
  left: auto;
2069
  }
2070
+
2071
+ .fw-force-xs .fw-col-sm-push-15,
2072
  .fw-force-xs .fw-col-sm-push-12,
2073
  .fw-force-xs .fw-col-sm-push-11,
2074
  .fw-force-xs .fw-col-sm-push-10,
2110
  .fw-force-xs .fw-col-lg-push-0 {
2111
  left: auto;
2112
  }
2113
+
2114
+ body.rtl .fw-force-xs .fw-col-sm-push-15,
2115
  body.rtl .fw-force-xs .fw-col-sm-push-12,
2116
  body.rtl .fw-force-xs .fw-col-sm-push-11,
2117
  body.rtl .fw-force-xs .fw-col-sm-push-10,
2153
  body.rtl .fw-force-xs .fw-col-lg-push-0 {
2154
  right: auto;
2155
  }
2156
+
2157
+ .fw-force-xs .fw-col-sm-offset-15,
2158
  .fw-force-xs .fw-col-sm-offset-12,
2159
  .fw-force-xs .fw-col-sm-offset-11,
2160
  .fw-force-xs .fw-col-sm-offset-10,
2196
  .fw-force-xs .fw-col-lg-offset-0 {
2197
  margin-left: 0;
2198
  }
2199
+
2200
+ body.rtl .fw-force-xs .fw-col-sm-offset-15,
2201
  body.rtl .fw-force-xs .fw-col-sm-offset-12,
2202
  body.rtl .fw-force-xs .fw-col-sm-offset-11,
2203
  body.rtl .fw-force-xs .fw-col-sm-offset-10,
2264
  .fw-force-xs .fw-col-xs-12 {
2265
  width: 100%;
2266
  }
2267
+ .fw-force-xs .fw-col-xs-15 {
2268
+ width: 100%;
2269
+ }
2270
  .fw-force-xs .fw-col-xs-11 {
2271
  width: 91.66666667%;
2272
  }
2300
  .fw-force-xs .fw-col-xs-1 {
2301
  width: 8.33333333%;
2302
  }
2303
+
2304
+ .fw-force-xs .fw-col-xs-pull-15 {
2305
+ right: 100%;
2306
+ }
2307
  .fw-force-xs .fw-col-xs-pull-12 {
2308
  right: 100%;
2309
  }
2343
  .fw-force-xs .fw-col-xs-pull-0 {
2344
  right: auto;
2345
  }
2346
+
2347
+ body.rtl .fw-force-xs .fw-col-xs-pull-15,
2348
  body.rtl .fw-force-xs .fw-col-xs-pull-12,
2349
  body.rtl .fw-force-xs .fw-col-xs-pull-11,
2350
  body.rtl .fw-force-xs .fw-col-xs-pull-10,
2360
  body.rtl .fw-force-xs .fw-col-xs-pull-0 {
2361
  right: auto;
2362
  }
2363
+
2364
+ body.rtl .fw-force-xs .fw-col-xs-pull-15 {
2365
+ left: 20%;
2366
+ }
2367
  body.rtl .fw-force-xs .fw-col-xs-pull-12 {
2368
  left: 100%;
2369
  }
2403
  body.rtl .fw-force-xs .fw-col-xs-pull-0 {
2404
  left: auto;
2405
  }
2406
+ .fw-force-xs .fw-col-xs-push-15 {
2407
+ left: 20%;
2408
+ }
2409
  .fw-force-xs .fw-col-xs-push-12 {
2410
  left: 100%;
2411
  }
2445
  .fw-force-xs .fw-col-xs-push-0 {
2446
  left: auto;
2447
  }
2448
+ body.rtl .fw-force-xs .fw-col-xs-push-15,
2449
  body.rtl .fw-force-xs .fw-col-xs-push-12,
2450
  body.rtl .fw-force-xs .fw-col-xs-push-11,
2451
  body.rtl .fw-force-xs .fw-col-xs-push-10,
2461
  body.rtl .fw-force-xs .fw-col-xs-push-0 {
2462
  left: auto;
2463
  }
2464
+
2465
+ body.rtl .fw-force-xs .fw-col-xs-push-15 {
2466
+ right: 20%;
2467
+ }
2468
  body.rtl .fw-force-xs .fw-col-xs-push-12 {
2469
  right: 100%;
2470
  }
2504
  body.rtl .fw-force-xs .fw-col-xs-push-0 {
2505
  right: auto;
2506
  }
2507
+ .fw-force-xs .fw-col-xs-offset-15 {
2508
+ margin-left: 20%;
2509
+ }
2510
  .fw-force-xs .fw-col-xs-offset-12 {
2511
  margin-left: 100%;
2512
  }
2546
  .fw-force-xs .fw-col-xs-offset-0 {
2547
  margin-left: 0;
2548
  }
2549
+ body.rtl .fw-force-xs .fw-col-xs-offset-15,
2550
  body.rtl .fw-force-xs .fw-col-xs-offset-12,
2551
  body.rtl .fw-force-xs .fw-col-xs-offset-11,
2552
  body.rtl .fw-force-xs .fw-col-xs-offset-10,
2562
  body.rtl .fw-force-xs .fw-col-xs-offset-0 {
2563
  margin-left: 0;
2564
  }
2565
+
2566
+ body.rtl .fw-force-xs .fw-col-xs-offset-15 {
2567
+ margin-right: 20%;
2568
+ }
2569
  body.rtl .fw-force-xs .fw-col-xs-offset-12 {
2570
  margin-right: 100%;
2571
  }
2770
  100% {opacity: 1;}
2771
  }
2772
 
 
 
 
 
 
 
 
 
 
 
 
2773
  /* end: fadeIn */
2774
 
2775
  /* end: Animations */
2807
  .fw-modal.fw-modal-open > .media-modal-backdrop {
2808
  opacity: .7; /* must be the same as in .media-modal-backdrop {} wp style */
2809
 
2810
+ -webkit-transition: opacity .3s ease-in-out; /* For Safari 3.1 to 6.0 */
2811
+ transition: opacity .3s ease-in-out;
 
 
 
2812
  }
2813
 
2814
  .fw-modal.fw-modal-open > .media-modal > .media-modal-content {
framework/static/js/backend-options.js CHANGED
@@ -95,10 +95,6 @@ jQuery(document).ready(function($){
95
  $this.addClass('fw-options-tabs-first-level');
96
  }
97
  });
98
-
99
- setTimeout(function(){
100
- $elements.fadeTo('fast', 1, function(){ $(this).css('opacity', ''); });
101
- }, 50);
102
  }
103
  });
104
 
@@ -106,33 +102,21 @@ jQuery(document).ready(function($){
106
  fwEvents.on('fw:options:init', function (data) {
107
  var $boxes = data.$elements.find('.fw-postbox:not(.fw-postbox-initialized)');
108
 
109
- {
110
- hideBoxEmptyTitles($boxes);
111
-
112
- /**
113
- * some times the titles are not hidden (don't know why)
114
- * so try to hide second time just to make sure
115
- */
116
- setTimeout(function(){
117
- hideBoxEmptyTitles($boxes);
118
- }, 300);
119
- }
120
-
121
- setTimeout(function(){
122
- addPostboxToggles($boxes);
123
- }, 100);
124
-
125
  /**
126
  * leave open only first boxes
127
  */
128
- data.$elements.find('.fw-backend-postboxes > .fw-postbox:not(:first-child)').addClass('closed');
129
 
130
  $boxes.addClass('fw-postbox-initialized');
131
 
132
- setTimeout(function(){
133
- // trigger on box custom event for others to do something after box initialized
134
- $boxes.trigger('fw-options-box:initialized');
135
- }, 100);
 
 
 
 
136
  });
137
 
138
  /** Fixes */
@@ -170,6 +154,10 @@ jQuery(document).ready(function($){
170
  data.$elements.find('.postbox-with-fw-options > .inside, .fw-postbox > .inside')
171
  .append('<div class="fw-backend-options-last-border-hider"></div>');
172
  }
 
 
 
 
173
  });
174
 
175
  /**
@@ -185,9 +173,5 @@ jQuery(document).ready(function($){
185
  });
186
  })();
187
 
188
- setTimeout(function(){
189
- hideBoxEmptyTitles($('.postbox-with-fw-options'));
190
- }, 55);
191
-
192
  $('#side-sortables').addClass('fw-force-xs');
193
  });
95
  $this.addClass('fw-options-tabs-first-level');
96
  }
97
  });
 
 
 
 
98
  }
99
  });
100
 
102
  fwEvents.on('fw:options:init', function (data) {
103
  var $boxes = data.$elements.find('.fw-postbox:not(.fw-postbox-initialized)');
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
 
112
+ hideBoxEmptyTitles(
113
+ $boxes.filter('.fw-backend-postboxes > .fw-postbox')
114
+ );
115
+
116
+ addPostboxToggles($boxes);
117
+
118
+ // trigger on box custom event for others to do something after box initialized
119
+ $boxes.trigger('fw-options-box:initialized');
120
  });
121
 
122
  /** Fixes */
154
  data.$elements.find('.postbox-with-fw-options > .inside, .fw-postbox > .inside')
155
  .append('<div class="fw-backend-options-last-border-hider"></div>');
156
  }
157
+
158
+ hideBoxEmptyTitles(
159
+ data.$elements.find('.postbox-with-fw-options')
160
+ );
161
  });
162
 
163
  /**
173
  });
174
  })();
175
 
 
 
 
 
176
  $('#side-sortables').addClass('fw-force-xs');
177
  });
framework/static/js/fw.js CHANGED
@@ -453,8 +453,7 @@ fw.getQueryString = function(name) {
453
  return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
454
  };
455
 
456
- (function() {
457
-
458
  /*
459
  * A stack-like structure to manage chains of modals
460
  * (modals that are opened one from another)
@@ -475,6 +474,36 @@ fw.getQueryString = function(name) {
475
  }
476
  };
477
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
478
  /**
479
  * Modal to edit backend options
480
  *
@@ -508,7 +537,6 @@ fw.getQueryString = function(name) {
508
  * modal.open();
509
  */
510
  fw.OptionsModal = Backbone.Model.extend({
511
- defaultSize: 'small', // 'large', 'medium', 'small'
512
  defaults: {
513
  /** Will be transformed to array with json_decode($options, true) and sent to fw()->backend->render_options() */
514
  options: [
@@ -529,7 +557,8 @@ fw.getQueryString = function(name) {
529
  * Content html
530
  * @private
531
  */
532
- html: ''
 
533
  },
534
  /**
535
  * Properties created in .initialize():
@@ -541,33 +570,6 @@ fw.getQueryString = function(name) {
541
  initialize: function() {
542
  var modal = this;
543
 
544
- var ContentView = Backbone.View.extend({
545
- tagName: 'form',
546
- attributes: {
547
- 'onsubmit': 'return false;'
548
- },
549
- render: function() {
550
- this.$el.html(
551
- this.model.get('html')
552
- );
553
-
554
- fwEvents.trigger('fw:options:init', {$elements: this.$el});
555
-
556
- /* options fixes */
557
- {
558
- // hide last border
559
- this.$el.prepend('<div class="fw-backend-options-last-border-hider"></div>');
560
-
561
- // hide last border from tabs
562
- this.$el.find('.fw-options-tabs-contents > .fw-inner > .fw-options-tab')
563
- .append('<div class="fw-backend-options-last-border-hider"></div>');
564
- }
565
- },
566
- initialize: function() {
567
- this.listenTo(this.model, 'change:html', this.render);
568
- }
569
- });
570
-
571
  // prepare this.frame
572
  {
573
  var ControllerMainState = wp.media.controller.State.extend({
@@ -602,7 +604,7 @@ fw.getQueryString = function(name) {
602
  if (_.indexOf(['large', 'medium', 'small'], size) !== -1) {
603
  $modalWrapper.addClass('fw-options-modal-' + size);
604
  } else {
605
- $modalWrapper.addClass('fw-options-modal-' + modal.defaultSize);
606
  }
607
 
608
  if (stackSize) {
@@ -721,7 +723,7 @@ fw.getQueryString = function(name) {
721
  items: [
722
  {
723
  style: 'primary',
724
- text: 'Save',
725
  priority: 40,
726
  click: function () {
727
  fw.loading.show();
@@ -762,7 +764,7 @@ fw.getQueryString = function(name) {
762
  * user completed the form with data and wants to submit data
763
  * do not delete all his work
764
  */
765
- alert(status+ ': '+ error.message);
766
  }
767
  });
768
  }
453
  return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
454
  };
455
 
456
+ (function(){
 
457
  /*
458
  * A stack-like structure to manage chains of modals
459
  * (modals that are opened one from another)
474
  }
475
  };
476
 
477
+ var ContentView = Backbone.View.extend({
478
+ tagName: 'form',
479
+ events: {
480
+ 'submit': 'onSubmit'
481
+ },
482
+ render: function() {
483
+ this.$el.html(
484
+ this.model.get('html')
485
+ );
486
+
487
+ fwEvents.trigger('fw:options:init', {$elements: this.$el});
488
+
489
+ /* options fixes */
490
+ {
491
+ // hide last border
492
+ this.$el.prepend('<div class="fw-backend-options-last-border-hider"></div>');
493
+
494
+ // hide last border from tabs
495
+ this.$el.find('.fw-options-tabs-contents > .fw-inner > .fw-options-tab')
496
+ .append('<div class="fw-backend-options-last-border-hider"></div>');
497
+ }
498
+ },
499
+ initialize: function() {
500
+ this.listenTo(this.model, 'change:html', this.render);
501
+ },
502
+ onSubmit: function(e) {
503
+ e.preventDefault();
504
+ }
505
+ });
506
+
507
  /**
508
  * Modal to edit backend options
509
  *
537
  * modal.open();
538
  */
539
  fw.OptionsModal = Backbone.Model.extend({
 
540
  defaults: {
541
  /** Will be transformed to array with json_decode($options, true) and sent to fw()->backend->render_options() */
542
  options: [
557
  * Content html
558
  * @private
559
  */
560
+ html: '',
561
+ size: 'small' // small, medium, large
562
  },
563
  /**
564
  * Properties created in .initialize():
570
  initialize: function() {
571
  var modal = this;
572
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
  // prepare this.frame
574
  {
575
  var ControllerMainState = wp.media.controller.State.extend({
604
  if (_.indexOf(['large', 'medium', 'small'], size) !== -1) {
605
  $modalWrapper.addClass('fw-options-modal-' + size);
606
  } else {
607
+ $modalWrapper.addClass('fw-options-modal-' + modal.defaults.size);
608
  }
609
 
610
  if (stackSize) {
723
  items: [
724
  {
725
  style: 'primary',
726
+ text: _fw_localized.l10n.save,
727
  priority: 40,
728
  click: function () {
729
  fw.loading.show();
764
  * user completed the form with data and wants to submit data
765
  * do not delete all his work
766
  */
767
+ alert(status +': '+ error.message);
768
  }
769
  });
770
  }
framework/static/js/option-types.js CHANGED
@@ -3,5 +3,5 @@ jQuery(document).ready(function ($) {
3
  fwEvents.trigger('fw:options:init', {
4
  $elements: $(document.body)
5
  });
6
- }, 50);
7
  });
3
  fwEvents.trigger('fw:options:init', {
4
  $elements: $(document.body)
5
  });
6
+ }, 30);
7
  });
framework/views/backend-tabs.php CHANGED
@@ -5,7 +5,7 @@
5
  * @var array $options_data
6
  */
7
  ?>
8
- <div class="fw-options-tabs-wrapper" style="opacity:0">
9
  <div class="fw-options-tabs-list">
10
  <ul>
11
  <?php foreach ($tabs as $tab_id => &$tab): ?>
5
  * @var array $options_data
6
  */
7
  ?>
8
+ <div class="fw-options-tabs-wrapper">
9
  <div class="fw-options-tabs-list">
10
  <ul>
11
  <?php foreach ($tabs as $tab_id => &$tab): ?>
readme.txt CHANGED
@@ -3,7 +3,7 @@ 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.1.25
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -86,6 +86,25 @@ Yes; Unyson will work with any theme.
86
 
87
  == Changelog ==
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  = 2.1.25 =
90
  * Fixed qTranslate function name
91
  * Improved default flash messages display position in frontend
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.0
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.0 =
90
+ * Added the possibility to load extensions from any directory
91
+
92
+ ```
93
+ function _filter_my_plugin_extensions($locations) {
94
+ $locations['/path/to/plugin/extensions'] = 'https://uri.to/plugin/extensions';
95
+ return $locations;
96
+ }
97
+ add_filter('fw_extensions_locations', '_filter_my_plugin_extensions');
98
+ ```
99
+
100
+ **Important!** Prefix your extension names to prevent conflicts.
101
+
102
+ * Removed `array_merge($old_opts, $new_opts)` from options save [#266](https://github.com/ThemeFuse/Unyson/issues/266)
103
+ * Tabs, Boxes, Groups, Options are now displayed in the order they are in array (not grouped) [#319](https://github.com/ThemeFuse/Unyson/issues/319)
104
+ * Option type `multi-picker` fixes [#296](https://github.com/ThemeFuse/Unyson/issues/296)
105
+ * Added the possibility to use custom `framework-customizations` directory name [#276](https://github.com/ThemeFuse/Unyson/issues/276)
106
+ * Minor fixes
107
+
108
  = 2.1.25 =
109
  * Fixed qTranslate function name
110
  * Improved default flash messages display position in frontend
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.1.25
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.0
7
  * Author: ThemeFuse
8
  * Author URI: http://themefuse.com
9
  * License: GPL2+