# adminify/4.3.2/Inc/Modules/Folders/Folders.php

Adminify – White Label, Admin Menu Editor, Login Customizer, version 4.3.2. 1,273 lines.

- Page: https://pluginprobe.com/plugins/adminify/4.3.2/code/Inc/Modules/Folders/Folders.php
- Raw: https://pluginprobe.com/plugins/adminify/4.3.2/raw/Inc/Modules/Folders/Folders.php
- Modified: 2026-05-26T20:56:06+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/adminify/4.3.2/code/Inc/Modules/Folders/Folders.php#L10-L20`.

```php
<?php

namespace PXLBSAdminify\Inc\Modules\Folders;

use PXLBSAdminify\Inc\Utils;
use PXLBSAdminify\Inc\Admin\AdminSettings;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Folders {
	private static $postIds;
	public $options;

	public function __construct() {
		$this->options = (array) AdminSettings::get_instance()->get('folders');

		add_action( 'init', [ $this, 'register_folders_terms' ], 999 );

		add_action( 'admin_footer', [ $this, 'add_footer_markup' ] );

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ], PHP_INT_MAX );

		add_action( 'wp_ajax_pxlbsadminify_folder', [ $this, 'handle_adminify_folder' ] );

		add_action( 'ajax_query_attachments_args', [ $this, 'filter_grid' ] );

		add_filter( 'pre_get_posts', [ $this, 'filter_list' ] );

		add_action( 'admin_init', [ $this, 'modify_columns_actions' ] );

		add_action( 'print_media_templates', [ $this, 'modify_media_templates' ] );

		add_filter( 'wp_prepare_attachment_for_js', [ $this, 'modify_attachment_for_js' ] );
	
		add_action( 'pre-upload-ui', [ $this, 'select_folder_when_upload' ] );

		add_action('wp_ajax_pxlbsadminify_assign_media_folder', array($this, 'handle_folder_assignment'));

		add_action( 'restrict_manage_posts', [$this, 'show_folders_in_list_filter'] );

		add_action( 'add_attachment', [$this ,'assign_media_folder_to_new_attachment'] );

		if ( ! empty( $this->options['media'] )) {
			add_filter( 'attachment_fields_to_edit', [$this, 'edit_attachment_fields'], 25, 2 );

			add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_scripts_for_media_uploads' ], PHP_INT_MAX );
			add_action( 'customize_controls_print_footer_scripts', [ $this, 'add_adminify_body_class' ] );
			add_action( 'customize_controls_enqueue_scripts', [ $this, 'enqueue_scripts_for_media_uploads' ], PHP_INT_MAX );
	
			// Elementor editor support
			add_action( 'elementor/editor/footer', [ $this, 'add_adminify_body_class' ] );
			add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue_scripts_for_media_uploads' ], PHP_INT_MAX );
	
			// Classic editor support (post/page/CPT edit screens without Gutenberg)
			add_action( 'admin_enqueue_scripts', [ $this, 'maybe_enqueue_for_classic_editor' ], PHP_INT_MAX );
		}
	}

	/**
	 * Enqueue folder scripts for classic editor (non-Gutenberg) post edit screens
	 */
	public function maybe_enqueue_for_classic_editor( $hook ) {
		// Only on post edit screens
		if ( ! in_array( $hook, [ 'post.php', 'post-new.php' ], true ) ) {
			return;
		}

		// Skip if Gutenberg/block editor is active for this post type
		$current_screen = get_current_screen();
		if ( $current_screen && method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
			return;
		}

		$this->enqueue_scripts_for_media_uploads();

		// Add classic editor body class for CSS scoping
		add_action( 'admin_footer', function() {
			echo '<script>document.body.classList.add("wp-adminify-classic-editor");</script>';
		});
	}

	/**
	 * Add wp-adminify class to body
	 * Required for folder widget CSS to work in media modal (Elementor, Customizer, etc.)
	 */
	public function add_adminify_body_class() {
		?>
		<script>
			document.body.classList.add('wp-adminify');
		</script>
		<?php
	}

	public function modify_attachment_for_js( $response ) {
		if ( ! (bool) $this->options['media'] ) return $response;

		$folders    = wp_get_post_terms( $response['id'], 'media_folder' );
		$folder_ids = wp_list_pluck( $folders, 'term_id' );
		$response['media_folder'] = implode(',', $folder_ids);

		return $response;
	}

	public function get_post_types() {
		$post_types = get_post_types(
			[
				'public' => true,
			]
		);

		return $post_types;
	}

	public function filter_list( $query ) {
		global $typenow, $pagenow;

		$post_type = $typenow;
		if ( empty( $post_type ) ) {
			if ( $pagenow == 'edit.php' ) {
				$post_type = 'post';
			} elseif ( $pagenow == 'upload.php' ) {
				$post_type = 'attachment';
			}
		}
		
		if ( ! isset( $query->query['post_type'] ) ) {
			return $query;
		}

		$taxonomy = self::get_post_type_taxonomy( $post_type );

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
		if ( ! isset( $_REQUEST[ $taxonomy ] ) ) {
			return $query;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
		$term = sanitize_text_field( wp_unslash( $_REQUEST[ $taxonomy ] ) );

		if ( $term != '-1' ) {
			return $query;
		}

		unset( $query->query_vars[ $taxonomy ] );

		$tax_query = [
			'taxonomy' => $taxonomy,
			'operator' => __( 'NOT EXISTS', 'adminify' ),
		];

		$query->set( 'tax_query', [ $tax_query ] );
		$query->tax_query = new \WP_Tax_Query( [ $tax_query ] );

		return $query;
	}

	public function filter_grid( $args ) {
		$taxonomy = self::get_post_type_taxonomy( 'attachment' );

		if ( ! isset( $args[ $taxonomy ] ) ) {
			return $args;
		}

		$term = sanitize_text_field( $args[ $taxonomy ] );

		if ( $term != '-1' ) {
			return $args;
		}

		unset( $args[ $taxonomy ] );

		$args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- intentional taxonomy filter on an admin-side query.
			[
				'taxonomy' => $taxonomy,
				'operator' => __( 'NOT EXISTS', 'adminify' ),
			],
		];

		return $args;
	}

	public function is_module_active( $post_type = null ) {
		$current_screen = get_current_screen();
		if ( empty( $current_screen->base ) || ! in_array( $current_screen->base, [ 'upload', 'edit', 'media' ] )   ) {
			return false;
		}
		if($current_screen->base === 'media' && $current_screen->action === 'add'){
			$post_type = 'attachment';
		}

		if ( empty( $post_type ) ) {
			if ( empty( $post_type = $current_screen->post_type ) ) {
				return false;
			}
		}

		
		if ( $post_type == 'attachment' ) {
			return (bool) $this->options['media'];
		}

		$default_post_types = [ 'post', 'page' ];
		$folders_enable_for = (array) $this->options['enable_for'];

		if ( ! jltwp_adminify()->can_use_premium_code() ) {
			$folders_enable_for = array_intersect( $folders_enable_for, $default_post_types );
		}

		if ( in_array( $post_type, $folders_enable_for ) ) {
			return true;
		}

		return false;
	}

	// Add folder app markup in footer but move it to wpbody
	public function add_footer_markup() {
		if ( $this->is_module_active() ) {
			?>
			<div id="wp-adminify--folder-app"></div>
			<script>
				(function() {
					var folderApp = document.getElementById('wp-adminify--folder-app');
					var wpbody = document.getElementById('wpbody');
					var wpbodyContent = document.getElementById('wpbody-content');
					if (folderApp && wpbody && wpbodyContent) {
						wpbody.insertBefore(folderApp, wpbodyContent);
					}
				})();
			</script>
			<?php
		}
	}

	public function get_uncategorized_posts( $post_type ) {
		global $wpdb;

		$post_table          = $wpdb->prefix . 'posts';
		$term_table          = $wpdb->prefix . 'term_relationships';
		$term_taxonomy_table = $wpdb->prefix . 'term_taxonomy';
		$post_type_tax       = self::get_post_type_taxonomy( $post_type );

		if ( $post_type != 'attachment' ) {
			$query = "SELECT COUNT(DISTINCT({$post_table}.ID)) AS total_records FROM {$post_table} WHERE 1=1  AND (
                NOT EXISTS (
                    SELECT 1
                    FROM {$term_table}
                    INNER JOIN {$term_taxonomy_table}
                    ON {$term_taxonomy_table}.term_taxonomy_id = {$term_table}.term_taxonomy_id
                    WHERE {$term_taxonomy_table}.taxonomy = '%s'
                    AND {$term_table}.object_id = {$post_table}.ID
                )
            ) 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'))";
		} else {
			$query = "SELECT COUNT(DISTINCT({$post_table}.ID)) AS total_records FROM {$post_table} WHERE 1=1  AND (
                NOT EXISTS (
                    SELECT 1
                    FROM {$term_table}
                    INNER JOIN {$term_taxonomy_table}
                    ON {$term_taxonomy_table}.term_taxonomy_id = {$term_table}.term_taxonomy_id
                    WHERE {$term_taxonomy_table}.taxonomy = '%s'
                    AND {$term_table}.object_id = {$post_table}.ID
                )
            ) AND {$post_table}.post_type = '%s' AND {$post_table}.post_status = 'inherit'";
		}

		// 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.
		$query = $wpdb->prepare( $query, $post_type_tax, $post_type );

		// 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.
		$total = $wpdb->get_var( $query );

		return empty( $total ) ? 0 : $total;
	}

	public function get_folders( $post_type, $post_type_tax ) {
		$args = [
			'taxonomy' => $post_type_tax,
			'hide_empty' => false,
			'pad_counts' => true,
		];

		$folders = get_terms( $args );

		if ( is_wp_error( $folders ) ) {
			return [];
		}

		foreach ( $folders as $folder ) {
			$posts = $this->get_posts_by_folder( $folder->term_id, $post_type, $post_type_tax );

			$color = get_term_meta( $folder->term_id, 'pxlbsadminify_fodler_color', true );
			if ( empty( $color ) ) {
				$color = '';
			}

			$folder->color = $color;
			$folder->count = count( $posts );
			$folder->posts = $posts;
		}

		return $folders;
	}

	public function enqueue_scripts() {
		if ( ! $this->is_module_active() ) {
			return;
		}

		$current_screen = get_current_screen();
		wp_enqueue_style( 'wp-adminify--folder', PXLBSADMINIFY_URL . 'assets/admin/css/wp-adminify--folder' . Utils::assets_ext( '.css' ), [], PXLBSADMINIFY_VER );
		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);

		$post_type     = $current_screen->post_type;
		$post_type_tax = self::get_post_type_taxonomy( $post_type );
		if($current_screen->base === 'media' && $current_screen->action === 'add'){
			$post_type = 'attachment';
			$post_type_tax = 'media_folder';
		}
		$media_folders = get_terms([
				'taxonomy' => 'media_folder',
				'hide_empty' => false,
		]);

		$media_folders_data = [];
		foreach ($media_folders as $media_folder) {
			$media_folders_data[$media_folder->term_id] = [
					'name' => $media_folder->name,
					'slug' => $media_folder->slug,
					'term_id' => $media_folder->term_id
			];
		}

		$data = [
			'adminurl'      => admin_url(),
			'ajaxurl'       => admin_url( 'admin-ajax.php' ),
			'post_type'     => $post_type,
			'nonce'         => wp_create_nonce( 'pxlbsadminify_folder_nonce' ),
			'post_type_tax' => $post_type_tax,
			'media_folders_data' => $media_folders_data,
			'is_pro'        => wp_validate_boolean( jltwp_adminify()->can_use_premium_code() ),
			'pro_notice'    => Utils::upgrade_pro_notice(),
		];

		$data = array_merge( $data, $this->refreshed_folder_data( $post_type, $post_type_tax ) );

		// Enqueue Scripts
		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);

		wp_localize_script( 'wp-adminify--folder', 'PXLBSADMINIFY_FOLDER_DATA', $data );

		$inline_script_content = '
			(function($) {
				$(document).ready(function() {

					let selectedFolder = null;

					if (typeof wp.media !== "undefined") {
						const urlParams = new URLSearchParams(window.location.search);
						const currentFolder = urlParams.get("media_folder");
						selectedFolder = currentFolder;

						const MediaLibraryTaxonomyFilter = wp.media.view.AttachmentFilters.extend({
							id: "wp-adminify-media-folder-filter",
							createFilters: function() {
								const filters = {};
								filters.all = {
									text: "All folders",
									props: { folders: "" },
									priority: 10
								};
								_.each(window.PXLBSADMINIFY_FOLDER_DATA?.media_folders_data || {}, function(value) {
									filters[value.slug] = {
										text: value.name,
										props: { folders: value.slug },
										priority: 20
									};
								});
								this.filters = filters;
							},
							initialize: function() {
								wp.media.view.AttachmentFilters.prototype.initialize.apply(this, arguments);
								if (currentFolder) {
									this.model.set("folders", currentFolder);
									setTimeout(() => {
										this.$el.find("select").val(currentFolder);
									}, 100);
								}
							}
						});

						const AttachmentsBrowser = wp.media.view.AttachmentsBrowser;
						wp.media.view.AttachmentsBrowser = AttachmentsBrowser.extend({
							createToolbar: function() {
								AttachmentsBrowser.prototype.createToolbar.call(this);
								this.toolbar.set("MediaLibraryTaxonomyFilter", new MediaLibraryTaxonomyFilter({
									controller: this.controller,
									model: this.collection.props,
									priority: -75
								}).render());
							}
						});
					}

					if (typeof wp.Uploader === "function") {
						$.extend(wp.Uploader.prototype, {
							init: function() {
								selectedFolder = $("#folders").val() || currentFolder;

								$("body").on("change", "#folders", function() {
									selectedFolder = $(this).val();
								});

								if (this.uploader) {
									this.uploader.bind("BeforeUpload", function(up, file) {
										up.settings.multipart_params = up.settings.multipart_params || {};
										up.settings.multipart_params.folder_id = selectedFolder;
									});
									this.uploader.bind("UploadComplete", function() {
										if (typeof wp !== "undefined" && wp.media && wp.media.frame) {
											const frame = wp.media.frame;
											const state = frame.state();
											const library = state.get("library");

											if (library) {
												library.props.set("ignore", Date.now());
												state.trigger("reset");
											}
										}
									});
								}
							}
						});
					}

				});
			})(jQuery);
		';

		wp_add_inline_script('media-views', $inline_script_content, 'after');
	}

	function enqueue_scripts_for_media_uploads(){
		wp_enqueue_media();
		wp_enqueue_script('media-views');
		wp_enqueue_script('wp-dom-ready');

		// Enqueue folder styles for modal
		wp_enqueue_style( 'wp-adminify--folder', PXLBSADMINIFY_URL . 'assets/admin/css/wp-adminify--folder' . Utils::assets_ext( '.css' ), [], PXLBSADMINIFY_VER );
		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);

		// Enqueue folder script for modal sidebar
		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);

		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);

		$post_type = 'attachment';
		$post_type_tax = 'media_folder';

		// Get simplified folder data for dropdown filter
		$media_folders = get_terms([
			'taxonomy' => 'media_folder',
			'hide_empty' => false,
		]);

		$media_folders_data = [];
		foreach ($media_folders as $folder) {
			$media_folders_data[$folder->term_id] = [
				'name' => $folder->name,
				'slug' => $folder->slug,
				'term_id' => $folder->term_id
			];
		}

		// Build full folder data for React sidebar
		$data = apply_filters(
			'pxlbsadminify_folder_data',
			[
				'adminurl'           => admin_url(),
				'ajaxurl'            => admin_url( 'admin-ajax.php' ),
				'post_type'          => $post_type,
				'nonce'              => wp_create_nonce( 'pxlbsadminify_folder_nonce' ),
				'post_type_tax'      => $post_type_tax,
				'media_folders_data' => $media_folders_data,
				'is_pro'             => wp_validate_boolean( jltwp_adminify()->can_use_premium_code() ),
				'pro_notice'         => Utils::upgrade_pro_notice(),
				'is_rtl'             => is_rtl(),
			]
		);

		// Add full folder data (folders, hierarchy, counts)
		$data = array_merge( $data, $this->refreshed_folder_data( $post_type, $post_type_tax ) );

		// Localize the data to multiple script handles
		// This ensures data is available in different contexts
		wp_localize_script( 'wp-adminify--folder', 'PXLBSADMINIFY_FOLDER_DATA', $data );
		wp_localize_script( 'wp-dom-ready', 'PXLBSADMINIFY_FOLDER_DATA', $data );
		wp_localize_script( 'media-views', 'PXLBSADMINIFY_FOLDER_DATA', $data );


		$inline_script_content = '
			(function($) {
				$(document).ready(function() {

					let selectedFolder = null;

					if (typeof wp.media !== "undefined") {
						const urlParams = new URLSearchParams(window.location.search);
						const currentFolder = urlParams.get("media_folder");
						selectedFolder = currentFolder;

						const MediaLibraryTaxonomyFilter = wp.media.view.AttachmentFilters.extend({
							id: "wp-adminify-media-folder-filter",
							createFilters: function() {
								const filters = {};
								filters.all = {
									text: "All folders",
									props: { folders: "" },
									priority: 10
								};
								_.each(window.PXLBSADMINIFY_FOLDER_DATA?.media_folders_data || {}, function(value) {
									filters[value.slug] = {
										text: value.name,
										props: { folders: value.slug },
										priority: 20
									};
								});
								this.filters = filters;
							},
							initialize: function() {
								wp.media.view.AttachmentFilters.prototype.initialize.apply(this, arguments);
								if (currentFolder) {
									this.model.set("folders", currentFolder);
									setTimeout(() => {
										this.$el.find("select").val(currentFolder);
									}, 100);
								}
							}
						});

						// Note: AttachmentsBrowser extension for folder sidebar is handled in wp-adminify--folder.js
					}

					if (typeof wp.Uploader === "function") {
						$.extend(wp.Uploader.prototype, {
							init: function() {
								selectedFolder = $("#folders").val() || currentFolder;

								$("body").on("change", "#folders", function() {
									selectedFolder = $(this).val();
								});

								if (this.uploader) {
									this.uploader.bind("BeforeUpload", function(up, file) {
										up.settings.multipart_params = up.settings.multipart_params || {};
										up.settings.multipart_params.folder_id = selectedFolder;
									});
									this.uploader.bind("UploadComplete", function() {
										if (typeof wp !== "undefined" && wp.media && wp.media.frame) {
											const frame = wp.media.frame;
											const state = frame.state();
											const library = state.get("library");

											if (library) {
												library.props.set("ignore", Date.now());
												state.trigger("reset");
											}
										}
									});
								}
							}
						});
					}

				});
			})(jQuery);
		';

		wp_add_inline_script('media-views', $inline_script_content, 'after');
	}
	public function modify_media_templates() { ?>
		<script>
			var attachment_template = jQuery('#tmpl-attachment');
            if ( attachment_template.length ) {
                var template = attachment_template.html().replace( 'data.orientation }}">', 'data.orientation }}" data-folders="{{ data.media_folder }}">' );
                attachment_template.html( template );
            }
		</script> 
		<?php
	}

	public function handle_adminify_folder() {
		check_ajax_referer( 'pxlbsadminify_folder_nonce' );

		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ) );
		}

		$allowed_routes = array(
			'delete_folders',
			'rename_folder',
			'move_to_folder',
			'refresh_folders',
			'create_new_folder',
		);

		if ( ! empty( $_POST['route'] ) ) {
			$route = sanitize_key( wp_unslash( $_POST['route'] ) );

			if ( in_array( $route, $allowed_routes, true ) ) {
				$route_handler = 'handle_' . $route;
				if ( method_exists( $this, $route_handler ) ) {
					$this->$route_handler( wp_unslash( $_POST ) );
				}
			}
		}

		wp_send_json_error( [ 'message' => __( 'Something is wrong, no route found', 'adminify' ) ], 400 );
	}

	public function handle_delete_folders( $data ) {
		check_ajax_referer( 'pxlbsadminify_folder_nonce' );

		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ], 403 );
		}

		if ( empty( $data['term_ids'] ) || empty( $data['post_type'] ) || empty( $data['post_type_tax'] ) ) {
			wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
		}

		$term_ids      = array_map( 'absint', (array) $data['term_ids'] );
		$post_type     = sanitize_key( $data['post_type'] );
		$post_type_tax = sanitize_key( $data['post_type_tax'] );

		foreach ( $term_ids as $term_id ) {
			wp_delete_term( $term_id, $post_type_tax );
		}

		$data = $this->refreshed_folder_data( $post_type, $post_type_tax );

		wp_send_json_success( $data );
	}

	public function handle_rename_folder( $data ) {
		check_ajax_referer( 'pxlbsadminify_folder_nonce' );

		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ], 403 );
		}

		if ( empty( $data['term_id'] ) || empty( $data['post_type'] ) || empty( $data['post_type_tax'] ) || empty( $data['folder_name'] ) || empty( $data['folder_color_tag'] ) ) {
			wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
		}

		$term_id          = absint( $data['term_id'] );
		$folder_name      = sanitize_text_field( $data['folder_name'] );
		$post_type        = sanitize_key( $data['post_type'] );
		$post_type_tax    = sanitize_key( $data['post_type_tax'] );
		$folder_color_tag = sanitize_text_field( $data['folder_color_tag'] );

		$update = wp_update_term( $term_id, $post_type_tax, [ 'name' => $folder_name ] );

		if ( is_wp_error( $update ) ) {
			wp_send_json_success( [ 'message' => $update->get_error_message() ], 202 );
		}

		update_term_meta( $term_id, 'pxlbsadminify_fodler_color', $folder_color_tag );
		$data = $this->refreshed_folder_data( $post_type, $post_type_tax );

		wp_send_json_success( $data );
	}

	public function handle_move_to_folder( $data ) {
		check_ajax_referer( 'pxlbsadminify_folder_nonce' );

		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ], 403 );
		}

		if ( empty( $data['post_ids'] ) || empty( $data['folder_id'] ) || empty( $data['post_type'] ) || empty( $data['post_type_tax'] ) ) {
			wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
		}

		$post_ids      = array_map( 'absint', (array) $data['post_ids'] );
		$folder_id     = sanitize_text_field( $data['folder_id'] );
		$post_type     = sanitize_text_field( $data['post_type'] );
		$post_type_tax = sanitize_text_field( $data['post_type_tax'] );
		$screen        = isset( $data['screen'] ) ? sanitize_text_field( $data['screen'] ) : '';

		global $mode;

		$mode           = empty( $data['mode'] ) ? 'list' : sanitize_text_field( $data['mode'] );
		$move_to_folder = wp_validate_boolean( $data['move_to_folder'] );

		foreach ( $post_ids as $post_id ) {
			if ( $folder_id == 'uncategorized' ) {
				wp_set_object_terms( $post_id, '', $post_type_tax, false );
				continue;
			}

			$term = get_term( $folder_id );

			if ( ! empty( $term ) && isset( $term->slug ) ) {
				wp_set_object_terms( $post_id, $term->slug, $post_type_tax, ! $move_to_folder );
			}
		}

		$updated_rows = '';

		if ( $post_type == 'attachment' ) {
			global $wp_query;

			$args = [
				'posts_per_page' => count( $post_ids ),
				'orderby'        => 'title',
				'order'          => 'ASC',
				'post_type'      => $post_type,
				'post_status'    => 'any',
				'post__in'       => $post_ids,
			];

			$wp_query = new \WP_Query( $args );

			$wp_list_table = \_get_list_table( 'WP_Media_List_Table', [ 'screen' => $screen ] );

			foreach ( $post_ids as $post_id ) {
				ob_start();
				$wp_list_table->display_rows();
				$updated_rows = ob_get_clean();
			}
		} else {
			$wp_list_table = \_get_list_table( 'WP_Posts_List_Table', [ 'screen' => $screen ] );

			foreach ( $post_ids as $post_id ) {
				$level = 0;

				if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) {
					$request_post = [ get_post( $post_id ) ];
					$parent       = $request_post[0]->post_parent;
					while ( $parent > 0 ) {
						$parent_post = get_post( $parent );
						$parent      = $parent_post->post_parent;
						$level++;
					}
				}

				ob_start();
				$wp_list_table->display_rows( [ get_post( $post_id ) ], $level );
				$updated_rows .= ob_get_clean();
			}
		}

		$data = [
			'updated_rows' => $updated_rows,
		];

		$data = array_merge( $data, $this->refreshed_folder_data( $post_type, $post_type_tax ) );

		wp_send_json_success( $data );
	}

	public function handle_refresh_folders( $data ) {
		check_ajax_referer( 'pxlbsadminify_folder_nonce' );

		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ], 403 );
		}

		if ( empty( $data['post_type'] ) || empty( $data['post_type_tax'] ) ) {
			wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
		}

		$post_type     = sanitize_key( $data['post_type'] );
		$post_type_tax = sanitize_key( $data['post_type_tax'] );

		wp_send_json_success( $this->refreshed_folder_data( $post_type, $post_type_tax ) );
	}

	public function handle_create_new_folder( $data ) {
		check_ajax_referer( 'pxlbsadminify_folder_nonce' );

		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action.', 'adminify' ) ], 403 );
		}

		if ( empty( $data['post_type'] ) || empty( $data['post_type_tax'] ) ) {
			wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
		}
		if ( empty( $data['new_folder_name'] ) || empty( $data['folder_color_tag'] ) ) {
			wp_send_json_error( [ 'message' => __( 'Something is wrong, few args are missing', 'adminify' ) ], 400 );
		}

		$post_type        = sanitize_key( $data['post_type'] );
		$post_type_tax    = sanitize_key( $data['post_type_tax'] );
		$new_folder_name  = sanitize_text_field( $data['new_folder_name'] );
		$folder_color_tag = sanitize_text_field( $data['folder_color_tag'] );
		$parent_term_id   = 0;

		if ( jltwp_adminify()->can_use_premium_code() && ! empty( $data['parent_folder'] ) ) {
			$parent_term_id = absint( $data['parent_folder'] );
		}

		$insert_data = wp_insert_term( $new_folder_name, $post_type_tax, [ 'parent' => $parent_term_id ] );

		if ( ! is_wp_error( $insert_data ) ) {
			update_term_meta( $insert_data['term_id'], 'pxlbsadminify_fodler_color', $folder_color_tag );
			wp_send_json_success( $this->refreshed_folder_data( $post_type, $post_type_tax ) );
		} else {
			wp_send_json_success( [ 'message' => $insert_data->get_error_message() ], 202 );
		}
	}

	function refreshed_folder_data( $post_type, $post_type_tax ) {
		remove_filter( 'pre_get_posts', [ $this, 'filter_list' ] );

		return [
			'folders'           => $this->get_folders( $post_type, $post_type_tax ),
			'folder_hierarchy'  => _get_term_hierarchy( $post_type_tax ),
			'total_posts'       => wp_count_posts( $post_type ),
			'total_uncat_posts' => $this->get_uncategorized_posts( $post_type ),
		];
	}

	function set_post_categories( $post_ID = 0, $post_categories = [], $append = false ) {
		$post_ID     = (int) $post_ID;
		$post_type   = get_post_type( $post_ID );
		$post_status = get_post_status( $post_ID );

		// If $post_categories isn't already an array, make it one.
		$post_categories = (array) $post_categories;

		if ( empty( $post_categories ) ) {
			/**
			 * Filters post types (in addition to 'post') that require a default category.
			 *
			 * @since 5.5.0
			 *
			 * @param string[] $post_types An array of post type names. Default empty array.
			 */
			$default_category_post_types = apply_filters( 'pxlbsadminify_default_category_post_types', [] );

			// Regular posts always require a default category.
			$default_category_post_types = array_merge( $default_category_post_types, [ 'post' ] );

			if ( in_array( $post_type, $default_category_post_types, true ) && is_object_in_taxonomy( $post_type, 'category' ) && 'auto-draft' !== $post_status ) {
				$post_categories = [ get_option( 'default_category' ) ];
				$append          = false;
			} else {
				$post_categories = [];
			}
		} elseif ( 1 === count( $post_categories ) && '' === reset( $post_categories ) ) {
			return true;
		}

		return wp_set_post_terms( $post_ID, $post_categories, 'category', $append );
	}

	public function get_posts_by_folder( $term_id, $post_type, $taxonomy ) {
		$posts = get_posts(
			[
				'numberposts' => -1,
				'post_status' => 'any',
				'post_type'   => $post_type,
				'fields'      => 'ids',
				'tax_query'   => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- intentional taxonomy filter on an admin-side query.
					[
						'operator' => 'IN',
						'taxonomy' => $taxonomy,
						'field'    => 'term_id',
						'terms'    => $term_id,
					],
				],
			]
		);

		return $posts;
	}

	public static function get_post_type_taxonomy( $post_type ) {
		if ( $post_type == 'page' ) {
			return 'folder';
		}

		if ( $post_type == 'attachment' ) {
			$post_type = 'media';
		}

		return $post_type . '_folder';
	}

	public function register_folders_terms() {
		$post_types = $this->get_post_types();

		foreach ( $post_types as $post_type ) {
			$labels = [
				'name'          => esc_html__( 'Folders', 'adminify' ),
				'singular_name' => esc_html__( 'Folder', 'adminify' ),
				'all_items'     => esc_html__( 'All Folders', 'adminify' ),
				'edit_item'     => esc_html__( 'Edit Folder', 'adminify' ),
				'update_item'   => esc_html__( 'Update Folder', 'adminify' ),
				'add_new_item'  => esc_html__( 'Add New Folder', 'adminify' ),
				'new_item_name' => esc_html__( 'Add folder name', 'adminify' ),
				'menu_name'     => esc_html__( 'Folders', 'adminify' ),
				'search_items'  => esc_html__( 'Search Folders', 'adminify' ),
				'parent_item'   => esc_html__( 'Parent Folder', 'adminify' ),
			];

			$args = [
				'label'             => esc_html__( 'Folder', 'adminify' ),
				'labels'            => $labels,
				'show_tagcloud'     => false,
				'hierarchical'      => true,
				'public'            => false,
				'show_ui'           => false,
				'show_in_menu'      => false,
				'show_in_rest'      => true,
				'show_admin_column' => true,
				'query_var'         => true,
				'rewrite'           => false,
				'capabilities'      => [
					'manage_terms' => 'manage_categories',
					'edit_terms'   => 'manage_categories',
					'delete_terms' => 'manage_categories',
					'assign_terms' => 'manage_categories',
				],
			];

			$taxonomy = self::get_post_type_taxonomy( $post_type );

			register_taxonomy( $taxonomy, $post_type, $args );
		}
	}

	public function modify_columns_actions() {
		$post_types = $this->get_post_types();

		foreach ( $post_types as $post_type ) {
			if ( $post_type == 'post' ) {
				add_filter( 'manage_edit-post_columns', [ $this, 'manage_columns_head' ] );
				add_filter( 'manage_posts_columns', [ $this, 'manage_columns_head' ] );
				add_action( 'manage_posts_custom_column', [ $this, 'manage_columns_content' ], 10, 2 );
				add_filter( 'bulk_actions-edit-post', [ $this, 'custom_bulk_action' ] );
			} elseif ( $post_type == 'page' ) {
				add_filter( 'manage_edit-page_columns', [ $this, 'manage_columns_head' ] );
				add_filter( 'manage_page_posts_columns', [ $this, 'manage_columns_head' ] );
				add_action( 'manage_page_posts_custom_column', [ $this, 'manage_columns_content' ], 10, 2 );
				add_filter( 'bulk_actions-edit-page', [ $this, 'custom_bulk_action' ] );
			} elseif ( $post_type == 'attachment' ) {
				add_filter( 'manage_media_columns', [ $this, 'manage_columns_head' ] );
				add_action( 'manage_media_custom_column', [ $this, 'manage_columns_content' ], 10, 2 );
			} else {
				add_filter( 'manage_edit-' . $post_type . '_columns', [ $this, 'manage_columns_head' ], 99999 );
				add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'manage_columns_content' ], 2, 2 );
				add_filter( 'bulk_actions-edit-' . $post_type, [ $this, 'custom_bulk_action' ] );
			}
		}
	}

	function manage_columns_head( $posts_columns ) {
		$post_type = null;

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
		if ( isset( $_REQUEST['post_type'] ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
			$post_type = sanitize_text_field( wp_unslash( $_REQUEST['post_type'] ) );
		}

		if ( $this->is_module_active( $post_type ) ) {
			$title = sprintf( __( 'Moving selected items', 'adminify' ), '<span class="adminify-folder-items--count"></span>' );
			return [
				'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>',
			] + $posts_columns;
		}

		return $posts_columns;
	}

	function manage_columns_content( $column_name, $post_ID ) {
		$post_type = null;

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
		if ( isset( $_REQUEST['post_type'] ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
			$post_type = sanitize_text_field( wp_unslash( $_REQUEST['post_type'] ) );
		}

		$postIDs = self::$postIds;

		$folder_ids = $this->get_folders_by_post( $post_ID );
		$folder_ids = implode( ',', $folder_ids );

		if ( ! is_array( $postIDs ) ) {
			$postIDs = [];
		}

		if ( ! in_array( $post_ID, $postIDs ) ) {
			$postIDs[]     = $post_ID;
			self::$postIds = $postIDs;

			if ( $this->is_module_active( $post_type ) ) {
				if ( $column_name == 'adminify_move' ) {
					$title = get_the_title();
					if ( strlen( $title ) > 20 ) {
						$title = substr( $title, 0, 20 ) . '...';
					}
					// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- kses_custom() is a wp_kses() wrapper; output already escaped.
					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>' );
				}
			}
		}
	}

	public function get_folders_by_post( $post_ID, $return_type = 'ids' ) {
		$folder_tax = self::get_post_type_taxonomy( get_post_type( $post_ID ) );
		$folders    = wp_get_post_terms( $post_ID, $folder_tax );
		if ( $return_type == 'ids' ) {
			return wp_list_pluck( $folders, 'term_id' );
		}
		return $folders;
	}

	public function custom_bulk_action( $bulk_actions ) {
		$bulk_actions['move_to_folder'] = __( 'Move to Folder', 'adminify' );
		return $bulk_actions;
	}

	public function select_folder_when_upload() {
		if ( ! $this->options['media'] ) {
			return;
		}
		// Only show the folder selector to users who can upload media.
		if ( ! current_user_can( 'upload_files' ) ) {
			return;
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
		$selected = isset($_GET['media_folder']) ? sanitize_text_field( wp_unslash( $_GET['media_folder'] ) ) : '';
		wp_dropdown_categories( array(
			'show_option_none' => 'Choose folder',
			'taxonomy'         => 'media_folder', 
			'name'             => 'folder_id', 
			'id'               => 'folders',
			'orderby'          => 'name',
			'selected'         => $selected,
			'hierarchical'     => true,
			'value_field'     => 'slug',
			'hide_empty'       => 0,
		) );

	}

	public function assign_media_folder_to_new_attachment( $post_ID ) {

		// Authorization: only users who can edit this attachment may change its folder.
		if ( ! current_user_can( 'upload_files' ) || ! current_user_can( 'edit_post', $post_ID ) ) {
			return;
		}

		// CSRF: the media upload request is signed with core's media-form nonce.
		$nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
		if ( ! $nonce || ! wp_verify_nonce( $nonce, 'media-form' ) ) {
			return;
		}

		$folder_id = isset( $_POST['folder_id'] ) ? sanitize_text_field( wp_unslash( $_POST['folder_id'] ) ) : '';

		if ( '' !== $folder_id && '-1' !== $folder_id ) {
			$term = get_term_by( 'slug', $folder_id, 'media_folder' );
			if ( ! $term && is_numeric( $folder_id ) ) {
				$term = get_term_by( 'id', (int) $folder_id, 'media_folder' );
			}

			$post_type     = 'attachment';
			$post_type_tax = $folder_id;

			if ( $term && ! is_wp_error( $term ) ) {
				wp_set_object_terms( $post_ID, (int) $term->term_id, 'media_folder' );
			} else {
				// if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				// 	error_log( 'Media Folder: Could not find term for value "' . $folder_id . '" for attachment ID ' . $post_ID );
				// }
			}
			$this->refreshed_folder_data( $post_type, $post_type_tax );
		}
	}

	
	public function edit_attachment_fields($form_fields, $post) {
    $folder_fields = array(
        'label' => 'Folders',
        'show_in_edit' => false,
        'input' => 'html',
        'value' => '',
    );
    
    $taxonomy_name = 'media_folder';
    
    // get the assigned media library folders from the cache
    $terms = get_the_terms($post->ID, $taxonomy_name);
    if ($terms) {
        $folder_fields['value'] = join(', ', wp_list_pluck($terms, 'slug'));
    }

    ob_start();
    $this->render_terms_dropdown($post->ID, $taxonomy_name);
    $html = ob_get_contents();
    ob_end_clean();

    $folder_fields['html'] = $html;
    $form_fields[$taxonomy_name] = $folder_fields;

    return $form_fields;
	}

	public function render_terms_dropdown($post_id, $taxonomy) {
    $selected_terms = wp_get_object_terms($post_id, $taxonomy, array('fields' => 'ids'));
    $selected_term = !empty($selected_terms) ? $selected_terms[0] : 0;
    
    $terms = get_terms(array(
        'taxonomy' => $taxonomy,
        'hide_empty' => false,
        'parent' => 0
    ));
    
    echo '<select name="media_folder_select" class="media-folder-select" data-attachment-id="' . esc_attr( $post_id ) . '">';
    echo '<option value="">— Select Folder —</option>';
    
    if (!empty($terms) && !is_wp_error($terms)) {
        foreach ($terms as $term) {
            $this->render_term_option($term, $selected_term, $taxonomy);
        }
    }
    
    echo '</select>';

    $this->add_folder_script();
	}

	public function render_term_option($term, $selected_term, $taxonomy, $depth = 0) {
			// Build the indentation from real non-breaking-space characters so it survives esc_html().
			$indent      = str_repeat( "\xC2\xA0\xC2\xA0\xC2\xA0", $depth );
			$is_selected = ( (int) $term->term_id === (int) $selected_term );

			echo '<option value="' . esc_attr( $term->term_id ) . '"';
			if ( $is_selected ) {
				echo ' selected="selected"';
			}
			echo '>' . esc_html( $indent ) . esc_html( $term->name ) . '</option>';
			
			// Get children
			$children = get_terms(array(
					'taxonomy' => $taxonomy,
					'hide_empty' => false,
					'parent' => $term->term_id
			));
			
			if (!empty($children) && !is_wp_error($children)) {
					foreach ($children as $child) {
							$this->render_term_option($child, $selected_term, $taxonomy, $depth + 1);
					}
			}
	}

	public function add_folder_script() {
    ?>
    <script type="text/javascript">
    jQuery(document).ready(function($) {
        $('.media-folder-select').on('change', function() {
            var $select = $(this);
            var attachment_id = $select.data('attachment-id');
            var term_id = $select.val();
            
            if (!term_id) return;
            
            // Store original value in case we need to revert
            $select.data('prev-value', $select.val());
            
            // Show loading indicator
            var $spinner = $('<span class="spinner is-active"></span>');
            $select
                .prop('disabled', true)
                .after($spinner);
            
            $.ajax({
                url: ajaxurl,
                type: 'POST',
                data: {
                    action: 'pxlbsadminify_assign_media_folder',
                    attachment_id: attachment_id,
                    term_id: term_id,
                    security: '<?php echo esc_attr( wp_create_nonce("pxlbsadminify_media_folder_nonce") ); ?>'
                },
                success: function(response) {
                    if (response.success) {
                    } else {
                        $select.val($select.data('prev-value'));
                        console.log('Error: ' + (response.data || 'Failed to update folder'));
                    }
                },
                error: function(xhr, status, error) {
                    $select.val($select.data('prev-value'));
                    console.log('Error: ' + error);
                },
                complete: function() {
                    // Always clean up
                    $spinner.remove();
                    $select.prop('disabled', false);
                }
            });
        });
    });
    </script>
    <?php
	}
	
	public function handle_folder_assignment() {
			check_ajax_referer('pxlbsadminify_media_folder_nonce', 'security');

			if ( ! current_user_can( 'upload_files' ) ) {
				wp_send_json_error( __( 'You do not have permission to perform this action.', 'adminify' ) );
			}

			$attachment_id = isset($_POST['attachment_id']) ? intval($_POST['attachment_id']) : 0;
			$term_id = isset($_POST['term_id']) ? intval($_POST['term_id']) : 0;
			$taxonomy = 'media_folder';
			
			if (!$attachment_id || !$term_id) {
					wp_send_json_error('Invalid data');
			}
			
			// First remove all terms from this taxonomy
			wp_delete_object_term_relationships($attachment_id, $taxonomy);
			
			// Add the new term
			$result = wp_set_object_terms($attachment_id, $term_id, $taxonomy);
			
			if (is_wp_error($result)) {
					wp_send_json_error($result->get_error_message());
			}
			
			wp_send_json_success();
	}

	public function show_folders_in_list_filter() {
    global $typenow;
    if (! $this->is_module_active()) {
        return;
    }
    if ('attachment' !== $typenow) {
        return;
    }

    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
    $selected = isset($_GET['media_folder']) ? sanitize_text_field( wp_unslash( $_GET['media_folder'] ) ) : '';
    wp_dropdown_categories(
        array(
            'show_option_all' => 'All folders',
            'taxonomy'        => 'media_folder',
            'name'           => 'folder_id',
            'id'              => 'wp-adminify-media-folder-filter',
            'orderby'         => 'name',
            'selected'        => $selected,
            'hierarchical'    => true,
            'value_field'     => 'slug',
            'depth'           => 3,
            'hide_empty'      => false,
        )
    );
	}
}

```
