Unyson - Version 2.6.0

Version Description

  • Added File Cache #1828
  • Migration to native term meta #1745
  • The possibility to register Available Extensions from theme
  • Fixed #1860, #1877, #1897, #1810
Download this release

Release Info

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

Code changes from version 2.5.12 to 2.6.0

Files changed (41) hide show
  1. framework/bootstrap-helpers.php +40 -25
  2. framework/bootstrap.php +9 -2
  3. framework/core/components/backend.php +17 -11
  4. framework/core/components/extensions.php +16 -2
  5. framework/core/components/extensions/manager/available-extensions.php +72 -26
  6. framework/core/components/extensions/manager/class--fw-extensions-manager.php +482 -247
  7. framework/core/components/extensions/manager/includes/available-ext/class--fw-available-extensions-register.php +7 -0
  8. framework/core/components/extensions/manager/includes/available-ext/class-fw-available-extension.php +131 -0
  9. framework/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source-register.php +9 -0
  10. framework/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source.php +20 -0
  11. framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-github.php +187 -0
  12. framework/core/components/extensions/manager/includes/download-source/types/init.php +15 -0
  13. framework/core/components/extensions/manager/static/extensions-page.css +12 -1
  14. framework/core/components/theme.php +13 -7
  15. framework/core/extends/class-fw-extension.php +31 -12
  16. framework/extensions/update/manifest.php +1 -1
  17. framework/extensions/update/views/updates-list.php +1 -1
  18. framework/helpers/class-fw-cache.php +6 -4
  19. framework/helpers/class-fw-file-cache.php +237 -0
  20. framework/helpers/class-fw-wp-filesystem.php +1 -1
  21. framework/helpers/database.php +182 -17
  22. framework/helpers/general.php +60 -27
  23. framework/helpers/type/class-fw-type-register.php +22 -2
  24. framework/includes/hooks.php +24 -57
  25. framework/includes/option-types/addable-box/static/js/scripts.js +10 -13
  26. framework/includes/option-types/addable-option/static/js/scripts.js +13 -3
  27. framework/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php +15 -7
  28. framework/includes/option-types/addable-popup/static/css/styles.css +6 -9
  29. framework/includes/option-types/addable-popup/static/js/addable-popup.js +10 -8
  30. framework/includes/option-types/addable-popup/{views/view.php → view.php} +17 -18
  31. framework/includes/option-types/color-picker/static/js/scripts.js +1 -1
  32. framework/includes/option-types/icon-v2/static/js/icon-picker.js +25 -2
  33. framework/includes/option-types/image-picker/class-fw-option-type-image-picker.php +7 -6
  34. framework/includes/option-types/oembed/static/js/oembed.js +24 -13
  35. framework/includes/option-types/rgba-color-picker/static/js/scripts.js +2 -2
  36. framework/includes/option-types/wp-editor/includes/class-fw-wp-editor-settings.php +13 -11
  37. framework/includes/option-types/wp-editor/static/scripts.js +4 -3
  38. framework/includes/term-meta/function_fw_term_meta_setup_blog.php +0 -38
  39. framework/manifest.php +1 -1
  40. readme.txt +10 -73
  41. unyson.php +11 -41
framework/bootstrap-helpers.php CHANGED
@@ -23,13 +23,16 @@ function fw_fix_path($path) {
23
  * @return string
24
  */
25
  function fw_get_framework_customizations_dir_rel_path($append = '') {
26
- static $cache = null;
27
-
28
- if ($cache === null) {
29
- $cache = apply_filters('fw_framework_customizations_dir_rel_path', '/framework-customizations');
 
 
 
30
  }
31
 
32
- return $cache . $append;
33
  }
34
 
35
  /** Child theme related functions */
@@ -71,13 +74,16 @@ function fw_get_framework_customizations_dir_rel_path($append = '') {
71
  * @return string
72
  */
73
  function fw_get_template_customizations_directory($rel_path = '') {
74
- static $cache = null;
75
-
76
- if ($cache === null) {
77
- $cache = get_template_directory() . fw_get_framework_customizations_dir_rel_path();
 
 
 
78
  }
79
 
80
- return $cache . $rel_path;
81
  }
82
 
83
  /**
@@ -86,13 +92,16 @@ function fw_get_framework_customizations_dir_rel_path($append = '') {
86
  * @return string
87
  */
88
  function fw_get_template_customizations_directory_uri($rel_path = '') {
89
- static $cache = null;
90
-
91
- if ($cache === null) {
92
- $cache = get_template_directory_uri() . fw_get_framework_customizations_dir_rel_path();
 
 
 
93
  }
94
 
95
- return $cache . $rel_path;
96
  }
97
  }
98
 
@@ -104,13 +113,16 @@ function fw_get_framework_customizations_dir_rel_path($append = '') {
104
  * @return string
105
  */
106
  function fw_get_framework_directory($rel_path = '') {
107
- static $cache = null;
108
-
109
- if ($cache === null) {
110
- $cache = apply_filters('fw_framework_directory', dirname(__FILE__));
 
 
 
111
  }
112
 
113
- return $cache . $rel_path;
114
  }
115
 
116
  /**
@@ -119,12 +131,15 @@ function fw_get_framework_customizations_dir_rel_path($append = '') {
119
  * @return string
120
  */
121
  function fw_get_framework_directory_uri($rel_path = '') {
122
- static $cache = null;
123
-
124
- if ($cache === null) {
125
- $cache = apply_filters('fw_framework_directory_uri', get_template_directory_uri() . '/framework');
 
 
 
126
  }
127
 
128
- return $cache . $rel_path;
129
  }
130
  }
23
  * @return string
24
  */
25
  function fw_get_framework_customizations_dir_rel_path($append = '') {
26
+ try {
27
+ $dir = FW_Cache::get($cache_key = 'fw_customizations_dir_rel_path');
28
+ } catch (FW_Cache_Not_Found_Exception $e) {
29
+ FW_Cache::set(
30
+ $cache_key,
31
+ $dir = apply_filters('fw_framework_customizations_dir_rel_path', '/framework-customizations')
32
+ );
33
  }
34
 
35
+ return $dir . $append;
36
  }
37
 
38
  /** Child theme related functions */
74
  * @return string
75
  */
76
  function fw_get_template_customizations_directory($rel_path = '') {
77
+ try {
78
+ $dir = FW_Cache::get($cache_key = 'fw_template_customizations_dir');
79
+ } catch (FW_Cache_Not_Found_Exception $e) {
80
+ FW_Cache::set(
81
+ $cache_key,
82
+ $dir = get_template_directory() . fw_get_framework_customizations_dir_rel_path()
83
+ );
84
  }
85
 
86
+ return $dir . $rel_path;
87
  }
88
 
89
  /**
92
  * @return string
93
  */
94
  function fw_get_template_customizations_directory_uri($rel_path = '') {
95
+ try {
96
+ $dir = FW_Cache::get($cache_key = 'fw_template_customizations_dir_uri');
97
+ } catch (FW_Cache_Not_Found_Exception $e) {
98
+ FW_Cache::set(
99
+ $cache_key,
100
+ $dir = get_template_directory_uri() . fw_get_framework_customizations_dir_rel_path()
101
+ );
102
  }
103
 
104
+ return $dir . $rel_path;
105
  }
106
  }
107
 
113
  * @return string
114
  */
115
  function fw_get_framework_directory($rel_path = '') {
116
+ try {
117
+ $dir = FW_Cache::get($cache_key = 'fw_framework_dir');
118
+ } catch (FW_Cache_Not_Found_Exception $e) {
119
+ FW_Cache::set(
120
+ $cache_key,
121
+ $dir = apply_filters('fw_framework_directory', dirname(__FILE__))
122
+ );
123
  }
124
 
125
+ return $dir . $rel_path;
126
  }
127
 
128
  /**
131
  * @return string
132
  */
133
  function fw_get_framework_directory_uri($rel_path = '') {
134
+ try {
135
+ $dir = FW_Cache::get($cache_key = 'fw_framework_dir_uri');
136
+ } catch (FW_Cache_Not_Found_Exception $e) {
137
+ FW_Cache::set(
138
+ $cache_key,
139
+ $dir = apply_filters('fw_framework_directory_uri', get_template_directory_uri() . '/framework')
140
+ );
141
  }
142
 
143
+ return $dir . $rel_path;
144
  }
145
  }
framework/bootstrap.php CHANGED
@@ -24,6 +24,12 @@ if (defined('FW')) {
24
 
25
  include $fw_dir .'/bootstrap-helpers.php';
26
 
 
 
 
 
 
 
27
  /**
28
  * Load core
29
  */
@@ -41,9 +47,10 @@ if (defined('FW')) {
41
  'meta',
42
  'class-fw-access-key',
43
  'class-fw-dumper',
44
- 'general',
45
  'class-fw-wp-filesystem',
46
- 'class-fw-cache',
 
47
  'class-fw-form',
48
  'class-fw-request',
49
  'class-fw-session',
24
 
25
  include $fw_dir .'/bootstrap-helpers.php';
26
 
27
+ // these are required when fw() is executed below
28
+ {
29
+ require $fw_dir .'/helpers/general.php';
30
+ require $fw_dir .'/helpers/class-fw-cache.php';
31
+ }
32
+
33
  /**
34
  * Load core
35
  */
47
  'meta',
48
  'class-fw-access-key',
49
  'class-fw-dumper',
50
+ // 'general', // included below
51
  'class-fw-wp-filesystem',
52
+ // 'class-fw-cache', // included below
53
+ 'class-fw-file-cache',
54
  'class-fw-form',
55
  'class-fw-request',
56
  'class-fw-session',
framework/core/components/backend.php CHANGED
@@ -1587,15 +1587,21 @@ final class _FW_Component_Backend {
1587
  'limit_option_types' => false,
1588
  'limit_container_types' => false,
1589
  'limit_level' => 0,
1590
- 'info_wrapper' => true,
1591
  ) );
1592
 
1593
- foreach ( $collected as &$option ) {
1594
- if ($option['group'] === 'option') {
1595
- fw()->backend->option_type($option['option']['type'])->enqueue_static($option['id'], $option['option']);
1596
- } elseif ($option['group'] === 'container') {
1597
- fw()->backend->container_type($option['option']['type'])->enqueue_static($option['id'], $option['option']);
1598
- }
 
 
 
 
 
 
1599
  }
1600
  }
1601
 
@@ -1963,9 +1969,9 @@ final class _FW_Component_Backend {
1963
  * not existing container types will throw notices.
1964
  * To prevent that, extract and send it only options (without containers)
1965
  */
1966
- fw_collect_options($options_for_enqueue, $customizer_options);
1967
-
1968
- fw()->backend->enqueue_options_static($options_for_enqueue);
1969
 
1970
  unset($options_for_enqueue, $customizer_options);
1971
  }
@@ -1973,7 +1979,7 @@ final class _FW_Component_Backend {
1973
  wp_enqueue_script(
1974
  'fw-backend-customizer',
1975
  fw_get_framework_directory_uri( '/static/js/backend-customizer.js' ),
1976
- array( 'jquery', 'fw-events', 'backbone' ),
1977
  fw()->manifest->get_version(),
1978
  true
1979
  );
1587
  'limit_option_types' => false,
1588
  'limit_container_types' => false,
1589
  'limit_level' => 0,
1590
+ 'callback' => array(__CLASS__, '_callback_fw_collect_options_enqueue_static'),
1591
  ) );
1592
 
1593
+ unset($collected);
1594
+ }
1595
+
1596
+ /**
1597
+ * @internal
1598
+ * @param array $data
1599
+ */
1600
+ public static function _callback_fw_collect_options_enqueue_static($data) {
1601
+ if ($data['group'] === 'option') {
1602
+ fw()->backend->option_type($data['option']['type'])->enqueue_static($data['id'], $data['option']);
1603
+ } elseif ($data['group'] === 'container') {
1604
+ fw()->backend->container_type($data['option']['type'])->enqueue_static($data['id'], $data['option']);
1605
  }
1606
  }
1607
 
1969
  * not existing container types will throw notices.
1970
  * To prevent that, extract and send it only options (without containers)
1971
  */
1972
+ fw_collect_options($options_for_enqueue, $customizer_options, array(
1973
+ 'callback' => array(__CLASS__, '_callback_fw_collect_options_enqueue_static'),
1974
+ ));
1975
 
1976
  unset($options_for_enqueue, $customizer_options);
1977
  }
1979
  wp_enqueue_script(
1980
  'fw-backend-customizer',
1981
  fw_get_framework_directory_uri( '/static/js/backend-customizer.js' ),
1982
+ array( 'jquery', 'fw-events', 'backbone', 'fw-backend-options' ),
1983
  fw()->manifest->get_version(),
1984
  true
1985
  );
framework/core/components/extensions.php CHANGED
@@ -147,7 +147,13 @@ final class _FW_Component_Extensions
147
  $data['parent'] = null;
148
  }
149
 
150
- $dirs = glob($data['path'] .'/*', GLOB_ONLYDIR);
 
 
 
 
 
 
151
 
152
  if (empty($dirs)) {
153
  return;
@@ -306,7 +312,15 @@ final class _FW_Component_Extensions
306
  }
307
 
308
  foreach ($paths as $path => $uri) {
309
- if ($files = glob($path . $dir_rel_path .'/*.php')) {
 
 
 
 
 
 
 
 
310
  foreach ($files as $dir_file_path) {
311
  fw_include_file_isolated($dir_file_path);
312
  }
147
  $data['parent'] = null;
148
  }
149
 
150
+ try {
151
+ $dirs = FW_File_Cache::get($cache_key = 'core:ext:load:glob:'. $data['path']);
152
+ } catch (FW_File_Cache_Not_Found_Exception $e) {
153
+ $dirs = glob($data['path'] .'/*', GLOB_ONLYDIR);
154
+
155
+ FW_File_Cache::set($cache_key, $dirs);
156
+ }
157
 
158
  if (empty($dirs)) {
159
  return;
312
  }
313
 
314
  foreach ($paths as $path => $uri) {
315
+ try {
316
+ $files = FW_File_Cache::get($cache_key = 'core:ext:glob:inc-all-php:'. $extension->get_name() .':'. $path);
317
+ } catch (FW_File_Cache_Not_Found_Exception $e) {
318
+ $files = glob($path . $dir_rel_path .'/*.php');
319
+
320
+ FW_File_Cache::set($cache_key, $files);
321
+ }
322
+
323
+ if ($files) {
324
  foreach ($files as $dir_file_path) {
325
  fw_include_file_isolated($dir_file_path);
326
  }
framework/core/components/extensions/manager/available-extensions.php CHANGED
@@ -13,11 +13,13 @@ $extensions = array(
13
  'description' => __( "Let's you easily build countless pages with the help of the drag and drop visual page builder that comes with a lot of already created shortcodes.", 'fw' ),
14
  'thumbnail' => $thumbnails_uri . '/page-builder.jpg',
15
  'download' => array(
16
- 'github' => array(
17
- 'user_repo' => $github_account . '/Unyson-PageBuilder-Extension',
18
- ),
 
19
  ),
20
  ),
 
21
  'wp-shortcodes' => array(
22
  'display' => true,
23
  'parent' => 'shortcodes',
@@ -28,11 +30,13 @@ $extensions = array(
28
  ),
29
  'thumbnail' => $thumbnails_uri . '/wp-shortcodes.jpg',
30
  'download' => array(
31
- 'github' => array(
 
32
  'user_repo' => 'ThemeFuse/Unyson-WP-Shortcodes-Extension',
33
  ),
34
  ),
35
  ),
 
36
  'backups' => array(
37
  'display' => true,
38
  'parent' => null,
@@ -40,11 +44,13 @@ $extensions = array(
40
  'description' => __( 'This extension lets you create an automated backup schedule, import demo content or even create a demo content archive for migration purposes.', 'fw' ),
41
  'thumbnail' => $thumbnails_uri . '/backups.jpg',
42
  'download' => array(
43
- 'github' => array(
 
44
  'user_repo' => $github_account . '/Unyson-Backups-Extension',
45
  ),
46
  ),
47
  ),
 
48
  'sidebars' => array(
49
  'display' => true,
50
  'parent' => null,
@@ -52,11 +58,13 @@ $extensions = array(
52
  'description' => __( 'Brings a new layer of customization freedom to your website by letting you add more than one sidebar to a page, or different sidebars on different pages.', 'fw' ),
53
  'thumbnail' => $thumbnails_uri . '/sidebars.jpg',
54
  'download' => array(
55
- 'github' => array(
 
56
  'user_repo' => $github_account . '/Unyson-Sidebars-Extension',
57
  ),
58
  ),
59
  ),
 
60
  'slider' => array(
61
  'display' => true,
62
  'parent' => 'media',
@@ -64,11 +72,13 @@ $extensions = array(
64
  'description' => __( 'Adds a sliders module to your website from where you\'ll be able to create different built in jQuery sliders for your homepage and rest of the pages.', 'fw' ),
65
  'thumbnail' => $thumbnails_uri . '/sliders.jpg',
66
  'download' => array(
67
- 'github' => array(
 
68
  'user_repo' => $github_account . '/Unyson-Sliders-Extension',
69
  ),
70
  ),
71
  ),
 
72
  'portfolio' => array(
73
  'display' => true,
74
  'parent' => null,
@@ -76,11 +86,13 @@ $extensions = array(
76
  'description' => __( 'This extension will add a fully fledged portfolio module that will let you display your projects using the built in portfolio pages.', 'fw' ),
77
  'thumbnail' => $thumbnails_uri . '/portfolio.jpg',
78
  'download' => array(
79
- 'github' => array(
 
80
  'user_repo' => $github_account . '/Unyson-Portfolio-Extension',
81
  ),
82
  ),
83
  ),
 
84
  'megamenu' => array(
85
  'display' => true,
86
  'parent' => null,
@@ -88,11 +100,13 @@ $extensions = array(
88
  'description' => __( 'The Mega Menu extension adds a user-friendly drop down menu that will let you easily create highly customized menu configurations.', 'fw' ),
89
  'thumbnail' => $thumbnails_uri . '/mega-menu.jpg',
90
  'download' => array(
91
- 'github' => array(
 
92
  'user_repo' => $github_account . '/Unyson-MegaMenu-Extension',
93
  ),
94
  ),
95
  ),
 
96
  'breadcrumbs' => array(
97
  'display' => true,
98
  'parent' => null,
@@ -100,11 +114,13 @@ $extensions = array(
100
  'description' => __( 'Creates a simplified navigation menu for the pages that can be placed anywhere in the theme. This will make navigating the website much easier.', 'fw' ),
101
  'thumbnail' => $thumbnails_uri . '/breadcrumbs.jpg',
102
  'download' => array(
103
- 'github' => array(
 
104
  'user_repo' => $github_account . '/Unyson-Breadcrumbs-Extension',
105
  ),
106
  ),
107
  ),
 
108
  'seo' => array(
109
  'display' => true,
110
  'parent' => null,
@@ -112,11 +128,13 @@ $extensions = array(
112
  'description' => __( 'This extension will enable you to have a fully optimized WordPress website by adding optimized meta titles, keywords and descriptions.', 'fw' ),
113
  'thumbnail' => $thumbnails_uri . '/seo.jpg',
114
  'download' => array(
115
- 'github' => array(
 
116
  'user_repo' => $github_account . '/Unyson-SEO-Extension',
117
  ),
118
  ),
119
  ),
 
120
  'events' => array(
121
  'display' => true,
122
  'parent' => null,
@@ -124,11 +142,13 @@ $extensions = array(
124
  'description' => __( 'This extension adds a fully fledged Events module to your theme. It comes with built in pages that contain a calendar where events can be added.', 'fw' ),
125
  'thumbnail' => $thumbnails_uri . '/events.jpg',
126
  'download' => array(
127
- 'github' => array(
 
128
  'user_repo' => $github_account . '/Unyson-Events-Extension',
129
  ),
130
  ),
131
  ),
 
132
  'analytics' => array(
133
  'display' => true,
134
  'parent' => null,
@@ -136,11 +156,13 @@ $extensions = array(
136
  'description' => __( 'Enables the possibility to add the Google Analytics tracking code that will let you get all the analytics about visitors, page views and more.', 'fw' ),
137
  'thumbnail' => $thumbnails_uri . '/analytics.jpg',
138
  'download' => array(
139
- 'github' => array(
 
140
  'user_repo' => $github_account . '/Unyson-Analytics-Extension',
141
  ),
142
  ),
143
  ),
 
144
  'feedback' => array(
145
  'display' => true,
146
  'parent' => null,
@@ -148,11 +170,13 @@ $extensions = array(
148
  'description' => __( 'Adds the possibility to leave feedback (comments, reviews and rating) about your products, articles, etc. This replaces the default comments system.', 'fw' ),
149
  'thumbnail' => $thumbnails_uri . '/feedback.jpg',
150
  'download' => array(
151
- 'github' => array(
 
152
  'user_repo' => $github_account . '/Unyson-Feedback-Extension',
153
  ),
154
  ),
155
  ),
 
156
  'learning' => array(
157
  'display' => true,
158
  'parent' => null,
@@ -160,11 +184,13 @@ $extensions = array(
160
  'description' => __( 'This extension adds a Learning module to your theme. Using this extension you can add courses, lessons and tests for your users to take.', 'fw' ),
161
  'thumbnail' => $thumbnails_uri . '/learning.jpg',
162
  'download' => array(
163
- 'github' => array(
 
164
  'user_repo' => $github_account . '/Unyson-Learning-Extension',
165
  ),
166
  ),
167
  ),
 
168
  'shortcodes' => array(
169
  'display' => false,
170
  'parent' => null,
@@ -172,11 +198,13 @@ $extensions = array(
172
  'description' => '',
173
  'thumbnail' => 'about:blank',
174
  'download' => array(
175
- 'github' => array(
 
176
  'user_repo' => $github_account . '/Unyson-Shortcodes-Extension',
177
  ),
178
  ),
179
  ),
 
180
  'builder' => array(
181
  'display' => false,
182
  'parent' => null,
@@ -184,11 +212,13 @@ $extensions = array(
184
  'description' => '',
185
  'thumbnail' => 'about:blank',
186
  'download' => array(
187
- 'github' => array(
 
188
  'user_repo' => $github_account . '/Unyson-Builder-Extension',
189
  ),
190
  ),
191
  ),
 
192
  'forms' => array(
193
  'display' => false,
194
  'parent' => null,
@@ -196,11 +226,13 @@ $extensions = array(
196
  'description' => __( 'This extension adds the possibility to create a contact form. Use the drag & drop form builder to create any contact form you\'ll ever want or need.', 'fw' ),
197
  'thumbnail' => $thumbnails_uri . '/forms.jpg',
198
  'download' => array(
199
- 'github' => array(
 
200
  'user_repo' => $github_account . '/Unyson-Forms-Extension',
201
  ),
202
  ),
203
  ),
 
204
  'mailer' => array(
205
  'display' => false,
206
  'parent' => null,
@@ -208,11 +240,13 @@ $extensions = array(
208
  'description' => __( 'This extension will let you set some global email options and it is used by other extensions (like Forms) to send emails.', 'fw' ),
209
  'thumbnail' => $thumbnails_uri . '/mailer.jpg',
210
  'download' => array(
211
- 'github' => array(
 
212
  'user_repo' => $github_account . '/Unyson-Mailer-Extension',
213
  ),
214
  ),
215
  ),
 
216
  'social' => array(
217
  'display' => true,
218
  'parent' => null,
@@ -220,11 +254,13 @@ $extensions = array(
220
  'description' => __( 'Use this extension to configure all your social related APIs. Other extensions will use the Social extension to connect to your social accounts.', 'fw' ),
221
  'thumbnail' => $thumbnails_uri . '/social.jpg',
222
  'download' => array(
223
- 'github' => array(
 
224
  'user_repo' => $github_account . '/Unyson-Social-Extension',
225
  ),
226
  ),
227
  ),
 
228
  'backup' => array(
229
  'display' => true,
230
  'parent' => null,
@@ -232,11 +268,13 @@ $extensions = array(
232
  'description' => __( 'This extension lets you set up daily, weekly or monthly backup schedule. You can choose between a full backup or a data base only backup.', 'fw' ),
233
  'thumbnail' => $thumbnails_uri . '/backup.jpg',
234
  'download' => array(
235
- 'github' => array(
 
236
  'user_repo' => $github_account . '/Unyson-Backup-Extension',
237
  ),
238
  ),
239
  ),
 
240
  'media' => array(
241
  'display' => false,
242
  'parent' => null,
@@ -244,11 +282,13 @@ $extensions = array(
244
  'description' => '',
245
  'thumbnail' => 'about:blank',
246
  'download' => array(
247
- 'github' => array(
 
248
  'user_repo' => $github_account . '/Unyson-Empty-Extension',
249
  ),
250
  ),
251
  ),
 
252
  'population-method' => array(
253
  'display' => false,
254
  'parent' => 'media',
@@ -256,11 +296,13 @@ $extensions = array(
256
  'description' => '',
257
  'thumbnail' => 'about:blank',
258
  'download' => array(
259
- 'github' => array(
 
260
  'user_repo' => $github_account . '/Unyson-PopulationMethods-Extension',
261
  ),
262
  ),
263
  ),
 
264
  'styling' => array(
265
  'display' => true,
266
  'parent' => null,
@@ -268,11 +310,13 @@ $extensions = array(
268
  'description' => __( 'This extension lets you control the website visual style. Starting from predefined styles to changing specific fonts and colors across the website.', 'fw' ),
269
  'thumbnail' => $thumbnails_uri . '/styling.jpg',
270
  'download' => array(
271
- 'github' => array(
 
272
  'user_repo' => $github_account . '/Unyson-Styling-Extension',
273
  ),
274
  ),
275
  ),
 
276
  'translation' => array(
277
  'display' => true,
278
  'parent' => null,
@@ -280,9 +324,11 @@ $extensions = array(
280
  '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' ),
281
  'thumbnail' => $thumbnails_uri . '/translation.jpg',
282
  'download' => array(
283
- 'github' => array(
 
284
  'user_repo' => $github_account . '/Unyson-Translation-Extension',
285
- ),
286
  ),
287
  ),
288
  );
 
13
  'description' => __( "Let's you easily build countless pages with the help of the drag and drop visual page builder that comes with a lot of already created shortcodes.", 'fw' ),
14
  'thumbnail' => $thumbnails_uri . '/page-builder.jpg',
15
  'download' => array(
16
+ 'source' => 'github',
17
+ 'opts' => array(
18
+ 'user_repo' => $github_account . '/Unyson-PageBuilder-Extension'
19
+ )
20
  ),
21
  ),
22
+
23
  'wp-shortcodes' => array(
24
  'display' => true,
25
  'parent' => 'shortcodes',
30
  ),
31
  'thumbnail' => $thumbnails_uri . '/wp-shortcodes.jpg',
32
  'download' => array(
33
+ 'source' => 'github',
34
+ 'opts' => array(
35
  'user_repo' => 'ThemeFuse/Unyson-WP-Shortcodes-Extension',
36
  ),
37
  ),
38
  ),
39
+
40
  'backups' => array(
41
  'display' => true,
42
  'parent' => null,
44
  'description' => __( 'This extension lets you create an automated backup schedule, import demo content or even create a demo content archive for migration purposes.', 'fw' ),
45
  'thumbnail' => $thumbnails_uri . '/backups.jpg',
46
  'download' => array(
47
+ 'source' => 'github',
48
+ 'opts' => array(
49
  'user_repo' => $github_account . '/Unyson-Backups-Extension',
50
  ),
51
  ),
52
  ),
53
+
54
  'sidebars' => array(
55
  'display' => true,
56
  'parent' => null,
58
  'description' => __( 'Brings a new layer of customization freedom to your website by letting you add more than one sidebar to a page, or different sidebars on different pages.', 'fw' ),
59
  'thumbnail' => $thumbnails_uri . '/sidebars.jpg',
60
  'download' => array(
61
+ 'source' => 'github',
62
+ 'opts' => array(
63
  'user_repo' => $github_account . '/Unyson-Sidebars-Extension',
64
  ),
65
  ),
66
  ),
67
+
68
  'slider' => array(
69
  'display' => true,
70
  'parent' => 'media',
72
  'description' => __( 'Adds a sliders module to your website from where you\'ll be able to create different built in jQuery sliders for your homepage and rest of the pages.', 'fw' ),
73
  'thumbnail' => $thumbnails_uri . '/sliders.jpg',
74
  'download' => array(
75
+ 'source' => 'github',
76
+ 'opts' => array(
77
  'user_repo' => $github_account . '/Unyson-Sliders-Extension',
78
  ),
79
  ),
80
  ),
81
+
82
  'portfolio' => array(
83
  'display' => true,
84
  'parent' => null,
86
  'description' => __( 'This extension will add a fully fledged portfolio module that will let you display your projects using the built in portfolio pages.', 'fw' ),
87
  'thumbnail' => $thumbnails_uri . '/portfolio.jpg',
88
  'download' => array(
89
+ 'source' => 'github',
90
+ 'opts' => array(
91
  'user_repo' => $github_account . '/Unyson-Portfolio-Extension',
92
  ),
93
  ),
94
  ),
95
+
96
  'megamenu' => array(
97
  'display' => true,
98
  'parent' => null,
100
  'description' => __( 'The Mega Menu extension adds a user-friendly drop down menu that will let you easily create highly customized menu configurations.', 'fw' ),
101
  'thumbnail' => $thumbnails_uri . '/mega-menu.jpg',
102
  'download' => array(
103
+ 'source' => 'github',
104
+ 'opts' => array(
105
  'user_repo' => $github_account . '/Unyson-MegaMenu-Extension',
106
  ),
107
  ),
108
  ),
109
+
110
  'breadcrumbs' => array(
111
  'display' => true,
112
  'parent' => null,
114
  'description' => __( 'Creates a simplified navigation menu for the pages that can be placed anywhere in the theme. This will make navigating the website much easier.', 'fw' ),
115
  'thumbnail' => $thumbnails_uri . '/breadcrumbs.jpg',
116
  'download' => array(
117
+ 'source' => 'github',
118
+ 'opts' => array(
119
  'user_repo' => $github_account . '/Unyson-Breadcrumbs-Extension',
120
  ),
121
  ),
122
  ),
123
+
124
  'seo' => array(
125
  'display' => true,
126
  'parent' => null,
128
  'description' => __( 'This extension will enable you to have a fully optimized WordPress website by adding optimized meta titles, keywords and descriptions.', 'fw' ),
129
  'thumbnail' => $thumbnails_uri . '/seo.jpg',
130
  'download' => array(
131
+ 'source' => 'github',
132
+ 'opts' => array(
133
  'user_repo' => $github_account . '/Unyson-SEO-Extension',
134
  ),
135
  ),
136
  ),
137
+
138
  'events' => array(
139
  'display' => true,
140
  'parent' => null,
142
  'description' => __( 'This extension adds a fully fledged Events module to your theme. It comes with built in pages that contain a calendar where events can be added.', 'fw' ),
143
  'thumbnail' => $thumbnails_uri . '/events.jpg',
144
  'download' => array(
145
+ 'source' => 'github',
146
+ 'opts' => array(
147
  'user_repo' => $github_account . '/Unyson-Events-Extension',
148
  ),
149
  ),
150
  ),
151
+
152
  'analytics' => array(
153
  'display' => true,
154
  'parent' => null,
156
  'description' => __( 'Enables the possibility to add the Google Analytics tracking code that will let you get all the analytics about visitors, page views and more.', 'fw' ),
157
  'thumbnail' => $thumbnails_uri . '/analytics.jpg',
158
  'download' => array(
159
+ 'source' => 'github',
160
+ 'opts' => array(
161
  'user_repo' => $github_account . '/Unyson-Analytics-Extension',
162
  ),
163
  ),
164
  ),
165
+
166
  'feedback' => array(
167
  'display' => true,
168
  'parent' => null,
170
  'description' => __( 'Adds the possibility to leave feedback (comments, reviews and rating) about your products, articles, etc. This replaces the default comments system.', 'fw' ),
171
  'thumbnail' => $thumbnails_uri . '/feedback.jpg',
172
  'download' => array(
173
+ 'source' => 'github',
174
+ 'opts' => array(
175
  'user_repo' => $github_account . '/Unyson-Feedback-Extension',
176
  ),
177
  ),
178
  ),
179
+
180
  'learning' => array(
181
  'display' => true,
182
  'parent' => null,
184
  'description' => __( 'This extension adds a Learning module to your theme. Using this extension you can add courses, lessons and tests for your users to take.', 'fw' ),
185
  'thumbnail' => $thumbnails_uri . '/learning.jpg',
186
  'download' => array(
187
+ 'source' => 'github',
188
+ 'opts' => array(
189
  'user_repo' => $github_account . '/Unyson-Learning-Extension',
190
  ),
191
  ),
192
  ),
193
+
194
  'shortcodes' => array(
195
  'display' => false,
196
  'parent' => null,
198
  'description' => '',
199
  'thumbnail' => 'about:blank',
200
  'download' => array(
201
+ 'source' => 'github',
202
+ 'opts' => array(
203
  'user_repo' => $github_account . '/Unyson-Shortcodes-Extension',
204
  ),
205
  ),
206
  ),
207
+
208
  'builder' => array(
209
  'display' => false,
210
  'parent' => null,
212
  'description' => '',
213
  'thumbnail' => 'about:blank',
214
  'download' => array(
215
+ 'source' => 'github',
216
+ 'opts' => array(
217
  'user_repo' => $github_account . '/Unyson-Builder-Extension',
218
  ),
219
  ),
220
  ),
221
+
222
  'forms' => array(
223
  'display' => false,
224
  'parent' => null,
226
  'description' => __( 'This extension adds the possibility to create a contact form. Use the drag & drop form builder to create any contact form you\'ll ever want or need.', 'fw' ),
227
  'thumbnail' => $thumbnails_uri . '/forms.jpg',
228
  'download' => array(
229
+ 'source' => 'github',
230
+ 'opts' => array(
231
  'user_repo' => $github_account . '/Unyson-Forms-Extension',
232
  ),
233
  ),
234
  ),
235
+
236
  'mailer' => array(
237
  'display' => false,
238
  'parent' => null,
240
  'description' => __( 'This extension will let you set some global email options and it is used by other extensions (like Forms) to send emails.', 'fw' ),
241
  'thumbnail' => $thumbnails_uri . '/mailer.jpg',
242
  'download' => array(
243
+ 'source' => 'github',
244
+ 'opts' => array(
245
  'user_repo' => $github_account . '/Unyson-Mailer-Extension',
246
  ),
247
  ),
248
  ),
249
+
250
  'social' => array(
251
  'display' => true,
252
  'parent' => null,
254
  'description' => __( 'Use this extension to configure all your social related APIs. Other extensions will use the Social extension to connect to your social accounts.', 'fw' ),
255
  'thumbnail' => $thumbnails_uri . '/social.jpg',
256
  'download' => array(
257
+ 'source' => 'github',
258
+ 'opts' => array(
259
  'user_repo' => $github_account . '/Unyson-Social-Extension',
260
  ),
261
  ),
262
  ),
263
+
264
  'backup' => array(
265
  'display' => true,
266
  'parent' => null,
268
  'description' => __( 'This extension lets you set up daily, weekly or monthly backup schedule. You can choose between a full backup or a data base only backup.', 'fw' ),
269
  'thumbnail' => $thumbnails_uri . '/backup.jpg',
270
  'download' => array(
271
+ 'source' => 'github',
272
+ 'opts' => array(
273
  'user_repo' => $github_account . '/Unyson-Backup-Extension',
274
  ),
275
  ),
276
  ),
277
+
278
  'media' => array(
279
  'display' => false,
280
  'parent' => null,
282
  'description' => '',
283
  'thumbnail' => 'about:blank',
284
  'download' => array(
285
+ 'source' => 'github',
286
+ 'opts' => array(
287
  'user_repo' => $github_account . '/Unyson-Empty-Extension',
288
  ),
289
  ),
290
  ),
291
+
292
  'population-method' => array(
293
  'display' => false,
294
  'parent' => 'media',
296
  'description' => '',
297
  'thumbnail' => 'about:blank',
298
  'download' => array(
299
+ 'source' => 'github',
300
+ 'opts' => array(
301
  'user_repo' => $github_account . '/Unyson-PopulationMethods-Extension',
302
  ),
303
  ),
304
  ),
305
+
306
  'styling' => array(
307
  'display' => true,
308
  'parent' => null,
310
  'description' => __( 'This extension lets you control the website visual style. Starting from predefined styles to changing specific fonts and colors across the website.', 'fw' ),
311
  'thumbnail' => $thumbnails_uri . '/styling.jpg',
312
  'download' => array(
313
+ 'source' => 'github',
314
+ 'opts' => array(
315
  'user_repo' => $github_account . '/Unyson-Styling-Extension',
316
  ),
317
  ),
318
  ),
319
+
320
  'translation' => array(
321
  'display' => true,
322
  'parent' => null,
324
  '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' ),
325
  'thumbnail' => $thumbnails_uri . '/translation.jpg',
326
  'download' => array(
327
+ 'source' => 'github',
328
+ 'opts' => array(
329
  'user_repo' => $github_account . '/Unyson-Translation-Extension',
330
+ )
331
  ),
332
  ),
333
  );
334
+
framework/core/components/extensions/manager/class--fw-extensions-manager.php CHANGED
@@ -21,10 +21,21 @@ final class _FW_Extensions_Manager
21
  'standalone' => false,
22
  );
23
 
24
- private $download_timeout = 300;
25
-
26
  private $default_thumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2PUsHf9DwAC8AGtfm5YCAAAAABJRU5ErkJgggAA';
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  public function __construct()
29
  {
30
  // In any case/permission, make sure to not miss the plugin update actions to prevent extensions delete
@@ -33,6 +44,17 @@ final class _FW_Extensions_Manager
33
  add_action('fw_plugin_post_update', array($this, '_action_plugin_post_update'));
34
  }
35
 
 
 
 
 
 
 
 
 
 
 
 
36
  if (!is_admin()) {
37
  return;
38
  }
@@ -191,9 +213,58 @@ final class _FW_Extensions_Manager
191
 
192
  return FW_Cache::get($cache_key);
193
  } catch (FW_Cache_Not_Found_Exception $e) {
194
- $vars = fw_get_variables_from_file( dirname( __FILE__ ) . '/available-extensions.php', array(
195
- 'extensions' => array()
196
- ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
 
198
  {
199
  $installed_extensions = $this->get_installed_extensions();
@@ -201,7 +272,7 @@ final class _FW_Extensions_Manager
201
 
202
  if (isset($installed_extensions['backup'])) {
203
  // make sure only Backup or Backups can be installed
204
- unset($vars['extensions']['backups']);
205
  }
206
 
207
  foreach (
@@ -213,14 +284,14 @@ final class _FW_Extensions_Manager
213
  &&
214
  !isset($installed_extensions[$obsolete_extension])
215
  ) {
216
- unset($vars['extensions'][$obsolete_extension]);
217
  }
218
  }
219
  }
220
 
221
- FW_Cache::set($cache_key, $vars['extensions']);
222
 
223
- return $vars['extensions'];
224
  }
225
  }
226
 
@@ -816,7 +887,15 @@ final class _FW_Extensions_Manager
816
  'display' => isset($ext_data['display'])
817
  ? $ext_data['display']
818
  : $this->manifest_default_values['display'],
 
819
  );
 
 
 
 
 
 
 
820
  }
821
 
822
  foreach (fw()->theme->manifest->get('supported_extensions', array()) as $required_ext_name => $required_ext_data) {
@@ -928,6 +1007,7 @@ final class _FW_Extensions_Manager
928
  if ($_SERVER['REQUEST_METHOD'] === 'POST') {
929
  if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
930
  $skin->error(__('Invalid nonce.', 'fw'));
 
931
  }
932
 
933
  if (!FW_WP_Filesystem::request_access(
@@ -1142,14 +1222,22 @@ final class _FW_Extensions_Manager
1142
  * Install parent extensions and the extension
1143
  */
1144
  {
1145
- $current_extension_path = fw_get_framework_directory();
 
 
 
 
1146
 
1147
  foreach ($parents as $parent_extension_name) {
1148
  $current_extension_path .= '/extensions/'. $parent_extension_name;
 
 
 
 
 
1149
 
1150
  if (isset($installed_extensions[$parent_extension_name])) {
1151
- // skip already installed extensions
1152
- continue;
1153
  }
1154
 
1155
  if ($verbose) {
@@ -1210,7 +1298,9 @@ final class _FW_Extensions_Manager
1210
 
1211
  $merge_result = $this->merge_extension(
1212
  $wp_fw_downloaded_dir,
1213
- FW_WP_Filesystem::real_path_to_filesystem_path($current_extension_path)
 
 
1214
  );
1215
 
1216
  if (is_wp_error($merge_result)) {
@@ -1425,6 +1515,7 @@ final class _FW_Extensions_Manager
1425
  if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1426
  if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1427
  $skin->error(__('Invalid nonce.', 'fw'));
 
1428
  }
1429
 
1430
  if (!FW_WP_Filesystem::request_access(
@@ -2352,7 +2443,7 @@ final class _FW_Extensions_Manager
2352
  /**
2353
  * Download an extension
2354
  *
2355
- * global $wp_filesystem; must me initialized
2356
  *
2357
  * @param string $extension_name
2358
  * @param array $data Extension data from the "available extensions" array
@@ -2362,6 +2453,7 @@ final class _FW_Extensions_Manager
2362
  {
2363
  $wp_error_id = 'fw_extension_download';
2364
 
 
2365
  if (empty($data['download'])) {
2366
  return new WP_Error(
2367
  $wp_error_id,
@@ -2394,220 +2486,103 @@ final class _FW_Extensions_Manager
2394
  }
2395
  }
2396
 
2397
- $theme_ext_requirements = fw()->theme->manifest->get('requirements/extensions');
2398
-
2399
- foreach ($data['download'] as $source => $source_data) {
2400
- switch ($source) {
2401
- case 'github':
2402
- if (empty($source_data['user_repo'])) {
2403
- return new WP_Error(
2404
- $wp_error_id,
2405
- sprintf(__('"%s" extension github source "user_repo" parameter is required', 'fw'), $this->get_extension_title($extension_name))
2406
- );
2407
- }
2408
-
2409
- {
2410
- $transient_name = 'fw_ext_mngr_gh_dl';
2411
- $transient_ttl = HOUR_IN_SECONDS;
2412
 
2413
- $cache = get_site_transient($transient_name);
2414
 
2415
- if ($cache === false) {
2416
- $cache = array();
2417
- }
2418
- }
2419
 
2420
- if (isset($cache[ $source_data['user_repo'] ])) {
2421
- $download_link = $cache[ $source_data['user_repo'] ]['zipball_url'];
2422
- } else {
2423
- $http = new WP_Http();
2424
-
2425
- if (
2426
- isset($theme_ext_requirements[$extension_name])
2427
- &&
2428
- isset($theme_ext_requirements[$extension_name]['max_version'])
2429
- ) {
2430
- $tag = 'tags/v'. $theme_ext_requirements[$extension_name]['max_version'];
2431
- } else {
2432
- $tag = 'latest';
2433
- }
2434
-
2435
- $response = $http->get(
2436
- apply_filters('fw_github_api_url', 'https://api.github.com')
2437
- . '/repos/'. $source_data['user_repo'] .'/releases/'. $tag
2438
- );
2439
-
2440
- unset($http);
2441
-
2442
- $response_code = intval(wp_remote_retrieve_response_code($response));
2443
-
2444
- if ($response_code !== 200) {
2445
- if ($response_code === 403) {
2446
- if ($json_response = json_decode($response['body'], true)) {
2447
- return new WP_Error(
2448
- $wp_error_id,
2449
- __('Github error:', 'fw') .' '. $json_response['message']
2450
- );
2451
- } else {
2452
- return new WP_Error(
2453
- $wp_error_id,
2454
- sprintf(
2455
- __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
2456
- $source_data['user_repo'], $response_code
2457
- )
2458
- );
2459
- }
2460
- } elseif ($response_code) {
2461
- return new WP_Error(
2462
- $wp_error_id,
2463
- sprintf(
2464
- __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
2465
- $source_data['user_repo'], $response_code
2466
- )
2467
- );
2468
- } elseif (is_wp_error($response)) {
2469
- return new WP_Error(
2470
- $wp_error_id,
2471
- sprintf(
2472
- __( 'Failed to access Github repository "%s" releases. (%s)', 'fw' ),
2473
- $source_data['user_repo'], $response->get_error_message()
2474
- )
2475
- );
2476
- } else {
2477
- return new WP_Error(
2478
- $wp_error_id,
2479
- sprintf(
2480
- __( 'Failed to access Github repository "%s" releases.', 'fw' ),
2481
- $source_data['user_repo']
2482
- )
2483
- );
2484
- }
2485
- }
2486
-
2487
- $release = json_decode($response['body'], true);
2488
-
2489
- unset($response);
2490
-
2491
- if (empty($release)) {
2492
- return new WP_Error(
2493
- $wp_error_id,
2494
- sprintf(
2495
- __('"%s" extension github repository "%s" has no releases.', 'fw'),
2496
- $this->get_extension_title($extension_name), $source_data['user_repo']
2497
- )
2498
- );
2499
- }
2500
 
2501
- {
2502
- $cache[ $source_data['user_repo'] ] = array(
2503
- 'zipball_url' => 'https://github.com/'. $source_data['user_repo'] .'/archive/'. $release['tag_name'] .'.zip',
2504
- 'tag_name' => $release['tag_name']
2505
- );
2506
 
2507
- set_site_transient($transient_name, $cache, $transient_ttl);
2508
- }
 
 
 
 
2509
 
2510
- $download_link = $cache[ $source_data['user_repo'] ]['zipball_url'];
 
 
 
 
 
 
 
 
2511
 
2512
- unset($release);
2513
- }
 
2514
 
2515
- {
2516
- $http = new WP_Http();
2517
-
2518
- $response = $http->request($download_link, array(
2519
- 'timeout' => $this->download_timeout,
2520
- ));
2521
-
2522
- unset($http);
2523
-
2524
- if (($response_code = intval(wp_remote_retrieve_response_code($response))) !== 200) {
2525
- if ($response_code) {
2526
- return new WP_Error(
2527
- $wp_error_id,
2528
- sprintf( __( 'Cannot download the "%s" extension zip. (Response code: %d)', 'fw' ),
2529
- $this->get_extension_title( $extension_name ), $response_code
2530
- )
2531
- );
2532
- } elseif (is_wp_error($response)) {
2533
- return new WP_Error(
2534
- $wp_error_id,
2535
- sprintf( __( 'Cannot download the "%s" extension zip. %s', 'fw' ),
2536
- $this->get_extension_title( $extension_name ),
2537
- $response->get_error_message()
2538
- )
2539
- );
2540
- } else {
2541
- return new WP_Error(
2542
- $wp_error_id,
2543
- sprintf( __( 'Cannot download the "%s" extension zip.', 'fw' ),
2544
- $this->get_extension_title( $extension_name )
2545
- )
2546
- );
2547
- }
2548
- }
2549
 
2550
- $zip_path = $wp_fs_tmp_dir .'/temp.zip';
2551
 
2552
- // save zip to file
2553
- if (!$wp_filesystem->put_contents($zip_path, $response['body'])) {
2554
- return new WP_Error(
2555
- $wp_error_id,
2556
- sprintf(__('Cannot save the "%s" extension zip.', 'fw'), $this->get_extension_title($extension_name))
2557
- );
2558
- }
2559
 
2560
- unset($response);
 
 
 
 
 
2561
 
2562
- $unzip_result = unzip_file(
2563
- FW_WP_Filesystem::filesystem_path_to_real_path($zip_path),
2564
- $wp_fs_tmp_dir
2565
- );
2566
 
2567
- if (is_wp_error($unzip_result)) {
2568
- return $unzip_result;
2569
- }
 
2570
 
2571
- // remove zip file
2572
- if (!$wp_filesystem->delete($zip_path, false, 'f')) {
2573
- return new WP_Error(
2574
- $wp_error_id,
2575
- sprintf(__('Cannot remove the "%s" extension downloaded zip.', 'fw'), $this->get_extension_title($extension_name))
2576
- );
2577
- }
2578
 
2579
- $unzipped_dir_files = $wp_filesystem->dirlist($wp_fs_tmp_dir);
 
 
 
 
 
 
2580
 
2581
- if (!$unzipped_dir_files) {
2582
- return new WP_Error(
2583
- $wp_error_id,
2584
- __('Cannot access the unzipped directory files.', 'fw')
2585
- );
2586
- }
2587
 
2588
- /**
2589
- * get first found directory
2590
- * (if everything worked well, there should be only one directory)
2591
- */
2592
- foreach ($unzipped_dir_files as $file) {
2593
- if ($file['type'] == 'd') {
2594
- return $wp_fs_tmp_dir .'/'. $file['name'];
2595
- }
2596
- }
2597
 
2598
- return new WP_Error(
2599
- $wp_error_id,
2600
- sprintf(__('The unzipped "%s" extension directory not found.', 'fw'), $this->get_extension_title($extension_name))
2601
- );
2602
- }
2603
- break;
2604
- default:
2605
- return new WP_Error(
2606
- $wp_error_id,
2607
- sprintf(__('Unknown "%s" extension download source "%s"', 'fw'), $this->get_extension_title($extension_name), $source)
2608
- );
2609
  }
2610
  }
 
 
 
 
 
2611
  }
2612
 
2613
  /**
@@ -2625,18 +2600,20 @@ final class _FW_Extensions_Manager
2625
 
2626
  $wp_error_id = 'fw_extensions_merge';
2627
 
2628
- $source_files = $wp_filesystem->dirlist($source_wp_fs_dir);
 
 
2629
 
2630
- if ($source_files === false) {
2631
- return new WP_Error(
2632
- $wp_error_id,
2633
- sprintf(__('Cannot read directory "%s".', 'fw'), $source_wp_fs_dir)
2634
- );
2635
- }
2636
 
2637
- if (empty($source_files)) {
2638
- // directory is empty, nothing to move
2639
- return;
2640
  }
2641
 
2642
  /**
@@ -2654,18 +2631,42 @@ final class _FW_Extensions_Manager
2654
  }
2655
 
2656
  if (!empty($destination_files)) {
2657
- // the directory contains some files, delete everything
2658
- foreach ($destination_files as $file) {
2659
- if ($file['name'] === 'extensions' && $file['type'] === 'd') {
2660
- // do not touch the extensions/ directory
2661
- continue;
2662
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2663
 
2664
- if (!$wp_filesystem->delete($destination_wp_fs_dir .'/'. $file['name'], true, $file['type'])) {
2665
- return new WP_Error(
2666
- $wp_error_id,
2667
- sprintf(__('Cannot delete "%s".', 'fw'), $destination_wp_fs_dir .'/'. $file['name'])
2668
- );
 
 
 
 
 
 
 
 
2669
  }
2670
  }
2671
 
@@ -2680,28 +2681,30 @@ final class _FW_Extensions_Manager
2680
  }
2681
  }
2682
 
2683
- $has_sub_extensions = false;
 
 
2684
 
2685
- foreach ($source_files as $file) {
2686
- if ($file['name'] === 'extensions' && $file['type'] === 'd') {
2687
- // do not touch the extensions/ directory
2688
- $has_sub_extensions = true;
2689
- continue;
2690
- }
2691
 
2692
- if (!$wp_filesystem->move($source_wp_fs_dir .'/'. $file['name'], $destination_wp_fs_dir .'/'. $file['name'])) {
2693
- return new WP_Error(
2694
- $wp_error_id,
2695
- sprintf(
2696
- __('Cannot move "%s" to "%s".', 'fw'),
2697
- $source_wp_fs_dir .'/'. $file['name'],
2698
- $destination_wp_fs_dir .'/'. $file['name']
2699
- )
2700
- );
 
2701
  }
2702
- }
2703
 
2704
- unset($source_files);
 
2705
 
2706
  if (!$has_sub_extensions) {
2707
  return;
@@ -2742,6 +2745,13 @@ final class _FW_Extensions_Manager
2742
  {
2743
  $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
2744
 
 
 
 
 
 
 
 
2745
  if (empty($supported_extensions)) {
2746
  return array();
2747
  }
@@ -3194,6 +3204,9 @@ final class _FW_Extensions_Manager
3194
  return $extensions;
3195
  }
3196
 
 
 
 
3197
  public function _action_admin_notices() {
3198
  /**
3199
  * In v2.4.12 was done a terrible mistake https://github.com/ThemeFuse/Unyson-Extensions-Approval/issues/160
@@ -3216,4 +3229,226 @@ final class _FW_Extensions_Manager
3216
  , '</p></div>';
3217
  }
3218
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3219
  }
21
  'standalone' => false,
22
  );
23
 
 
 
24
  private $default_thumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2PUsHf9DwAC8AGtfm5YCAAAAABJRU5ErkJgggAA';
25
 
26
+ /**
27
+ * @var FW_Access_Key
28
+ */
29
+ private static $access_key;
30
+
31
+ private static function get_access_key() {
32
+ if (!self::$access_key) {
33
+ self::$access_key = new FW_Access_Key('fw_ext_manager');
34
+ }
35
+
36
+ return self::$access_key;
37
+ }
38
+
39
  public function __construct()
40
  {
41
  // In any case/permission, make sure to not miss the plugin update actions to prevent extensions delete
44
  add_action('fw_plugin_post_update', array($this, '_action_plugin_post_update'));
45
  }
46
 
47
+ // Preserve {theme}/framework-customizations/theme/available-extensions.php
48
+ {
49
+ add_filter('upgrader_pre_install', array($this, '_filter_theme_available_extensions_copy'), 999, 2);
50
+
51
+ /**
52
+ * Must be executed after
53
+ * https://github.com/WordPress/WordPress/blob/4.6/wp-admin/includes/class-theme-upgrader.php#L204-L205
54
+ */
55
+ add_action('upgrader_process_complete', array($this, '_action_theme_available_extensions_restore'), 999, 2);
56
+ }
57
+
58
  if (!is_admin()) {
59
  return;
60
  }
213
 
214
  return FW_Cache::get($cache_key);
215
  } catch (FW_Cache_Not_Found_Exception $e) {
216
+ $extensions = fw_get_variables_from_file(
217
+ dirname( __FILE__ ) . '/available-extensions.php',
218
+ array( 'extensions' => array() )
219
+ );
220
+ $extensions = $extensions['extensions'];
221
+
222
+ // Allow theme to register available extensions
223
+ if (file_exists(
224
+ $theme_available_ext_file = fw_fix_path(get_template_directory())
225
+ . fw_get_framework_customizations_dir_rel_path( '/theme/available-extensions.php' )
226
+ )) {
227
+ require_once dirname( __FILE__ ) . '/includes/available-ext/class--fw-available-extensions-register.php';
228
+ require_once dirname( __FILE__ ) . '/includes/available-ext/class-fw-available-extension.php';
229
+
230
+ $register = new _FW_Available_Extensions_Register(self::get_access_key()->get_key());
231
+
232
+ /**
233
+ * Usage:
234
+ * Create {theme}/framework-customizations/theme/available-extensions.php with the following contents:
235
+ * $extension = new FW_Available_Extension();
236
+ * $extension->set_...();
237
+ * $register->register($extension);
238
+ */
239
+ fw_get_variables_from_file($theme_available_ext_file, array(), array('register' => $register));
240
+
241
+ foreach ($register->_get_types(self::$access_key) as $extension) {
242
+ /** @var FW_Available_Extension $extension */
243
+ if (isset($extensions[ $extension->get_name() ])) {
244
+ trigger_error(
245
+ 'Overwriting default extension "'. $extension->get_name() .'" is not allowed',
246
+ E_USER_WARNING
247
+ );
248
+ continue;
249
+ } elseif (!$extension->is_valid()) {
250
+ trigger_error(
251
+ 'Theme extension "'. $extension->get_name() .'" is not valid',
252
+ E_USER_WARNING
253
+ );
254
+ continue;
255
+ } else {
256
+ $extensions[ $extension->get_name() ] = array(
257
+ 'theme' => true, // Registered by theme
258
+ 'display' => $extension->get_display(),
259
+ 'parent' => $extension->get_parent(),
260
+ 'name' => $extension->get_title(),
261
+ 'description' => $extension->get_description(),
262
+ 'thumbnail' => $extension->get_thumbnail(),
263
+ 'download' => $extension->get_download_source(),
264
+ );
265
+ }
266
+ }
267
+ }
268
 
269
  {
270
  $installed_extensions = $this->get_installed_extensions();
272
 
273
  if (isset($installed_extensions['backup'])) {
274
  // make sure only Backup or Backups can be installed
275
+ unset($extensions['backups']);
276
  }
277
 
278
  foreach (
284
  &&
285
  !isset($installed_extensions[$obsolete_extension])
286
  ) {
287
+ unset($extensions[$obsolete_extension]);
288
  }
289
  }
290
  }
291
 
292
+ FW_Cache::set($cache_key, $extensions);
293
 
294
+ return $extensions;
295
  }
296
  }
297
 
887
  'display' => isset($ext_data['display'])
888
  ? $ext_data['display']
889
  : $this->manifest_default_values['display'],
890
+ 'theme' => isset($ext_data['theme']) && $ext_data['theme'],
891
  );
892
+
893
+ if ($lists['available'][$ext_name]['theme']) {
894
+ $lists['supported'][$ext_name] = array(
895
+ 'name' => $lists['available'][$ext_name]['name'],
896
+ 'description' => $lists['available'][$ext_name]['description'],
897
+ );
898
+ }
899
  }
900
 
901
  foreach (fw()->theme->manifest->get('supported_extensions', array()) as $required_ext_name => $required_ext_data) {
1007
  if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1008
  if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1009
  $skin->error(__('Invalid nonce.', 'fw'));
1010
+ break;
1011
  }
1012
 
1013
  if (!FW_WP_Filesystem::request_access(
1222
  * Install parent extensions and the extension
1223
  */
1224
  {
1225
+ $destination_path = array(
1226
+ 'framework' => fw_get_framework_directory(),
1227
+ 'theme' => fw_fix_path(get_template_directory()) . fw_get_framework_customizations_dir_rel_path()
1228
+ );
1229
+ $current_extension_path = '';
1230
 
1231
  foreach ($parents as $parent_extension_name) {
1232
  $current_extension_path .= '/extensions/'. $parent_extension_name;
1233
+ $destination = (
1234
+ isset($available_extensions[$parent_extension_name]['theme'])
1235
+ &&
1236
+ $available_extensions[$parent_extension_name]['theme']
1237
+ ) ? 'theme' : 'framework';
1238
 
1239
  if (isset($installed_extensions[$parent_extension_name])) {
1240
+ continue; // skip already installed extensions
 
1241
  }
1242
 
1243
  if ($verbose) {
1298
 
1299
  $merge_result = $this->merge_extension(
1300
  $wp_fw_downloaded_dir,
1301
+ FW_WP_Filesystem::real_path_to_filesystem_path(
1302
+ $destination_path[$destination] . $current_extension_path
1303
+ )
1304
  );
1305
 
1306
  if (is_wp_error($merge_result)) {
1515
  if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1516
  if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1517
  $skin->error(__('Invalid nonce.', 'fw'));
1518
+ break;
1519
  }
1520
 
1521
  if (!FW_WP_Filesystem::request_access(
2443
  /**
2444
  * Download an extension
2445
  *
2446
+ * global $wp_filesystem; must be initialized
2447
  *
2448
  * @param string $extension_name
2449
  * @param array $data Extension data from the "available extensions" array
2453
  {
2454
  $wp_error_id = 'fw_extension_download';
2455
 
2456
+ // TODO: more checks for $data['download']
2457
  if (empty($data['download'])) {
2458
  return new WP_Error(
2459
  $wp_error_id,
2486
  }
2487
  }
2488
 
2489
+ require_once dirname( __FILE__ ) . '/includes/download-source/class--fw-ext-download-source.php';
2490
+ require_once dirname( __FILE__ ) . '/includes/download-source/class--fw-ext-download-source-register.php';
 
 
 
 
 
 
 
 
 
 
 
 
 
2491
 
2492
+ require_once dirname( __FILE__ ) . '/includes/download-source/types/init.php';
2493
 
2494
+ $register = new _FW_Ext_Download_Source_Register(self::get_access_key()->get_key());
 
 
 
2495
 
2496
+ /**
2497
+ * Register download sources for extensions.
2498
+ *
2499
+ * Usage:
2500
+ * $download_source = new FW_Ext_Download_Source();
2501
+ * $register->register($download_source);
2502
+ */
2503
+ do_action( 'fw_register_ext_download_sources', $register );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2504
 
2505
+ $download_source = $register->_get_type(
2506
+ self::get_access_key(), $data['download']['source']
2507
+ );
 
 
2508
 
2509
+ if (!$download_source) {
2510
+ return new WP_Error(
2511
+ 'invalid_dl_source',
2512
+ sprintf(__('Invalid download source: %s', 'fw'), $data['download']['source'])
2513
+ );
2514
+ }
2515
 
2516
+ return $this->perform_zip_download(
2517
+ $download_source,
2518
+ array_merge(array(
2519
+ 'extension_name' => $extension_name,
2520
+ 'extension_title' => $this->get_extension_title($extension_name)
2521
+ ), $data['download']['opts']),
2522
+ $wp_fs_tmp_dir
2523
+ );
2524
+ }
2525
 
2526
+ private function perform_zip_download(FW_Ext_Download_Source $download_source, array $opts, $wp_fs_tmp_dir)
2527
+ {
2528
+ $wp_error_id = 'fw_extension_download';
2529
 
2530
+ /** @var WP_Filesystem_Base $wp_filesystem */
2531
+ global $wp_filesystem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2532
 
2533
+ $zip_path = $wp_fs_tmp_dir . '/temp.zip';
2534
 
2535
+ $download_result = $download_source->download($opts, $zip_path);
 
 
 
 
 
 
2536
 
2537
+ /**
2538
+ * Pass further the error, if the service returned one.
2539
+ */
2540
+ if (is_wp_error($download_result)) {
2541
+ return $download_result;
2542
+ }
2543
 
2544
+ $extension_name = $opts['extension_name'];
 
 
 
2545
 
2546
+ $unzip_result = unzip_file(
2547
+ FW_WP_Filesystem::filesystem_path_to_real_path($zip_path),
2548
+ $wp_fs_tmp_dir
2549
+ );
2550
 
2551
+ if (is_wp_error($unzip_result)) {
2552
+ return $unzip_result;
2553
+ }
 
 
 
 
2554
 
2555
+ // remove zip file
2556
+ if (!$wp_filesystem->delete($zip_path, false, 'f')) {
2557
+ return new WP_Error(
2558
+ $wp_error_id,
2559
+ sprintf(__('Cannot remove the "%s" extension downloaded zip.', 'fw'), $this->get_extension_title($extension_name))
2560
+ );
2561
+ }
2562
 
2563
+ $unzipped_dir_files = $wp_filesystem->dirlist($wp_fs_tmp_dir);
 
 
 
 
 
2564
 
2565
+ if (!$unzipped_dir_files) {
2566
+ return new WP_Error(
2567
+ $wp_error_id,
2568
+ __('Cannot access the unzipped directory files.', 'fw')
2569
+ );
2570
+ }
 
 
 
2571
 
2572
+ /**
2573
+ * get first found directory
2574
+ * (if everything worked well, there should be only one directory)
2575
+ */
2576
+ foreach ($unzipped_dir_files as $file) {
2577
+ if ($file['type'] == 'd') {
2578
+ return $wp_fs_tmp_dir .'/'. $file['name'];
 
 
 
 
2579
  }
2580
  }
2581
+
2582
+ return new WP_Error(
2583
+ $wp_error_id,
2584
+ sprintf(__('The unzipped "%s" extension directory not found.', 'fw'), $this->get_extension_title($extension_name))
2585
+ );
2586
  }
2587
 
2588
  /**
2600
 
2601
  $wp_error_id = 'fw_extensions_merge';
2602
 
2603
+ // check source
2604
+ {
2605
+ $source_files = $wp_filesystem->dirlist($source_wp_fs_dir);
2606
 
2607
+ if ($source_files === false) {
2608
+ return new WP_Error(
2609
+ $wp_error_id,
2610
+ sprintf(__('Cannot read directory "%s".', 'fw'), $source_wp_fs_dir)
2611
+ );
2612
+ }
2613
 
2614
+ if (empty($source_files)) {
2615
+ return; // directory is empty, nothing to move
2616
+ }
2617
  }
2618
 
2619
  /**
2631
  }
2632
 
2633
  if (!empty($destination_files)) {
2634
+ if (
2635
+ count($source_files) == 1
2636
+ &&
2637
+ ($file = reset($source_files))
2638
+ &&
2639
+ $file['name'] === 'extensions'
2640
+ &&
2641
+ $file['type'] === 'd'
2642
+ ) {
2643
+ /**
2644
+ * Source extension is empty
2645
+ * It happens when you merge a directory which contains child extensions
2646
+ * Do not delete current destination files, just go in the next child extensions level
2647
+ * Used by https://github.com/ThemeFuse/Unyson/issues/1874
2648
+ */
2649
+ } else {
2650
+ // the directory contains some files, delete everything
2651
+ foreach ($destination_files as $file) {
2652
+ if ($file['name'] === 'extensions' && $file['type'] === 'd') {
2653
+ // do not touch the extensions/ directory
2654
+ continue;
2655
+ }
2656
 
2657
+ if (!$wp_filesystem->delete(
2658
+ $destination_wp_fs_dir .'/'. $file['name'],
2659
+ true,
2660
+ $file['type']
2661
+ )) {
2662
+ return new WP_Error(
2663
+ $wp_error_id,
2664
+ sprintf(
2665
+ __('Cannot delete "%s".', 'fw'),
2666
+ $destination_wp_fs_dir .'/'. $file['name']
2667
+ )
2668
+ );
2669
+ }
2670
  }
2671
  }
2672
 
2681
  }
2682
  }
2683
 
2684
+ // Move files from source to destination
2685
+ {
2686
+ $has_sub_extensions = false;
2687
 
2688
+ foreach ($source_files as $file) {
2689
+ if ($file['name'] === 'extensions' && $file['type'] === 'd') {
2690
+ $has_sub_extensions = true; // do not touch the extensions/ directory
2691
+ continue;
2692
+ }
 
2693
 
2694
+ if (!$wp_filesystem->move($source_wp_fs_dir .'/'. $file['name'], $destination_wp_fs_dir .'/'. $file['name'])) {
2695
+ return new WP_Error(
2696
+ $wp_error_id,
2697
+ sprintf(
2698
+ __('Cannot move "%s" to "%s".', 'fw'),
2699
+ $source_wp_fs_dir .'/'. $file['name'],
2700
+ $destination_wp_fs_dir .'/'. $file['name']
2701
+ )
2702
+ );
2703
+ }
2704
  }
 
2705
 
2706
+ unset($source_files);
2707
+ }
2708
 
2709
  if (!$has_sub_extensions) {
2710
  return;
2745
  {
2746
  $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
2747
 
2748
+ // Add Available Extensions registered by the theme
2749
+ foreach ($this->get_available_extensions() as $name => $extension) {
2750
+ if (isset($extension['theme']) && $extension['theme']) {
2751
+ $supported_extensions[$name] = array();
2752
+ }
2753
+ }
2754
+
2755
  if (empty($supported_extensions)) {
2756
  return array();
2757
  }
3204
  return $extensions;
3205
  }
3206
 
3207
+ /**
3208
+ * @internal
3209
+ */
3210
  public function _action_admin_notices() {
3211
  /**
3212
  * In v2.4.12 was done a terrible mistake https://github.com/ThemeFuse/Unyson-Extensions-Approval/issues/160
3229
  , '</p></div>';
3230
  }
3231
  }
3232
+
3233
+ /**
3234
+ * Copy Theme Available Extensions to a tmp directory
3235
+ * Used before theme update
3236
+ * @since 2.6.0
3237
+ * @return null|WP_Error
3238
+ */
3239
+ public function theme_available_extensions_copy() {
3240
+ /** @var WP_Filesystem_Base $wp_filesystem */
3241
+ global $wp_filesystem;
3242
+
3243
+ if (!$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())) {
3244
+ return new WP_Error(
3245
+ 'fs_not_initialized',
3246
+ __('WP Filesystem is not initialized', 'fw')
3247
+ );
3248
+ }
3249
+
3250
+ // Prepare temporary directory
3251
+ {
3252
+ $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3253
+ $this->get_tmp_dir('/theme-ext')
3254
+ );
3255
+
3256
+ if (
3257
+ $wp_filesystem->exists( $wpfs_tmp_dir )
3258
+ &&
3259
+ ! $wp_filesystem->rmdir( $wpfs_tmp_dir, true )
3260
+ ) {
3261
+ return new WP_Error(
3262
+ 'tmp_dir_rm_fail',
3263
+ sprintf(__('Temporary directory cannot be removed: %s', 'fw'), $wpfs_tmp_dir)
3264
+ );
3265
+ }
3266
+
3267
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $wpfs_tmp_dir ) ) {
3268
+ return new WP_Error(
3269
+ 'tmp_dir_rm_fail',
3270
+ sprintf(__('Temporary directory cannot be created: %s', 'fw'), $wpfs_tmp_dir)
3271
+ );
3272
+ }
3273
+ }
3274
+
3275
+ $available_extensions = $this->get_available_extensions();
3276
+ $installed_extensions = $this->get_installed_extensions(true);
3277
+ $base_dir = fw_get_template_customizations_directory('/extensions');
3278
+
3279
+ foreach ($installed_extensions as $name => $ext) {
3280
+ if ( ! (
3281
+ isset($available_extensions[$name])
3282
+ &&
3283
+ isset($available_extensions[$name]['theme'])
3284
+ &&
3285
+ $available_extensions[$name]['theme']
3286
+ ) ) {
3287
+ continue;
3288
+ }
3289
+
3290
+ if ( ($rel_path = preg_replace('/^'. preg_quote($base_dir, '/') .'/', '', $ext['path'])) === $base_dir ) {
3291
+ return new WP_Error(
3292
+ 'rel_path_failed',
3293
+ sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path'])
3294
+ );
3295
+ }
3296
+
3297
+ if ( ($wpfs_path = FW_WP_Filesystem::real_path_to_filesystem_path($ext['path'])) === false) {
3298
+ return new WP_Error(
3299
+ 'real_to_wpfs_filed',
3300
+ sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path'])
3301
+ );
3302
+ }
3303
+
3304
+ $wpfs_dest_dir = $wpfs_tmp_dir . $rel_path;
3305
+
3306
+ if ( ! FW_WP_Filesystem::mkdir_recursive($wpfs_dest_dir) ) {
3307
+ return new WP_Error(
3308
+ 'dest_dir_mk_fail',
3309
+ sprintf(__('Failed to create directory %s', 'fw'), $wpfs_dest_dir)
3310
+ );
3311
+ }
3312
+
3313
+ if ( is_wp_error( $copy_result = copy_dir($wpfs_path, $wpfs_dest_dir) ) ) {
3314
+ /** @var WP_Error $copy_result */
3315
+ return new WP_Error(
3316
+ 'ext_copy_failed',
3317
+ sprintf( __('Failed to copy extension to %s', 'fw'), $wpfs_dest_dir )
3318
+ );
3319
+ }
3320
+ }
3321
+ }
3322
+
3323
+ /**
3324
+ * Copy Theme Available Extensions from tmp directory to theme
3325
+ * Used after theme update
3326
+ * @since 2.6.0
3327
+ * @return null|WP_Error
3328
+ */
3329
+ public function theme_available_extensions_restore() {
3330
+ /** @var WP_Filesystem_Base $wp_filesystem */
3331
+ global $wp_filesystem;
3332
+
3333
+ if (!$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())) {
3334
+ return new WP_Error(
3335
+ 'fs_not_initialized',
3336
+ __('WP Filesystem is not initialized', 'fw')
3337
+ );
3338
+ }
3339
+
3340
+ if ( ! $wp_filesystem->exists(
3341
+ $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3342
+ $this->get_tmp_dir('/theme-ext')
3343
+ )
3344
+ ) ) {
3345
+ return new WP_Error(
3346
+ 'no_tmp_dir',
3347
+ sprintf(__('Temporary directory does not exist: %s', 'fw'), $wpfs_tmp_dir)
3348
+ );
3349
+ }
3350
+
3351
+ /**
3352
+ * Fixes the case when the theme path before update was
3353
+ * wp-content/themes/theme-name/theme-name-parent
3354
+ * but after update it became
3355
+ * wp-content/themes/theme-name-parent
3356
+ *
3357
+ * and at this point get_template_directory() returns old theme directory
3358
+ * so fw_get_template_customizations_directory() also returns old path
3359
+ */
3360
+ $theme_dir = wp_get_theme()->get_theme_root() .'/'. wp_get_theme()->get_template();
3361
+
3362
+ if ( ! ($wpfs_base_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3363
+ $base_dir = $theme_dir . fw_get_framework_customizations_dir_rel_path('/extensions')
3364
+ ) ) ) {
3365
+ return new WP_Error(
3366
+ 'base_dir_to_wpfs_fail',
3367
+ sprintf( __('Cannot obtain WP Filesystem dir for %s', 'fw'), $base_dir )
3368
+ );
3369
+ }
3370
+
3371
+ if ( ! ( $dirlist = $wp_filesystem->dirlist($wpfs_tmp_dir) ) ) {
3372
+ return;
3373
+ }
3374
+
3375
+ foreach ( $dirlist as $filename => $fileinfo ) {
3376
+ if ( 'd' !== $fileinfo['type'] ) {
3377
+ continue;
3378
+ }
3379
+
3380
+ if ( is_wp_error($merge_result = $this->merge_extension(
3381
+ $wpfs_tmp_dir .'/'. $filename,
3382
+ $wpfs_base_dir .'/'. $filename
3383
+ )) ) {
3384
+ return $merge_result;
3385
+ }
3386
+ }
3387
+
3388
+ $wp_filesystem->rmdir( $wpfs_tmp_dir, true );
3389
+ }
3390
+
3391
+ /**
3392
+ * Copy Theme Available Extensions to tmp dir
3393
+ * @param bool|WP_Error $result
3394
+ * @param array $data
3395
+ *
3396
+ * @return bool|WP_Error
3397
+ */
3398
+ public function _filter_theme_available_extensions_copy($result, $data) {
3399
+ if (
3400
+ !is_wp_error($result)
3401
+ &&
3402
+ is_array($data)
3403
+ &&
3404
+ isset($data['theme'])
3405
+ &&
3406
+ $data['theme'] === wp_get_theme()->get_template()
3407
+ ) {
3408
+ if ( is_wp_error( $copy_result = fw()->extensions->manager->theme_available_extensions_copy() ) ) {
3409
+ return $copy_result;
3410
+ }
3411
+ }
3412
+
3413
+ return $result;
3414
+ }
3415
+
3416
+ /**
3417
+ * Restore Theme Available Extensions from tmp dir
3418
+ * @param Theme_Upgrader $instance
3419
+ * @param array $data
3420
+ *
3421
+ * @return bool|WP_Error
3422
+ */
3423
+ public function _action_theme_available_extensions_restore($instance, $data) {
3424
+ if (
3425
+ !is_wp_error($instance->skin->result)
3426
+ &&
3427
+ is_array($data)
3428
+ &&
3429
+ isset($data['action']) && $data['action'] === 'update'
3430
+ &&
3431
+ isset($data['type']) && $data['type'] === 'theme'
3432
+ &&
3433
+ isset($data['themes'])
3434
+ &&
3435
+ ($template = wp_get_theme()->get_template())
3436
+ &&
3437
+ (
3438
+ in_array($template, $data['themes'])
3439
+ ||
3440
+ /**
3441
+ * Fixes the case when the theme path before update was
3442
+ * wp-content/themes/theme-name/theme-name-parent
3443
+ * but after update it became
3444
+ * wp-content/themes/theme-name-parent
3445
+ */
3446
+ ( preg_match($regex = '/\-parent$/', $template)
3447
+ ? in_array( preg_replace($regex, '', $template) .'/'. $template, $data['themes'] )
3448
+ : false )
3449
+ )
3450
+ ) {
3451
+ fw()->extensions->manager->theme_available_extensions_restore();
3452
+ }
3453
+ }
3454
  }
framework/core/components/extensions/manager/includes/available-ext/class--fw-available-extensions-register.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ class _FW_Available_Extensions_Register extends FW_Type_Register {
4
+ protected function validate_type( FW_Type $type ) {
5
+ return $type instanceof FW_Available_Extension;
6
+ }
7
+ }
framework/core/components/extensions/manager/includes/available-ext/class-fw-available-extension.php ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Used to define extension in framework Available Extensions list
5
+ * @since 2.5.12
6
+ */
7
+ class FW_Available_Extension extends FW_Type {
8
+ /**
9
+ * Extension (directory) name
10
+ */
11
+ private $name;
12
+
13
+ /**
14
+ * @var null|string Parent extension name
15
+ */
16
+ private $parent = null;
17
+
18
+ /**
19
+ * @var bool If visible in extensions list
20
+ */
21
+ private $display = true;
22
+
23
+ /**
24
+ * @var string
25
+ */
26
+ private $title;
27
+
28
+ /**
29
+ * @var string
30
+ */
31
+ private $description;
32
+
33
+ /**
34
+ * @var string Image url
35
+ */
36
+ private $thumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2PUsHf9DwAC8AGtfm5YCAAAAABJRU5ErkJgggAA';
37
+
38
+ /**
39
+ * @var array {source: id, opts: {...}}
40
+ * @see FW_Ext_Download_Source::get_type() is id
41
+ * @see FW_Ext_Download_Source
42
+ */
43
+ private $download_source = array();
44
+
45
+ /**
46
+ * @return bool
47
+ * @since 2.6.0
48
+ */
49
+ public function is_valid() {
50
+ return (
51
+ !empty($this->name) && is_string($this->name)
52
+ &&
53
+ !empty($this->title) && is_string($this->title)
54
+ &&
55
+ !empty($this->description) && is_string($this->description)
56
+ &&
57
+ !empty($this->download_source)
58
+ &&
59
+ is_bool($this->display)
60
+ &&
61
+ (is_null($this->parent) || is_string($this->parent))
62
+ );
63
+ }
64
+
65
+ /**
66
+ * @return string
67
+ * @internal
68
+ */
69
+ final public function get_type() {
70
+ return $this->get_name();
71
+ }
72
+
73
+ public function get_name() {
74
+ return $this->name;
75
+ }
76
+
77
+ public function set_name($name) {
78
+ $this->name = $name;
79
+ }
80
+
81
+ public function get_parent() {
82
+ return $this->parent;
83
+ }
84
+
85
+ public function set_parent($parent) {
86
+ $this->parent = $parent;
87
+ }
88
+
89
+ public function get_display() {
90
+ return $this->display;
91
+ }
92
+
93
+ public function set_display($display) {
94
+ $this->display = $display;
95
+ }
96
+
97
+ public function get_title() {
98
+ return $this->title;
99
+ }
100
+
101
+ public function set_title($title) {
102
+ $this->title = $title;
103
+ }
104
+
105
+ public function get_description() {
106
+ return $this->description;
107
+ }
108
+
109
+ public function set_description($description) {
110
+ $this->description = $description;
111
+ }
112
+
113
+ public function get_thumbnail() {
114
+ return $this->thumbnail;
115
+ }
116
+
117
+ public function set_thumbnail($thumbnail) {
118
+ $this->thumbnail = $thumbnail;
119
+ }
120
+
121
+ public function get_download_source() {
122
+ return $this->download_source;
123
+ }
124
+
125
+ public function set_download_source($id, $data) {
126
+ $this->download_source = array(
127
+ 'source' => $id,
128
+ 'opts' => $data
129
+ );
130
+ }
131
+ }
framework/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source-register.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php if (! defined('FW')) { die('Forbidden'); }
2
+
3
+ class _FW_Ext_Download_Source_Register extends FW_Type_Register
4
+ {
5
+ protected function validate_type( FW_Type $type ) {
6
+ return $type instanceof FW_Ext_Download_Source;
7
+ }
8
+ }
9
+
framework/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if (! defined('FW')) { die('Forbidden'); }
2
+
3
+ /**
4
+ * User to specify multiple download sources for an extension.
5
+ * @since 2.5.12
6
+ */
7
+ abstract class FW_Ext_Download_Source extends FW_Type
8
+ {
9
+ /**
10
+ * Perform the actual download.
11
+ * It should download, by convention, a zip file which absolute path
12
+ * is $path.
13
+ *
14
+ * @param array $opts {extension_name: '...', extension_title: '...', ...}
15
+ * @param string $zip_path Absolute file of the future ZIP file
16
+ * @return null|WP_Error
17
+ */
18
+ abstract public function download(array $opts, $zip_path);
19
+ }
20
+
framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-github.php ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if (! defined('FW')) { die('Forbidden'); }
2
+
3
+ class FW_Ext_Download_Source_Github extends FW_Ext_Download_Source
4
+ {
5
+ private $download_timeout = 300;
6
+
7
+ public function get_type() {
8
+ return 'github';
9
+ }
10
+
11
+ /**
12
+ * @param array $opts {user_repo: 'ThemeFuse/Unyson'}
13
+ * @param string $zip_path
14
+ *
15
+ * @return WP_Error
16
+ */
17
+ public function download(array $opts, $zip_path) {
18
+ $wp_error_id = 'fw_ext_github_download_source';
19
+ $theme_ext_requirements = fw()->theme->manifest->get('requirements/extensions');
20
+
21
+ /** @var WP_Filesystem_Base $wp_filesystem */
22
+ global $wp_filesystem;
23
+
24
+ $extension_name = $opts['extension_name'];
25
+ $extension_title = $opts['extension_title'];
26
+
27
+ if (empty($opts['user_repo'])) {
28
+ return new WP_Error(
29
+ $wp_error_id,
30
+ sprintf(__('"%s" extension github source "user_repo" parameter is required', 'fw'), $extension_title)
31
+ );
32
+ }
33
+
34
+ {
35
+ $transient_name = 'fw_ext_mngr_gh_dl';
36
+ $transient_ttl = HOUR_IN_SECONDS;
37
+
38
+ $cache = get_site_transient($transient_name);
39
+
40
+ if ($cache === false) {
41
+ $cache = array();
42
+ }
43
+ }
44
+
45
+ if (isset($cache[ $opts['user_repo'] ])) {
46
+ $download_link = $cache[ $opts['user_repo'] ]['zipball_url'];
47
+ } else {
48
+ $http = new WP_Http();
49
+
50
+ if (
51
+ isset($theme_ext_requirements[$extension_name])
52
+ &&
53
+ isset($theme_ext_requirements[$extension_name]['max_version'])
54
+ ) {
55
+ $tag = 'tags/v'. $theme_ext_requirements[$extension_name]['max_version'];
56
+ } else {
57
+ $tag = 'latest';
58
+ }
59
+
60
+ $response = $http->get(
61
+ apply_filters('fw_github_api_url', 'https://api.github.com')
62
+ . '/repos/'. $opts['user_repo'] .'/releases/'. $tag
63
+ );
64
+
65
+ unset($http);
66
+
67
+ $response_code = intval(wp_remote_retrieve_response_code($response));
68
+
69
+ if ($response_code !== 200) {
70
+ if ($response_code === 403) {
71
+ if ($json_response = json_decode($response['body'], true)) {
72
+ return new WP_Error(
73
+ $wp_error_id,
74
+ __('Github error:', 'fw') .' '. $json_response['message']
75
+ );
76
+ } else {
77
+ return new WP_Error(
78
+ $wp_error_id,
79
+ sprintf(
80
+ __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
81
+ $opts['user_repo'], $response_code
82
+ )
83
+ );
84
+ }
85
+ } elseif ($response_code) {
86
+ return new WP_Error(
87
+ $wp_error_id,
88
+ sprintf(
89
+ __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
90
+ $opts['user_repo'], $response_code
91
+ )
92
+ );
93
+ } elseif (is_wp_error($response)) {
94
+ return new WP_Error(
95
+ $wp_error_id,
96
+ sprintf(
97
+ __( 'Failed to access Github repository "%s" releases. (%s)', 'fw' ),
98
+ $opts['user_repo'], $response->get_error_message()
99
+ )
100
+ );
101
+ } else {
102
+ return new WP_Error(
103
+ $wp_error_id,
104
+ sprintf(
105
+ __( 'Failed to access Github repository "%s" releases.', 'fw' ),
106
+ $opts['user_repo']
107
+ )
108
+ );
109
+ }
110
+ }
111
+
112
+ $release = json_decode($response['body'], true);
113
+
114
+ unset($response);
115
+
116
+ if (empty($release)) {
117
+ return new WP_Error(
118
+ $wp_error_id,
119
+ sprintf(
120
+ __('"%s" extension github repository "%s" has no releases.', 'fw'),
121
+ $extension_title, $opts['user_repo']
122
+ )
123
+ );
124
+ }
125
+
126
+ {
127
+ $cache[ $opts['user_repo'] ] = array(
128
+ 'zipball_url' => 'https://github.com/'. $opts['user_repo'] .'/archive/'. $release['tag_name'] .'.zip',
129
+ 'tag_name' => $release['tag_name']
130
+ );
131
+
132
+ set_site_transient($transient_name, $cache, $transient_ttl);
133
+ }
134
+
135
+ $download_link = $cache[ $opts['user_repo'] ]['zipball_url'];
136
+
137
+
138
+ unset($release);
139
+ }
140
+
141
+ {
142
+ $http = new WP_Http();
143
+
144
+ $response = $http->request($download_link, array(
145
+ 'timeout' => $this->download_timeout,
146
+ ));
147
+
148
+ unset($http);
149
+
150
+ if (($response_code = intval(wp_remote_retrieve_response_code($response))) !== 200) {
151
+ if ($response_code) {
152
+ return new WP_Error(
153
+ $wp_error_id,
154
+ sprintf( __( 'Cannot download the "%s" extension zip. (Response code: %d)', 'fw' ),
155
+ $extension_title, $response_code
156
+ )
157
+ );
158
+ } elseif (is_wp_error($response)) {
159
+ return new WP_Error(
160
+ $wp_error_id,
161
+ sprintf( __( 'Cannot download the "%s" extension zip. %s', 'fw' ),
162
+ $extension_title,
163
+ $response->get_error_message()
164
+ )
165
+ );
166
+ } else {
167
+ return new WP_Error(
168
+ $wp_error_id,
169
+ sprintf( __( 'Cannot download the "%s" extension zip.', 'fw' ),
170
+ $extension_title
171
+ )
172
+ );
173
+ }
174
+ }
175
+
176
+ // save zip to file
177
+ if (!$wp_filesystem->put_contents($zip_path, $response['body'])) {
178
+ return new WP_Error(
179
+ $wp_error_id,
180
+ sprintf(__('Cannot save the "%s" extension zip.', 'fw'), $extension_title)
181
+ );
182
+ }
183
+
184
+ unset($response);
185
+ }
186
+ }
187
+ }
framework/core/components/extensions/manager/includes/download-source/types/init.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if (! defined('FW')) { die('Forbidden'); }
2
+
3
+ if ( ! function_exists( '_action_fw_register_ext_download_sources' ) ) {
4
+ function _action_fw_register_ext_download_sources(_FW_Ext_Download_Source_Register $download_sources) {
5
+ $dir = dirname(__FILE__);
6
+
7
+ require_once $dir . '/class-fw-download-source-github.php';
8
+ $download_sources->register(new FW_Ext_Download_Source_Github());
9
+ }
10
+ }
11
+
12
+ add_action(
13
+ 'fw_register_ext_download_sources',
14
+ '_action_fw_register_ext_download_sources'
15
+ );
framework/core/components/extensions/manager/static/extensions-page.css CHANGED
@@ -134,9 +134,13 @@
134
  width: 10px;
135
  }
136
 
137
- .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) {
138
  padding-left: 20px;
139
  }
 
 
 
 
140
 
141
  .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 {
142
  margin-bottom: 0;
@@ -149,6 +153,10 @@
149
  right: 20px;
150
  width: auto;
151
  }
 
 
 
 
152
 
153
  .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 {
154
  display: inline-block;
@@ -158,6 +166,9 @@
158
  padding: 0 0 4px 15px;
159
  vertical-align: bottom;
160
  }
 
 
 
161
 
162
  .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.extension-delete-form .btn-icon {
163
  font-size: 16px;
134
  width: 10px;
135
  }
136
 
137
+ .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):not(:last-child) {
138
  padding-left: 20px;
139
  }
140
+ body.rtl .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):not(:last-child) {
141
+ padding-left: 0;
142
+ padding-right: 20px;
143
+ }
144
 
145
  .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 {
146
  margin-bottom: 0;
153
  right: 20px;
154
  width: auto;
155
  }
156
+ body.rtl .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 {
157
+ right: auto;
158
+ left: 20px;
159
+ }
160
 
161
  .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 {
162
  display: inline-block;
166
  padding: 0 0 4px 15px;
167
  vertical-align: bottom;
168
  }
169
+ body.rtl .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.extension-delete-form {
170
+ padding: 0 13px 4px 0;
171
+ }
172
 
173
  .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.extension-delete-form .btn-icon {
174
  font-size: 16px;
framework/core/components/theme.php CHANGED
@@ -46,15 +46,21 @@ final class _FW_Component_Theme
46
  */
47
  public function locate_path($rel_path)
48
  {
49
- if (is_child_theme() && file_exists(fw_get_stylesheet_customizations_directory('/theme'. $rel_path))) {
50
- return fw_get_stylesheet_customizations_directory('/theme'. $rel_path);
51
- }
 
 
 
 
 
 
 
52
 
53
- if (file_exists(fw_get_template_customizations_directory('/theme'. $rel_path))) {
54
- return fw_get_template_customizations_directory('/theme'. $rel_path);
55
- }
56
 
57
- return false;
 
58
  }
59
 
60
  /**
46
  */
47
  public function locate_path($rel_path)
48
  {
49
+ try {
50
+ return FW_File_Cache::get($cache_key = 'core:theme:path:'. $rel_path);
51
+ } catch (FW_File_Cache_Not_Found_Exception $e) {
52
+ if (is_child_theme() && file_exists(fw_get_stylesheet_customizations_directory('/theme'. $rel_path))) {
53
+ $path = fw_get_stylesheet_customizations_directory('/theme'. $rel_path);
54
+ } elseif (file_exists(fw_get_template_customizations_directory('/theme'. $rel_path))) {
55
+ $path = fw_get_template_customizations_directory('/theme'. $rel_path);
56
+ } else {
57
+ $path = false;
58
+ }
59
 
60
+ FW_File_Cache::set($cache_key, $path);
 
 
61
 
62
+ return $path;
63
+ }
64
  }
65
 
66
  /**
framework/core/extends/class-fw-extension.php CHANGED
@@ -152,15 +152,26 @@ abstract class FW_Extension
152
  */
153
  final public function locate_path($rel_path)
154
  {
155
- $locations = $this->customizations_locations;
156
- $locations[$this->get_path()] = $this->get_uri();
 
 
 
 
157
 
158
- foreach ($locations as $path => $uri) {
159
- if (file_exists($path . $rel_path)) {
160
- return $path . $rel_path;
 
 
161
  }
 
 
 
 
162
  }
163
 
 
164
  return false;
165
  }
166
 
@@ -170,16 +181,24 @@ abstract class FW_Extension
170
  */
171
  final public function locate_URI($rel_path)
172
  {
173
- $locations = $this->customizations_locations;
174
- $locations[$this->get_path()] = $this->get_uri();
 
 
 
 
175
 
176
- foreach ($locations as $path => $uri) {
177
- if (file_exists($path . $rel_path)) {
178
- return $uri . $rel_path;
 
 
179
  }
180
- }
181
 
182
- return false;
 
 
 
183
  }
184
 
185
  /**
152
  */
153
  final public function locate_path($rel_path)
154
  {
155
+ try {
156
+ return FW_File_Cache::get($cache_key = 'ext:'. $this->get_name() .':path:'. $rel_path);
157
+ } catch (FW_File_Cache_Not_Found_Exception $e) {
158
+ $result = false;
159
+ $locations = $this->customizations_locations;
160
+ $locations[$this->get_path()] = $this->get_uri();
161
 
162
+ foreach ($locations as $path => $uri) {
163
+ if (file_exists($path . $rel_path)) {
164
+ $result = $path . $rel_path;
165
+ break;
166
+ }
167
  }
168
+
169
+ FW_File_Cache::set($cache_key, $result);
170
+
171
+ return $result;
172
  }
173
 
174
+
175
  return false;
176
  }
177
 
181
  */
182
  final public function locate_URI($rel_path)
183
  {
184
+ try {
185
+ return FW_File_Cache::get($cache_key = 'ext:'. $this->get_name() .':uri:'. $rel_path);
186
+ } catch (FW_File_Cache_Not_Found_Exception $e) {
187
+ $result = false;
188
+ $locations = $this->customizations_locations;
189
+ $locations[$this->get_path()] = $this->get_uri();
190
 
191
+ foreach ($locations as $path => $uri) {
192
+ if (file_exists($path . $rel_path)) {
193
+ $result = $uri . $rel_path;
194
+ break;
195
+ }
196
  }
 
197
 
198
+ FW_File_Cache::set($cache_key, $result);
199
+
200
+ return $result;
201
+ }
202
  }
203
 
204
  /**
framework/extensions/update/manifest.php CHANGED
@@ -6,5 +6,5 @@ $manifest['name'] = __('Update', 'fw');
6
  $manifest['description'] = __('Keep you framework, extensions and theme up to date.', 'fw');
7
  $manifest['standalone'] = true;
8
 
9
- $manifest['version'] = '1.0.10';
10
  $manifest['github_update'] = 'ThemeFuse/Unyson-Update-Extension';
6
  $manifest['description'] = __('Keep you framework, extensions and theme up to date.', 'fw');
7
  $manifest['standalone'] = true;
8
 
9
+ $manifest['version'] = '1.0.11';
10
  $manifest['github_update'] = 'ThemeFuse/Unyson-Update-Extension';
framework/extensions/update/views/updates-list.php CHANGED
@@ -32,7 +32,7 @@
32
  <?php if ($updates['theme'] !== false): ?>
33
  <div id="fw-ext-update-theme">
34
  <a name="fw-theme"></a>
35
- <h3><?php $theme = wp_get_theme(); _e(sprintf('%s Theme', $theme->parent()->get('Name')), 'fw') ?></h3>
36
  <?php if (empty($updates['theme'])): ?>
37
  <p><?php _e('Your theme is up to date.', 'fw') ?></p>
38
  <?php else: ?>
32
  <?php if ($updates['theme'] !== false): ?>
33
  <div id="fw-ext-update-theme">
34
  <a name="fw-theme"></a>
35
+ <h3><?php $theme = wp_get_theme(); _e(sprintf('%s Theme', (is_child_theme() ? $theme->parent()->get('Name') : $theme->get('Name'))), 'fw') ?></h3>
36
  <?php if (empty($updates['theme'])): ?>
37
  <p><?php _e('Your theme is up to date.', 'fw') ?></p>
38
  <?php else: ?>
framework/helpers/class-fw-cache.php CHANGED
@@ -2,7 +2,6 @@
2
 
3
  /**
4
  * Memory Cache
5
- * Only for internal usage in other functions/methods, because it throws exceptions
6
  *
7
  * Recommended usage example:
8
  * try {
@@ -151,14 +150,17 @@ class FW_Cache
151
  'clean_user_cache' => true,
152
  'process_text_diff_html' => true,
153
  ) as $hook => $tmp) {
154
- add_filter($hook, array(__CLASS__, 'free_memory'), 9999);
155
  }
156
 
157
  /**
158
- * When WP global state is changed, better to flush the cache
159
  */
160
  foreach (array(
161
  'switch_blog' => true,
 
 
 
162
  ) as $hook => $tmp) {
163
  add_filter($hook, array(__CLASS__, 'clear'), 1);
164
  }
@@ -263,7 +265,7 @@ class FW_Cache
263
 
264
  /**
265
  * Empty the cache
266
- * @param mixed $dummy
267
  * @return mixed
268
  */
269
  public static function clear($dummy = null)
2
 
3
  /**
4
  * Memory Cache
 
5
  *
6
  * Recommended usage example:
7
  * try {
150
  'clean_user_cache' => true,
151
  'process_text_diff_html' => true,
152
  ) as $hook => $tmp) {
153
+ add_filter($hook, array(__CLASS__, 'free_memory'), 1);
154
  }
155
 
156
  /**
157
+ * Flush the cache when something major is changed (files or db values)
158
  */
159
  foreach (array(
160
  'switch_blog' => true,
161
+ 'upgrader_post_install' => true,
162
+ 'upgrader_process_complete' => true,
163
+ 'switch_theme' => true,
164
  ) as $hook => $tmp) {
165
  add_filter($hook, array(__CLASS__, 'clear'), 1);
166
  }
265
 
266
  /**
267
  * Empty the cache
268
+ * @param mixed $dummy When method is used in add_filter()
269
  * @return mixed
270
  */
271
  public static function clear($dummy = null)
framework/helpers/class-fw-file-cache.php ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Persistent cache saved in uploads/fw-file-cache.php
5
+ *
6
+ * It is reset when:
7
+ * - the user is logged in as administrator
8
+ * - important action related to theme switch or file changes is triggered (for e.g. JetPack can do changes remotely)
9
+ *
10
+ * Usage:
11
+ * try {
12
+ * return FW_File_Cache::get($cache_key = '...'. $rel_path);
13
+ * } catch (FW_File_Cache_Not_Found_Exception $e) {
14
+ * $result = ...;
15
+ *
16
+ * FW_File_Cache::set($cache_key, $result);
17
+ *
18
+ * return $result; // IMPORTANT: Do not use FW_File_Cache::get($cache_key) again
19
+ * }
20
+ *
21
+ * @since 2.6.0
22
+ */
23
+ class FW_File_Cache {
24
+ /**
25
+ * @var int
26
+ */
27
+ private static $expires = 3600;
28
+
29
+ /**
30
+ * @var bool
31
+ */
32
+ private static $changed = false;
33
+
34
+ /**
35
+ * @var string
36
+ */
37
+ private static $path;
38
+
39
+ /**
40
+ * @var array
41
+ */
42
+ private static $cache;
43
+
44
+ private static function get_defaults() {
45
+ return array(
46
+ 'created' => time(),
47
+ 'updated' => time(),
48
+ 'data' => array(),
49
+ );
50
+ }
51
+
52
+ private static function load() {
53
+ if ( is_array( self::$cache ) ) {
54
+ return true; // already loaded
55
+ }
56
+
57
+ $dir = dirname(self::$path);
58
+ $path = self::$path;
59
+ $code = '<?php return array();';
60
+ $shhh = defined('DOING_AJAX') && DOING_AJAX; // prevent warning in ajax requests
61
+
62
+ // check directory
63
+ if ( ! file_exists($dir) ) {
64
+ if ( ! mkdir($dir, 0755, true) ) {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ // check file
70
+ if ( file_exists($path) ) {
71
+ if ( ! is_writable($path) ) {
72
+ if (
73
+ ( $shhh
74
+ ? @unlink($path)
75
+ : unlink($path)
76
+ )
77
+ &&
78
+ file_put_contents($path, $code)
79
+ ) {
80
+ // file re-created
81
+ } else {
82
+ return false;
83
+ }
84
+ }
85
+ } elseif ( ! ( $shhh
86
+ ? @file_put_contents($path, $code)
87
+ : file_put_contents($path, $code)
88
+ ) ) {
89
+ return false; // cannot create the file
90
+ }
91
+
92
+ self::$cache = include $path;
93
+
94
+ // check the loaded cache
95
+ {
96
+ $reset = false;
97
+
98
+ do {
99
+ foreach ( self::get_defaults() as $def_key => $def_val ) {
100
+ if (
101
+ !isset( self::$cache[ $def_key ] )
102
+ ||
103
+ gettype( self::$cache[ $def_key ] ) !== gettype($def_val)
104
+ ) {
105
+ $reset = true;
106
+ break 2;
107
+ }
108
+ }
109
+
110
+ if ( self::$cache['created'] < ( time() - self::$expires ) ) {
111
+ $reset = true;
112
+ break;
113
+ }
114
+ } while(false);
115
+
116
+ if ($reset) {
117
+ self::$cache = self::get_defaults();
118
+ self::$changed = true;
119
+ }
120
+ }
121
+
122
+ return true;
123
+ }
124
+
125
+ private static function update_path() {
126
+ $path = wp_upload_dir();
127
+ $path = fw_fix_path($path['basedir']) .'/fw/file-cache.php';
128
+
129
+ self::$path = $path;
130
+ }
131
+
132
+ /**
133
+ * @param mixed $filter_value When this method is used in filter, it must return the unchanged filter value
134
+ * @return bool
135
+ */
136
+ public static function reset($filter_value = null) {
137
+ if ( ! self::load() ) {
138
+ return is_null($filter_value) ? true : $filter_value;
139
+ }
140
+
141
+ self::save();
142
+ self::update_path();
143
+ self::$cache = self::get_defaults();
144
+ self::$changed = true;
145
+
146
+ return is_null($filter_value) ? true : $filter_value;
147
+ }
148
+
149
+ public static function save() {
150
+ if ( ! self::$changed ) {
151
+ return;
152
+ }
153
+
154
+ file_put_contents(self::$path, '<?php return '. var_export(self::$cache, true) .';');
155
+
156
+ self::$changed = false;
157
+ }
158
+
159
+ /**
160
+ * @internal
161
+ */
162
+ public static function _init() {
163
+ self::update_path();
164
+
165
+ /**
166
+ * Reset when current user is administrator
167
+ * because it can be a developer that added/removed some files
168
+ */
169
+ if ( current_user_can('manage_options') ) {
170
+ self::reset();
171
+ } else {
172
+ /**
173
+ * Reset on actions which may change something related to files
174
+ * - themes/plugins activation (new files must be loaded from other paths)
175
+ * - after some files was added/deleted
176
+ */
177
+ foreach (array(
178
+ 'switch_blog' => true,
179
+ 'fw_extensions_before_activation' => true,
180
+ 'fw_extensions_after_activation' => true,
181
+ 'fw_extensions_before_deactivation' => true,
182
+ 'fw_extensions_after_deactivation' => true,
183
+ 'fw_extensions_install' => true,
184
+ 'fw_extensions_uninstall' => true,
185
+ 'activated_plugin' => true,
186
+ 'deactivated_plugin' => true,
187
+ 'switch_theme' => true,
188
+ 'after_switch_theme' => true,
189
+ 'upgrader_post_install' => true,
190
+ 'automatic_updates_complete' => true,
191
+ 'upgrader_process_complete' => true,
192
+ ) as $action => $x) {
193
+ add_action( $action, array(__CLASS__, 'reset') );
194
+ }
195
+ }
196
+
197
+ add_action( 'shutdown', array(__CLASS__, 'save') );
198
+ }
199
+
200
+ /**
201
+ * @param string $key No multiKey because it must be fast
202
+ * @return mixed
203
+ * @throws FW_File_Cache_Not_Found_Exception
204
+ */
205
+ public static function get($key) {
206
+ if ( ! self::load() ) {
207
+ throw new FW_File_Cache_Not_Found_Exception();
208
+ }
209
+
210
+ if (array_key_exists($key, self::$cache['data'])) {
211
+ return self::$cache['data'][$key];
212
+ } else {
213
+ throw new FW_File_Cache_Not_Found_Exception();
214
+ }
215
+ }
216
+
217
+ /**
218
+ * @param string $key
219
+ * @param mixed $value
220
+ * @return bool
221
+ */
222
+ public static function set($key, $value) {
223
+ if ( ! self::load() ) {
224
+ return false;
225
+ }
226
+
227
+ self::$changed = true;
228
+ self::$cache['updated'] = time();
229
+ self::$cache['data'][ $key ] = $value;
230
+
231
+ return true;
232
+ }
233
+ }
234
+
235
+ class FW_File_Cache_Not_Found_Exception extends Exception {}
236
+
237
+ FW_File_Cache::_init();
framework/helpers/class-fw-wp-filesystem.php CHANGED
@@ -302,7 +302,7 @@ class FW_WP_Filesystem
302
  }
303
  }
304
 
305
- if (!$wp_filesystem->mkdir($path, FS_CHMOD_DIR)) {
306
  return false;
307
  }
308
  }
302
  }
303
  }
304
 
305
+ if (!$wp_filesystem->mkdir($path)) {
306
  return false;
307
  }
308
  }
framework/helpers/database.php CHANGED
@@ -26,6 +26,31 @@ class FW_Db_Options_Model_Settings extends FW_Db_Options_Model {
26
  );
27
  }
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  protected function _init() {
30
  /**
31
  * Get a theme settings option value from the database
@@ -136,15 +161,15 @@ class FW_Db_Options_Model_Post extends FW_Db_Options_Model {
136
  }
137
 
138
  protected function get_values($item_id, array $extra_data = array()) {
139
- return FW_WP_Meta::get( 'post', $item_id, 'fw_options', array() );
140
  }
141
 
142
  protected function set_values($item_id, $values, array $extra_data = array()) {
143
- FW_WP_Meta::set( 'post', $item_id, 'fw_options', $values );
144
  }
145
 
146
  protected function get_fw_storage_params($item_id, array $extra_data = array()) {
147
- return array( 'post-id' => $item_id );
148
  }
149
 
150
  protected function _get_cache_key($key, $item_id, array $extra_data = array()) {
@@ -169,23 +194,14 @@ class FW_Db_Options_Model_Post extends FW_Db_Options_Model {
169
  /**
170
  * Option id
171
  * First level multi-key
172
- *
173
- * For e.g.
174
- *
175
- * if $option_id is 'hello/world/7'
176
- * this will be 'hello'
177
  */
178
  $option_id,
179
  /**
180
  * The remaining sub-keys
181
- *
182
  * For e.g.
183
- *
184
- * if $option_id is 'hello/world/7'
185
- * $option_id_keys will be array('world', '7')
186
- *
187
- * if $option_id is 'hello'
188
- * $option_id_keys will be array()
189
  */
190
  explode('/', $sub_keys),
191
  /**
@@ -234,11 +250,15 @@ class FW_Db_Options_Model_Term extends FW_Db_Options_Model {
234
  }
235
 
236
  protected function get_values($item_id, array $extra_data = array()) {
237
- return FW_WP_Meta::get( 'fw_term', $item_id, 'fw_options', array(), null );
 
 
238
  }
239
 
240
  protected function set_values($item_id, $values, array $extra_data = array()) {
241
- FW_WP_Meta::set( 'fw_term', $item_id, 'fw_options', $values );
 
 
242
  }
243
 
244
  protected function get_options($item_id, array $extra_data = array()) {
@@ -260,6 +280,123 @@ class FW_Db_Options_Model_Term extends FW_Db_Options_Model {
260
  }
261
  }
262
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  protected function _init() {
264
  /**
265
  * Get term option value from the database
@@ -301,6 +438,9 @@ class FW_Db_Options_Model_Term extends FW_Db_Options_Model {
301
  'taxonomy' => $taxonomy
302
  ));
303
  }
 
 
 
304
  }
305
  }
306
  new FW_Db_Options_Model_Term();
@@ -393,6 +533,31 @@ class FW_Db_Options_Model_Customizer extends FW_Db_Options_Model {
393
  );
394
  }
395
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  protected function _init() {
397
  /**
398
  * Get a customizer framework option value from the database
26
  );
27
  }
28
 
29
+ protected function _after_set($item_id, $option_id, $sub_keys, $old_value, array $extra_data = array()) {
30
+ /**
31
+ * @since 2.6.0
32
+ */
33
+ do_action('fw_settings_options_update', array(
34
+ /**
35
+ * Option id
36
+ * First level multi-key
37
+ * For e.g. if $option_id is 'hello/world/7' this will be 'hello'
38
+ */
39
+ 'option_id' => $option_id,
40
+ /**
41
+ * The remaining sub-keys
42
+ * For e.g.
43
+ * if $option_id is 'hello/world/7' this will be array('world', '7')
44
+ * if $option_id is 'hello' this will be array()
45
+ */
46
+ 'sub_keys' => explode('/', $sub_keys),
47
+ /**
48
+ * Old option(s) value
49
+ */
50
+ 'old_value' => $old_value
51
+ ));
52
+ }
53
+
54
  protected function _init() {
55
  /**
56
  * Get a theme settings option value from the database
161
  }
162
 
163
  protected function get_values($item_id, array $extra_data = array()) {
164
+ return FW_WP_Meta::get( 'post', $this->get_post_id($item_id), 'fw_options', array() );
165
  }
166
 
167
  protected function set_values($item_id, $values, array $extra_data = array()) {
168
+ FW_WP_Meta::set( 'post', $this->get_post_id($item_id), 'fw_options', $values );
169
  }
170
 
171
  protected function get_fw_storage_params($item_id, array $extra_data = array()) {
172
+ return array( 'post-id' => $this->get_post_id($item_id) );
173
  }
174
 
175
  protected function _get_cache_key($key, $item_id, array $extra_data = array()) {
194
  /**
195
  * Option id
196
  * First level multi-key
197
+ * For e.g. if $option_id is 'hello/world/7' this will be 'hello'
 
 
 
 
198
  */
199
  $option_id,
200
  /**
201
  * The remaining sub-keys
 
202
  * For e.g.
203
+ * if $option_id is 'hello/world/7' this will be array('world', '7')
204
+ * if $option_id is 'hello' this will be array()
 
 
 
 
205
  */
206
  explode('/', $sub_keys),
207
  /**
250
  }
251
 
252
  protected function get_values($item_id, array $extra_data = array()) {
253
+ self::migrate($item_id);
254
+
255
+ return (array)get_term_meta( $item_id, 'fw_options', true);
256
  }
257
 
258
  protected function set_values($item_id, $values, array $extra_data = array()) {
259
+ self::migrate($item_id);
260
+
261
+ update_term_meta($item_id, 'fw_options', $values);
262
  }
263
 
264
  protected function get_options($item_id, array $extra_data = array()) {
280
  }
281
  }
282
 
283
+ /**
284
+ * Cache termmeta table name if exists
285
+ * @var string|false
286
+ */
287
+ private static $old_table_name;
288
+
289
+ /**
290
+ * @return string|false
291
+ */
292
+ private static function get_old_table_name() {
293
+ if (is_null(self::$old_table_name)) {
294
+ /** @var WPDB $wpdb */
295
+ global $wpdb;
296
+
297
+ $table_name = $wpdb->get_results( "show tables like '{$wpdb->prefix}fw_termmeta'", ARRAY_A );
298
+ $table_name = $table_name ? array_pop($table_name[0]) : false;
299
+
300
+ if ( $table_name && ! $wpdb->get_results( "SELECT 1 FROM `{$table_name}` LIMIT 1" ) ) {
301
+ // The table is empty, delete it
302
+ $wpdb->query( "DROP TABLE `{$table_name}`" );
303
+ $table_name = false;
304
+ }
305
+
306
+ self::$old_table_name = $table_name;
307
+ }
308
+
309
+ return self::$old_table_name;
310
+ }
311
+
312
+ /**
313
+ * @internal
314
+ */
315
+ public static function _action_switch_blog() {
316
+ self::$old_table_name = null; // reset
317
+ }
318
+
319
+ /**
320
+ * When a term is deleted, delete its meta from old fw_termmeta table
321
+ *
322
+ * @param mixed $term_id
323
+ *
324
+ * @return void
325
+ * @internal
326
+ */
327
+ public static function _action_fw_delete_term( $term_id ) {
328
+ if ( ! ( $table_name = self::get_old_table_name() ) ) {
329
+ return;
330
+ }
331
+
332
+ $term_id = (int) $term_id;
333
+
334
+ if ( ! $term_id ) {
335
+ return;
336
+ }
337
+
338
+ /** @var WPDB $wpdb */
339
+ global $wpdb;
340
+
341
+ $wpdb->delete( $table_name, array( 'fw_term_id' => $term_id ), array( '%d' ) );
342
+ }
343
+
344
+ /**
345
+ * In WP 4.4 was introduced native term meta https://codex.wordpress.org/Version_4.4#For_Developers
346
+ * All data from old table must be migrated to native term meta
347
+ * @param int $term_id
348
+ * @return bool
349
+ */
350
+ private static function migrate($term_id) {
351
+ global $wpdb; /** @var wpdb $wpdb */
352
+
353
+ if (
354
+ ( $old_table_name = self::get_old_table_name() )
355
+ &&
356
+ ( $value = $wpdb->get_col( $wpdb->prepare(
357
+ "SELECT meta_value FROM `{$old_table_name}` WHERE fw_term_id = %d AND meta_key = 'fw_options' LIMIT 1",
358
+ $term_id
359
+ ) ) )
360
+ &&
361
+ ( $value = unserialize( $value[0] ) )
362
+ ) {
363
+ $wpdb->delete( $old_table_name, array( 'fw_term_id' => $term_id ), array( '%d' ) );
364
+
365
+ update_term_meta( $term_id, 'fw_options', $value );
366
+
367
+ return true;
368
+ } else {
369
+ return false;
370
+ }
371
+ }
372
+
373
+ protected function _after_set($item_id, $option_id, $sub_keys, $old_value, array $extra_data = array()) {
374
+ /**
375
+ * @since 2.6.0
376
+ */
377
+ do_action('fw_term_options_update', array(
378
+ 'term_id' => $item_id,
379
+ 'taxonomy' => $extra_data['taxonomy'],
380
+ /**
381
+ * Option id
382
+ * First level multi-key
383
+ * For e.g. if $option_id is 'hello/world/7' this will be 'hello'
384
+ */
385
+ 'option_id' => $option_id,
386
+ /**
387
+ * The remaining sub-keys
388
+ * For e.g.
389
+ * if $option_id is 'hello/world/7' this will be array('world', '7')
390
+ * if $option_id is 'hello' this will be array()
391
+ */
392
+ 'sub_keys' => explode('/', $sub_keys),
393
+ /**
394
+ * Old option(s) value
395
+ */
396
+ 'old_value' => $old_value
397
+ ));
398
+ }
399
+
400
  protected function _init() {
401
  /**
402
  * Get term option value from the database
438
  'taxonomy' => $taxonomy
439
  ));
440
  }
441
+
442
+ add_action( 'switch_blog', array( __CLASS__, '_action_switch_blog' ) );
443
+ add_action( 'delete_term', array( __CLASS__, '_action_fw_delete_term' ) );
444
  }
445
  }
446
  new FW_Db_Options_Model_Term();
533
  );
534
  }
535
 
536
+ protected function _after_set($item_id, $option_id, $sub_keys, $old_value, array $extra_data = array()) {
537
+ /**
538
+ * @since 2.6.0
539
+ */
540
+ do_action('fw_customizer_options_update', array(
541
+ /**
542
+ * Option id
543
+ * First level multi-key
544
+ * For e.g. if $option_id is 'hello/world/7' this will be 'hello'
545
+ */
546
+ 'option_id' => $option_id,
547
+ /**
548
+ * The remaining sub-keys
549
+ * For e.g.
550
+ * if $option_id is 'hello/world/7' this will be array('world', '7')
551
+ * if $option_id is 'hello' this will be array()
552
+ */
553
+ 'sub_keys' => explode('/', $sub_keys),
554
+ /**
555
+ * Old option(s) value
556
+ */
557
+ 'old_value' => $old_value
558
+ ));
559
+ }
560
+
561
  protected function _init() {
562
  /**
563
  * Get a customizer framework option value from the database
framework/helpers/general.php CHANGED
@@ -489,15 +489,21 @@ function fw_current_screen_match(array $rules) {
489
  * @return string URI
490
  */
491
  function fw_locate_theme_path_uri($rel_path) {
492
- if (is_child_theme() && file_exists(get_stylesheet_directory() . $rel_path)) {
493
- return get_stylesheet_directory_uri() . $rel_path;
494
- }
 
 
 
 
 
 
 
495
 
496
- if (file_exists(get_template_directory() . $rel_path)) {
497
- return get_template_directory_uri() . $rel_path;
498
- }
499
 
500
- return 'about:blank#theme-file-not-found:'. $rel_path;
 
501
  }
502
 
503
  /**
@@ -507,15 +513,21 @@ function fw_locate_theme_path_uri($rel_path) {
507
  * @return string URI
508
  */
509
  function fw_locate_theme_path($rel_path) {
510
- if (is_child_theme() && file_exists(get_stylesheet_directory() . $rel_path)) {
511
- return get_stylesheet_directory() . $rel_path;
512
- }
 
 
 
 
 
 
 
513
 
514
- if (file_exists(get_template_directory() . $rel_path)) {
515
- return get_template_directory() . $rel_path;
516
- }
517
 
518
- return false;
 
519
  }
520
 
521
  /**
@@ -741,33 +753,38 @@ function fw_collect_options(&$result, &$options, $settings = array(), $_recursio
741
  * @type int Limit the number of options that will be collected
742
  */
743
  'limit' => 0,
 
 
 
 
 
744
  );
745
 
746
  static $access_key = null;
747
 
748
- if (is_null($access_key)) {
749
- $access_key = new FW_Access_Key('fw_collect_options');
750
- }
751
-
752
  if (empty($options)) {
753
  return;
754
  }
755
 
756
- $settings = array_merge($default_settings, $settings);
757
-
758
  if (empty($_recursion_data)) {
 
 
 
 
 
 
759
  $_recursion_data = array(
760
  'level' => 1,
761
  'access_key' => $access_key,
762
  // todo: maybe add 'parent' => array('id' => '{id}', 'type' => 'container|option') ?
763
  );
764
- } elseif (
765
- !isset($_recursion_data['access_key'])
766
- ||
767
- !($_recursion_data['access_key'] instanceof FW_Access_Key)
768
- ||
769
- !($_recursion_data['access_key']->get_key() === 'fw_collect_options')
770
- ) {
771
  trigger_error('Call not allowed', E_USER_ERROR);
772
  }
773
 
@@ -813,6 +830,14 @@ function fw_collect_options(&$result, &$options, $settings = array(), $_recursio
813
  } else {
814
  $result[$option_id] = &$option;
815
  }
 
 
 
 
 
 
 
 
816
  } while(false);
817
 
818
  fw_collect_options(
@@ -884,6 +909,14 @@ function fw_collect_options(&$result, &$options, $settings = array(), $_recursio
884
  } else {
885
  $result[$option_id] = &$option;
886
  }
 
 
 
 
 
 
 
 
887
  } else {
888
  trigger_error('Invalid option: '. $option_id, E_USER_WARNING);
889
  }
489
  * @return string URI
490
  */
491
  function fw_locate_theme_path_uri($rel_path) {
492
+ try {
493
+ return FW_File_Cache::get($cache_key = 'theme-uri:'. $rel_path);
494
+ } catch (FW_File_Cache_Not_Found_Exception $e) {
495
+ if (is_child_theme() && file_exists(get_stylesheet_directory() . $rel_path)) {
496
+ $result = get_stylesheet_directory_uri() . $rel_path;
497
+ } elseif (file_exists(get_template_directory() . $rel_path)) {
498
+ $result = get_template_directory_uri() . $rel_path;
499
+ } else {
500
+ $result = 'about:blank#theme-file-not-found:'. $rel_path;
501
+ }
502
 
503
+ FW_File_Cache::set($cache_key, $result);
 
 
504
 
505
+ return $result;
506
+ }
507
  }
508
 
509
  /**
513
  * @return string URI
514
  */
515
  function fw_locate_theme_path($rel_path) {
516
+ try {
517
+ return FW_File_Cache::get($cache_key = 'theme-path:'. $rel_path);
518
+ } catch (FW_File_Cache_Not_Found_Exception $e) {
519
+ if (is_child_theme() && file_exists(get_stylesheet_directory() . $rel_path)) {
520
+ $result = get_stylesheet_directory() . $rel_path;
521
+ } elseif (file_exists(get_template_directory() . $rel_path)) {
522
+ $result = get_template_directory() . $rel_path;
523
+ } else {
524
+ $result = false;
525
+ }
526
 
527
+ FW_File_Cache::set($cache_key, $result);
 
 
528
 
529
+ return $result;
530
+ }
531
  }
532
 
533
  /**
753
  * @type int Limit the number of options that will be collected
754
  */
755
  'limit' => 0,
756
+ /**
757
+ * @type callable Executed on each collected option
758
+ * @since 2.6.0
759
+ */
760
+ 'callback' => null,
761
  );
762
 
763
  static $access_key = null;
764
 
 
 
 
 
765
  if (empty($options)) {
766
  return;
767
  }
768
 
 
 
769
  if (empty($_recursion_data)) {
770
+ if (is_null($access_key)) {
771
+ $access_key = new FW_Access_Key('fw_collect_options');
772
+ }
773
+
774
+ $settings = array_merge($default_settings, $settings);
775
+
776
  $_recursion_data = array(
777
  'level' => 1,
778
  'access_key' => $access_key,
779
  // todo: maybe add 'parent' => array('id' => '{id}', 'type' => 'container|option') ?
780
  );
781
+ } elseif (!(
782
+ isset($_recursion_data['access_key'])
783
+ &&
784
+ ($_recursion_data['access_key'] instanceof FW_Access_Key)
785
+ &&
786
+ ($_recursion_data['access_key']->get_key() === 'fw_collect_options')
787
+ )) {
788
  trigger_error('Call not allowed', E_USER_ERROR);
789
  }
790
 
830
  } else {
831
  $result[$option_id] = &$option;
832
  }
833
+
834
+ if ($settings['callback']) {
835
+ call_user_func_array($settings['callback'], array(array(
836
+ 'group' => 'container',
837
+ 'id' => $option_id,
838
+ 'option' => &$option,
839
+ )));
840
+ }
841
  } while(false);
842
 
843
  fw_collect_options(
909
  } else {
910
  $result[$option_id] = &$option;
911
  }
912
+
913
+ if ($settings['callback']) {
914
+ call_user_func_array($settings['callback'], array(array(
915
+ 'group' => 'option',
916
+ 'id' => $option_id,
917
+ 'option' => &$option,
918
+ )));
919
+ }
920
  } else {
921
  trigger_error('Invalid option: '. $option_id, E_USER_WARNING);
922
  }
framework/helpers/type/class-fw-type-register.php CHANGED
@@ -1,6 +1,10 @@
1
  <?php if ( ! defined( 'FW' ) ) die( 'Forbidden' );
2
 
3
  /**
 
 
 
 
4
  * @since 2.4.10
5
  */
6
  abstract class FW_Type_Register {
@@ -14,13 +18,13 @@ abstract class FW_Type_Register {
14
  /**
15
  * @var FW_Type[]
16
  */
17
- private $types = array();
18
 
19
  /**
20
  * Only these access keys will be able to access the registered types
21
  * @var array {'key': true}
22
  */
23
- private $access_keys = array();
24
 
25
  final public function __construct($access_keys) {
26
  {
@@ -66,4 +70,20 @@ abstract class FW_Type_Register {
66
 
67
  return $this->types;
68
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
1
  <?php if ( ! defined( 'FW' ) ) die( 'Forbidden' );
2
 
3
  /**
4
+ * Give users the possibility to register a type safely
5
+ * Instead of doing apply_filters('my_types', $types) where someone can mess your data
6
+ * with this class you do do_action('register_my_types', $types)
7
+ * and users will be able only to $types->register(new Allowed_Type_Class())
8
  * @since 2.4.10
9
  */
10
  abstract class FW_Type_Register {
18
  /**
19
  * @var FW_Type[]
20
  */
21
+ protected $types = array();
22
 
23
  /**
24
  * Only these access keys will be able to access the registered types
25
  * @var array {'key': true}
26
  */
27
+ protected $access_keys = array();
28
 
29
  final public function __construct($access_keys) {
30
  {
70
 
71
  return $this->types;
72
  }
73
+
74
+ /**
75
+ * @param FW_Access_Key $access_key
76
+ * @param $type
77
+ *
78
+ * @return FW_Type|null
79
+ * @internal
80
+ * @since 2.5.12
81
+ */
82
+ public function _get_type(FW_Access_Key $access_key, $type) {
83
+ if (!isset($this->access_keys[$access_key->get_key()])) {
84
+ trigger_error('Method call denied', E_USER_ERROR);
85
+ }
86
+
87
+ return isset($this->types[$type]) ? $this->types[$type] : null;
88
+ }
89
  }
framework/includes/hooks.php CHANGED
@@ -41,63 +41,6 @@
41
  add_action('fw_container_types_init', '_action_fw_init_container_types');
42
  }
43
 
44
- /**
45
- * Term Meta
46
- */
47
- {
48
- /**
49
- * Prepare $wpdb as soon as it's possible
50
- * @internal
51
- */
52
- function _action_term_meta_wpdb_fix() {
53
- /** @var WPDB $wpdb */
54
- global $wpdb;
55
-
56
- $wpdb->fw_termmeta = $wpdb->prefix . 'fw_termmeta';
57
-
58
- {
59
- require_once dirname(__FILE__) .'/term-meta/function_fw_term_meta_setup_blog.php';
60
- _fw_term_meta_setup_blog();
61
- }
62
- }
63
- add_action( 'switch_blog', '_action_term_meta_wpdb_fix', 3 );
64
-
65
- _action_term_meta_wpdb_fix();
66
-
67
- /**
68
- * When a term is deleted, delete its meta.
69
- *
70
- * @param mixed $term_id
71
- *
72
- * @return void
73
- * @internal
74
- */
75
- function _action_fw_delete_term( $term_id ) {
76
- $term_id = (int) $term_id;
77
-
78
- if ( ! $term_id ) {
79
- return;
80
- }
81
-
82
- /** @var WPDB $wpdb */
83
- global $wpdb;
84
-
85
- $wpdb->delete( $wpdb->fw_termmeta, array( 'fw_term_id' => $term_id ), array( '%d' ) );
86
- }
87
- add_action( 'delete_term', '_action_fw_delete_term' );
88
-
89
- /**
90
- * Make sure to setup the fw_termmeta table
91
- * (useful in cases when the framework is used not as a plugin)
92
- * @internal
93
- */
94
- function _action_fw_setup_term_meta_after_theme_switch() {
95
- require_once dirname(__FILE__) .'/term-meta/function_fw_term_meta_setup_blog.php';
96
- _fw_term_meta_setup_blog();
97
- }
98
- add_action('after_switch_theme', '_action_fw_setup_term_meta_after_theme_switch', 7);
99
- }
100
-
101
  /**
102
  * Custom Github API service
103
  * Provides the same responses but is "unlimited"
@@ -109,3 +52,27 @@ function _fw_filter_github_api_url($url) {
109
  return 'http://github-api-cache.unyson.io';
110
  }
111
  add_filter('fw_github_api_url', '_fw_filter_github_api_url');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  add_action('fw_container_types_init', '_action_fw_init_container_types');
42
  }
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  /**
45
  * Custom Github API service
46
  * Provides the same responses but is "unlimited"
52
  return 'http://github-api-cache.unyson.io';
53
  }
54
  add_filter('fw_github_api_url', '_fw_filter_github_api_url');
55
+
56
+ /**
57
+ * Javascript events related to tinymce init
58
+ * @since 2.6.0
59
+ */
60
+ {
61
+ add_action('wp_tiny_mce_init', '_fw_action_tiny_mce_init');
62
+ function _fw_action_tiny_mce_init($mce_settings) {
63
+ ?>
64
+ <script type="text/javascript">
65
+ if (typeof fwEvents != 'undefined') { fwEvents.trigger('fw:tinymce:init:before'); }
66
+ </script>
67
+ <?php
68
+ }
69
+
70
+ add_action('after_wp_tiny_mce', '_fw_action_after_wp_tiny_mce');
71
+ function _fw_action_after_wp_tiny_mce($mce_settings) {
72
+ ?>
73
+ <script type="text/javascript">
74
+ if (typeof fwEvents != 'undefined') { fwEvents.trigger('fw:tinymce:init:after'); }
75
+ </script>
76
+ <?php
77
+ }
78
+ }
framework/includes/option-types/addable-box/static/js/scripts.js CHANGED
@@ -253,19 +253,16 @@ jQuery(document).ready(function ($) {
253
 
254
  $boxes.append($newBox);
255
 
256
- /**
257
- * Re-render wp-editor from newBox
258
- */
259
- if ($option.find('.fw-option-box').length > 1 && window.fwWpEditorRefreshIds) {
260
- if ($option.find('.fw-option-type-wp-editor').length > 0) {
261
- fwWpEditorRefreshIds(
262
- jQuery(
263
- $option.find('.default-box-template').attr('data-template')
264
- ).find('textarea').attr('id'),
265
-
266
- $newBox
267
- );
268
- }
269
  }
270
 
271
  methods.initControls($newBox);
253
 
254
  $boxes.append($newBox);
255
 
256
+ // Re-render wp-editor
257
+ if (
258
+ window.fwWpEditorRefreshIds
259
+ &&
260
+ $newBox.find('.fw-option-type-wp-editor:first').length
261
+ ) {
262
+ fwWpEditorRefreshIds(
263
+ $newBox.find('.fw-option-type-wp-editor textarea:first').attr('id'),
264
+ $newBox
265
+ );
 
 
 
266
  }
267
 
268
  methods.initControls($newBox);
framework/includes/option-types/addable-option/static/js/scripts.js CHANGED
@@ -71,9 +71,19 @@ jQuery(document).ready(function ($) {
71
 
72
  $button.attr('data-increment', increment + 1);
73
 
74
- $options.append(
75
- $newOption
76
- );
 
 
 
 
 
 
 
 
 
 
77
 
78
  // remove focus form "Add" button to prevent pressing space/enter to add easy many options
79
  $newOption.find('input,select,textarea').first().focus();
71
 
72
  $button.attr('data-increment', increment + 1);
73
 
74
+ $options.append($newOption);
75
+
76
+ // Re-render wp-editor
77
+ if (
78
+ window.fwWpEditorRefreshIds
79
+ &&
80
+ $newOption.find('.fw-option-type-wp-editor:first').length
81
+ ) {
82
+ fwWpEditorRefreshIds(
83
+ $newOption.find('.fw-option-type-wp-editor textarea:first').attr('id'),
84
+ $newOption
85
+ );
86
+ }
87
 
88
  // remove focus form "Add" button to prevent pressing space/enter to add easy many options
89
  $newOption.find('input,select,textarea').first().focus();
framework/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php CHANGED
@@ -45,18 +45,26 @@ class FW_Option_Type_Addable_Popup extends FW_Option_Type
45
  {
46
  unset($option['attr']['name'], $option['attr']['value']);
47
 
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
- 'limit' => $option['limit']
 
 
 
 
 
 
 
 
54
  ));
55
 
56
  $sortable_image = fw_get_framework_directory_uri('/static/img/sort-vertically.png');
57
 
58
  return fw_render_view(
59
- fw_get_framework_directory('/includes/option-types/' . $this->get_type() . '/views/view.php'),
60
  compact('id', 'option', 'data', 'sortable_image')
61
  );
62
  }
45
  {
46
  unset($option['attr']['name'], $option['attr']['value']);
47
 
48
+ $option['attr']['data-for-js'] =
49
+ /**
50
+ * Prevent js error when the generated html is used in another option type js template with {{...}}
51
+ * Do this trick because {{ is not escaped/encoded by fw_htmlspecialchars()
52
+ * Fixes https://github.com/ThemeFuse/Unyson/issues/1877
53
+ */
54
+ json_encode(explode('{{',
55
+ json_encode(array(
56
+ 'title' => empty($option['popup-title']) ? $option['label'] : $option['popup-title'],
57
+ 'options' => $this->transform_options($option['popup-options']),
58
+ 'template' => $option['template'],
59
+ 'size' => $option['size'],
60
+ 'limit' => $option['limit']
61
+ ))
62
  ));
63
 
64
  $sortable_image = fw_get_framework_directory_uri('/static/img/sort-vertically.png');
65
 
66
  return fw_render_view(
67
+ fw_get_framework_directory('/includes/option-types/' . $this->get_type() . '/view.php'),
68
  compact('id', 'option', 'data', 'sortable_image')
69
  );
70
  }
framework/includes/option-types/addable-popup/static/css/styles.css CHANGED
@@ -1,4 +1,4 @@
1
- .fw-option-type-addable-popup .item{
2
  position: relative;
3
  background-color: #fff;
4
  min-height: 40px;
@@ -10,23 +10,23 @@
10
  word-wrap: break-word;
11
  }
12
 
13
- .fw-option-type-addable-popup .item:not(.default) + .item:not(.default){
14
  border-top: 1px dashed #E1E1E1;
15
  }
16
 
17
- .fw-option-type-addable-popup .items-wrapper{
18
  border: 1px solid #e1e1e1;
19
  margin-bottom: 20px;
20
  }
21
 
22
- .fw-option-type-addable-popup .delete-item{
23
  position: absolute;
24
  right: 7px;
25
  top:50%;
26
  margin-top: -10px;
27
  }
28
 
29
- .fw-option-type-addable-popup .sort-item{
30
  position: absolute;
31
  left: 11px;
32
  top:50%;
@@ -34,10 +34,7 @@
34
  width: 7px;
35
  }
36
 
37
- .fw-option-type-addable-popup .item.default{
38
- display: none;
39
- }
40
-
41
  .fw-option-type-addable-popup:not(.is-sortable) .sort-item {
42
  display: none;
43
  }
1
+ .fw-option-type-addable-popup .item {
2
  position: relative;
3
  background-color: #fff;
4
  min-height: 40px;
10
  word-wrap: break-word;
11
  }
12
 
13
+ .fw-option-type-addable-popup .item + .item {
14
  border-top: 1px dashed #E1E1E1;
15
  }
16
 
17
+ .fw-option-type-addable-popup .items-wrapper {
18
  border: 1px solid #e1e1e1;
19
  margin-bottom: 20px;
20
  }
21
 
22
+ .fw-option-type-addable-popup .delete-item {
23
  position: absolute;
24
  right: 7px;
25
  top:50%;
26
  margin-top: -10px;
27
  }
28
 
29
+ .fw-option-type-addable-popup .sort-item {
30
  position: absolute;
31
  left: 11px;
32
  top:50%;
34
  width: 7px;
35
  }
36
 
37
+ .fw-option-type-addable-popup .default-item,
 
 
 
38
  .fw-option-type-addable-popup:not(.is-sortable) .sort-item {
39
  display: none;
40
  }
framework/includes/option-types/addable-popup/static/js/addable-popup.js CHANGED
@@ -1,16 +1,18 @@
1
  (function ($, _, fwEvents, window) {
2
  var addablePopup = function () {
3
  var $this = $(this),
4
- $defaultItem = $this.find('.item.default'),
5
  nodes = {
6
  $optionWrapper: $this,
7
  $addButton: $this.find('.add-new-item'),
8
  $itemsWrapper: $this.find('.items-wrapper'),
9
  getDefaultItem: function () {
10
- return $defaultItem.clone().removeClass('default');
11
  }
12
  },
13
- data = JSON.parse(nodes.$optionWrapper.attr('data-for-js')),
 
 
14
  utils = {
15
  modal: new fw.OptionsModal({
16
  title: data.title,
@@ -18,10 +20,10 @@
18
  size : data.size
19
  }),
20
  countItems: function () {
21
- return nodes.$itemsWrapper.find('.item:not(.default)').length;
22
  },
23
  removeDefaultItem: function () {
24
- nodes.$itemsWrapper.find('.item.default').remove();
25
  },
26
  toogleNodes : function(){
27
  utils.toogleItemsWrapper();
@@ -54,7 +56,7 @@
54
  }
55
 
56
  nodes.$itemsWrapper.sortable({
57
- items: '.item:not(.default)',
58
  cursor: 'move',
59
  distance: 2,
60
  tolerance: 'pointer',
@@ -73,7 +75,7 @@
73
  });
74
  },
75
  initItemsTemplates: function () {
76
- var $items = nodes.$itemsWrapper.find('.item:not(.default)');
77
  if ($items.length > 0) {
78
  $items.each(function () {
79
  utils.editItem($(this), JSON.parse($(this).find('input').val()));
@@ -134,7 +136,7 @@
134
  nodes.$optionWrapper.trigger('change'); // for customizer
135
  });
136
 
137
- nodes.$itemsWrapper.on('click', '.item', function (e) {
138
  e.preventDefault();
139
 
140
  var values = {};
1
  (function ($, _, fwEvents, window) {
2
  var addablePopup = function () {
3
  var $this = $(this),
4
+ $defaultItem = $this.find('.default-item:first'),
5
  nodes = {
6
  $optionWrapper: $this,
7
  $addButton: $this.find('.add-new-item'),
8
  $itemsWrapper: $this.find('.items-wrapper'),
9
  getDefaultItem: function () {
10
+ return $defaultItem.clone().removeClass('default-item').addClass('item');
11
  }
12
  },
13
+ data = JSON.parse(
14
+ JSON.parse(nodes.$optionWrapper.attr('data-for-js')).join('{{') // check option php class
15
+ ),
16
  utils = {
17
  modal: new fw.OptionsModal({
18
  title: data.title,
20
  size : data.size
21
  }),
22
  countItems: function () {
23
+ return nodes.$itemsWrapper.find('> .item').length;
24
  },
25
  removeDefaultItem: function () {
26
+ nodes.$optionWrapper.find('.default-item:first').remove();
27
  },
28
  toogleNodes : function(){
29
  utils.toogleItemsWrapper();
56
  }
57
 
58
  nodes.$itemsWrapper.sortable({
59
+ items: '> .item',
60
  cursor: 'move',
61
  distance: 2,
62
  tolerance: 'pointer',
75
  });
76
  },
77
  initItemsTemplates: function () {
78
+ var $items = nodes.$itemsWrapper.find('> .item');
79
  if ($items.length > 0) {
80
  $items.each(function () {
81
  utils.editItem($(this), JSON.parse($(this).find('input').val()));
136
  nodes.$optionWrapper.trigger('change'); // for customizer
137
  });
138
 
139
+ nodes.$itemsWrapper.on('click', '> .item', function (e) {
140
  e.preventDefault();
141
 
142
  var values = {};
framework/includes/option-types/addable-popup/{views/view.php → view.php} RENAMED
@@ -17,24 +17,12 @@ if ($option['sortable']) {
17
  $increment_placeholder = '###-addable-popup-increment-'. fw_rand_md5() .'-###';
18
  ?>
19
  <div <?php echo fw_attr_to_html($attr); ?>>
 
 
 
 
 
20
  <div class="items-wrapper">
21
- <!-- Fixes https://github.com/ThemeFuse/Unyson/issues/1278 -->
22
- <?php echo fw()->backend->option_type('hidden')->render($id, array('value' => '~'), array(
23
- 'id_prefix' => $data['id_prefix'],
24
- 'name_prefix' => $data['name_prefix'],
25
- )); ?>
26
- <div class="item default">
27
- <div class="input-wrapper">
28
- <?php echo fw()->backend->option_type('hidden')->render('', array('value' => '[]'), array(
29
- 'id_prefix' => $data['id_prefix'] . $id . '-' . $increment_placeholder,
30
- 'name_prefix' => $data['name_prefix'] . '[' . $id . ']',
31
- )); ?>
32
- </div>
33
- <img src="<?php echo esc_attr($sortable_image); ?>" class="sort-item"/>
34
-
35
- <div class="content"></div>
36
- <a href="#" class="dashicons fw-x delete-item"></a>
37
- </div>
38
  <?php foreach ($data['value'] as $key => $value): ?>
39
  <div class="item">
40
  <div class="input-wrapper">
@@ -50,7 +38,18 @@ $increment_placeholder = '###-addable-popup-increment-'. fw_rand_md5() .'-###';
50
  </div>
51
  <?php endforeach; ?>
52
  </div>
53
- <!--<div class="dashicons dashicons-plus add-new-item"></div>-->
 
 
 
 
 
 
 
 
 
 
 
54
  <?php
55
  echo fw_html_tag('button', array(
56
  'type' => 'button',
17
  $increment_placeholder = '###-addable-popup-increment-'. fw_rand_md5() .'-###';
18
  ?>
19
  <div <?php echo fw_attr_to_html($attr); ?>>
20
+ <!-- Fixes https://github.com/ThemeFuse/Unyson/issues/1278 -->
21
+ <?php echo fw()->backend->option_type('hidden')->render($id, array('value' => '~'), array(
22
+ 'id_prefix' => $data['id_prefix'],
23
+ 'name_prefix' => $data['name_prefix'],
24
+ )); ?>
25
  <div class="items-wrapper">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  <?php foreach ($data['value'] as $key => $value): ?>
27
  <div class="item">
28
  <div class="input-wrapper">
38
  </div>
39
  <?php endforeach; ?>
40
  </div>
41
+ <div class="default-item">
42
+ <div class="input-wrapper">
43
+ <?php echo fw()->backend->option_type('hidden')->render('', array('value' => '[]'), array(
44
+ 'id_prefix' => $data['id_prefix'] . $id . '-' . $increment_placeholder,
45
+ 'name_prefix' => $data['name_prefix'] . '[' . $id . ']',
46
+ )); ?>
47
+ </div>
48
+ <img src="<?php echo esc_attr($sortable_image); ?>" class="sort-item"/>
49
+
50
+ <div class="content"></div>
51
+ <a href="#" class="dashicons fw-x delete-item"></a>
52
+ </div>
53
  <?php
54
  echo fw_html_tag('button', array(
55
  'type' => 'button',
framework/includes/option-types/color-picker/static/js/scripts.js CHANGED
@@ -2,7 +2,7 @@ jQuery(document).ready(function($){
2
  var helpers = {
3
  optionClass: 'fw-option-type-color-picker',
4
  eventNamespace: '.fwOptionTypeColorPicker',
5
- colorRegex: /^#[a-f0-9]{3}([a-f0-9]{3})?$/,
6
  localized: window._fw_option_type_color_picker_localized,
7
  /**
8
  * Return true if color is dark
2
  var helpers = {
3
  optionClass: 'fw-option-type-color-picker',
4
  eventNamespace: '.fwOptionTypeColorPicker',
5
+ colorRegex: /^#[a-f0-9]{3}([a-f0-9]{3})?$/i,
6
  localized: window._fw_option_type_color_picker_localized,
7
  /**
8
  * Return true if color is dark
framework/includes/option-types/icon-v2/static/js/icon-picker.js CHANGED
@@ -13,9 +13,32 @@ window.fwOptionTypeIconV2Picker = (function ($) {
13
  $(document).on(
14
  'input',
15
  '.fw-icon-v2-icons-library .fw-icon-v2-toolbar input',
16
- applyFilters
17
  );
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  return {
20
  pick: pick
21
  };
@@ -439,5 +462,5 @@ window.fwOptionTypeIconV2Picker = (function ($) {
439
  ).height() - toolbarHeight - 50
440
  );
441
  }
442
-
443
  })(jQuery);
 
13
  $(document).on(
14
  'input',
15
  '.fw-icon-v2-icons-library .fw-icon-v2-toolbar input',
16
+ handleInput
17
  );
18
 
19
+ var throttledApplyFilters = _.throttle(applyFilters, 200);
20
+
21
+ var previousSearch = '';
22
+
23
+ function handleInput () {
24
+ console.log(previousSearch);
25
+
26
+ if (
27
+ previousSearch.trim().length === 0
28
+ &&
29
+ $(this).val().trim().length === 0
30
+ ) return;
31
+
32
+ if ( $(this).val().trim().length === 0 ) {
33
+ throttledApplyFilters();
34
+ }
35
+
36
+ if ($(this).val().trim().length > 2)
37
+ throttledApplyFilters();
38
+
39
+ previousSearch = $(this).val();
40
+ }
41
+
42
  return {
43
  pick: pick
44
  };
462
  ).height() - toolbarHeight - 50
463
  );
464
  }
 
465
  })(jQuery);
466
+
framework/includes/option-types/image-picker/class-fw-option-type-image-picker.php CHANGED
@@ -117,8 +117,7 @@ class Fw_Option_Type_Image_Picker extends FW_Option_Type
117
  $attr['selected'] = 'selected';
118
  }
119
 
120
- if (is_string($choice)) {
121
- // is 'http://.../small.png'
122
  $choice = array(
123
  'small' => array(
124
  'src' => $choice
@@ -126,16 +125,14 @@ class Fw_Option_Type_Image_Picker extends FW_Option_Type
126
  );
127
  }
128
 
129
- if (is_string($choice['small'])) {
130
- // is 'http://.../small.png'
131
  $choice['small'] = array(
132
  'src' => $choice['small']
133
  );
134
  }
135
  $attr['data-small-img-attr'] = json_encode($choice['small']);
136
 
137
- // required by image-picker plugin
138
- $attr['data-img-src'] = $choice['small']['src'];
139
 
140
  if (!empty($choice['large'])) {
141
  if (is_string($choice['large'])) {
@@ -155,6 +152,10 @@ class Fw_Option_Type_Image_Picker extends FW_Option_Type
155
  $attr['data-extra-data'] = json_encode($choice['data']);
156
  }
157
 
 
 
 
 
158
  $html .= fw_html_tag('option', $attr, fw_htmlspecialchars(isset($choice['label']) ? $choice['label'] : ''));
159
  }
160
 
117
  $attr['selected'] = 'selected';
118
  }
119
 
120
+ if (is_string($choice)) { // is 'http://.../small.png'
 
121
  $choice = array(
122
  'small' => array(
123
  'src' => $choice
125
  );
126
  }
127
 
128
+ if (is_string($choice['small'])) { // is 'http://.../small.png'
 
129
  $choice['small'] = array(
130
  'src' => $choice['small']
131
  );
132
  }
133
  $attr['data-small-img-attr'] = json_encode($choice['small']);
134
 
135
+ $attr['data-img-src'] = $choice['small']['src']; // required by image-picker plugin
 
136
 
137
  if (!empty($choice['large'])) {
138
  if (is_string($choice['large'])) {
152
  $attr['data-extra-data'] = json_encode($choice['data']);
153
  }
154
 
155
+ if (!empty($choice['attr'])) {
156
+ $attr = array_merge($choice['attr'], $attr);
157
+ }
158
+
159
  $html .= fw_html_tag('option', $attr, fw_htmlspecialchars(isset($choice['label']) ? $choice['label'] : ''));
160
  }
161
 
framework/includes/option-types/oembed/static/js/oembed.js CHANGED
@@ -1,23 +1,34 @@
1
  (function ($, _, fwEvents) {
 
 
 
 
 
 
2
  var oembed = function () {
3
  var $wrapper = $(this);
4
  var $input = $wrapper.find('input[type=text]');
5
  var $iframeWrapper = $wrapper.find('.fw-oembed-preview');
6
 
7
  $input.on('input',
8
- _.debounce(function () {
9
- wp.ajax.post(
10
- 'get_oembed_response',
11
- {
12
- '_nonce': $wrapper.data('nonce'),
13
- 'preview': $wrapper.data('preview'),
14
- 'url': $input.val()
15
- }).done(function (data) {
16
- $iframeWrapper.html(data.response);
17
- }).fail(function () {
18
- })
19
-
20
- }, 300)
 
 
 
 
 
21
  );
22
  };
23
 
1
  (function ($, _, fwEvents) {
2
+
3
+ var is_url = function(str) {
4
+ var pattern = new RegExp(/^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/, 'i');
5
+ return pattern.test(str);
6
+ };
7
+
8
  var oembed = function () {
9
  var $wrapper = $(this);
10
  var $input = $wrapper.find('input[type=text]');
11
  var $iframeWrapper = $wrapper.find('.fw-oembed-preview');
12
 
13
  $input.on('input',
14
+ _.debounce(function () {
15
+ if( $input.val() && is_url( $input.val() ) ) {
16
+ wp.ajax.post(
17
+ 'get_oembed_response',
18
+ {
19
+ '_nonce': $wrapper.data('nonce'),
20
+ 'preview': $wrapper.data('preview'),
21
+ 'url': $input.val()
22
+ }).done(function (data) {
23
+ $iframeWrapper.html(data.response);
24
+ }).fail(function () {
25
+ $iframeWrapper.html('');
26
+ console.error('Get Oembed Response: Ajax error.', error);
27
+ })
28
+ } else {
29
+ $iframeWrapper.html('');
30
+ }
31
+ }, 300)
32
  );
33
  };
34
 
framework/includes/option-types/rgba-color-picker/static/js/scripts.js CHANGED
@@ -35,7 +35,7 @@ jQuery(function($){
35
  var helpers = {
36
  optionClass: 'fw-option-type-rgba-color-picker',
37
  eventNamespace: '.fwOptionTypeRgbaColorPicker',
38
- hexColorRegex: /^#[a-f0-9]{3}([a-f0-9]{3})?$/,
39
  localized: window._fw_option_type_rgba_color_picker_localized,
40
  increment: 0,
41
  isColorDark: function(rgbaColor) {
@@ -97,7 +97,7 @@ jQuery(function($){
97
  */
98
  $input.one('focus', function(){
99
  if (!$.trim($input.val()).length) { // If the input value is empty, there a glitches with opacity slider
100
- $input.val('rgba(0,0,0,0)');
101
  }
102
 
103
  $input.iris({
35
  var helpers = {
36
  optionClass: 'fw-option-type-rgba-color-picker',
37
  eventNamespace: '.fwOptionTypeRgbaColorPicker',
38
+ hexColorRegex: /^#[a-f0-9]{3}([a-f0-9]{3})?$/i,
39
  localized: window._fw_option_type_rgba_color_picker_localized,
40
  increment: 0,
41
  isColorDark: function(rgbaColor) {
97
  */
98
  $input.one('focus', function(){
99
  if (!$.trim($input.val()).length) { // If the input value is empty, there a glitches with opacity slider
100
+ $input.val('rgba(255,255,255,1)');
101
  }
102
 
103
  $input.iris({
framework/includes/option-types/wp-editor/includes/class-fw-wp-editor-settings.php CHANGED
@@ -182,17 +182,19 @@ class FW_WP_Editor_Manager {
182
  /**
183
  * Loop thought all settings and decode json values
184
  */
185
- foreach ($mce_settings as &$setting) {
186
- if (
187
- is_string($setting)
188
- &&
189
- !empty($setting)
190
- &&
191
- in_array($setting{0}, array('[', '{'), true)
192
- &&
193
- ! is_null($decoded = json_decode($setting))
194
- ) {
195
- $setting = $decoded;
 
 
196
  }
197
  }
198
 
182
  /**
183
  * Loop thought all settings and decode json values
184
  */
185
+ if ($mce_settings) {
186
+ foreach ($mce_settings as &$setting) {
187
+ if (
188
+ is_string($setting)
189
+ &&
190
+ !empty($setting)
191
+ &&
192
+ in_array($setting{0}, array('[', '{'), true)
193
+ &&
194
+ ! is_null($decoded = json_decode($setting))
195
+ ) {
196
+ $setting = $decoded;
197
+ }
198
  }
199
  }
200
 
framework/includes/option-types/wp-editor/static/scripts.js CHANGED
@@ -93,6 +93,9 @@
93
 
94
  try {
95
  tinymce.init( tinyMCEPreInit.mceInit[ id ] );
 
 
 
96
  } catch(e){
97
  console.error('wp-editor init error', id, e);
98
  return;
@@ -101,10 +104,8 @@
101
  // fixes https://github.com/ThemeFuse/Unyson/issues/1615
102
  if (typeof window.wpLink != 'undefined') {
103
  try {
104
- window.wpLink.open(id);
105
- } catch (e) {}
106
 
107
- try {
108
  window.wpLink.close();
109
 
110
  /**
93
 
94
  try {
95
  tinymce.init( tinyMCEPreInit.mceInit[ id ] );
96
+
97
+ // Remove garbage. This caused lag on page scroll after OptionsModal with wp-editor close
98
+ $option.on('remove', function(){ tinymce.execCommand('mceRemoveEditor', false, id); });
99
  } catch(e){
100
  console.error('wp-editor init error', id, e);
101
  return;
104
  // fixes https://github.com/ThemeFuse/Unyson/issues/1615
105
  if (typeof window.wpLink != 'undefined') {
106
  try {
107
+ // do not do .open() // fixes https://github.com/ThemeFuse/Unyson/issues/1901
 
108
 
 
109
  window.wpLink.close();
110
 
111
  /**
framework/includes/term-meta/function_fw_term_meta_setup_blog.php DELETED
@@ -1,38 +0,0 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- if (!function_exists('_fw_term_meta_setup_blog')):
4
-
5
- /**
6
- * Setup term meta storage for current blog
7
- * @internal
8
- */
9
- function _fw_term_meta_setup_blog() {
10
- /** @var WPDB $wpdb */
11
- global $wpdb;
12
-
13
- $charset_collate = '';
14
- if ( ! empty( $wpdb->charset ) ) {
15
- $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
16
- }
17
- if ( ! empty( $wpdb->collate ) ) {
18
- $charset_collate .= " COLLATE $wpdb->collate";
19
- }
20
-
21
- $table_name = $wpdb->prefix .'fw_termmeta'; // note: same table name is used in hooks.php for $wpdb->fw_termmeta
22
-
23
- $tables = $wpdb->get_results( "show tables like '{$table_name}'" );
24
- if ( empty( $tables ) ) {
25
- $wpdb->query( "CREATE TABLE {$table_name} (
26
- meta_id bigint(20) unsigned NOT NULL auto_increment,
27
- fw_term_id bigint(20) unsigned NOT NULL default '0',
28
- meta_key varchar(255) default NULL,
29
- meta_value longtext,
30
- PRIMARY KEY (meta_id),
31
- KEY fw_term_id (fw_term_id),
32
- KEY `meta_key` (`meta_key`(191))
33
- ) $charset_collate;"
34
- );
35
- }
36
- }
37
-
38
- endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
framework/manifest.php CHANGED
@@ -4,4 +4,4 @@ $manifest = array();
4
 
5
  $manifest['name'] = __('Unyson', 'fw');
6
 
7
- $manifest['version'] = '2.5.12';
4
 
5
  $manifest['name'] = __('Unyson', 'fw');
6
 
7
+ $manifest['version'] = '2.6.0';
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === Unyson ===
2
  Contributors: unyson
3
  Tags: page builder, 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.3
5
  Tested up to: 4.6
6
- Stable tag: 2.5.12
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -44,9 +44,9 @@ Theme developers can test the compatibility of their themes with new extensions
44
 
45
  = Minimum Requirements =
46
 
47
- * WordPress 4.0 or greater
48
  * PHP version 5.2.4 or greater
49
- * MySQL version 5.0 or greater
50
 
51
  = Installation =
52
 
@@ -83,6 +83,12 @@ Yes; Unyson will work with any theme.
83
 
84
  == Changelog ==
85
 
 
 
 
 
 
 
86
  = 2.5.12 =
87
  * Fixed `wp-editor` option error when used in Theme Settings [#1860](https://github.com/ThemeFuse/Unyson/issues/1860)
88
 
@@ -305,73 +311,4 @@ Yes; Unyson will work with any theme.
305
  * Options can be used in [Customizer](https://codex.wordpress.org/Theme_Customization_API) [#410](https://github.com/ThemeFuse/Unyson/issues/410)
306
  * Fixed [#77](https://github.com/ThemeFuse/Unyson/issues/77)
307
 
308
- = 2.2.10 =
309
- * Fixed [#539](https://github.com/ThemeFuse/Unyson/issues/539)
310
-
311
- = 2.2.9 =
312
- * Fixed [#530](https://github.com/ThemeFuse/Unyson/issues/530), [#529](https://github.com/ThemeFuse/Unyson/issues/529), [#502](https://github.com/ThemeFuse/Unyson/issues/502)
313
- * Fixes for [#520](https://github.com/ThemeFuse/Unyson/issues/520)
314
- * Minor fix in autosave
315
-
316
- = 2.2.8 =
317
- * Fixed [#453](https://github.com/ThemeFuse/Unyson/issues/453)
318
- * Improved option type `multi-picker` html render [#442](https://github.com/ThemeFuse/Unyson/issues/442)
319
- * Option type `rgba-color-picker` optimizations [#442](https://github.com/ThemeFuse/Unyson/issues/442)
320
- * `fw_resize()` improvements [#447](https://github.com/ThemeFuse/Unyson/issues/447)
321
- * Fixed [#445](https://github.com/ThemeFuse/Unyson/issues/445), [#161](https://github.com/ThemeFuse/Unyson/issues/161), [#484](https://github.com/ThemeFuse/Unyson/issues/484), [#456](https://github.com/ThemeFuse/Unyson/issues/456)
322
- * Added the possibility to prevent box auto-close [#466](https://github.com/ThemeFuse/Unyson/issues/466)
323
- * Fixed the `_get_value_from_input()` method in some option types [#275](https://github.com/ThemeFuse/Unyson/issues/275#issuecomment-94084590)
324
- * Added the `limit` parameter for option type `addable-popup` [#478](https://github.com/ThemeFuse/Unyson/issues/478)
325
- * Fixed popup position in IE [#483](https://github.com/ThemeFuse/Unyson/issues/483)
326
- * Created `fw_post_options_update` action
327
- * Improved post save: Options are saved in revision and autosave. Restore from revision works.
328
-
329
- = 2.2.7 =
330
- * Option type `popup` fixes
331
- * Added "Show/Hide other extensions" button [#307](https://github.com/ThemeFuse/Unyson/issues/307)
332
- * `fw.soleModal` added `afterOpen` and `afterClose` callbacks [#379](https://github.com/ThemeFuse/Unyson/issues/379)
333
- * Fixed [#432](https://github.com/ThemeFuse/Unyson/issues/432), [#408](https://github.com/ThemeFuse/Unyson/issues/408)
334
-
335
- = 2.2.6 =
336
- * Fixed [#404](https://github.com/ThemeFuse/Unyson/issues/404)
337
- * Added the Translation extension in Available Extensions list
338
-
339
- = 2.2.5 =
340
- * Fixed [PageBuilder#26](https://github.com/ThemeFuse/Unyson-PageBuilder-Extension/issues/26)
341
-
342
- = 2.2.4 =
343
- * Fixed [#398](https://github.com/ThemeFuse/Unyson/issues/398)
344
- * Removed option type `runnable` [#399](https://github.com/ThemeFuse/Unyson/issues/399)
345
-
346
- = 2.2.3 =
347
- * Fixed [#397](https://github.com/ThemeFuse/Unyson/issues/397), [#394](https://github.com/ThemeFuse/Unyson/issues/394), [#389](https://github.com/ThemeFuse/Unyson/issues/389), [#384](https://github.com/ThemeFuse/Unyson/issues/384), [#355](https://github.com/ThemeFuse/Unyson/issues/355)
348
- * Added option type `runnable`
349
-
350
- = 2.2.2 =
351
- * Added experimental `$option['option_handler']` [636ed56](https://github.com/ThemeFuse/Unyson/commit/636ed56fe499a4e855b5f49198747460833539a3)
352
- * `<input required ... />` works in `fw.OptionsModal` [#274](https://github.com/ThemeFuse/Unyson/issues/274)
353
- * Fixed [#381](https://github.com/ThemeFuse/Unyson/issues/381), [#382](https://github.com/ThemeFuse/Unyson/issues/382), [#385](https://github.com/ThemeFuse/Unyson/issues/385), [Shortcodes#15](https://github.com/ThemeFuse/Unyson-Shortcodes-Extension/issues/15)
354
-
355
- = 2.2.1 =
356
- * Fixed: Sub-extensions were not loaded [#368](https://github.com/ThemeFuse/Unyson/issues/368)
357
- * Fixed: $extension->locate_URI('/...') bug
358
-
359
- = 2.2.0 =
360
- * Added the possibility to load extensions from any directory
361
-
362
- ```
363
- function _filter_my_plugin_extensions($locations) {
364
- $locations['/path/to/plugin/extensions'] = 'https://uri.to/plugin/extensions';
365
- return $locations;
366
- }
367
- add_filter('fw_extensions_locations', '_filter_my_plugin_extensions');
368
- ```
369
-
370
- **Important!** Prefix your extension names to prevent conflicts.
371
-
372
- * Removed `array_merge($old_opts, $new_opts)` from options save [#266](https://github.com/ThemeFuse/Unyson/issues/266)
373
- * Tabs, Boxes, Groups, Options are now displayed in the order they are in array (not grouped) [#319](https://github.com/ThemeFuse/Unyson/issues/319)
374
- * Option type `multi-picker` fixes [#296](https://github.com/ThemeFuse/Unyson/issues/296)
375
- * Added the possibility to use custom `framework-customizations` directory name [#276](https://github.com/ThemeFuse/Unyson/issues/276)
376
- * Minor fixes
377
 
1
  === Unyson ===
2
  Contributors: unyson
3
  Tags: page builder, 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.4
5
  Tested up to: 4.6
6
+ Stable tag: 2.6.0
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
44
 
45
  = Minimum Requirements =
46
 
47
+ * WordPress 4.4 or greater
48
  * PHP version 5.2.4 or greater
49
+ * MySQL version 5.6 or greater
50
 
51
  = Installation =
52
 
83
 
84
  == Changelog ==
85
 
86
+ = 2.6.0 =
87
+ * Added [File Cache](https://github.com/ThemeFuse/Unyson/blob/16709330f1acc29453928fce0fafe69a8ea592c7/framework/helpers/class-fw-file-cache.php) [#1828](https://github.com/ThemeFuse/Unyson/issues/1828)
88
+ * Migration to native term meta [#1745](https://github.com/ThemeFuse/Unyson/issues/1745)
89
+ * The possibility to [register Available Extensions from theme](https://github.com/ThemeFuse/Unyson/blob/16709330f1acc29453928fce0fafe69a8ea592c7/framework/core/components/extensions/manager/class--fw-extensions-manager.php#L232-L238)
90
+ * Fixed [#1860](https://github.com/ThemeFuse/Unyson/issues/1860), [#1877](https://github.com/ThemeFuse/Unyson/issues/1877), [#1897](https://github.com/ThemeFuse/Unyson/issues/1897), [#1810](https://github.com/ThemeFuse/Unyson/issues/1810)
91
+
92
  = 2.5.12 =
93
  * Fixed `wp-editor` option error when used in Theme Settings [#1860](https://github.com/ThemeFuse/Unyson/issues/1860)
94
 
311
  * Options can be used in [Customizer](https://codex.wordpress.org/Theme_Customization_API) [#410](https://github.com/ThemeFuse/Unyson/issues/410)
312
  * Fixed [#77](https://github.com/ThemeFuse/Unyson/issues/77)
313
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
unyson.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: Unyson
4
  * Plugin URI: http://unyson.io/
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.5.12
7
  * Author: ThemeFuse
8
  * Author URI: http://themefuse.com
9
  * License: GPL2+
@@ -38,26 +38,7 @@ if (defined('FW')) {
38
  {
39
  /** @internal */
40
  function _action_fw_plugin_activate() {
41
- {
42
- require_once dirname(__FILE__) .'/framework/includes/term-meta/function_fw_term_meta_setup_blog.php';
43
-
44
- if (is_multisite() && is_network_admin()) {
45
- global $wpdb;
46
-
47
- $blogs = $wpdb->get_col( "SELECT blog_id FROM {$wpdb->blogs} WHERE site_id = '{$wpdb->siteid}'" );
48
- foreach ( $blogs as $blog_id ) {
49
- switch_to_blog( $blog_id );
50
- _fw_term_meta_setup_blog( $blog_id );
51
- }
52
-
53
- do {} while ( restore_current_blog() );
54
- } else {
55
- _fw_term_meta_setup_blog();
56
- }
57
- }
58
-
59
- // add special option (is used in another action)
60
- update_option('_fw_plugin_activated', true, false);
61
  }
62
  register_activation_hook( __FILE__, '_action_fw_plugin_activate' );
63
 
@@ -69,20 +50,11 @@ if (defined('FW')) {
69
  do_action('fw_after_plugin_activate');
70
  }
71
  }
72
- add_action('current_screen', '_action_fw_plugin_check_if_was_activated', 100);
73
- // as late as possible, but to be able to make redirects (content not started)
74
-
75
- /** @internal */
76
- function _action_fw_term_meta_new_blog( $blog_id, $user_id, $domain, $path, $site_id, $meta ) {
77
- if ( is_plugin_active_for_network( plugin_basename( __FILE__ ) ) ) {
78
- require_once dirname(__FILE__) .'/framework/includes/term-meta/function_fw_term_meta_setup_blog.php';
79
-
80
- switch_to_blog( $blog_id );
81
- _fw_term_meta_setup_blog();
82
- do {} while ( restore_current_blog() );
83
- }
84
- }
85
- add_action( 'wpmu_new_blog', '_action_fw_term_meta_new_blog', 10, 6 );
86
 
87
  /**
88
  * @param int $blog_id Blog ID
@@ -90,13 +62,11 @@ if (defined('FW')) {
90
  * @internal
91
  */
92
  function _action_fw_delete_blog( $blog_id, $drop ) {
93
- if ($drop) { // delete table created by the _fw_term_meta_setup_blog() function
94
- /** @var WPDB $wpdb */
95
- global $wpdb;
96
 
97
- if (property_exists($wpdb, 'fw_termmeta')) { // it should exist, but check to be sure
98
- $wpdb->query("DROP TABLE IF EXISTS {$wpdb->fw_termmeta};");
99
- }
100
  }
101
  }
102
  add_action( 'delete_blog', '_action_fw_delete_blog', 10, 2 );
3
  * Plugin Name: Unyson
4
  * Plugin URI: http://unyson.io/
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.6.0
7
  * Author: ThemeFuse
8
  * Author URI: http://themefuse.com
9
  * License: GPL2+
38
  {
39
  /** @internal */
40
  function _action_fw_plugin_activate() {
41
+ update_option('_fw_plugin_activated', true, false); // add special option (is used in another action)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  }
43
  register_activation_hook( __FILE__, '_action_fw_plugin_activate' );
44
 
50
  do_action('fw_after_plugin_activate');
51
  }
52
  }
53
+ add_action(
54
+ 'current_screen', // as late as possible, but to be able to make redirects (content not started)
55
+ '_action_fw_plugin_check_if_was_activated',
56
+ 100
57
+ );
 
 
 
 
 
 
 
 
 
58
 
59
  /**
60
  * @param int $blog_id Blog ID
62
  * @internal
63
  */
64
  function _action_fw_delete_blog( $blog_id, $drop ) {
65
+ if ($drop) {
66
+ global $wpdb; /** @var WPDB $wpdb */
 
67
 
68
+ // delete old termmeta table
69
+ $wpdb->query("DROP TABLE IF EXISTS `{$wpdb->prefix}fw_termmeta`;");
 
70
  }
71
  }
72
  add_action( 'delete_blog', '_action_fw_delete_blog', 10, 2 );