PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.9.5
Secure Custom Fields v6.9.5
6.9.5 6.9.4 6.9.3 6.9.2 6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / includes / local-json.php
secure-custom-fields / includes Last commit date
Blocks 1 month ago Datastore 3 days ago Meta 1 year ago abilities 1 month ago admin 1 week ago ajax 2 months ago api 3 days ago fields 3 days ago forms 1 week ago legacy 1 year ago locations 2 weeks ago post-types 3 months ago rest-api 3 days ago walkers 1 year ago acf-bidirectional-functions.php 3 days ago acf-field-functions.php 3 months ago acf-field-group-functions.php 8 months ago acf-form-functions.php 1 year ago acf-helper-functions.php 1 year ago acf-hook-functions.php 1 year ago acf-input-functions.php 8 months ago acf-internal-post-type-functions.php 8 months ago acf-meta-functions.php 1 month ago acf-post-functions.php 1 year ago acf-post-type-functions.php 1 year ago acf-taxonomy-functions.php 1 year ago acf-user-functions.php 1 month ago acf-utility-functions.php 1 year ago acf-value-functions.php 1 year ago acf-wp-functions.php 1 month ago assets.php 1 month ago blocks-auto-inline-editing.php 2 weeks ago blocks.php 1 week ago class-acf-data.php 11 months ago class-acf-internal-post-type.php 1 month ago class-acf-options-page.php 1 year ago class-acf-site-health.php 5 months ago class-scf-json-schema-validator.php 7 months ago class-scf-schema-builder.php 3 months ago compatibility.php 1 year ago datastore.php 2 months ago deprecated.php 1 year ago fields.php 11 months ago index.php 1 year ago l10n.php 1 year ago local-fields.php 1 year ago local-json.php 1 week ago local-meta.php 1 year ago locations.php 1 year ago loop.php 11 months ago media.php 1 year ago rest-api.php 11 months ago revisions.php 2 months ago scf-ui-options-page-functions.php 1 year ago third-party.php 8 months ago upgrades.php 1 month ago validation.php 11 months ago wpml.php 1 year ago
local-json.php
831 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit; // Exit if accessed directly
5 }
6
7 if ( ! class_exists( 'ACF_Local_JSON' ) ) :
8
9 class ACF_Local_JSON {
10
11 /**
12 * The found JSON field group files.
13 *
14 * @since ACF 5.9.0
15 * @var array
16 */
17 private $files = array();
18
19 /**
20 * Whether an expected Local JSON write failed during the current request.
21 *
22 * @var boolean
23 */
24 private $save_file_failure = false;
25
26 /**
27 * Constructor.
28 *
29 * @date 14/4/20
30 * @since ACF 5.9.0
31 *
32 * @return void
33 */
34 public function __construct() {
35
36 // Update settings.
37 acf_update_setting( 'save_json', get_stylesheet_directory() . '/acf-json' );
38 acf_append_setting( 'load_json', get_stylesheet_directory() . '/acf-json' );
39
40 // Add listeners.
41 add_action( 'acf/update_field_group', array( $this, 'update_field_group' ) );
42 add_action( 'acf/untrash_field_group', array( $this, 'update_field_group' ) );
43 add_filter( 'acf/trash_field_group', array( $this, 'delete_field_group' ) );
44 add_filter( 'acf/delete_field_group', array( $this, 'delete_field_group' ) );
45 add_filter( 'acf/update_post_type', array( $this, 'update_internal_post_type' ) );
46 add_filter( 'acf/untrash_post_type', array( $this, 'update_internal_post_type' ) );
47 add_filter( 'acf/trash_post_type', array( $this, 'delete_internal_post_type' ) );
48 add_filter( 'acf/delete_post_type', array( $this, 'delete_internal_post_type' ) );
49 add_filter( 'acf/update_taxonomy', array( $this, 'update_internal_post_type' ) );
50 add_filter( 'acf/untrash_taxonomy', array( $this, 'update_internal_post_type' ) );
51 add_filter( 'acf/trash_taxonomy', array( $this, 'delete_internal_post_type' ) );
52 add_filter( 'acf/delete_taxonomy', array( $this, 'delete_internal_post_type' ) );
53
54 // Include fields.
55 add_action( 'acf/include_fields', array( $this, 'include_fields' ) );
56 add_action( 'acf/include_post_types', array( $this, 'include_post_types' ) );
57 add_action( 'acf/include_taxonomies', array( $this, 'include_taxonomies' ) );
58
59 if ( is_admin() ) {
60 add_filter( 'redirect_post_location', array( $this, 'redirect_post_location' ) );
61 add_action( 'current_screen', array( $this, 'maybe_show_save_failure_notice' ) );
62 }
63 }
64
65 /**
66 * Returns true if this component is enabled.
67 *
68 * @date 14/4/20
69 * @since ACF 5.9.0
70 *
71 * @return boolean
72 */
73 public function is_enabled() {
74 return (bool) acf_get_setting( 'json' );
75 }
76
77 /**
78 * Returns true if a Local JSON save failure has been recorded for this request.
79 *
80 * @since ACF 6.8.1
81 *
82 * @return boolean
83 */
84 public function has_save_file_failure() {
85 return $this->save_file_failure;
86 }
87
88 /**
89 * Records a Local JSON save failure for this request.
90 *
91 * @since ACF 6.8.1
92 *
93 * @return void
94 */
95 private function record_save_file_failure() {
96 $this->save_file_failure = true;
97 }
98
99 /**
100 * Appends a Local JSON save failure query arg to the post save redirect.
101 *
102 * @since ACF 6.8.1
103 *
104 * @param string $location The redirect location.
105 * @return string
106 */
107 public function redirect_post_location( $location ) {
108 if ( ! $this->has_save_file_failure() ) {
109 return $location;
110 }
111
112 // Only users who can manage SCF should see SCF admin save state.
113 if ( ! current_user_can( acf_get_setting( 'capability' ) ) ) {
114 return $location;
115 }
116
117 return add_query_arg( 'acf_local_json_save_failed', 1, $location );
118 }
119
120 /**
121 * Adds an admin notice when a Local JSON save failure is present in the request.
122 *
123 * @since ACF 6.8.1
124 *
125 * @param WP_Screen $current_screen The current WP_Screen object.
126 * @return void
127 */
128 public function maybe_show_save_failure_notice( $current_screen ) {
129 if ( ! acf_maybe_get_GET( 'acf_local_json_save_failed', false ) ) {
130 return;
131 }
132
133 if ( empty( $current_screen->post_type ) || ! in_array( $current_screen->post_type, acf_get_internal_post_types(), true ) ) {
134 return;
135 }
136
137 // Match the capability used by SCF internal post type save handlers.
138 if ( ! current_user_can( acf_get_setting( 'capability' ) ) ) {
139 return;
140 }
141
142 acf_add_admin_notice(
143 __( 'SCF saved your changes to the database, but could not update the Local JSON file. Check that the configured Local JSON save path is writable.', 'secure-custom-fields' ),
144 'warning'
145 );
146 }
147
148 /**
149 * Gets the path(s) to load JSON from.
150 *
151 * @since ACF 6.2
152 *
153 * @return array
154 */
155 public function get_load_paths() {
156 $paths = (array) acf_get_setting( 'load_json' );
157
158 /**
159 * Filters the path(s) used to load JSON from.
160 *
161 * @since ACF 6.2
162 *
163 * @param array $paths An array of potential paths to load JSON from.
164 * @return array
165 */
166 return (array) apply_filters( 'acf/json/load_paths', $paths );
167 }
168
169 /**
170 * Gets the path(s) to save JSON to.
171 *
172 * @since ACF 6.2
173 *
174 * @param string $key The key to get paths for (optional).
175 * @param array $post The main ACF post array (optional).
176 * @return array
177 */
178 public function get_save_paths( $key = '', $post = array() ) {
179 $name = ! empty( $post['title'] ) ? (string) $post['title'] : '';
180 $post_type = acf_determine_internal_post_type( $key );
181 $paths = array();
182
183 // Paths are sorted by priority, with key overriding name, etc.
184 $paths[] = acf_get_setting( "save_json/key={$key}" );
185 $paths[] = acf_get_setting( "save_json/name={$name}" );
186 $paths[] = acf_get_setting( "save_json/type={$post_type}" );
187 $paths[] = acf_get_setting( 'save_json' );
188 $paths = array_values( array_filter( $paths ) );
189
190 /**
191 * Filters the paths used to save JSON.
192 *
193 * @since ACF 6.2
194 *
195 * @param array $paths An array of the potential paths to save JSON to.
196 * @param array $post The ACF field group, post type, or taxonomy array.
197 * @return array
198 */
199 return (array) apply_filters( 'acf/json/save_paths', $paths, $post );
200 }
201
202 /**
203 * Returns whether Local JSON writes must be restricted for this request.
204 *
205 * @since SCF 6.9.3
206 *
207 * @return boolean
208 */
209 protected function should_restrict_multisite_writes() {
210 return is_multisite() && ! is_super_admin();
211 }
212
213 /**
214 * Gets the directories the current request may write to in multisite.
215 *
216 * Single-site requests and multisite super admins retain unrestricted
217 * Local JSON writes. All other multisite requests may only write to
218 * existing save path directories that canonically reside inside the
219 * current site's uploads directory — the only location WordPress
220 * isolates per site. The "sites" subdirectory of the uploads directory
221 * is always excluded: on the main site it holds the other sites'
222 * files. Sites that need restricted Local JSON writes can point the
223 * save_json setting or the acf/json/save_paths filter at a directory
224 * inside their uploads directory.
225 *
226 * @since SCF 6.9.3
227 *
228 * @param array $paths The configured save path candidates.
229 * @return array|null An array of canonical allowed directories, or null when unrestricted.
230 */
231 private function get_multisite_allowed_write_paths( $paths ) {
232 if ( ! $this->should_restrict_multisite_writes() ) {
233 return null;
234 }
235
236 $uploads = wp_get_upload_dir();
237 $base_dir = isset( $uploads['basedir'] ) && is_string( $uploads['basedir'] ) ? realpath( $uploads['basedir'] ) : false;
238
239 if ( false === $base_dir ) {
240 return array();
241 }
242
243 $base_dir = wp_normalize_path( $base_dir );
244 $sites_dir = trailingslashit( $base_dir ) . 'sites';
245 $allowed_paths = array();
246
247 foreach ( (array) $paths as $path ) {
248 if ( ! is_string( $path ) || '' === $path ) {
249 continue;
250 }
251
252 $real_path = realpath( $path );
253
254 if ( false === $real_path || ! is_dir( $real_path ) ) {
255 continue;
256 }
257
258 $real_path = wp_normalize_path( $real_path );
259
260 if ( $real_path !== $base_dir && 0 !== strpos( $real_path, trailingslashit( $base_dir ) ) ) {
261 continue;
262 }
263
264 if ( $real_path === $sites_dir || 0 === strpos( $real_path, trailingslashit( $sites_dir ) ) ) {
265 continue;
266 }
267
268 $allowed_paths[] = $real_path;
269 }
270
271 return array_values( array_unique( $allowed_paths ) );
272 }
273
274 /**
275 * Returns whether a Local JSON file target is within an allowed directory.
276 *
277 * Saves resolve existing files so symlinks cannot redirect writes outside
278 * an allowed directory. Deletes resolve the lexical file's parent because
279 * unlinking a symlink mutates that directory, not the symlink target. New
280 * targets also resolve their parent directory. Broken or otherwise
281 * unresolvable symlinks are never authorized. Targets match allowed
282 * directories by exact canonical path.
283 *
284 * @since SCF 6.9.3
285 *
286 * @param string $file The target file path.
287 * @param array|null $allowed_paths Canonical allowed directories, or null when unrestricted.
288 * @param string $operation The filesystem operation, either "save" or "delete".
289 * @return boolean
290 */
291 private function is_write_path_allowed( $file, $allowed_paths, $operation ) {
292 if ( null === $allowed_paths ) {
293 return true;
294 }
295
296 if ( array() === $allowed_paths ) {
297 return false;
298 }
299
300 if ( is_link( $file ) && false === realpath( $file ) ) {
301 return false;
302 }
303
304 if ( 'save' === $operation && ( file_exists( $file ) || is_link( $file ) ) ) {
305 $real_file = realpath( $file );
306
307 if ( false === $real_file ) {
308 return false;
309 }
310
311 $directory = dirname( $real_file );
312 } else {
313 $directory = realpath( dirname( $file ) );
314
315 if ( false === $directory ) {
316 return false;
317 }
318 }
319
320 return in_array( wp_normalize_path( $directory ), $allowed_paths, true );
321 }
322
323 /**
324 * Deletes a Local JSON file after checking WordPress's final filtered path.
325 *
326 * @since SCF 6.9.3
327 *
328 * @param string $file The target file path.
329 * @param array|null $allowed_paths Canonical allowed directories, or null when unrestricted.
330 * @return void
331 */
332 private function delete_allowed_file( $file, $allowed_paths ) {
333 if ( null === $allowed_paths ) {
334 wp_delete_file( $file );
335 return;
336 }
337
338 $delete_file = apply_filters( 'wp_delete_file', $file );
339
340 if ( ! is_string( $delete_file ) || empty( $delete_file ) || ! $this->is_write_path_allowed( $delete_file, $allowed_paths, 'delete' ) ) {
341 return;
342 }
343
344 @unlink( $delete_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink, WordPress.PHP.NoSilencedErrors.Discouraged -- Mirrors wp_delete_file() after authorizing its filtered path.
345 }
346
347 /**
348 * Writes field group data to JSON file.
349 *
350 * @date 14/4/20
351 * @since ACF 5.9.0
352 *
353 * @param array $field_group The field group.
354 * @return void
355 */
356 public function update_field_group( $field_group ) {
357
358 // Bail early if disabled.
359 if ( ! $this->is_enabled() ) {
360 return false;
361 }
362
363 // Append fields.
364 $field_group['fields'] = acf_get_fields( $field_group );
365
366 // Save to file.
367 $this->save_file( $field_group['key'], $field_group );
368 }
369
370 /**
371 * Writes ACF posts to the JSON file.
372 *
373 * @since ACF 6.1
374 *
375 * @param array $post The main ACF post array.
376 * @return boolean
377 */
378 public function update_internal_post_type( $post ) {
379 if ( ! $this->is_enabled() ) {
380 return false;
381 }
382
383 /**
384 * Filters the ACF post before saving it to the file.
385 *
386 * @since ACF 6.1
387 *
388 * @param array $post The main ACF post array
389 */
390 $post = apply_filters( 'acf/pre_save_json_file', $post );
391
392 return $this->save_file( $post['key'], $post );
393 }
394
395 /**
396 * Deletes a field group JSON file.
397 *
398 * @date 14/4/20
399 * @since ACF 5.9.0
400 *
401 * @param array $field_group The field group.
402 * @return boolean
403 */
404 public function delete_field_group( $field_group ) {
405 return $this->delete_internal_post_type( $field_group );
406 }
407
408 /**
409 * Deletes an ACF JSON file.
410 *
411 * @since ACF 6.1
412 *
413 * @param array $post The main ACF post array.
414 * @return boolean
415 */
416 public function delete_internal_post_type( $post ) {
417 if ( ! $this->is_enabled() ) {
418 return false;
419 }
420
421 // WP appends '__trashed' to the end of 'key' (post_name).
422 $key = str_replace( '__trashed', '', $post['key'] );
423
424 return $this->delete_file( $key, $post );
425 }
426
427 /**
428 * Includes all local JSON fields.
429 *
430 * @date 14/4/20
431 * @since ACF 5.9.0
432 *
433 * @return void
434 */
435 public function include_fields() {
436
437 // Bail early if disabled.
438 if ( ! $this->is_enabled() ) {
439 return false;
440 }
441
442 // Get load paths.
443 $files = $this->scan_files( 'acf-field-group' );
444 foreach ( $files as $key => $file ) {
445 $json = json_decode( file_get_contents( $file ), true );
446 $json['local'] = 'json';
447 $json['local_file'] = $file;
448 acf_add_local_field_group( $json );
449 }
450 }
451
452 /**
453 * Includes all local JSON post types.
454 *
455 * @since ACF 6.1
456 */
457 public function include_post_types() {
458 // Bail early if disabled.
459 if ( ! $this->is_enabled() ) {
460 return false;
461 }
462
463 // Get load paths.
464 $files = $this->scan_files( 'acf-post-type' );
465 foreach ( $files as $key => $file ) {
466 $json = json_decode( file_get_contents( $file ), true );
467 $json['local'] = 'json';
468 $json['local_file'] = $file;
469 acf_add_local_internal_post_type( $json, 'acf-post-type' );
470 }
471 }
472
473 /**
474 * Includes all local JSON taxonomies.
475 *
476 * @since ACF 6.1
477 */
478 public function include_taxonomies() {
479 // Bail early if disabled.
480 if ( ! $this->is_enabled() ) {
481 return false;
482 }
483
484 // Get load paths.
485 $files = $this->scan_files( 'acf-taxonomy' );
486 foreach ( $files as $key => $file ) {
487 $json = json_decode( file_get_contents( $file ), true );
488 $json['local'] = 'json';
489 $json['local_file'] = $file;
490 acf_add_local_internal_post_type( $json, 'acf-taxonomy' );
491 }
492 }
493
494 /**
495 * Scans for JSON field groups.
496 *
497 * @date 14/4/20
498 * @since ACF 5.9.0
499 *
500 * @return array
501 */
502 function scan_field_groups() {
503 return $this->scan_files( 'acf-field-group' );
504 }
505
506 /**
507 * Scans for JSON files.
508 *
509 * @since ACF 6.1
510 *
511 * @param string $post_type The ACF post type to scan for.
512 * @return array
513 */
514 function scan_files( $post_type = 'acf-field-group' ) {
515 $json_files = array();
516
517 // Loop over "local_json" paths and parse JSON files.
518 foreach ( $this->get_load_paths() as $path ) {
519 if ( is_dir( $path ) ) {
520 $files = scandir( $path );
521 if ( $files ) {
522 foreach ( $files as $filename ) {
523
524 // Ignore hidden files.
525 if ( $filename[0] === '.' ) {
526 continue;
527 }
528
529 // Ignore sub directories.
530 $file = untrailingslashit( $path ) . '/' . $filename;
531 if ( is_dir( $file ) ) {
532 continue;
533 }
534
535 // Ignore non JSON files.
536 $ext = pathinfo( $filename, PATHINFO_EXTENSION );
537 if ( $ext !== 'json' ) {
538 continue;
539 }
540
541 // Read JSON data.
542 $json = json_decode( file_get_contents( $file ), true );
543 if ( ! is_array( $json ) || ! isset( $json['key'] ) ) {
544 continue;
545 }
546
547 // Append data.
548 $json_files[ $json['key'] ] = $file;
549 }
550 }
551 }
552 }
553
554 // Store data and return.
555 $this->files = $json_files;
556 return $this->get_files( $post_type );
557 }
558
559 /**
560 * Returns an array of found JSON files.
561 *
562 * @date 14/4/20
563 * @since ACF 5.9.0
564 *
565 * @param string $post_type The ACF post type to get files for.
566 * @return array
567 */
568 public function get_files( $post_type = 'acf-field-group' ) {
569 $files = array();
570
571 foreach ( $this->files as $key => $path ) {
572 $internal_post_type = acf_determine_internal_post_type( $key );
573
574 if ( $internal_post_type === $post_type ) {
575 $files[ $key ] = $path;
576 } elseif ( 'acf-field-group' === $post_type ) {
577 // If we can't figure out the ACF post type, make an educated guess that it's a field group.
578 $json = json_decode( file_get_contents( $path ), true );
579 if ( ! is_array( $json ) ) {
580 continue;
581 }
582
583 if ( isset( $json['fields'] ) ) {
584 $files[ $key ] = $path;
585 }
586 }
587 }
588
589 return $files;
590 }
591
592 /**
593 * Gets the filename for an ACF JSON file.
594 *
595 * @since ACF 6.3
596 *
597 * @param string $key The ACF post key.
598 * @param array $post The main ACF post array.
599 * @return string|boolean
600 */
601 public function get_filename( $key, $post ) {
602 $load_path = '';
603
604 if ( is_array( $this->files ) && isset( $this->files[ $key ] ) ) {
605 $load_path = $this->files[ $key ];
606 }
607
608 /**
609 * Filters the filename used when saving JSON.
610 *
611 * @since ACF 6.2
612 *
613 * @param string $filename The default filename.
614 * @param array $post The main post array for the item being saved.
615 * @param string $load_path The path that the item was loaded from.
616 */
617 $filename = apply_filters( 'acf/json/save_file_name', $key . '.json', $post, $load_path );
618
619 if ( ! is_string( $filename ) ) {
620 return false;
621 }
622
623 $filename = sanitize_file_name( $filename );
624
625 // sanitize_file_name() can potentially remove all characters.
626 if ( empty( $filename ) ) {
627 return false;
628 }
629
630 return $filename;
631 }
632
633 /**
634 * Saves an ACF JSON file.
635 *
636 * @date 17/4/20
637 * @since ACF 5.9.0
638 *
639 * @param string $key The ACF post key.
640 * @param array $post The main ACF post array.
641 * @return boolean
642 */
643 public function save_file( $key, $post ) {
644 $paths = $this->get_save_paths( $key, $post );
645 $filename = $this->get_filename( $key, $post );
646 $allowed_paths = $this->get_multisite_allowed_write_paths( $paths );
647 $file = false;
648 $first_writable = false;
649
650 // When writes are restricted, skip the existing-file heuristic so an
651 // authorization denial is not later recorded as a filesystem write failure.
652 $has_existing_file = null === $allowed_paths && is_array( $this->files ) && isset( $this->files[ $key ] );
653
654 if ( ! $filename ) {
655 return false;
656 }
657
658 foreach ( $paths as $path ) {
659 if ( ! is_string( $path ) || '' === $path ) {
660 continue;
661 }
662
663 $file_to_check = trailingslashit( $path ) . $filename;
664
665 if ( ! $this->is_write_path_allowed( $file_to_check, $allowed_paths, 'save' ) ) {
666 continue;
667 }
668
669 if ( is_file( $file_to_check ) ) {
670 $has_existing_file = true;
671 }
672
673 if ( ! is_writable( $path ) ) { //phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- non-compatible function for this purpose.
674 continue;
675 }
676
677 if ( false === $first_writable ) {
678 $first_writable = $path;
679 }
680
681 if ( is_file( $file_to_check ) ) {
682 $file = $file_to_check;
683 }
684 }
685
686 if ( ! $file ) {
687 if ( $first_writable ) {
688 $file = trailingslashit( $first_writable ) . $filename;
689 } else {
690 if ( $has_existing_file ) {
691 $this->record_save_file_failure();
692 }
693
694 return false;
695 }
696 }
697
698 // Make sure this is a valid ACF post type.
699 $post_type = acf_determine_internal_post_type( $key );
700 if ( ! $post_type ) {
701 return false;
702 }
703
704 // Append modified time.
705 if ( $post['ID'] ) {
706 $post['modified'] = get_post_modified_time( 'U', true, $post['ID'] );
707 } else {
708 $post['modified'] = strtotime( 'now' );
709 }
710
711 // Prepare for export and save the file.
712 $post = acf_prepare_internal_post_type_for_export( $post, $post_type );
713 $result = file_put_contents( $file, acf_json_encode( $post ) . apply_filters( 'acf/json/eof_newline', PHP_EOL ) ); //phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- potentially could run outside of admin.
714
715 if ( ! is_int( $result ) && $has_existing_file ) {
716 $this->record_save_file_failure();
717 }
718
719 // Return true if bytes were written.
720 return is_int( $result );
721 }
722
723 /**
724 * Deletes an ACF JSON file.
725 *
726 * @date 17/4/20
727 * @since ACF 5.9.0
728 *
729 * @param string $key The ACF post key.
730 * @param array $post The main ACF post array.
731 * @return boolean
732 */
733 public function delete_file( $key, $post = array() ) {
734 $paths = $this->get_save_paths( $key, $post );
735 $filename = $this->get_filename( $key, $post );
736 $allowed_paths = $this->get_multisite_allowed_write_paths( $paths );
737
738 if ( ! $filename ) {
739 return false;
740 }
741
742 foreach ( $paths as $path_to_check ) {
743 if ( ! is_string( $path_to_check ) || '' === $path_to_check ) {
744 continue;
745 }
746
747 $file = untrailingslashit( $path_to_check ) . '/' . $filename;
748
749 if ( ! $this->is_write_path_allowed( $file, $allowed_paths, 'delete' ) ) {
750 continue;
751 }
752
753 if ( is_writable( $file ) ) { //phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- non-compatible function for this purpose.
754 $this->delete_allowed_file( $file, $allowed_paths );
755 }
756 }
757
758 return true;
759 }
760
761 /**
762 * Includes all local JSON files.
763 *
764 * @date 10/03/2014
765 * @since ACF 5.0.0
766 * @deprecated 5.9.0
767 */
768 public function include_json_folders() {
769 _deprecated_function( __METHOD__, '5.9.0', 'ACF_Local_JSON::include_fields()' );
770 $this->include_fields();
771 }
772
773 /**
774 * Includes local JSON files within a specific folder.
775 *
776 * @date 01/05/2017
777 * @since ACF 5.5.13
778 * @deprecated 5.9.0
779 *
780 * @param string $path The path to a specific JSON folder.
781 * @return void
782 */
783 public function include_json_folder( $path = '' ) {
784 _deprecated_function( __METHOD__, '5.9.0' );
785 // Do nothing.
786 }
787 }
788
789 // Initialize.
790 acf_new_instance( 'ACF_Local_JSON' );
791 endif; // class_exists check
792
793 /**
794 * Returns an array of found JSON field group files.
795 *
796 * @date 14/4/20
797 * @since ACF 5.9.0
798 *
799 * @param string $post_type The ACF post type to get files for.
800 * @return array
801 */
802 function acf_get_local_json_files( $post_type = 'acf-field-group' ) {
803 return acf_get_instance( 'ACF_Local_JSON' )->get_files( $post_type );
804 }
805
806 /**
807 * Saves a field group JSON file.
808 *
809 * @date 5/12/2014
810 * @since ACF 5.1.5
811 *
812 * @param array $field_group The field group.
813 * @return boolean
814 */
815 function acf_write_json_field_group( $field_group ) {
816 return acf_get_instance( 'ACF_Local_JSON' )->save_file( $field_group['key'], $field_group );
817 }
818
819 /**
820 * Deletes a field group JSON file.
821 *
822 * @date 5/12/2014
823 * @since ACF 5.1.5
824 *
825 * @param string $key The field group key.
826 * @return boolean True on success.
827 */
828 function acf_delete_json_field_group( $key ) {
829 return acf_get_instance( 'ACF_Local_JSON' )->delete_file( $key );
830 }
831