PluginProbe
Adminify – White Label, Admin Menu Editor, Login Customizer / 4.2.15
Adminify – White Label, Admin Menu Editor, Login Customizer v4.2.15
4.3.1 4.3.0 4.2.26 4.2.25 4.2.24 4.2.23 4.2.22 4.2.21 4.2.20 4.2.19 4.2.18 4.2.17 4.2.16 4.2.15 4.2.14 4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.1.17 All 164 releases
adminify / Inc / Modules / Folders / Folders.php

Folders.php in Adminify – White Label, Admin Menu Editor, Login Customizer 4.2.15, at Inc/Modules/Folders/Folders.php

1,273 lines 43.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace PXLBSAdminify\Inc\Modules\Folders;
4
5 use PXLBSAdminify\Inc\Utils;
6 use PXLBSAdminify\Inc\Admin\AdminSettings;
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 class Folders {
13 private static $postIds;
14 public $options;
15
16 public function __construct() {
17 $this->options = (array) AdminSettings::get_instance()->get('folders');
18
19 add_action( 'init', [ $this, 'register_folders_terms' ], 999 );
20
21 add_action( 'admin_footer', [ $this, 'add_footer_markup' ] );
22
23 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ], PHP_INT_MAX );
24
25 add_action( 'wp_ajax_pxlbsadminify_folder', [ $this, 'handle_adminify_folder' ] );
26
27 add_action( 'ajax_query_attachments_args', [ $this, 'filter_grid' ] );
28
29 add_filter( 'pre_get_posts', [ $this, 'filter_list' ] );
30
31 add_action( 'admin_init', [ $this, 'modify_columns_actions' ] );
32
33 add_action( 'print_media_templates', [ $this, 'modify_media_templates' ] );
34
35 add_filter( 'wp_prepare_attachment_for_js', [ $this, 'modify_attachment_for_js' ] );
36
37 add_action( 'pre-upload-ui', [ $this, 'select_folder_when_upload' ] );
38
39 add_action('wp_ajax_pxlbsadminify_assign_media_folder', array($this, 'handle_folder_assignment'));
40
41 add_action( 'restrict_manage_posts', [$this, 'show_folders_in_list_filter'] );
42
43 add_action( 'add_attachment', [$this ,'assign_media_folder_to_new_attachment'] );
44
45 if ( ! empty( $this->options['media'] )) {
46 add_filter( 'attachment_fields_to_edit', [$this, 'edit_attachment_fields'], 25, 2 );
47
48 add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_scripts_for_media_uploads' ], PHP_INT_MAX );
49 add_action( 'customize_controls_print_footer_scripts', [ $this, 'add_adminify_body_class' ] );
50 add_action( 'customize_controls_enqueue_scripts', [ $this, 'enqueue_scripts_for_media_uploads' ], PHP_INT_MAX );
51
52 // Elementor editor support
53 add_action( 'elementor/editor/footer', [ $this, 'add_adminify_body_class' ] );
54 add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue_scripts_for_media_uploads' ], PHP_INT_MAX );
55
56 // Classic editor support (post/page/CPT edit screens without Gutenberg)
57 add_action( 'admin_enqueue_scripts', [ $this, 'maybe_enqueue_for_classic_editor' ], PHP_INT_MAX );
58 }
59 }
60
61 /**
62 * Enqueue folder scripts for classic editor (non-Gutenberg) post edit screens
63 */
64 public function maybe_enqueue_for_classic_editor( $hook ) {
65 // Only on post edit screens
66 if ( ! in_array( $hook, [ 'post.php', 'post-new.php' ], true ) ) {
67 return;
68 }
69
70 // Skip if Gutenberg/block editor is active for this post type
71 $current_screen = get_current_screen();
72 if ( $current_screen && method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
73 return;
74 }
75
76 $this->enqueue_scripts_for_media_uploads();
77
78 // Add classic editor body class for CSS scoping
79 add_action( 'admin_footer', function() {
80 echo '<script>document.body.classList.add("wp-adminify-classic-editor");</script>';
81 });
82 }
83
84 /**
85 * Add wp-adminify class to body
86 * Required for folder widget CSS to work in media modal (Elementor, Customizer, etc.)
87 */
88 public function add_adminify_body_class() {
89 ?>
90 <script>
91 document.body.classList.add('wp-adminify');
92 </script>
93 <?php
94 }
95
96 public function modify_attachment_for_js( $response ) {
97 if ( ! (bool) $this->options['media'] ) return $response;
98
99 $folders = wp_get_post_terms( $response['id'], 'media_folder' );
100 $folder_ids = wp_list_pluck( $folders, 'term_id' );
101 $response['media_folder'] = implode(',', $folder_ids);
102
103 return $response;
104 }
105
106 public function get_post_types() {
107 $post_types = get_post_types(
108 [
109 'public' => true,
110 ]
111 );
112
113 return $post_types;
114 }
115
116 public function filter_list( $query ) {
117 global $typenow, $pagenow;
118
119 $post_type = $typenow;
120 if ( empty( $post_type ) ) {
121 if ( $pagenow == 'edit.php' ) {
122 $post_type = 'post';
123 } elseif ( $pagenow == 'upload.php' ) {
124 $post_type = 'attachment';
125 }
126 }
127
128 if ( ! isset( $query->query['post_type'] ) ) {
129 return $query;
130 }
131
132 $taxonomy = self::get_post_type_taxonomy( $post_type );
133
134 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
135 if ( ! isset( $_REQUEST[ $taxonomy ] ) ) {
136 return $query;
137 }
138
139 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
140 $term = sanitize_text_field( wp_unslash( $_REQUEST[ $taxonomy ] ) );
141
142 if ( $term != '-1' ) {
143 return $query;
144 }
145
146 unset( $query->query_vars[ $taxonomy ] );
147
148 $tax_query = [
149 'taxonomy' => $taxonomy,
150 'operator' => __( 'NOT EXISTS', 'adminify' ),
151 ];
152
153 $query->set( 'tax_query', [ $tax_query ] );
154 $query->tax_query = new \WP_Tax_Query( [ $tax_query ] );
155
156 return $query;
157 }
158
159 public function filter_grid( $args ) {
160 $taxonomy = self::get_post_type_taxonomy( 'attachment' );
161
162 if ( ! isset( $args[ $taxonomy ] ) ) {
163 return $args;
164 }
165
166 $term = sanitize_text_field( $args[ $taxonomy ] );
167
168 if ( $term != '-1' ) {
169 return $args;
170 }
171
172 unset( $args[ $taxonomy ] );
173
174 $args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- intentional taxonomy filter on an admin-side query.
175 [
176 'taxonomy' => $taxonomy,
177 'operator' => __( 'NOT EXISTS', 'adminify' ),
178 ],
179 ];
180
181 return $args;
182 }
183
184 public function is_module_active( $post_type = null ) {
185 $current_screen = get_current_screen();
186 if ( empty( $current_screen->base ) || ! in_array( $current_screen->base, [ 'upload', 'edit', 'media' ] ) ) {
187 return false;
188 }
189 if($current_screen->base === 'media' && $current_screen->action === 'add'){
190 $post_type = 'attachment';
191 }
192
193 if ( empty( $post_type ) ) {
194 if ( empty( $post_type = $current_screen->post_type ) ) {
195 return false;
196 }
197 }
198
199
200 if ( $post_type == 'attachment' ) {
201 return (bool) $this->options['media'];
202 }
203
204 $default_post_types = [ 'post', 'page' ];
205 $folders_enable_for = (array) $this->options['enable_for'];
206
207 if ( ! jltwp_adminify()->can_use_premium_code() ) {
208 $folders_enable_for = array_intersect( $folders_enable_for, $default_post_types );
209 }
210
211 if ( in_array( $post_type, $folders_enable_for ) ) {
212 return true;
213 }
214
215 return false;
216 }
217
218 // Add folder app markup in footer but move it to wpbody
219 public function add_footer_markup() {
220 if ( $this->is_module_active() ) {
221 ?>
222 <div id="wp-adminify--folder-app"></div>
223 <script>
224 (function() {
225 var folderApp = document.getElementById('wp-adminify--folder-app');
226 var wpbody = document.getElementById('wpbody');
227 var wpbodyContent = document.getElementById('wpbody-content');
228 if (folderApp && wpbody && wpbodyContent) {
229 wpbody.insertBefore(folderApp, wpbodyContent);
230 }
231 })();
232 </script>
233 <?php
234 }
235 }
236
237 public function get_uncategorized_posts( $post_type ) {
238 global $wpdb;
239
240 $post_table = $wpdb->prefix . 'posts';
241 $term_table = $wpdb->prefix . 'term_relationships';
242 $term_taxonomy_table = $wpdb->prefix . 'term_taxonomy';
243 $post_type_tax = self::get_post_type_taxonomy( $post_type );
244
245 if ( $post_type != 'attachment' ) {
246 $query = "SELECT COUNT(DISTINCT({$post_table}.ID)) AS total_records FROM {$post_table} WHERE 1=1 AND (
247 NOT EXISTS (
248 SELECT 1
249 FROM {$term_table}
250 INNER JOIN {$term_taxonomy_table}
251 ON {$term_taxonomy_table}.term_taxonomy_id = {$term_table}.term_taxonomy_id
252 WHERE {$term_taxonomy_table}.taxonomy = '%s'
253 AND {$term_table}.object_id = {$post_table}.ID
254 )
255 ) AND {$post_table}.post_type = '%s' AND (({$post_table}.post_status = 'publish' OR {$post_table}.post_status = 'future' OR {$post_table}.post_status = 'draft' OR {$post_table}.post_status = 'private'))";
256 } else {
257 $query = "SELECT COUNT(DISTINCT({$post_table}.ID)) AS total_records FROM {$post_table} WHERE 1=1 AND (
258 NOT EXISTS (
259 SELECT 1
260 FROM {$term_table}
261 INNER JOIN {$term_taxonomy_table}
262 ON {$term_taxonomy_table}.term_taxonomy_id = {$term_table}.term_taxonomy_id
263 WHERE {$term_taxonomy_table}.taxonomy = '%s'
264 AND {$term_table}.object_id = {$post_table}.ID
265 )
266 ) AND {$post_table}.post_type = '%s' AND {$post_table}.post_status = 'inherit'";
267 }
268
269 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $query is composed only of trusted internal $wpdb->prefix table names; user values are bound here as %s placeholders.
270 $query = $wpdb->prepare( $query, $post_type_tax, $post_type );
271
272 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- direct query required for uncategorized post count; not cached intentionally; $query already prepared above.
273 $total = $wpdb->get_var( $query );
274
275 return empty( $total ) ? 0 : $total;
276 }
277
278 public function get_folders( $post_type, $post_type_tax ) {
279 $args = [
280 'taxonomy' => $post_type_tax,
281 'hide_empty' => false,
282 'pad_counts' => true,
283 ];
284
285 $folders = get_terms( $args );
286
287 if ( is_wp_error( $folders ) ) {
288 return [];
289 }
290
291 foreach ( $folders as $folder ) {
292 $posts = $this->get_posts_by_folder( $folder->term_id, $post_type, $post_type_tax );
293
294 $color = get_term_meta( $folder->term_id, 'pxlbsadminify_fodler_color', true );
295 if ( empty( $color ) ) {
296 $color = '';
297 }
298
299 $folder->color = $color;
300 $folder->count = count( $posts );
301 $folder->posts = $posts;
302 }
303
304 return $folders;
305 }
306
307 public function enqueue_scripts() {
308 if ( ! $this->is_module_active() ) {
309 return;
310 }
311
312 $current_screen = get_current_screen();
313 wp_enqueue_style( 'wp-adminify--folder', PXLBSADMINIFY_URL . 'assets/admin/css/wp-adminify--folder' . Utils::assets_ext( '.css' ), [], PXLBSADMINIFY_VER );
314 wp_enqueue_style('adminify-simple-line-icons', PXLBSADMINIFY_ASSETS . '/vendors/font-icons/simple-line-icons/css/simple-line-icons' . Utils::assets_ext('.css'), false, PXLBSADMINIFY_VER);
315
316 $post_type = $current_screen->post_type;
317 $post_type_tax = self::get_post_type_taxonomy( $post_type );
318 if($current_screen->base === 'media' && $current_screen->action === 'add'){
319 $post_type = 'attachment';
320 $post_type_tax = 'media_folder';
321 }
322 $media_folders = get_terms([
323 'taxonomy' => 'media_folder',
324 'hide_empty' => false,
325 ]);
326
327 $media_folders_data = [];
328 foreach ($media_folders as $media_folder) {
329 $media_folders_data[$media_folder->term_id] = [
330 'name' => $media_folder->name,
331 'slug' => $media_folder->slug,
332 'term_id' => $media_folder->term_id
333 ];
334 }
335
336 $data = [
337 'adminurl' => admin_url(),
338 'ajaxurl' => admin_url( 'admin-ajax.php' ),
339 'post_type' => $post_type,
340 'nonce' => wp_create_nonce( 'pxlbsadminify_folder_nonce' ),
341 'post_type_tax' => $post_type_tax,
342 'media_folders_data' => $media_folders_data,
343 'is_pro' => wp_validate_boolean( jltwp_adminify()->can_use_premium_code() ),
344 'pro_notice' => Utils::upgrade_pro_notice(),
345 ];
346
347 $data = array_merge( $data, $this->refreshed_folder_data( $post_type, $post_type_tax ) );
348
349 // Enqueue Scripts
350 wp_enqueue_script('wp-adminify--folder', PXLBSADMINIFY_ASSETS . 'admin/js/wp-adminify--folder' . Utils::assets_ext('.js'), array('jquery', 'jquery-ui-droppable', 'jquery-ui-draggable'), PXLBSADMINIFY_VER, true);
351
352 wp_localize_script( 'wp-adminify--folder', 'PXLBSADMINIFY_FOLDER_DATA', $data );
353
354 $inline_script_content = '
355 (function($) {
356 $(document).ready(function() {
357
358 let selectedFolder = null;
359
360 if (typeof wp.media !== "undefined") {
361 const urlParams = new URLSearchParams(window.location.search);
362 const currentFolder = urlParams.get("media_folder");
363 selectedFolder = currentFolder;
364
365 const MediaLibraryTaxonomyFilter = wp.media.view.AttachmentFilters.extend({
366 id: "wp-adminify-media-folder-filter",
367 createFilters: function() {
368 const filters = {};
369 filters.all = {
370 text: "All folders",
371 props: { folders: "" },
372 priority: 10
373 };
374 _.each(window.PXLBSADMINIFY_FOLDER_DATA?.media_folders_data || {}, function(value) {
375 filters[value.slug] = {
376 text: value.name,
377 props: { folders: value.slug },
378 priority: 20
379 };
380 });
381 this.filters = filters;
382 },
383 initialize: function() {
384 wp.media.view.AttachmentFilters.prototype.initialize.apply(this, arguments);
385 if (currentFolder) {
386 this.model.set("folders", currentFolder);
387 setTimeout(() => {
388 this.$el.find("select").val(currentFolder);
389 }, 100);
390 }
391 }
392 });
393
394 const AttachmentsBrowser = wp.media.view.AttachmentsBrowser;
395 wp.media.view.AttachmentsBrowser = AttachmentsBrowser.extend({
396 createToolbar: function() {
397 AttachmentsBrowser.prototype.createToolbar.call(this);
398 this.toolbar.set("MediaLibraryTaxonomyFilter", new MediaLibraryTaxonomyFilter({
399 controller: this.controller,
400 model: this.collection.props,
401 priority: -75
402 }).render());
403 }
404 });
405 }
406
407 if (typeof wp.Uploader === "function") {
408 $.extend(wp.Uploader.prototype, {
409 init: function() {
410 selectedFolder = $("#folders").val() || currentFolder;
411
412 $("body").on("change", "#folders", function() {
413 selectedFolder = $(this).val();
414 });
415
416 if (this.uploader) {
417 this.uploader.bind("BeforeUpload", function(up, file) {
418 up.settings.multipart_params = up.settings.multipart_params || {};
419 up.settings.multipart_params.folder_id = selectedFolder;
420 });
421 this.uploader.bind("UploadComplete", function() {
422 if (typeof wp !== "undefined" && wp.media && wp.media.frame) {
423 const frame = wp.media.frame;
424 const state = frame.state();
425 const library = state.get("library");
426
427 if (library) {
428 library.props.set("ignore", Date.now());
429 state.trigger("reset");
430 }
431 }
432 });
433 }
434 }
435 });
436 }
437
438 });
439 })(jQuery);
440 ';
441
442 wp_add_inline_script('media-views', $inline_script_content, 'after');
443 }
444
445 function enqueue_scripts_for_media_uploads(){
446 wp_enqueue_media();
447 wp_enqueue_script('media-views');
448 wp_enqueue_script('wp-dom-ready');
449
450 // Enqueue folder styles for modal
451 wp_enqueue_style( 'wp-adminify--folder', PXLBSADMINIFY_URL . 'assets/admin/css/wp-adminify--folder' . Utils::assets_ext( '.css' ), [], PXLBSADMINIFY_VER );
452 wp_enqueue_style('adminify-simple-line-icons', PXLBSADMINIFY_ASSETS . '/vendors/font-icons/simple-line-icons/css/simple-line-icons' . Utils::assets_ext('.css'), false, PXLBSADMINIFY_VER);
453
454 // Enqueue folder script for modal sidebar
455 wp_enqueue_script('wp-adminify--popup-folder', PXLBSADMINIFY_ASSETS . 'admin/js/wp-adminify--popup-folder' . Utils::assets_ext('.js'), array('jquery', 'jquery-ui-droppable', 'jquery-ui-draggable', 'wp-element', 'wp-dom-ready'), PXLBSADMINIFY_VER, true);
456
457 wp_enqueue_script('wp-adminify--folder', PXLBSADMINIFY_ASSETS . 'admin/js/wp-adminify--folder' . Utils::assets_ext('.js'), array('jquery', 'jquery-ui-droppable', 'jquery-ui-draggable', 'wp-element', 'wp-dom-ready'), PXLBSADMINIFY_VER, true);
458
459 $post_type = 'attachment';
460 $post_type_tax = 'media_folder';
461
462 // Get simplified folder data for dropdown filter
463 $media_folders = get_terms([
464 'taxonomy' => 'media_folder',
465 'hide_empty' => false,
466 ]);
467
468 $media_folders_data = [];
469 foreach ($media_folders as $folder) {
470 $media_folders_data[$folder->term_id] = [
471 'name' => $folder->name,
472 'slug' => $folder->slug,
473 'term_id' => $folder->term_id
474 ];
475 }
476
477 // Build full folder data for React sidebar
478 $data = apply_filters(
479 'pxlbsadminify_folder_data',
480 [
481 'adminurl' => admin_url(),
482 'ajaxurl' => admin_url( 'admin-ajax.php' ),
483 'post_type' => $post_type,
484 'nonce' => wp_create_nonce( 'pxlbsadminify_folder_nonce' ),
485 'post_type_tax' => $post_type_tax,
486 'media_folders_data' => $media_folders_data,
487 'is_pro' => wp_validate_boolean( jltwp_adminify()->can_use_premium_code() ),
488 'pro_notice' => Utils::upgrade_pro_notice(),
489 'is_rtl' => is_rtl(),
490 ]
491 );
492
493 // Add full folder data (folders, hierarchy, counts)
494 $data = array_merge( $data, $this->refreshed_folder_data( $post_type, $post_type_tax ) );
495
496 // Localize the data to multiple script handles
497 // This ensures data is available in different contexts
498 wp_localize_script( 'wp-adminify--folder', 'PXLBSADMINIFY_FOLDER_DATA', $data );
499 wp_localize_script( 'wp-dom-ready', 'PXLBSADMINIFY_FOLDER_DATA', $data );
500 wp_localize_script( 'media-views', 'PXLBSADMINIFY_FOLDER_DATA', $data );
501
502
503 $inline_script_content = '
504 (function($) {
505 $(document).ready(function() {
506
507 let selectedFolder = null;
508
509 if (typeof wp.media !== "undefined") {
510 const urlParams = new URLSearchParams(window.location.search);
511 const currentFolder = urlParams.get("media_folder");
512 selectedFolder = currentFolder;
513
514 const MediaLibraryTaxonomyFilter = wp.media.view.AttachmentFilters.extend({
515 id: "wp-adminify-media-folder-filter",
516 createFilters: function() {
517 const filters = {};
518 filters.all = {
519 text: "All folders",
520 props: { folders: "" },
521 priority: 10
522 };
523 _.each(window.PXLBSADMINIFY_FOLDER_DATA?.media_folders_data || {}, function(value) {
524 filters[value.slug] = {
525 text: value.name,
526 props: { folders: value.slug },
527 priority: 20
528 };
529 });
530 this.filters = filters;
531 },
532 initialize: function() {
533 wp.media.view.AttachmentFilters.prototype.initialize.apply(this, arguments);
534 if (currentFolder) {
535 this.model.set("folders", currentFolder);
536 setTimeout(() => {
537 this.$el.find("select").val(currentFolder);
538 }, 100);
539 }
540 }
541 });
542
543 // Note: AttachmentsBrowser extension for folder sidebar is handled in wp-adminify--folder.js
544 }
545
546 if (typeof wp.Uploader === "function") {
547 $.extend(wp.Uploader.prototype, {
548 init: function() {
549 selectedFolder = $("#folders").val() || currentFolder;
550
551 $("body").on("change", "#folders", function() {
552 selectedFolder = $(this).val();
553 });
554
555 if (this.uploader) {
556 this.uploader.bind("BeforeUpload", function(up, file) {
557 up.settings.multipart_params = up.settings.multipart_params || {};
558 up.settings.multipart_params.folder_id = selectedFolder;
559 });
560 this.uploader.bind("UploadComplete", function() {
561 if (typeof wp !== "undefined" && wp.media && wp.media.frame) {
562 const frame = wp.media.frame;
563 const state = frame.state();
564 const library = state.get("library");
565
566 if (library) {
567 library.props.set("ignore", Date.now());
568 state.trigger("reset");
569 }
570 }
571 });
572 }
573 }
574 });
575 }
576
577 });
578 })(jQuery);
579 ';
580
581 wp_add_inline_script('media-views', $inline_script_content, 'after');
582 }
583 public function modify_media_templates() { ?>
584 <script>
585 var attachment_template = jQuery('#tmpl-attachment');
586 if ( attachment_template.length ) {
587 var template = attachment_template.html().replace( 'data.orientation }}">', 'data.orientation }}" data-folders="{{ data.media_folder }}">' );
588 attachment_template.html( template );
589 }
590 </script>
591 <?php
592 }
593
594 public function handle_adminify_folder() {
595 check_ajax_referer( 'pxlbsadminify_folder_nonce' );
596
597 if ( ! current_user_can( 'edit_posts' ) ) {
598 wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ) );
599 }
600
601 $allowed_routes = array(
602 'delete_folders',
603 'rename_folder',
604 'move_to_folder',
605 'refresh_folders',
606 'create_new_folder',
607 );
608
609 if ( ! empty( $_POST['route'] ) ) {
610 $route = sanitize_key( wp_unslash( $_POST['route'] ) );
611
612 if ( in_array( $route, $allowed_routes, true ) ) {
613 $route_handler = 'handle_' . $route;
614 if ( method_exists( $this, $route_handler ) ) {
615 $this->$route_handler( wp_unslash( $_POST ) );
616 }
617 }
618 }
619
620 wp_send_json_error( [ 'message' => __( 'Something is wrong, no route found', 'adminify' ) ], 400 );
621 }
622
623 public function handle_delete_folders( $data ) {
624 check_ajax_referer( 'pxlbsadminify_folder_nonce' );
625
626 if ( ! current_user_can( 'edit_posts' ) ) {
627 wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ], 403 );
628 }
629
630 if ( empty( $data['term_ids'] ) || empty( $data['post_type'] ) || empty( $data['post_type_tax'] ) ) {
631 wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
632 }
633
634 $term_ids = array_map( 'absint', (array) $data['term_ids'] );
635 $post_type = sanitize_key( $data['post_type'] );
636 $post_type_tax = sanitize_key( $data['post_type_tax'] );
637
638 foreach ( $term_ids as $term_id ) {
639 wp_delete_term( $term_id, $post_type_tax );
640 }
641
642 $data = $this->refreshed_folder_data( $post_type, $post_type_tax );
643
644 wp_send_json_success( $data );
645 }
646
647 public function handle_rename_folder( $data ) {
648 check_ajax_referer( 'pxlbsadminify_folder_nonce' );
649
650 if ( ! current_user_can( 'edit_posts' ) ) {
651 wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ], 403 );
652 }
653
654 if ( empty( $data['term_id'] ) || empty( $data['post_type'] ) || empty( $data['post_type_tax'] ) || empty( $data['folder_name'] ) || empty( $data['folder_color_tag'] ) ) {
655 wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
656 }
657
658 $term_id = absint( $data['term_id'] );
659 $folder_name = sanitize_text_field( $data['folder_name'] );
660 $post_type = sanitize_key( $data['post_type'] );
661 $post_type_tax = sanitize_key( $data['post_type_tax'] );
662 $folder_color_tag = sanitize_text_field( $data['folder_color_tag'] );
663
664 $update = wp_update_term( $term_id, $post_type_tax, [ 'name' => $folder_name ] );
665
666 if ( is_wp_error( $update ) ) {
667 wp_send_json_success( [ 'message' => $update->get_error_message() ], 202 );
668 }
669
670 update_term_meta( $term_id, 'pxlbsadminify_fodler_color', $folder_color_tag );
671 $data = $this->refreshed_folder_data( $post_type, $post_type_tax );
672
673 wp_send_json_success( $data );
674 }
675
676 public function handle_move_to_folder( $data ) {
677 check_ajax_referer( 'pxlbsadminify_folder_nonce' );
678
679 if ( ! current_user_can( 'edit_posts' ) ) {
680 wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ], 403 );
681 }
682
683 if ( empty( $data['post_ids'] ) || empty( $data['folder_id'] ) || empty( $data['post_type'] ) || empty( $data['post_type_tax'] ) ) {
684 wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
685 }
686
687 $post_ids = array_map( 'absint', (array) $data['post_ids'] );
688 $folder_id = sanitize_text_field( $data['folder_id'] );
689 $post_type = sanitize_text_field( $data['post_type'] );
690 $post_type_tax = sanitize_text_field( $data['post_type_tax'] );
691 $screen = isset( $data['screen'] ) ? sanitize_text_field( $data['screen'] ) : '';
692
693 global $mode;
694
695 $mode = empty( $data['mode'] ) ? 'list' : sanitize_text_field( $data['mode'] );
696 $move_to_folder = wp_validate_boolean( $data['move_to_folder'] );
697
698 foreach ( $post_ids as $post_id ) {
699 if ( $folder_id == 'uncategorized' ) {
700 wp_set_object_terms( $post_id, '', $post_type_tax, false );
701 continue;
702 }
703
704 $term = get_term( $folder_id );
705
706 if ( ! empty( $term ) && isset( $term->slug ) ) {
707 wp_set_object_terms( $post_id, $term->slug, $post_type_tax, ! $move_to_folder );
708 }
709 }
710
711 $updated_rows = '';
712
713 if ( $post_type == 'attachment' ) {
714 global $wp_query;
715
716 $args = [
717 'posts_per_page' => count( $post_ids ),
718 'orderby' => 'title',
719 'order' => 'ASC',
720 'post_type' => $post_type,
721 'post_status' => 'any',
722 'post__in' => $post_ids,
723 ];
724
725 $wp_query = new \WP_Query( $args );
726
727 $wp_list_table = \_get_list_table( 'WP_Media_List_Table', [ 'screen' => $screen ] );
728
729 foreach ( $post_ids as $post_id ) {
730 ob_start();
731 $wp_list_table->display_rows();
732 $updated_rows = ob_get_clean();
733 }
734 } else {
735 $wp_list_table = \_get_list_table( 'WP_Posts_List_Table', [ 'screen' => $screen ] );
736
737 foreach ( $post_ids as $post_id ) {
738 $level = 0;
739
740 if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) {
741 $request_post = [ get_post( $post_id ) ];
742 $parent = $request_post[0]->post_parent;
743 while ( $parent > 0 ) {
744 $parent_post = get_post( $parent );
745 $parent = $parent_post->post_parent;
746 $level++;
747 }
748 }
749
750 ob_start();
751 $wp_list_table->display_rows( [ get_post( $post_id ) ], $level );
752 $updated_rows .= ob_get_clean();
753 }
754 }
755
756 $data = [
757 'updated_rows' => $updated_rows,
758 ];
759
760 $data = array_merge( $data, $this->refreshed_folder_data( $post_type, $post_type_tax ) );
761
762 wp_send_json_success( $data );
763 }
764
765 public function handle_refresh_folders( $data ) {
766 check_ajax_referer( 'pxlbsadminify_folder_nonce' );
767
768 if ( ! current_user_can( 'edit_posts' ) ) {
769 wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ], 403 );
770 }
771
772 if ( empty( $data['post_type'] ) || empty( $data['post_type_tax'] ) ) {
773 wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
774 }
775
776 $post_type = sanitize_key( $data['post_type'] );
777 $post_type_tax = sanitize_key( $data['post_type_tax'] );
778
779 wp_send_json_success( $this->refreshed_folder_data( $post_type, $post_type_tax ) );
780 }
781
782 public function handle_create_new_folder( $data ) {
783 check_ajax_referer( 'pxlbsadminify_folder_nonce' );
784
785 if ( ! current_user_can( 'edit_posts' ) ) {
786 wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ], 403 );
787 }
788
789 if ( empty( $data['post_type'] ) || empty( $data['post_type_tax'] ) ) {
790 wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
791 }
792 if ( empty( $data['new_folder_name'] ) || empty( $data['folder_color_tag'] ) ) {
793 wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
794 }
795
796 $post_type = sanitize_key( $data['post_type'] );
797 $post_type_tax = sanitize_key( $data['post_type_tax'] );
798 $new_folder_name = sanitize_text_field( $data['new_folder_name'] );
799 $folder_color_tag = sanitize_text_field( $data['folder_color_tag'] );
800 $parent_term_id = 0;
801
802 if ( jltwp_adminify()->can_use_premium_code() && ! empty( $data['parent_folder'] ) ) {
803 $parent_term_id = absint( $data['parent_folder'] );
804 }
805
806 $insert_data = wp_insert_term( $new_folder_name, $post_type_tax, [ 'parent' => $parent_term_id ] );
807
808 if ( ! is_wp_error( $insert_data ) ) {
809 update_term_meta( $insert_data['term_id'], 'pxlbsadminify_fodler_color', $folder_color_tag );
810 wp_send_json_success( $this->refreshed_folder_data( $post_type, $post_type_tax ) );
811 } else {
812 wp_send_json_success( [ 'message' => $insert_data->get_error_message() ], 202 );
813 }
814 }
815
816 function refreshed_folder_data( $post_type, $post_type_tax ) {
817 remove_filter( 'pre_get_posts', [ $this, 'filter_list' ] );
818
819 return [
820 'folders' => $this->get_folders( $post_type, $post_type_tax ),
821 'folder_hierarchy' => _get_term_hierarchy( $post_type_tax ),
822 'total_posts' => wp_count_posts( $post_type ),
823 'total_uncat_posts' => $this->get_uncategorized_posts( $post_type ),
824 ];
825 }
826
827 function set_post_categories( $post_ID = 0, $post_categories = [], $append = false ) {
828 $post_ID = (int) $post_ID;
829 $post_type = get_post_type( $post_ID );
830 $post_status = get_post_status( $post_ID );
831
832 // If $post_categories isn't already an array, make it one.
833 $post_categories = (array) $post_categories;
834
835 if ( empty( $post_categories ) ) {
836 /**
837 * Filters post types (in addition to 'post') that require a default category.
838 *
839 * @since 5.5.0
840 *
841 * @param string[] $post_types An array of post type names. Default empty array.
842 */
843 $default_category_post_types = apply_filters( 'pxlbsadminify_default_category_post_types', [] );
844
845 // Regular posts always require a default category.
846 $default_category_post_types = array_merge( $default_category_post_types, [ 'post' ] );
847
848 if ( in_array( $post_type, $default_category_post_types, true ) && is_object_in_taxonomy( $post_type, 'category' ) && 'auto-draft' !== $post_status ) {
849 $post_categories = [ get_option( 'default_category' ) ];
850 $append = false;
851 } else {
852 $post_categories = [];
853 }
854 } elseif ( 1 === count( $post_categories ) && '' === reset( $post_categories ) ) {
855 return true;
856 }
857
858 return wp_set_post_terms( $post_ID, $post_categories, 'category', $append );
859 }
860
861 public function get_posts_by_folder( $term_id, $post_type, $taxonomy ) {
862 $posts = get_posts(
863 [
864 'numberposts' => -1,
865 'post_status' => 'any',
866 'post_type' => $post_type,
867 'fields' => 'ids',
868 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- intentional taxonomy filter on an admin-side query.
869 [
870 'operator' => 'IN',
871 'taxonomy' => $taxonomy,
872 'field' => 'term_id',
873 'terms' => $term_id,
874 ],
875 ],
876 ]
877 );
878
879 return $posts;
880 }
881
882 public static function get_post_type_taxonomy( $post_type ) {
883 if ( $post_type == 'page' ) {
884 return 'folder';
885 }
886
887 if ( $post_type == 'attachment' ) {
888 $post_type = 'media';
889 }
890
891 return $post_type . '_folder';
892 }
893
894 public function register_folders_terms() {
895 $post_types = $this->get_post_types();
896
897 foreach ( $post_types as $post_type ) {
898 $labels = [
899 'name' => esc_html__( 'Folders', 'adminify' ),
900 'singular_name' => esc_html__( 'Folder', 'adminify' ),
901 'all_items' => esc_html__( 'All Folders', 'adminify' ),
902 'edit_item' => esc_html__( 'Edit Folder', 'adminify' ),
903 'update_item' => esc_html__( 'Update Folder', 'adminify' ),
904 'add_new_item' => esc_html__( 'Add New Folder', 'adminify' ),
905 'new_item_name' => esc_html__( 'Add folder name', 'adminify' ),
906 'menu_name' => esc_html__( 'Folders', 'adminify' ),
907 'search_items' => esc_html__( 'Search Folders', 'adminify' ),
908 'parent_item' => esc_html__( 'Parent Folder', 'adminify' ),
909 ];
910
911 $args = [
912 'label' => esc_html__( 'Folder', 'adminify' ),
913 'labels' => $labels,
914 'show_tagcloud' => false,
915 'hierarchical' => true,
916 'public' => false,
917 'show_ui' => false,
918 'show_in_menu' => false,
919 'show_in_rest' => true,
920 'show_admin_column' => true,
921 'query_var' => true,
922 'rewrite' => false,
923 'capabilities' => [
924 'manage_terms' => 'manage_categories',
925 'edit_terms' => 'manage_categories',
926 'delete_terms' => 'manage_categories',
927 'assign_terms' => 'manage_categories',
928 ],
929 ];
930
931 $taxonomy = self::get_post_type_taxonomy( $post_type );
932
933 register_taxonomy( $taxonomy, $post_type, $args );
934 }
935 }
936
937 public function modify_columns_actions() {
938 $post_types = $this->get_post_types();
939
940 foreach ( $post_types as $post_type ) {
941 if ( $post_type == 'post' ) {
942 add_filter( 'manage_edit-post_columns', [ $this, 'manage_columns_head' ] );
943 add_filter( 'manage_posts_columns', [ $this, 'manage_columns_head' ] );
944 add_action( 'manage_posts_custom_column', [ $this, 'manage_columns_content' ], 10, 2 );
945 add_filter( 'bulk_actions-edit-post', [ $this, 'custom_bulk_action' ] );
946 } elseif ( $post_type == 'page' ) {
947 add_filter( 'manage_edit-page_columns', [ $this, 'manage_columns_head' ] );
948 add_filter( 'manage_page_posts_columns', [ $this, 'manage_columns_head' ] );
949 add_action( 'manage_page_posts_custom_column', [ $this, 'manage_columns_content' ], 10, 2 );
950 add_filter( 'bulk_actions-edit-page', [ $this, 'custom_bulk_action' ] );
951 } elseif ( $post_type == 'attachment' ) {
952 add_filter( 'manage_media_columns', [ $this, 'manage_columns_head' ] );
953 add_action( 'manage_media_custom_column', [ $this, 'manage_columns_content' ], 10, 2 );
954 } else {
955 add_filter( 'manage_edit-' . $post_type . '_columns', [ $this, 'manage_columns_head' ], 99999 );
956 add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'manage_columns_content' ], 2, 2 );
957 add_filter( 'bulk_actions-edit-' . $post_type, [ $this, 'custom_bulk_action' ] );
958 }
959 }
960 }
961
962 function manage_columns_head( $posts_columns ) {
963 $post_type = null;
964
965 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
966 if ( isset( $_REQUEST['post_type'] ) ) {
967 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
968 $post_type = sanitize_text_field( wp_unslash( $_REQUEST['post_type'] ) );
969 }
970
971 if ( $this->is_module_active( $post_type ) ) {
972 $title = sprintf( __( 'Moving selected items', 'adminify' ), '<span class="adminify-folder-items--count"></span>' );
973 return [
974 'adminify_move' => '<div class="adminify-move-multiple adminify-col" title="' . esc_attr__( 'Move selected items', 'adminify' ) . '"><span class="dashicons dashicons-move"></span><span class="adminify-move-file--title">' . wp_kses_post( $title ) . '</span><div class="adminify-items"></div></div>',
975 ] + $posts_columns;
976 }
977
978 return $posts_columns;
979 }
980
981 function manage_columns_content( $column_name, $post_ID ) {
982 $post_type = null;
983
984 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
985 if ( isset( $_REQUEST['post_type'] ) ) {
986 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
987 $post_type = sanitize_text_field( wp_unslash( $_REQUEST['post_type'] ) );
988 }
989
990 $postIDs = self::$postIds;
991
992 $folder_ids = $this->get_folders_by_post( $post_ID );
993 $folder_ids = implode( ',', $folder_ids );
994
995 if ( ! is_array( $postIDs ) ) {
996 $postIDs = [];
997 }
998
999 if ( ! in_array( $post_ID, $postIDs ) ) {
1000 $postIDs[] = $post_ID;
1001 self::$postIds = $postIDs;
1002
1003 if ( $this->is_module_active( $post_type ) ) {
1004 if ( $column_name == 'adminify_move' ) {
1005 $title = get_the_title();
1006 if ( strlen( $title ) > 20 ) {
1007 $title = substr( $title, 0, 20 ) . '...';
1008 }
1009 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- kses_custom() is a wp_kses() wrapper; output already escaped.
1010 echo Utils::kses_custom( '<div class="adminify-move-file" data-id="' . esc_attr( $post_ID ) . '" data-folders="' . esc_attr( $folder_ids ) . '"><span class="adminify-move dashicons dashicons-move"></span><span class="adminify-move-file--title">' . esc_html( $title ) . '</span></div>' );
1011 }
1012 }
1013 }
1014 }
1015
1016 public function get_folders_by_post( $post_ID, $return_type = 'ids' ) {
1017 $folder_tax = self::get_post_type_taxonomy( get_post_type( $post_ID ) );
1018 $folders = wp_get_post_terms( $post_ID, $folder_tax );
1019 if ( $return_type == 'ids' ) {
1020 return wp_list_pluck( $folders, 'term_id' );
1021 }
1022 return $folders;
1023 }
1024
1025 public function custom_bulk_action( $bulk_actions ) {
1026 $bulk_actions['move_to_folder'] = __( 'Move to Folder', 'adminify' );
1027 return $bulk_actions;
1028 }
1029
1030 public function select_folder_when_upload() {
1031 if ( ! $this->options['media'] ) {
1032 return;
1033 }
1034 // Only show the folder selector to users who can upload media.
1035 if ( ! current_user_can( 'upload_files' ) ) {
1036 return;
1037 }
1038 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
1039 $selected = isset($_GET['media_folder']) ? sanitize_text_field( wp_unslash( $_GET['media_folder'] ) ) : '';
1040 wp_dropdown_categories( array(
1041 'show_option_none' => 'Choose folder',
1042 'taxonomy' => 'media_folder',
1043 'name' => 'folder_id',
1044 'id' => 'folders',
1045 'orderby' => 'name',
1046 'selected' => $selected,
1047 'hierarchical' => true,
1048 'value_field' => 'slug',
1049 'hide_empty' => 0,
1050 ) );
1051
1052 }
1053
1054 public function assign_media_folder_to_new_attachment( $post_ID ) {
1055
1056 // Authorization: only users who can edit this attachment may change its folder.
1057 if ( ! current_user_can( 'upload_files' ) || ! current_user_can( 'edit_post', $post_ID ) ) {
1058 return;
1059 }
1060
1061 // CSRF: the media upload request is signed with core's media-form nonce.
1062 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
1063 if ( ! $nonce || ! wp_verify_nonce( $nonce, 'media-form' ) ) {
1064 return;
1065 }
1066
1067 $folder_id = isset( $_POST['folder_id'] ) ? sanitize_text_field( wp_unslash( $_POST['folder_id'] ) ) : '';
1068
1069 if ( '' !== $folder_id && '-1' !== $folder_id ) {
1070 $term = get_term_by( 'slug', $folder_id, 'media_folder' );
1071 if ( ! $term && is_numeric( $folder_id ) ) {
1072 $term = get_term_by( 'id', (int) $folder_id, 'media_folder' );
1073 }
1074
1075 $post_type = 'attachment';
1076 $post_type_tax = $folder_id;
1077
1078 if ( $term && ! is_wp_error( $term ) ) {
1079 wp_set_object_terms( $post_ID, (int) $term->term_id, 'media_folder' );
1080 } else {
1081 // if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
1082 // error_log( 'Media Folder: Could not find term for value "' . $folder_id . '" for attachment ID ' . $post_ID );
1083 // }
1084 }
1085 $this->refreshed_folder_data( $post_type, $post_type_tax );
1086 }
1087 }
1088
1089
1090 public function edit_attachment_fields($form_fields, $post) {
1091 $folder_fields = array(
1092 'label' => 'Folders',
1093 'show_in_edit' => false,
1094 'input' => 'html',
1095 'value' => '',
1096 );
1097
1098 $taxonomy_name = 'media_folder';
1099
1100 // get the assigned media library folders from the cache
1101 $terms = get_the_terms($post->ID, $taxonomy_name);
1102 if ($terms) {
1103 $folder_fields['value'] = join(', ', wp_list_pluck($terms, 'slug'));
1104 }
1105
1106 ob_start();
1107 $this->render_terms_dropdown($post->ID, $taxonomy_name);
1108 $html = ob_get_contents();
1109 ob_end_clean();
1110
1111 $folder_fields['html'] = $html;
1112 $form_fields[$taxonomy_name] = $folder_fields;
1113
1114 return $form_fields;
1115 }
1116
1117 public function render_terms_dropdown($post_id, $taxonomy) {
1118 $selected_terms = wp_get_object_terms($post_id, $taxonomy, array('fields' => 'ids'));
1119 $selected_term = !empty($selected_terms) ? $selected_terms[0] : 0;
1120
1121 $terms = get_terms(array(
1122 'taxonomy' => $taxonomy,
1123 'hide_empty' => false,
1124 'parent' => 0
1125 ));
1126
1127 echo '<select name="media_folder_select" class="media-folder-select" data-attachment-id="' . esc_attr( $post_id ) . '">';
1128 echo '<option value="">— Select Folder —</option>';
1129
1130 if (!empty($terms) && !is_wp_error($terms)) {
1131 foreach ($terms as $term) {
1132 $this->render_term_option($term, $selected_term, $taxonomy);
1133 }
1134 }
1135
1136 echo '</select>';
1137
1138 $this->add_folder_script();
1139 }
1140
1141 public function render_term_option($term, $selected_term, $taxonomy, $depth = 0) {
1142 // Build the indentation from real non-breaking-space characters so it survives esc_html().
1143 $indent = str_repeat( "\xC2\xA0\xC2\xA0\xC2\xA0", $depth );
1144 $is_selected = ( (int) $term->term_id === (int) $selected_term );
1145
1146 echo '<option value="' . esc_attr( $term->term_id ) . '"';
1147 if ( $is_selected ) {
1148 echo ' selected="selected"';
1149 }
1150 echo '>' . esc_html( $indent ) . esc_html( $term->name ) . '</option>';
1151
1152 // Get children
1153 $children = get_terms(array(
1154 'taxonomy' => $taxonomy,
1155 'hide_empty' => false,
1156 'parent' => $term->term_id
1157 ));
1158
1159 if (!empty($children) && !is_wp_error($children)) {
1160 foreach ($children as $child) {
1161 $this->render_term_option($child, $selected_term, $taxonomy, $depth + 1);
1162 }
1163 }
1164 }
1165
1166 public function add_folder_script() {
1167 ?>
1168 <script type="text/javascript">
1169 jQuery(document).ready(function($) {
1170 $('.media-folder-select').on('change', function() {
1171 var $select = $(this);
1172 var attachment_id = $select.data('attachment-id');
1173 var term_id = $select.val();
1174
1175 if (!term_id) return;
1176
1177 // Store original value in case we need to revert
1178 $select.data('prev-value', $select.val());
1179
1180 // Show loading indicator
1181 var $spinner = $('<span class="spinner is-active"></span>');
1182 $select
1183 .prop('disabled', true)
1184 .after($spinner);
1185
1186 $.ajax({
1187 url: ajaxurl,
1188 type: 'POST',
1189 data: {
1190 action: 'pxlbsadminify_assign_media_folder',
1191 attachment_id: attachment_id,
1192 term_id: term_id,
1193 security: '<?php echo esc_attr( wp_create_nonce("pxlbsadminify_media_folder_nonce") ); ?>'
1194 },
1195 success: function(response) {
1196 if (response.success) {
1197 } else {
1198 $select.val($select.data('prev-value'));
1199 console.log('Error: ' + (response.data || 'Failed to update folder'));
1200 }
1201 },
1202 error: function(xhr, status, error) {
1203 $select.val($select.data('prev-value'));
1204 console.log('Error: ' + error);
1205 },
1206 complete: function() {
1207 // Always clean up
1208 $spinner.remove();
1209 $select.prop('disabled', false);
1210 }
1211 });
1212 });
1213 });
1214 </script>
1215 <?php
1216 }
1217
1218 public function handle_folder_assignment() {
1219 check_ajax_referer('pxlbsadminify_media_folder_nonce', 'security');
1220
1221 if ( ! current_user_can( 'upload_files' ) ) {
1222 wp_send_json_error( __( 'You do not have permission to perform this action.', 'adminify' ) );
1223 }
1224
1225 $attachment_id = isset($_POST['attachment_id']) ? intval($_POST['attachment_id']) : 0;
1226 $term_id = isset($_POST['term_id']) ? intval($_POST['term_id']) : 0;
1227 $taxonomy = 'media_folder';
1228
1229 if (!$attachment_id || !$term_id) {
1230 wp_send_json_error('Invalid data');
1231 }
1232
1233 // First remove all terms from this taxonomy
1234 wp_delete_object_term_relationships($attachment_id, $taxonomy);
1235
1236 // Add the new term
1237 $result = wp_set_object_terms($attachment_id, $term_id, $taxonomy);
1238
1239 if (is_wp_error($result)) {
1240 wp_send_json_error($result->get_error_message());
1241 }
1242
1243 wp_send_json_success();
1244 }
1245
1246 public function show_folders_in_list_filter() {
1247 global $typenow;
1248 if (! $this->is_module_active()) {
1249 return;
1250 }
1251 if ('attachment' !== $typenow) {
1252 return;
1253 }
1254
1255 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
1256 $selected = isset($_GET['media_folder']) ? sanitize_text_field( wp_unslash( $_GET['media_folder'] ) ) : '';
1257 wp_dropdown_categories(
1258 array(
1259 'show_option_all' => 'All folders',
1260 'taxonomy' => 'media_folder',
1261 'name' => 'folder_id',
1262 'id' => 'wp-adminify-media-folder-filter',
1263 'orderby' => 'name',
1264 'selected' => $selected,
1265 'hierarchical' => true,
1266 'value_field' => 'slug',
1267 'depth' => 3,
1268 'hide_empty' => false,
1269 )
1270 );
1271 }
1272 }
1273