PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / trunk
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts vtrunk
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
← All changes | admin/includes/class.import.snippet.php +242 -96 2.6.0trunk View file →
@@ -2,11 +2,9 @@
2 2
3 3 /**
4 4 * Import snippet
5 5 *
6 - * @author Webcraftic <wordpress.webraftic@gmail.com>
7 - * @copyright (c) 16.11.2018, Webcraftic
8 - * @version 1.0
6 + * @package Woody_Code_Snippets
9 7 */
10 8
11 9 // Exit if accessed directly
12 10 if ( ! defined( 'ABSPATH' ) ) {
@@ -15,108 +13,230 @@
15 13
16 14 class WINP_Import_Snippet {
17 15
18 16 /**
19 - * WINP_Export_Snippet constructor.
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
20 23 */
21 - public function __construct() {
22 - $this->registerHooks();
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 + ];
23 98 }
24 99
25 100 /**
26 - * Register hooks
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>
27 107 */
28 - public function registerHooks() {
29 - add_action( 'admin_init', [ $this, 'import_snippets_proccess' ] );
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;
30 120 }
31 121
32 122 /**
33 - * Process the uploaded import files
123 + * Import snippets from ZIP archive
34 124 *
35 - * @uses import_snippets() to process the import file
36 - * @uses wp_safe_redirect() to pass the import results to the page
37 - * @uses add_query_arg() to append the results to the current URI
125 + * @param string $file File path.
126 + * @param string $dup_action Duplicate action: 'ignore', 'replace', or 'skip'.
127 + *
128 + * @return int|bool|array<int>
38 129 */
39 - public function import_snippets_proccess() {
40 - if ( isset( $_POST['wbcr_inp_import_form_action'] ) ) {
130 + public function import_zip_snippets( $file, $dup_action ) {
131 + if ( ! class_exists( 'ZipArchive' ) ) {
132 + return false;
133 + }
41 134
42 - if ( ! WINP_Plugin::app()->currentUserCan() ) {
43 - wp_die( __( 'Sorry, you are not allowed to import snippets as this user.' ), __( 'You need a higher level of permission.' ), 403 );
44 - }
135 + $zip = new ZipArchive();
45 136
46 - check_admin_referer( 'wbcr_inp_import_form', 'wbcr_inp_import_form_nonce_field' );
137 + if ( true !== $zip->open( $file ) ) {
138 + return false;
139 + }
47 140
48 - if ( ! isset( $_FILES['wbcr_inp_import_files'] ) || ! count( $_FILES['wbcr_inp_import_files'] ) || ! isset( $_FILES['wbcr_inp_import_files']['tmp_name'][0] ) || empty( $_FILES['wbcr_inp_import_files']['tmp_name'][0] ) ) {
49 - return;
50 - }
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();
51 145
52 - $url = esc_url(remove_query_arg( [ 'wbcr_inp_error', 'wbcr_inp_imported' ] ));
146 + // Create extraction directory.
147 + if ( ! wp_mkdir_p( $unzip_path ) ) {
148 + $zip->close();
149 + return false;
150 + }
53 151
54 - // Only ine files for free version
55 - if ( ! WINP_Plugin::app()->get_api_object()->is_key() && count( $_FILES['wbcr_inp_import_files']['tmp_name'] ) > 1 ) {
56 - $url = esc_url(add_query_arg( [ 'wbcr_import_error' => true ], $url ));
57 - wp_safe_redirect( $url );
58 - exit;
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;
59 159 }
160 +
161 + // Security: Skip files with directory traversal attempts.
162 + if ( false !== strpos( $filename, '..' ) || false !== strpos( $filename, '/' ) || false !== strpos( $filename, '\\' ) ) {
163 + continue;
164 + }
60 165
61 - $count = 0;
62 - $uploads = $_FILES['wbcr_inp_import_files'];
63 - $dup_action = WINP_Plugin::app()->request->post( 'duplicate_action', 'ignore', true );
64 - $error = false;
166 + // Only extract files (not directories).
167 + $file_info = $zip->statIndex( $i );
168 + if ( ! empty( $file_info ) && 0 === $file_info['size'] ) {
169 + continue;
170 + }
65 171
66 - foreach ( $uploads['tmp_name'] as $i => $import_file ) {
67 - $ext = pathinfo( $uploads['name'][ $i ] );
68 - $ext = $ext['extension'];
69 - $mime_type = $uploads['type'][ $i ];
172 + $zip->extractTo( $unzip_path, $filename );
173 + }
70 174
71 - if ( 'json' === $ext || 'application/json' === $mime_type ) {
72 - $result = $this->importSnippet( $import_file, $dup_action );
73 - } else {
74 - $result = apply_filters( 'wbcr/inp/import/snippet', false, $ext, $mime_type, $import_file, $dup_action );
75 - }
175 + $zip->close();
76 176
77 - if ( false === $result || - 1 === $result ) {
78 - $error = true;
79 - } else {
80 - $count += count( $result );
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 + }
81 187 }
82 188 }
189 + } catch ( Exception $e ) {
190 + $this->cleanup_directory( $unzip_path );
191 + return false;
192 + }
83 193
84 - $url = esc_url(add_query_arg( $error ? [ 'wbcr_inp_error' => true ] : [ 'wbcr_inp_imported' => $count ], $url ));
85 - wp_safe_redirect( $url );
86 - exit;
87 - }
194 + // Cleanup: Delete all files and directory.
195 + $this->cleanup_directory( $unzip_path );
196 +
197 + return $result;
88 198 }
89 199
90 200 /**
91 - * Import snippets
201 + * Cleanup directory and its contents
92 202 *
93 - * @param $file
94 - * @param $dup_action
95 - *
96 - * @return int|bool|array
203 + * @param string $dir_path Directory path.
204 + *
205 + * @return void
97 206 */
98 - public function importSnippet( $file, $dup_action ) {
99 - if ( ! file_exists( $file ) || ! is_file( $file ) ) {
100 - return false;
207 + private function cleanup_directory( $dir_path ) {
208 + if ( ! is_dir( $dir_path ) ) {
209 + return;
101 210 }
102 211
103 - $raw_data = file_get_contents( $file );
104 - $data = json_decode( $raw_data, true );
105 - $snippets = isset( $data['snippets'] ) ? $data['snippets'] : [];
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 + }
106 222
107 - $imported = $this->saveImportedSnippets( $snippets, $dup_action );
223 + $wp_filesystem = WINP_Helper::get_wp_filesystem();
108 224
109 - return $imported;
225 + if ( false !== $wp_filesystem ) {
226 + $wp_filesystem->rmdir( $dir_path, true );
227 + }
110 228 }
111 229
112 230 /**
113 231 * Update taxonomy tags
114 232 *
115 - * @param $snippet_id
116 - * @param $tags
233 + * @param int $snippet_id Snippet ID.
234 + * @param array<string> $tags Tags slugs.
235 + *
236 + * @return void
117 237 */
118 - private function updateTaxonomyTags( $snippet_id, $tags ) {
238 + private function update_taxonomy_tags( $snippet_id, $tags ) {
119 239 if ( ! empty( $tags ) ) {
120 240 foreach ( $tags as $tag_slug ) {
121 241 $term = get_term_by( 'slug', $tag_slug, WINP_SNIPPETS_TAXONOMY );
122 242 if ( $term ) {
@@ -128,24 +248,26 @@
128 248
129 249 /**
130 250 * Update post meta
131 251 *
132 - * @param $post_id
133 - * @param $meta_name
134 - * @param $meta_value
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
135 257 */
136 - private function updateMeta( $post_id, $meta_name, $meta_value ) {
137 - update_post_meta( $post_id, WINP_Plugin::app()->getPrefix() . $meta_name, $meta_value );
258 + private function update_meta( $post_id, $meta_name, $meta_value ) {
259 + update_post_meta( $post_id, 'wbcr_inp_' . $meta_name, $meta_value );
138 260 }
139 261
140 262 /**
141 263 * Save snippet
142 264 *
143 - * @param $snippet
265 + * @param array<string, mixed> $snippet Snippet data.
144 266 *
145 267 * @return int
146 268 */
147 - private function saveSnippet( $snippet ) {
269 + private function save_snippet( $snippet ) {
148 270 $content = $snippet['content'];
149 271
150 272 if ( WINP_SNIPPET_TYPE_TEXT != $snippet['type'] && WINP_SNIPPET_TYPE_AD != $snippet['type'] ) {
151 273 $content = empty( $content ) && isset( $snippet['code'] ) && ! empty( $snippet['code'] ) ? $snippet['code'] : $content;
@@ -154,9 +276,9 @@
154 276 $data = [
155 277 'post_title' => $snippet['title'],
156 278 'post_content' => $content,
157 279 'post_status' => 'publish',
158 - 'post_type' => WINP_SNIPPETS_POST_TYPE
280 + 'post_type' => WINP_SNIPPETS_POST_TYPE,
159 281 ];
160 282
161 283 if ( isset( $snippet['id'] ) && 0 != $snippet['id'] ) {
162 284 $data['ID'] = $snippet['id'];
@@ -163,19 +285,20 @@
163 285 }
164 286
165 287 $snippet['id'] = wp_insert_post( $data );
166 288
167 - $this->updateMeta( $snippet['id'], 'snippet_location', $snippet['location'] );
168 - $this->updateMeta( $snippet['id'], 'snippet_type', $snippet['type'] );
169 - $this->updateMeta( $snippet['id'], 'snippet_filters', $snippet['filters'] );
170 - $this->updateMeta( $snippet['id'], 'changed_filters', $snippet['changed_filters'] );
171 - $this->updateMeta( $snippet['id'], 'snippet_scope', $snippet['scope'] );
172 - $this->updateMeta( $snippet['id'], 'snippet_description', $snippet['description'] );
173 - $this->updateMeta( $snippet['id'], 'snippet_tags', $snippet['attributes'] );
174 - $this->updateMeta( $snippet['id'], 'snippet_activate', 0 );
175 - $this->updateMeta( $snippet['id'], 'snippet_priority', $snippet['priority'] );
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'] );
176 299
177 - $this->updateTaxonomyTags( $snippet['id'], $snippet['tags'] );
300 + $this->update_taxonomy_tags( $snippet['id'], $snippet['tags'] );
178 301
179 302 return $snippet['id'];
180 303 }
181 304
@@ -181,19 +304,29 @@
181 304
182 305 /**
183 306 * Save imported snippets
184 307 *
185 - * @param $snippets
186 - * @param $dup_action
308 + * @param array<array<string, mixed>> $snippets Snippets data.
309 + * @param string $dup_action Duplicate action: 'ignore', 'replace', or 'skip'.
187 310 *
188 - * @return array
311 + * @return array<int> Imported snippet IDs.
189 312 */
190 - private function saveImportedSnippets( $snippets, $dup_action ) {
313 + private function save_imported_snippets( $snippets, $dup_action ) {
191 314 $existing_snippets = [];
315 +
192 316 if ( 'replace' === $dup_action || 'skip' === $dup_action ) {
193 - $all_snippets = get_posts( [ 'post_type' => WINP_SNIPPETS_POST_TYPE ] );
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 +
194 325 foreach ( $all_snippets as $snippet ) {
195 - $existing_snippets[ $snippet->post_name ] = $snippet->ID;
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;
196 329 }
197 330 }
198 331
199 332 $imported = [];
@@ -198,17 +331,31 @@
198 331
199 332 $imported = [];
200 333
201 334 foreach ( $snippets as $snippet ) {
202 - if ( 'ignore' !== $dup_action && isset( $existing_snippets[ $snippet['name'] ] ) ) {
203 - if ( 'replace' === $dup_action ) {
204 - $snippet['id'] = $existing_snippets[ $snippet['name'] ];
205 - } else if ( 'skip' === $dup_action ) {
206 - continue;
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'] ];
207 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 + }
208 354 }
209 355
210 - if ( $snippet_id = $this->saveSnippet( $snippet ) ) {
356 + $snippet_id = $this->save_snippet( $snippet );
357 + if ( $snippet_id ) {
211 358 $imported[] = $snippet_id;
212 359 }
213 360 }
214 361
@@ -213,6 +360,5 @@
213 360 }
214 361
215 362 return $imported;
216 363 }
217 -
218 364 }