PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / 2.7.0
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts v2.7.0
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.0, at admin/includes/class.import.snippet.php

362 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 // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_unlink -- Deleting temporary files in wp_upload_dir().
217 unlink( $file_info->getPathname() );
218 }
219 }
220 } catch ( Exception $e ) {
221 return;
222 }
223
224 // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.directory_rmdir -- Deleting temporary directory in wp_upload_dir().
225 rmdir( $dir_path );
226 }
227
228 /**
229 * Update taxonomy tags
230 *
231 * @param int $snippet_id Snippet ID.
232 * @param array<string> $tags Tags slugs.
233 *
234 * @return void
235 */
236 private function update_taxonomy_tags( $snippet_id, $tags ) {
237 if ( ! empty( $tags ) ) {
238 foreach ( $tags as $tag_slug ) {
239 $term = get_term_by( 'slug', $tag_slug, WINP_SNIPPETS_TAXONOMY );
240 if ( $term ) {
241 wp_set_post_terms( $snippet_id, [ $term->term_id ], WINP_SNIPPETS_TAXONOMY, true );
242 }
243 }
244 }
245 }
246
247 /**
248 * Update post meta
249 *
250 * @param int $post_id Post ID.
251 * @param string $meta_name Meta name.
252 * @param mixed $meta_value Meta value.
253 *
254 * @return void
255 */
256 private function update_meta( $post_id, $meta_name, $meta_value ) {
257 update_post_meta( $post_id, 'wbcr_inp_' . $meta_name, $meta_value );
258 }
259
260 /**
261 * Save snippet
262 *
263 * @param array<string, mixed> $snippet Snippet data.
264 *
265 * @return int
266 */
267 private function save_snippet( $snippet ) {
268 $content = $snippet['content'];
269
270 if ( WINP_SNIPPET_TYPE_TEXT != $snippet['type'] && WINP_SNIPPET_TYPE_AD != $snippet['type'] ) {
271 $content = empty( $content ) && isset( $snippet['code'] ) && ! empty( $snippet['code'] ) ? $snippet['code'] : $content;
272 }
273
274 $data = [
275 'post_title' => $snippet['title'],
276 'post_content' => $content,
277 'post_status' => 'publish',
278 'post_type' => WINP_SNIPPETS_POST_TYPE,
279 ];
280
281 if ( isset( $snippet['id'] ) && 0 != $snippet['id'] ) {
282 $data['ID'] = $snippet['id'];
283 }
284
285 $snippet['id'] = wp_insert_post( $data );
286
287 $this->update_meta( $snippet['id'], 'snippet_location', $snippet['location'] );
288 $this->update_meta( $snippet['id'], 'snippet_type', $snippet['type'] );
289 $this->update_meta( $snippet['id'], 'snippet_filters', $snippet['filters'] );
290 $this->update_meta( $snippet['id'], 'changed_filters', $snippet['changed_filters'] );
291 $this->update_meta( $snippet['id'], 'snippet_scope', $snippet['scope'] );
292 $this->update_meta( $snippet['id'], 'snippet_description', $snippet['description'] );
293 $this->update_meta( $snippet['id'], 'snippet_tags', $snippet['attributes'] );
294 $this->update_meta( $snippet['id'], 'snippet_activate', 0 );
295 $this->update_meta( $snippet['id'], 'snippet_priority', $snippet['priority'] );
296
297 $this->update_taxonomy_tags( $snippet['id'], $snippet['tags'] );
298
299 return $snippet['id'];
300 }
301
302 /**
303 * Save imported snippets
304 *
305 * @param array<array<string, mixed>> $snippets Snippets data.
306 * @param string $dup_action Duplicate action: 'ignore', 'replace', or 'skip'.
307 *
308 * @return array<int> Imported snippet IDs.
309 */
310 private function save_imported_snippets( $snippets, $dup_action ) {
311 $existing_snippets = [];
312
313 if ( 'replace' === $dup_action || 'skip' === $dup_action ) {
314 $all_snippets = get_posts(
315 [
316 'post_type' => WINP_SNIPPETS_POST_TYPE,
317 'posts_per_page' => -1,
318 'post_status' => 'any',
319 ]
320 );
321
322 foreach ( $all_snippets as $snippet ) {
323 // Store by both post_name (slug) and post_title for matching.
324 $existing_snippets[ $snippet->post_name ] = $snippet->ID;
325 $existing_snippets[ $snippet->post_title ] = $snippet->ID;
326 }
327 }
328
329 $imported = [];
330
331 foreach ( $snippets as $snippet ) {
332 $is_duplicate = false;
333 $duplicate_id = null;
334
335 if ( 'ignore' !== $dup_action ) {
336 if ( isset( $snippet['name'] ) && isset( $existing_snippets[ $snippet['name'] ] ) ) {
337 $is_duplicate = true;
338 $duplicate_id = $existing_snippets[ $snippet['name'] ];
339 } elseif ( isset( $snippet['title'] ) && isset( $existing_snippets[ $snippet['title'] ] ) ) {
340 $is_duplicate = true;
341 $duplicate_id = $existing_snippets[ $snippet['title'] ];
342 }
343
344 if ( $is_duplicate ) {
345 if ( 'replace' === $dup_action ) {
346 $snippet['id'] = $duplicate_id;
347 } elseif ( 'skip' === $dup_action ) {
348 continue;
349 }
350 }
351 }
352
353 $snippet_id = $this->save_snippet( $snippet );
354 if ( $snippet_id ) {
355 $imported[] = $snippet_id;
356 }
357 }
358
359 return $imported;
360 }
361 }
362