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 ''; }); } /** * 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() { ?> 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() ) { ?>
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() { ?> __( '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' ), '' ); return [ 'adminify_move' => '
' . wp_kses_post( $title ) . '
', ] + $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( '
' . esc_html( $title ) . '
' ); } } } } 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 ''; $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 ''; // 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() { ?> 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, ) ); } }