PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.1.1
Kirki – Freeform Page Builder, Website Builder & Customizer v6.1.1
6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / app / Services / AppsService.php
kirki / app / Services Last commit date
AppsService.php 3 days ago CollaborationCommentService.php 3 weeks ago CollaborationService.php 3 weeks ago CollectionItemService.php 3 weeks ago CollectionService.php 3 weeks ago EditorService.php 3 weeks ago FontService.php 3 days ago GlobalDataService.php 3 days ago MediaService.php 3 weeks ago PageService.php 3 days ago PageSettingsService.php 3 weeks ago UtilityPageService.php 3 days ago
AppsService.php
287 lines
1 <?php
2
3 namespace Kirki\App\Services;
4
5 defined('ABSPATH') || exit;
6
7 use Exception;
8 use Kirki\App\Constants\GlobalDataKeys;
9 use Kirki\App\DTO\Apps\AppDTO;
10 use Kirki\App\DTO\Apps\InstalledAppDTO;
11 use Kirki\App\Supports\Facades\GlobalData;
12 use Kirki\App\Supports\FileHandler;
13 use Kirki\App\Supports\FilterHooks;
14 use Kirki\Framework\Supports\Facades\DB;
15 use Kirki\Framework\Supports\Facades\File;
16 use Kirki\Framework\Supports\Facades\Http;
17 use Throwable;
18
19 use function Kirki\Framework\clean_path;
20 use function Kirki\Framework\collection;
21
22 class AppsService
23 {
24 /**
25 * @return AppDTO[]
26 */
27 public function get_all_apps()
28 {
29 $apps = Http::get(KIRKI_APPS_BASE_URL . '/apps.json');
30
31 if ($apps->failed()) {
32 throw new Exception(esc_html__('Failed to get apps.', 'kirki'));
33 }
34
35 return AppDTO::from_list($apps->json());
36 }
37
38 /**
39 * @return InstalledAppDTO[]
40 */
41 public function get_installed_apps()
42 {
43 $apps = GlobalData::get(GlobalDataKeys::INSTALLED_APPS);
44
45 return InstalledAppDTO::from_list(is_array($apps) ? $apps : []);
46 }
47
48 /**
49 * @return array
50 */
51 public function get_app_settings(string $app_slug)
52 {
53 $settings = GlobalData::get(sprintf('%s%s', GlobalDataKeys::APP_SETTINGS, $app_slug)) ?? [];
54
55 return FilterHooks::kirki_apps_configuration($app_slug, $settings);
56 }
57
58 /**
59 * @param string $app_slug
60 * @param array $settings
61 * @return array
62 */
63 public function save_app_settings(string $app_slug, array $settings)
64 {
65 GlobalData::update(sprintf('%s%s', GlobalDataKeys::APP_SETTINGS, $app_slug), $settings);
66
67 return $settings;
68 }
69
70 public function install_app(AppDTO $payload)
71 {
72 $app_slug = preg_replace('/[^a-zA-Z0-9\-]/', '', $payload->slug);
73 $src = $payload->src;
74 $version = $payload->version;
75
76 FileHandler::verify_directory_traversal($src);
77 FileHandler::verify_directory_traversal($app_slug);
78
79 $installed_apps = $this->get_installed_apps();
80
81 $is_already_installed = collection($installed_apps)->some(function(InstalledAppDTO $app) use ($app_slug) {
82 return $app->app_slug === $app_slug;
83 });
84
85 if ($is_already_installed) {
86 throw new Exception(esc_html__('This app is already installed. Please check the kirki-apps directory in wp-content folders.', 'kirki'));
87 }
88
89 // Define the remote zip URL and generate a unique file name
90 $remote_zip_url = KIRKI_APPS_BASE_URL . $src;
91 $file_name_new = uniqid('', true) . '.zip'; // 'random.ext'
92
93 $zip_file_path = FileHandler::download_zip_from_remote($remote_zip_url, $file_name_new);
94
95 if ($zip_file_path === false) {
96 throw new Exception(esc_html__('Failed to download the app zip file. Please check the remote URL.', 'kirki'));
97 }
98
99 // Get the WordPress wp-content directory
100 $base_upload_dir = WP_CONTENT_DIR; // Absolute path to 'uploads'
101 $temp_folder_path = clean_path($base_upload_dir . '/kirki-apps', false);
102
103 if (!File::is_writable($base_upload_dir)) {
104 File::delete($zip_file_path);
105 throw new Exception(esc_html__('The wp-content directory is not writable. Please check folder permissions.', 'kirki'));
106 }
107
108 if (File::missing($base_upload_dir)) {
109 if (!File::make_dir($temp_folder_path)) {
110 File::delete($zip_file_path);
111 throw new Exception(esc_html__('Failed to create directory: kirki-apps. Please check folder permissions.', 'kirki'));
112 }
113 }
114
115 $result = FileHandler::extract_zip_file($zip_file_path, $temp_folder_path);
116
117 if ($result === false) {
118 File::delete($zip_file_path);
119 throw new Exception(esc_html__('Failed to extract the app zip file. Please ensure the file is valid.', 'kirki'));
120 }
121
122 try {
123 $installed_apps = collection($installed_apps)->push(InstalledAppDTO::from_array([
124 'app_slug' => $app_slug,
125 'version' => $version,
126 ]))->map(fn(InstalledAppDTO $app) => $app->to_array())->to_array();
127
128 GlobalData::update(GlobalDataKeys::INSTALLED_APPS, $installed_apps);
129
130 File::delete($zip_file_path);
131
132 return [
133 'app_slug' => $app_slug,
134 'version' => $version
135 ];
136 } catch (Throwable $throwable) {
137 File::delete($zip_file_path);
138 throw new Exception(
139 sprintf(
140 /* translators: 1: error message */
141 esc_html__('An error occurred during the app installation: %s', 'kirki'),
142 esc_html($throwable->getMessage())
143 )
144 );
145 }
146 }
147
148 public function update_app(AppDTO $payload)
149 {
150 $app_slug = preg_replace('/[^a-zA-Z0-9\-]/', '', $payload->slug);
151 $src = $payload->src;
152 $new_version = $payload->version;
153
154 FileHandler::verify_directory_traversal($src);
155 FileHandler::verify_directory_traversal($app_slug);
156
157 $installed_apps = $this->get_installed_apps();
158
159 if (empty($installed_apps)) {
160 throw new Exception(esc_html__('No apps are currently installed.', 'kirki'));
161 }
162
163 $is_installed = collection($installed_apps)->some(function(InstalledAppDTO $app) use ($app_slug) {
164 return $app->app_slug === $app_slug;
165 });
166
167 if (!$is_installed) {
168 throw new Exception(esc_html__('App not found in installed apps.', 'kirki'));
169 }
170
171 $base_upload_dir = WP_CONTENT_DIR;
172 $apps_folder = clean_path($base_upload_dir . '/kirki-apps', false);
173 $app_folder = clean_path($apps_folder . '/' . $app_slug, false);
174 $backup_folder = $app_folder . '_backup_' . time();
175
176 // Create backup of current app
177 if (!File::move($app_folder, $backup_folder)) {
178 throw new Exception(esc_html__('Failed to create backup of current app version.', 'kirki'));
179 }
180
181 $remote_zip_url = KIRKI_APPS_BASE_URL . $src;
182 $file_name_new = uniqid( '', true ) . '.zip';
183 $zip_file_path = FileHandler::download_zip_from_remote($remote_zip_url, $file_name_new);
184
185 if ($zip_file_path === false) {
186 // Restore backup
187 File::move($backup_folder, $app_folder);
188 throw new Exception(esc_html__('Failed to download the new app version.', 'kirki'));
189 }
190
191 $result = FileHandler::extract_zip_file($zip_file_path, $apps_folder);
192
193 File::delete($zip_file_path);
194
195 if ($result === false) {
196 // Restore backup
197 File::move($backup_folder, $app_folder);
198 throw new Exception(esc_html__('Failed to extract the new app version.', 'kirki'));
199 }
200
201
202 try {
203 $installed_apps = collection($installed_apps)->map(function(InstalledAppDTO $app) use ($app_slug, $new_version) {
204 if ($app->app_slug === $app_slug) {
205 $app->version = $new_version;
206 }
207
208 return $app->to_array();
209 })->to_array();
210
211 GlobalData::update(GlobalDataKeys::INSTALLED_APPS, $installed_apps);
212 } catch (Throwable $throwable) {
213 if (File::exists($app_folder)) {
214 File::delete($app_folder);
215 }
216
217 if (File::exists($backup_folder)) {
218 File::move($backup_folder, $app_folder);
219 }
220
221 throw new Exception(
222 sprintf(
223 /* translators: 1: error message */
224 esc_html__('An error occurred during the app update: %s', 'kirki'),
225 esc_html($throwable->getMessage())
226 )
227 );
228 }
229
230 File::delete($backup_folder);
231
232 return [
233 'app_slug' => $app_slug,
234 'version' => $new_version
235 ];
236 }
237
238 public function delete_app(string $app_slug)
239 {
240 $installed_apps = $this->get_installed_apps();
241
242 if (empty($installed_apps)) {
243 throw new Exception(esc_html__('No apps are currently installed.', 'kirki'));
244 }
245
246 $is_installed = collection($installed_apps)->some(function(InstalledAppDTO $app) use ($app_slug) {
247 return $app->app_slug === $app_slug;
248 });
249
250 if (!$is_installed) {
251 throw new Exception(esc_html__('App not found in installed apps.', 'kirki'));
252 }
253
254 $base_upload_dir = WP_CONTENT_DIR; // Absolute path to 'uploads'
255 $app_folder = clean_path($base_upload_dir . '/kirki-apps/' . $app_slug, false);
256
257 FileHandler::verify_directory_traversal($app_folder);
258
259 $installed_apps = collection($installed_apps)->filter(function(InstalledAppDTO $app) use ($app_slug) {
260 return $app->app_slug !== $app_slug;
261 })->map(fn(InstalledAppDTO $app) => $app->to_array())->values()->to_array();
262
263 DB::begin_transaction();
264
265 try {
266 GlobalData::update(GlobalDataKeys::INSTALLED_APPS, $installed_apps);
267 $this->save_app_settings($app_slug, []);
268
269 if (!File::delete($app_folder)) {
270 throw new Exception(esc_html__('Failed to delete the app folder.', 'kirki'));
271 }
272
273 DB::commit();
274
275 return true;
276 } catch (Throwable $throwable) {
277 DB::rollback();
278 throw new Exception(
279 sprintf(
280 /* translators: 1: error message */
281 esc_html__('An error occurred during the app deletion: %s', 'kirki'),
282 esc_html($throwable->getMessage())
283 )
284 );
285 }
286 }
287 }