PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / 2.7.7
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts v2.7.7
2.7.7 2.7.6 2.7.5 2.7.4 trunk 1.3 2.0.4 2.0.6 2.1.91 2.2.4 2.2.7 2.2.9 2.3.1 2.3.10 2.4.10 2.4.2 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.6.0 2.6.1 2.7.0 All 28 releases
insert-php / admin / includes / class.import.snippet.php

class.import.snippet.php in Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts 2.7.7, at admin/includes/class.import.snippet.php

365 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Import snippet
5 *
6 * @package Woody_Code_Snippets
7 */
8
9 // Exit if accessed directly
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit;
12 }
13
14 class WINP_Import_Snippet {
15
16 /**
17 * Process import files and return results
18 *
19 * @param array<array<string, mixed>>|array<string, mixed> $files Array of files from $_FILES or REST API.
20 * @param string $dup_action Duplicate action: 'ignore', 'replace', or 'skip'.
21 *
22 * @return array<string, mixed> Array with 'count', 'error', and 'errors' keys
23 */
24 public function process_import_files( $files, $dup_action = 'ignore' ) {
25 $count = 0;
26 $error = false;
27 $errors = [];
28
29 // Sanitize duplicate action.
30 $dup_action = sanitize_text_field( $dup_action );
31 if ( ! in_array( $dup_action, [ 'ignore', 'replace', 'skip' ], true ) ) {
32 $dup_action = 'ignore';
33 }
34
35 // Normalize file array structure.
36 $normalized_files = [];
37 if ( isset( $files['tmp_name'] ) ) {
38 // Handle both single file and multiple files format.
39 $file_count = count( $files['tmp_name'] );
40 for ( $i = 0; $i < $file_count; $i++ ) {
41 $normalized_files[] = [
42 'name' => isset( $files['name'][ $i ] ) ? sanitize_file_name( $files['name'][ $i ] ) : '',
43 'type' => isset( $files['type'][ $i ] ) ? sanitize_text_field( $files['type'][ $i ] ) : '',
44 'tmp_name' => isset( $files['tmp_name'][ $i ] ) ? $files['tmp_name'][ $i ] : '',
45 'error' => isset( $files['error'][ $i ] ) ? (int) $files['error'][ $i ] : UPLOAD_ERR_NO_FILE,
46 'size' => isset( $files['size'][ $i ] ) ? (int) $files['size'][ $i ] : 0,
47 ];
48 }
49 } else {
50 // Already in correct format (from REST API) - still need to sanitize.
51 foreach ( $files as $file ) {
52 $normalized_files[] = [
53 'name' => isset( $file['name'] ) ? sanitize_file_name( $file['name'] ) : '',
54 'type' => isset( $file['type'] ) ? sanitize_text_field( $file['type'] ) : '',
55 'tmp_name' => isset( $file['tmp_name'] ) ? $file['tmp_name'] : '',
56 'error' => isset( $file['error'] ) ? (int) $file['error'] : UPLOAD_ERR_NO_FILE,
57 'size' => isset( $file['size'] ) ? (int) $file['size'] : 0,
58 ];
59 }
60 }
61
62 foreach ( $normalized_files as $file ) {
63 // Validate tmp_name path.
64 if ( empty( $file['tmp_name'] ) || ! file_exists( $file['tmp_name'] ) ) {
65 $error = true;
66 // translators: %s is the file name.
67 $errors[] = sprintf( __( 'Invalid or missing temporary file for: %s', 'insert-php' ), $file['name'] );
68 continue;
69 }
70
71 $ext = pathinfo( $file['name'], PATHINFO_EXTENSION );
72 $ext = strtolower( sanitize_text_field( $ext ) );
73 $mime_type = $file['type'];
74 $import_file = $file['tmp_name'];
75
76 if ( 'json' === $ext || 'application/json' === $mime_type ) {
77 $result = $this->import_snippet( $import_file, $dup_action );
78 } elseif ( 'zip' === $ext || 'application/zip' === $mime_type ) {
79 $result = $this->import_zip_snippets( $import_file, $dup_action );
80 } else {
81 $result = apply_filters( 'wbcr/inp/import/snippet', false, $ext, $mime_type, $import_file, $dup_action );
82 }
83
84 if ( false === $result || - 1 === $result ) {
85 $error = true;
86 // translators: %s is the file name.
87 $errors[] = sprintf( __( 'Failed to import file: %s', 'insert-php' ), $file['name'] );
88 } else {
89 $count += count( $result );
90 }
91 }
92
93 return [
94 'count' => $count,
95 'error' => $error,
96 'errors' => $errors,
97 ];
98 }
99
100 /**
101 * Import snippets
102 *
103 * @param string $file File path.
104 * @param string $dup_action Duplicate action: 'ignore', 'replace', or 'skip'.
105 *
106 * @return int|bool|array<int>
107 */
108 public function import_snippet( $file, $dup_action ) {
109 if ( ! file_exists( $file ) || ! is_file( $file ) ) {
110 return false;
111 }
112
113 $raw_data = file_get_contents( $file );
114 $data = json_decode( $raw_data, true );
115 $snippets = isset( $data['snippets'] ) ? $data['snippets'] : [];
116
117 $imported = $this->save_imported_snippets( $snippets, $dup_action );
118
119 return $imported;
120 }
121
122 /**
123 * Import snippets from ZIP archive
124 *
125 * @param string $file File path.
126 * @param string $dup_action Duplicate action: 'ignore', 'replace', or 'skip'.
127 *
128 * @return int|bool|array<int>
129 */
130 public function import_zip_snippets( $file, $dup_action ) {
131 if ( ! class_exists( 'ZipArchive' ) ) {
132 return false;
133 }
134
135 $zip = new ZipArchive();
136
137 if ( true !== $zip->open( $file ) ) {
138 return false;
139 }
140
141 $result = [];
142 $upload_dir = wp_get_upload_dir();
143 // Use unique directory name to prevent race conditions.
144 $unzip_path = $upload_dir['path'] . '/winp_' . uniqid();
145
146 // Create extraction directory.
147 if ( ! wp_mkdir_p( $unzip_path ) ) {
148 $zip->close();
149 return false;
150 }
151
152 // Validate and extract only safe files.
153 for ( $i = 0; $i < $zip->numFiles; $i++ ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
154 $filename = $zip->getNameIndex( $i );
155
156 // Skip if filename cannot be retrieved.
157 if ( false === $filename ) {
158 continue;
159 }
160
161 // Security: Skip files with directory traversal attempts.
162 if ( false !== strpos( $filename, '..' ) || false !== strpos( $filename, '/' ) || false !== strpos( $filename, '\\' ) ) {
163 continue;
164 }
165
166 // Only extract files (not directories).
167 $file_info = $zip->statIndex( $i );
168 if ( ! empty( $file_info ) && 0 === $file_info['size'] ) {
169 continue;
170 }
171
172 $zip->extractTo( $unzip_path, $filename );
173 }
174
175 $zip->close();
176
177 // Process extracted files using modern iterator.
178 try {
179 $iterator = new DirectoryIterator( $unzip_path );
180 foreach ( $iterator as $file_info ) {
181 if ( $file_info->isFile() && $file_info->getSize() > 0 ) {
182 $filepath = $file_info->getPathname();
183 $_result = $this->import_snippet( $filepath, $dup_action );
184 if ( is_array( $_result ) ) {
185 $result = array_merge( $result, $_result );
186 }
187 }
188 }
189 } catch ( Exception $e ) {
190 $this->cleanup_directory( $unzip_path );
191 return false;
192 }
193
194 // Cleanup: Delete all files and directory.
195 $this->cleanup_directory( $unzip_path );
196
197 return $result;
198 }
199
200 /**
201 * Cleanup directory and its contents
202 *
203 * @param string $dir_path Directory path.
204 *
205 * @return void
206 */
207 private function cleanup_directory( $dir_path ) {
208 if ( ! is_dir( $dir_path ) ) {
209 return;
210 }
211
212 try {
213 $iterator = new DirectoryIterator( $dir_path );
214 foreach ( $iterator as $file_info ) {
215 if ( $file_info->isFile() ) {
216 wp_delete_file( $file_info->getPathname() );
217 }
218 }
219 } catch ( Exception $e ) {
220 return;
221 }
222
223 $wp_filesystem = WINP_Helper::get_wp_filesystem();
224
225 if ( false !== $wp_filesystem ) {
226 $wp_filesystem->rmdir( $dir_path, true );
227 }
228 }
229
230 /**
231 * Update taxonomy tags
232 *
233 * @param int $snippet_id Snippet ID.
234 * @param array<string> $tags Tags slugs.
235 *
236 * @return void
237 */
238 private function update_taxonomy_tags( $snippet_id, $tags ) {
239 if ( ! empty( $tags ) ) {
240 foreach ( $tags as $tag_slug ) {
241 $term = get_term_by( 'slug', $tag_slug, WINP_SNIPPETS_TAXONOMY );
242 if ( $term ) {
243 wp_set_post_terms( $snippet_id, [ $term->term_id ], WINP_SNIPPETS_TAXONOMY, true );
244 }
245 }
246 }
247 }
248
249 /**
250 * Update post meta
251 *
252 * @param int $post_id Post ID.
253 * @param string $meta_name Meta name.
254 * @param mixed $meta_value Meta value.
255 *
256 * @return void
257 */
258 private function update_meta( $post_id, $meta_name, $meta_value ) {
259 update_post_meta( $post_id, 'wbcr_inp_' . $meta_name, $meta_value );
260 }
261
262 /**
263 * Save snippet
264 *
265 * @param array<string, mixed> $snippet Snippet data.
266 *
267 * @return int
268 */
269 private function save_snippet( $snippet ) {
270 $content = $snippet['content'];
271
272 if ( WINP_SNIPPET_TYPE_TEXT != $snippet['type'] && WINP_SNIPPET_TYPE_AD != $snippet['type'] ) {
273 $content = empty( $content ) && isset( $snippet['code'] ) && ! empty( $snippet['code'] ) ? $snippet['code'] : $content;
274 }
275
276 $data = [
277 'post_title' => $snippet['title'],
278 'post_content' => $content,
279 'post_status' => 'publish',
280 'post_type' => WINP_SNIPPETS_POST_TYPE,
281 ];
282
283 if ( isset( $snippet['id'] ) && 0 != $snippet['id'] ) {
284 $data['ID'] = $snippet['id'];
285 }
286
287 $snippet['id'] = wp_insert_post( $data );
288
289 $this->update_meta( $snippet['id'], 'snippet_location', $snippet['location'] );
290 $this->update_meta( $snippet['id'], 'snippet_type', $snippet['type'] );
291 $this->update_meta( $snippet['id'], 'snippet_filters', $snippet['filters'] );
292 $this->update_meta( $snippet['id'], 'changed_filters', $snippet['changed_filters'] );
293 $this->update_meta( $snippet['id'], 'snippet_scope', $snippet['scope'] );
294 $this->update_meta( $snippet['id'], 'snippet_description', $snippet['description'] );
295 $this->update_meta( $snippet['id'], 'snippet_tags', $snippet['attributes'] );
296 $this->update_meta( $snippet['id'], 'snippet_activate', 0 );
297 $this->update_meta( $snippet['id'], 'snippet_priority', $snippet['priority'] );
298 $this->update_meta( $snippet['id'], 'snippet_custom_name', $snippet['custom_name'] );
299
300 $this->update_taxonomy_tags( $snippet['id'], $snippet['tags'] );
301
302 return $snippet['id'];
303 }
304
305 /**
306 * Save imported snippets
307 *
308 * @param array<array<string, mixed>> $snippets Snippets data.
309 * @param string $dup_action Duplicate action: 'ignore', 'replace', or 'skip'.
310 *
311 * @return array<int> Imported snippet IDs.
312 */
313 private function save_imported_snippets( $snippets, $dup_action ) {
314 $existing_snippets = [];
315
316 if ( 'replace' === $dup_action || 'skip' === $dup_action ) {
317 $all_snippets = get_posts(
318 [
319 'post_type' => WINP_SNIPPETS_POST_TYPE,
320 'posts_per_page' => -1,
321 'post_status' => 'any',
322 ]
323 );
324
325 foreach ( $all_snippets as $snippet ) {
326 // Store by both post_name (slug) and post_title for matching.
327 $existing_snippets[ $snippet->post_name ] = $snippet->ID;
328 $existing_snippets[ $snippet->post_title ] = $snippet->ID;
329 }
330 }
331
332 $imported = [];
333
334 foreach ( $snippets as $snippet ) {
335 $is_duplicate = false;
336 $duplicate_id = null;
337
338 if ( 'ignore' !== $dup_action ) {
339 if ( isset( $snippet['name'] ) && isset( $existing_snippets[ $snippet['name'] ] ) ) {
340 $is_duplicate = true;
341 $duplicate_id = $existing_snippets[ $snippet['name'] ];
342 } elseif ( isset( $snippet['title'] ) && isset( $existing_snippets[ $snippet['title'] ] ) ) {
343 $is_duplicate = true;
344 $duplicate_id = $existing_snippets[ $snippet['title'] ];
345 }
346
347 if ( $is_duplicate ) {
348 if ( 'replace' === $dup_action ) {
349 $snippet['id'] = $duplicate_id;
350 } elseif ( 'skip' === $dup_action ) {
351 continue;
352 }
353 }
354 }
355
356 $snippet_id = $this->save_snippet( $snippet );
357 if ( $snippet_id ) {
358 $imported[] = $snippet_id;
359 }
360 }
361
362 return $imported;
363 }
364 }
365