<?php // exit if accessed directly if ( ! defined( 'ABSPATH' ) ) exit; /** * Responsive Lightbox folders class. * * @class Responsive_Lightbox_Folders */ class Responsive_Lightbox_Folders { private $mode = ''; private $rl_media_tag_terms = []; private $term_counters = [ 'keys' => [], 'values' => [] ]; private $allowed_select_html = [ 'select' => [ 'name' => true, 'id' => true, 'class' => true, 'required' => true, 'tabindex' => true, 'aria-describedby' => true ], 'option' => [ 'value' => true, 'class' => true, 'selected' => true ] ]; /** * Class constructor. * * @param bool $read_only Whether plugin is in read only mode * @return void */ public function __construct( $read_only = false ) { // set instance Responsive_Lightbox()->folders = $this; // allow to load old taxonomies even in read only mode add_action( 'wp_ajax_rl-folders-load-old-taxonomies', [ $this, 'load_old_taxonomies' ] ); if ( $read_only ) return; // actions add_action( 'init', [ $this, 'detect_library_mode' ], 11 ); add_action( 'restrict_manage_posts', [ $this, 'restrict_manage_posts' ] ); add_action( 'wp_enqueue_media', [ $this, 'add_library_scripts' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'add_library_scripts' ] ); add_action( 'pre-upload-ui', [ $this, 'pre_upload_ui' ] ); add_action( 'post-upload-ui', [ $this, 'post_upload_ui' ] ); add_action( 'add_attachment', [ $this, 'add_attachment' ] ); add_action( 'wp_ajax_save-attachment-compat', [ $this, 'ajax_save_attachment_compat' ], 0 ); add_action( 'wp_ajax_rl-folders-delete-term', [ $this, 'delete_term' ] ); add_action( 'wp_ajax_rl-folders-rename-term', [ $this, 'rename_term' ] ); add_action( 'wp_ajax_rl-folders-add-term', [ $this, 'add_term' ] ); add_action( 'wp_ajax_rl-folders-move-term', [ $this, 'move_term' ] ); add_action( 'wp_ajax_rl-folders-move-attachments', [ $this, 'move_attachments' ] ); // filters add_filter( 'admin_body_class', [ $this, 'admin_body_class' ] ); add_filter( 'parse_query', [ $this, 'parse_query' ] ); add_filter( 'ajax_query_attachments_args', [ $this, 'ajax_query_attachments_args' ] ); add_filter( 'attachment_fields_to_edit', [ $this, 'attachment_fields_to_edit' ], 10, 2 ); add_filter( 'rl_count_attachments', [ $this, 'count_attachments' ], 10 ); } /** * Load previously used media taxonomies via AJAX. * * @return void */ public function load_old_taxonomies() { // no data? if ( ! isset( $_POST['taxonomies'], $_POST['nonce'] ) ) wp_send_json_error(); // invalid taxonomies format? if ( ! is_array( $_POST['taxonomies'] ) ) wp_send_json_error(); // invalid nonce? if ( ! ctype_alnum( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'rl-folders-ajax-taxonomies-nonce' ) ) wp_send_json_error(); // get all possible (current and previous) taxonomies $fields = $this->get_taxonomies(); // any results? if ( ! empty( $fields ) ) { // remove main taxonomy if ( ( $key = array_search( 'rl_media_folder', $fields, true ) ) !== false ) unset( $fields[$key] ); // remove media tags if ( ( $key = array_search( 'rl_media_tag', $fields, true ) ) !== false ) unset( $fields[$key] ); // sanitize taxonomies $taxonomies = array_map( 'santize_key', $_POST['taxonomies'] ); foreach ( $taxonomies as $taxonomy ) { // remove available taxonomy if ( ( $key = array_search( $taxonomy, $fields, true ) ) !== false ) unset( $fields[$key] ); } } // send taxonomies, reindex them to avoid casting to an object in js wp_send_json_success( [ 'taxonomies' => array_values( $fields ) ] ); } /** * Detect library mode (list or grid). * * @global string $pagenow * * @return void */ public function detect_library_mode() { global $pagenow; if ( $pagenow === 'upload.php' ) { // available modes $modes = [ 'grid', 'list' ]; // sanitize mode if ( isset( $_GET['mode'] ) ) $mode = sanitize_key( $_GET['mode'] ); else $mode = ''; // check mode if ( ! ( $mode && ctype_lower( $mode ) && in_array( $mode, $modes, true ) ) ) { // get user mode $user_mode = (string) get_user_option( 'media_library_mode' ); // valid user mode? if ( in_array( $user_mode, $modes, true ) ) $mode = $user_mode; // default wp mode else $mode = 'grid'; } // store mode $this->mode = $mode; } if ( $pagenow === 'upload.php' || wp_doing_ajax() ) { $this->rl_media_tag_terms = get_terms( [ 'taxonomy' => 'rl_media_tag', 'hide_empty' => false, 'orderby' => 'name', 'order' => 'asc', 'number' => 0, 'fields' => 'id=>name', 'hierarchical' => false ] ); } } /** * Admin body classes. * * @global string $pagenow * * @param array $classes Admin body classes * @return array */ public function admin_body_class( $classes ) { global $pagenow; if ( $pagenow === 'upload.php' ) { // append class $classes .= ' rl-folders-upload-' . $this->mode . '-mode'; } return $classes; } /** * Get folders dropdown HTML. * * @param string $taxonomy Folders taxonomy * @param string $selected Folders taxonomy ID * @return string */ private function get_folders( $taxonomy, $selected = 0 ) { // get only 1 term to check if taxonomy is empty $any_terms = get_terms( [ 'taxonomy' => $taxonomy, 'hide_empty' => false, 'fields' => 'ids', 'hierarchical' => false, 'number' => 1 ] ); // prepare dropdown categories parameters $args = [ 'orderby' => 'name', 'order' => 'asc', 'show_option_all' => __( 'Root Folder', 'responsive-lightbox' ), 'show_count' => false, 'hide_empty' => false, 'hierarchical' => true, 'hide_if_empty' => false, 'echo' => false, 'selected' => (int) $selected, 'id' => 'rl_folders_upload_files', 'name' => 'rl_folders_upload_files_term_id', 'taxonomy' => $taxonomy ]; // no terms? if ( ! is_wp_error( $any_terms ) && empty( $any_terms ) ) { $args['show_option_none'] = __( 'Root Folder', 'responsive-lightbox' ); $args['option_none_value'] = 0; } return wp_dropdown_categories( $args ); } /** * Add filter to add media folder id to the uploader * * @return void */ public function pre_upload_ui() { add_filter( 'upload_post_params', [ $this, 'upload_post_params' ] ); } /** * Add media folder id param to the uploader. * * @param array $params Plupload parameters * @return array */ public function upload_post_params( $params ) { $params['rl_folders_upload_files_term_id'] = 0; return $params; } /** * Display dropdown at media upload UI screen. * * @return void */ public function post_upload_ui() { // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; // get only 1 term to check if taxonomy is empty $any_terms = get_terms( [ 'taxonomy' => $taxonomy, 'hide_empty' => false, 'fields' => 'ids', 'hierarchical' => false, 'number' => 1 ] ); // prepare dropdown categories parameters $args = [ 'orderby' => 'name', 'order' => 'asc', 'show_option_all' => __( 'Root Folder', 'responsive-lightbox' ), 'show_count' => false, 'hide_empty' => false, 'hierarchical' => true, 'hide_if_empty' => false, 'echo' => false, 'selected' => isset( $_GET[$taxonomy] ) ? (int) $_GET[$taxonomy] : 0, 'id' => 'rl_folders_upload_files', 'name' => 'rl_folders_upload_files_term_id', 'taxonomy' => $taxonomy ]; // no terms? if ( ! is_wp_error( $any_terms ) && empty( $any_terms ) ) { $args['show_option_none'] = __( 'Root Folder', 'responsive-lightbox' ); $args['option_none_value'] = 0; } // display select echo '<p><label>' . esc_html__( 'Upload files to', 'responsive-lightbox' ) . ': ' . wp_kses( wp_dropdown_categories( $args ), $this->allowed_select_html ) . '</label></p>'; } /** * Assign attachment to given term. * * @param int $post_id Current attachment ID * @return void */ public function add_attachment( $post_id ) { if ( isset( $_POST['rl_folders_upload_files_term_id'] ) ) { // cast term id $term_id = (int) $_POST['rl_folders_upload_files_term_id']; // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; // valid term? if ( is_array( term_exists( $term_id, $taxonomy ) ) ) wp_set_object_terms( $post_id, $term_id, $taxonomy, false ); } } /** * Add filterable dropdown to media library. * * @global string $pagenow * * @return void */ public function restrict_manage_posts() { global $pagenow; if ( $pagenow === 'upload.php' ) { // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; $html = wp_dropdown_categories( [ 'orderby' => 'name', 'order' => 'asc', 'id' => 'media-attachment-rl-folders-filters', 'show_option_all' => __( 'All Files', 'responsive-lightbox' ), 'show_count' => false, 'hide_empty' => false, 'hierarchical' => true, 'selected' => ( isset( $_GET[$taxonomy] ) ? (int) $_GET[$taxonomy] : 0 ), 'name' => $taxonomy, 'taxonomy' => $taxonomy, 'hide_if_empty' => true, 'echo' => false ] ); if ( $html === '' ) echo '<select name="' . esc_attr( $taxonomy ) . '" id="media-attachment-rl-folders-filters" class="postform"><option>' . esc_html__( 'All Files', 'responsive-lightbox' ) . '</option></select> '; else echo wp_kses( $html, $this->allowed_select_html ); } } /** * Change query to adjust taxonomy if needed. * * @global string $pagenow * * @param object $query WP Query * @return object */ public function parse_query( $query ) { global $pagenow; // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; if ( $pagenow === 'upload.php' && isset( $_GET[$taxonomy] ) ) { // get tax query $tax_query = $query->get( 'tax_query' ); if ( empty( $tax_query ) || ! is_array( $tax_query ) ) $tax_query = []; // -1 === root, 0 === all files, >0 === term_id $term_id = (int) $_GET[$taxonomy]; if ( $term_id !== 0 && ( $query->is_main_query() || empty( $query->query['rl_folders_root'] ) ) ) { $tax = [ 'taxonomy' => $taxonomy, 'field' => 'id' ]; // root folder? if ( $term_id === -1 ) { $tax['terms'] = 0; $tax['operator'] = 'NOT EXISTS'; $tax['include_children'] = false; // specified term id } else { $tax['terms'] = $term_id; $tax['include_children'] = false; } // add new tax query $tax_query[] = [ 'relation' => 'AND', $tax ]; // set new tax query $query->set( 'tax_query', $tax_query ); } } return $query; } /** * Change AJAX query parameters to adjust taxonomy in the media library if needed. * * @param array $query Query arguments * @return array */ public function ajax_query_attachments_args( $query ) { // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; if ( isset( $_POST['query'][$taxonomy] ) ) { $term_id = sanitize_key( $_POST['query'][$taxonomy] ); if ( $term_id === 'all' ) return $query; $term_id = (int) $term_id; if ( $term_id < 0 ) return $query; if ( empty( $query['tax_query'] ) || ! is_array( $query['tax_query'] ) ) $query['tax_query'] = []; $query['tax_query'][] = [ 'relation' => 'AND', [ 'taxonomy' => $taxonomy, 'field' => 'id', 'terms' => $term_id, 'include_children' => ( ! ( isset( $_POST['query']['include_children'] ) && $_POST['query']['include_children'] === 'false' ) ), 'operator' => ( $term_id === 0 ? 'NOT EXISTS' : 'IN' ) ] ]; } return $query; } /** * Filter the array of attachment fields that are displayed when editing an attachment. * * @param array $fields Attachment fields * @param object $post Post object * @return array */ public function attachment_fields_to_edit( $fields, $post ) { if ( wp_doing_ajax() ) { // get main instance $rl = Responsive_Lightbox(); // get taxonomy option $taxonomy = $rl->options['folders']['media_taxonomy']; // get taxonomy object $tax = (array) get_taxonomy( $taxonomy ); if ( ! empty( $tax ) ) { if ( ! $tax['public'] || ! $tax['show_ui'] ) return $fields; if ( empty( $tax['args'] ) ) $tax['args'] = []; $ids = wp_get_post_terms( $post->ID, $taxonomy, [ 'fields' => 'ids' ] ); // get select HTML $dropdown = wp_dropdown_categories( [ 'orderby' => 'name', 'order' => 'asc', 'show_option_none' => __( 'Root Folder', 'responsive-lightbox' ), 'show_option_all' => false, 'show_count' => false, 'hide_empty' => false, 'hierarchical' => true, 'selected' => ( ! empty( $ids ) ? reset( $ids ) : 0 ), 'name' => $taxonomy . '_term', 'taxonomy' => $taxonomy, 'hide_if_empty' => false, 'echo' => false ] ); $tax['input'] = 'html'; $tax['html'] = wp_kses( $dropdown, $this->allowed_select_html ); $fields[$taxonomy] = $tax; } if ( $rl->options['folders']['media_tags'] && taxonomy_exists( 'rl_media_tag' ) ) { // get taxonomy object $tax = (array) get_taxonomy( 'rl_media_tag' ); if ( ! empty( $tax ) ) { if ( ! $tax['public'] || ! $tax['show_ui'] ) return $fields; if ( empty( $tax['args'] ) ) $tax['args'] = []; $tags_html = ''; // get terms $tags = wp_get_post_terms( $post->ID, 'rl_media_tag', [ 'fields' => 'id=>name' ] ); // valid terms? if ( ! is_wp_error( $tags ) && ! empty( $tags ) ) { foreach ( $tags as $tag_name ) { $tags_html .= '<option value="' . esc_attr( $tag_name ) . '" selected="selected">' . esc_html( $tag_name ) . '</li>'; } } else $tags = []; // update input $tax['input'] = 'html'; // $tags_html is already escaped here $tax['html'] = ' <select class="rl-media-tag-select2" multiple="multiple" name="attachments[' . (int) $post->ID . '][rl_media_tag]"> ' . $tags_html . ' </select>'; // update taxonomy $fields['rl_media_tag'] = $tax; } } } return $fields; } /** * Assign new term IDs to given attachment ID via AJAX in modal attachment edit screen. * * @return void */ function ajax_save_attachment_compat() { // no attachment id? if ( ! isset( $_REQUEST['id'] ) ) wp_send_json_error(); $id = (int) $_REQUEST['id']; // invalid id? if ( $id <= 0 ) wp_send_json_error(); // no valid data? if ( empty( $_REQUEST['attachments'][$id] ) || ! is_array( $_REQUEST['attachments'][$id] ) ) wp_send_json_error(); // no sanitization like in wordpress core: wp_ajax_save_attachment_compat() function $attachment_data = $_REQUEST['attachments'][$id]; // check nonce check_ajax_referer( 'update-post_' . $id, 'nonce' ); if ( ! current_user_can( 'edit_post', $id ) ) wp_send_json_error(); // get post $post = get_post( $id, ARRAY_A ); if ( empty( $post ) || $post['post_type'] !== 'attachment' ) wp_send_json_error(); // update attachment data if needed $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data ); if ( isset( $post['errors'] ) ) wp_send_json_error(); // update attachment wp_update_post( $post ); // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; // first if needed? if ( isset( $attachment_data[$taxonomy] ) ) wp_set_object_terms( $id, (int) reset( array_map( 'trim', $attachment_data[$taxonomy] ) ), $taxonomy, false ); elseif ( isset( $_REQUEST[$taxonomy . '_term'] ) ) wp_set_object_terms( $id, (int) $_REQUEST[$taxonomy . '_term'], $taxonomy, false ); else wp_set_object_terms( $id, '', $taxonomy, false ); // check media tags if ( isset( $attachment_data['rl_media_tag'] ) && is_string( $attachment_data['rl_media_tag'] ) ) { $media_tags = explode( ',', $attachment_data['rl_media_tag'] ); if ( ! empty( $media_tags ) && is_array( $media_tags ) ) $media_tags = array_filter( array_map( 'sanitize_title', $media_tags ) ); // any media tags? if ( ! empty( $media_tags ) ) wp_set_object_terms( $id, $media_tags, 'rl_media_tag', false ); else wp_set_object_terms( $id, '', 'rl_media_tag', false ); } // get attachment data $attachment = wp_prepare_attachment_for_js( $id ); // invalid attachment? if ( ! $attachment ) wp_send_json_error(); // finally send success wp_send_json_success( $attachment ); } /** * AJAX action to delete term. * * @return void */ public function delete_term() { // no data? if ( ! isset( $_POST['term_id'], $_POST['nonce'], $_POST['children'] ) ) wp_send_json_error(); // invalid nonce? if ( ! ctype_alnum( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'rl-folders-ajax-library-nonce' ) ) wp_send_json_error(); // sanitize term id $term_id = (int) $_POST['term_id']; if ( $term_id <= 0 ) wp_send_json_error(); // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; $remove_children = (int) $_POST['children']; // delete children? if ( $remove_children === 1 ) { // get term children $children = get_term_children( $term_id, $taxonomy ); // found any children? if ( ! empty( $children ) && ! is_wp_error( $children ) ) { // reverse array to delete terms with no children first foreach ( array_reverse( $children ) as $child_id ) { // delete child wp_delete_term( $child_id, $taxonomy ); } } } // delete parent if ( is_wp_error( wp_delete_term( $term_id, $taxonomy ) ) ) wp_send_json_error(); else wp_send_json_success( $this->get_folders( $taxonomy ) ); } /** * AJAX action to assign new parent of the term. * * @return void */ public function move_term() { // no data? if ( ! isset( $_POST['parent_id'], $_POST['term_id'], $_POST['nonce'] ) ) wp_send_json_error(); // invalid nonce? if ( ! ctype_alnum( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'rl-folders-ajax-library-nonce' ) ) wp_send_json_error(); // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; // update term $update = wp_update_term( (int) $_POST['term_id'], $taxonomy, [ 'parent' => (int) $_POST['parent_id'] ] ); // error? if ( is_wp_error( $update ) ) wp_send_json_error(); else wp_send_json_success( $this->get_folders( $taxonomy ) ); } /** * AJAX action to add new term. * * @return void */ public function add_term() { // no data? if ( ! isset( $_POST['parent_id'], $_POST['name'], $_POST['nonce'] ) ) wp_send_json_error(); // invalid nonce? if ( ! ctype_alnum( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'rl-folders-ajax-library-nonce' ) ) wp_send_json_error(); // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; // prepare data $original_slug = $slug = sanitize_title( $_POST['name'] ); $name = sanitize_text_field( $_POST['name'] ); $parent_id = (int) $_POST['parent_id']; // empty name? if ( $original_slug === '' || $name === '' ) wp_send_json_error(); // get all term slugs $terms = get_terms( [ 'taxonomy' => $taxonomy, 'hide_empty' => false, 'number' => 0, 'fields' => 'id=>slug', 'hierarchical' => true ] ); // any terms? if ( ! is_wp_error( $terms ) && is_array( $terms ) && ! empty( $terms ) ) { $i = 2; // slug already exists? create unique one while ( in_array( $slug, $terms, true ) ) { $slug = $original_slug . '-' . $i ++; } } // add new term, name is sanitized inside wp_insert_term with sanitize_term function $term = wp_insert_term( $name, $taxonomy, [ 'parent' => $parent_id, 'slug' => $slug ] ); // error? if ( is_wp_error( $term ) ) wp_send_json_error(); $term = get_term( $term['term_id'], $taxonomy ); // error? if ( is_wp_error( $term ) ) wp_send_json_error(); else { wp_send_json_success( [ 'name' => $term->name, 'term_id' => $term->term_id, 'url' => admin_url( 'upload.php?mode=' . $this->mode . '&' . $taxonomy . '=' . $term->term_id ), 'select' => $this->get_folders( $taxonomy, $term->term_id ) ] ); } } /** * AJAX action to rename term. * * @return void */ public function rename_term() { // no data? if ( ! isset( $_POST['term_id'], $_POST['name'], $_POST['nonce'] ) ) wp_send_json_error(); // invalid nonce? if ( ! ctype_alnum( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'rl-folders-ajax-library-nonce' ) ) wp_send_json_error(); // sanitize term id $term_id = (int) $_POST['term_id']; if ( $term_id <= 0 ) wp_send_json_error(); // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; // update term, name is sanitized inside wp_update_term with sanitize_term function $update = wp_update_term( $term_id, $taxonomy, [ 'name' => $_POST['name'] ] ); // error? if ( is_wp_error( $update ) ) wp_send_json_error(); $term = get_term( $term_id, $taxonomy ); // error? if ( is_wp_error( $term ) ) wp_send_json_error(); else { wp_send_json_success( [ 'name' => $term->name, 'select' => $this->get_folders( $taxonomy, $term_id ) ] ); } } /** * AJAX action to assign new term to attachments. * * @return void */ public function move_attachments() { // no data? if ( ! isset( $_POST['attachment_ids'], $_POST['old_term_id'], $_POST['new_term_id'], $_POST['nonce'] ) ) wp_send_json_error(); // invalid nonce? if ( ! ctype_alnum( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'rl-folders-ajax-library-nonce' ) ) wp_send_json_error(); // not empty attachment ids array? if ( empty( $_POST['attachment_ids'] ) || ! is_array( $_POST['attachment_ids'] ) ) wp_send_json_error(); // prepare data $ids = $all_terms = []; $attachments = [ 'success' => [], 'failure' => [], 'duplicated' => [] ]; // filter unwanted data $ids = array_unique( array_filter( array_map( 'intval', $_POST['attachment_ids'] ) ) ); // no ids? if ( empty( $ids ) ) wp_send_json_error(); // prepare term ids $old_term_id = (int) $_POST['old_term_id']; $new_term_id = (int) $_POST['new_term_id']; // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; // moving to root folder? if ( $new_term_id === 0 ) { foreach ( $ids as $id ) { // get attachment term ids $all_terms[$id] = wp_get_object_terms( $id, $taxonomy, [ 'fields' => 'ids' ] ); // remove all terms assigned to attachment if ( ! is_wp_error( wp_set_object_terms( $id, null, $taxonomy, false ) ) ) $attachments['success'][] = $id; else $attachments['failure'][] = $id; } } else { foreach ( $ids as $id ) { // get attachment term ids $terms = wp_get_object_terms( $id, $taxonomy, [ 'fields' => 'ids' ] ); // got terms? if ( ! is_wp_error( $terms ) ) { // save existing term (attachment already assigned to this term) if ( in_array( $new_term_id, $terms, true ) ) $attachments['duplicated'][] = $id; // update attachment's term if ( ! is_wp_error( wp_set_object_terms( $id, $new_term_id, $taxonomy, false ) ) ) $attachments['success'][] = $id; else $attachments['failure'][] = $id; } } } if ( empty( $attachments['success'] ) ) wp_send_json_error(); else { wp_send_json_success( [ 'attachments' => $attachments, 'terms' => $all_terms ] ); } } /** * Change wp_list_categories HTML link. * * @param array $matches Matched elements * @return string */ public function replace_folders_href( $matches ) { // get taxonomy $taxonomy = Responsive_Lightbox()->options['folders']['media_taxonomy']; // set 'all files' folder $term_id = -1; $url_term_id = 0; // any matches? if ( ! empty( $matches[1] ) ) { $params = parse_url( html_entity_decode( urldecode( $matches[1] ) ) ); if ( isset( $params['query'] ) ) { // parse query string parse_str( $params['query'], $atts ); if ( isset( $atts['term'] ) ) { // get term $term = get_term_by( 'slug', $atts['term'], $taxonomy ); // valid term? if ( $term !== false ) { $this->term_counters['keys'][] = $term->term_id; // set term id $url_term_id = $term_id = $term->term_id; } } } } // escape url early return 'href="' . esc_url( apply_filters( 'rl_folders_media_folder_url', add_query_arg( [ 'mode' => $this->mode, $taxonomy => $url_term_id ] ), $matches, $this->mode, $url_term_id ) ) . '" data-term_id="' . (int) $term_id . '"'; } /** * Change wp_list_categories HTML link by adding attachment counter. * * @param array $matches Matched elements * @return string */ public function replace_folders_count( $matches ) { if ( isset( $matches[1] ) ) { $count = (int) str_replace( [ ' ', ' ' ], '', $matches[1] ); $this->term_counters['values'][] = $count; return ' (' . $count . ')</a>'; } return '</a>'; } /** * Change wp_list_categories HTML output by adding jsTree attributes if needed. * * @param array $matches Matched elements * @return string */ public function open_folders( $matches ) { if ( isset( $matches[0] ) ) { // open parent term if ( isset( $matches[0] ) && strpos( $matches[0], 'current-cat-ancestor' ) !== false ) return $matches[0] . ' data-jstree=\'{ "opened": true }\''; // select current term if ( strpos( $matches[0], 'current-cat' ) !== false ) return $matches[0] . ' data-jstree=\'{ "selected": true }\''; } } /** * Enqueue all needed scripts and styles for media library and modal screens. * * @global string $pagenow * @global object $wp_list_table * @global array $_wp_admin_css_colors * * @param string $page Current page similar to $pagenow depends on from which filter function was called * @return void */ public function add_library_scripts( $page ) { // count how many times function was executed, allow this only once static $run = 0; // allow only wp media scripts (empty $page), upload.php or media-new.php if ( ! ( ( $page === '' || $page === 'upload.php' || $page === 'media-new.php' ) && $run < 1 ) ) return; global $pagenow; $run++; // change page for wp_enqueue_media if ( $page === '' ) { if ( $pagenow === 'upload.php' ) $page = 'upload.php'; else $page = 'media'; } // get main instance $rl = Responsive_Lightbox(); // include select2 styles wp_enqueue_style( 'responsive-lightbox-admin-select2', RESPONSIVE_LIGHTBOX_URL . '/assets/select2/select2' . ( ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.min' : '' ) . '.css', [], $rl->defaults['version'] ); // filterable media folders taxonomy $taxonomy = get_taxonomy( $rl->options['folders']['media_taxonomy'] ); // no taxonomy? it should be available here, updated in init_folders method if ( $taxonomy === false ) return; // main script dependencies $dependencies = [ 'jquery', 'underscore', 'jquery-ui-draggable', 'jquery-ui-droppable', 'media-models', 'tags-suggest' ]; // create folder counters $counters = []; if ( $page !== 'media' ) { // prepare variables $no_items = ''; $childless = false; // include styles wp_enqueue_style( 'responsive-lightbox-folders-admin-css', RESPONSIVE_LIGHTBOX_URL . '/css/admin-folders.css' ); wp_enqueue_style( 'responsive-lightbox-folders-perfect-scrollbar', RESPONSIVE_LIGHTBOX_URL . '/assets/perfect-scrollbar/perfect-scrollbar' . ( ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.min' : '' ) . '.css' ); wp_enqueue_style( 'responsive-lightbox-folders-jstree', RESPONSIVE_LIGHTBOX_URL . '/assets/jstree/themes/default/style' . ( ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.min' : '' ) . '.css' ); // get color scheme global global $_wp_admin_css_colors; // set default color; $color = '0,160,210'; if ( ! empty( $_wp_admin_css_colors ) ) { // get current admin color scheme name $current_color_scheme = get_user_option( 'admin_color' ); if ( empty( $current_color_scheme ) ) $current_color_scheme = 'fresh'; // color exists? some schemes don't have 4 colors if ( isset( $_wp_admin_css_colors[$current_color_scheme] ) && property_exists( $_wp_admin_css_colors[$current_color_scheme], 'colors' ) && isset( $_wp_admin_css_colors[$current_color_scheme]->colors[3] ) ) { // convert color $rgb = $rl->hex2rgb( $_wp_admin_css_colors[$current_color_scheme]->colors[3] ); // valid color? if ( $rgb !== false ) $color = implode( ',', $rgb ); } } // $color is already validated, no escaping wp_add_inline_style( 'responsive-lightbox-folders-jstree', '#rl-folders-tree-container .jstree .rl-folders-state-active.rl-folders-state-hover { background: #fff !important; } #rl-folders-tree-container .jstree-container-ul .jstree-wholerow-clicked, #rl-folders-tree-container .jstree-container-ul:not(.jstree-wholerow-ul) .jstree-clicked { background: rgba(' . esc_attr( $color ) . ', 0.15); } #rl-folders-tree-container .jstree-container-ul .jstree-wholerow-hovered, #rl-folders-tree-container .jstree-container-ul:not(.jstree-wholerow-ul) .jstree-hovered { background: rgba(' . esc_attr( $color ) . ', 0.05); }' ); // list categories parameters $categories = [ 'orderby' => 'name', 'order' => 'asc', 'show_count' => true, 'show_option_all' => '', 'show_option_none' => '', 'use_desc_for_title' => false, 'title_li' => '', 'hide_empty' => false, 'hierarchical' => true, 'taxonomy' => $taxonomy->name, 'hide_title_if_empty' => true, 'echo' => false ]; // get current term id $term_id = isset( $_GET[$taxonomy->name] ) ? (int) $_GET[$taxonomy->name] : 0; // list mode? if ( $this->mode === 'list' ) { // get global wp list table instance global $wp_list_table; // empty instance? if ( is_null( $wp_list_table ) ) $wp_list_table = _get_list_table( 'WP_Media_List_Table' ); // start buffering ob_start(); // display "no media" table row echo '<tr class="no-items"><td class="colspanchange" colspan="' . (int) $wp_list_table->get_column_count() . '">'; $wp_list_table->no_items(); echo '</td></tr>'; // save "no media" table row $no_items = ob_get_contents(); // clear the buffer ob_end_clean(); // valid term? if ( $term_id > 0 ) { $children = get_term_children( $term_id, $taxonomy->name ); // found any children? $childless = ! ( ! empty( $children ) && ! is_wp_error( $children ) ); } } // set current term id if ( $term_id > 0 ) $categories['current_category'] = $term_id; // hide filter for grid if ( $this->mode !== 'list' ) { wp_add_inline_style( 'responsive-lightbox-folders-admin-css', '#media-attachment-rl-folders-filters { display: none; } .media-modal-content .media-frame select.attachment-filters { max-width: 100%; min-width: auto; }' ); } // get taxonomy html output $html = wp_list_categories( $categories ); if ( $html !== '' ) { // fix for urls $html = preg_replace_callback( '/href=(?:\'|")(.*?)(?:\'|")/', [ $this, 'replace_folders_href' ], $html ); // fix for counters $html = preg_replace_callback( '/<\/a> \(((?:\d+| )+)\)/', [ $this, 'replace_folders_count' ], $html ); // open all needed folders at start if ( $term_id > 0 ) $html = preg_replace_callback( '/class="cat-item cat-item-(\d+)(?:[a-z\s0-9-]+)?"/', [ $this, 'open_folders' ], $html ); // check whether counters are valid if ( ! ( empty( $this->term_counters['keys'] ) || empty( $this->term_counters['values'] ) || count( $this->term_counters['keys'] ) !== count( $this->term_counters['values'] ) ) ) { //@TODO counters are supposed to be used in JS but not implemented yet // update folder counters $counters = array_combine( $this->term_counters['keys'], $this->term_counters['values'] ); } } // root folder query $root_query = new WP_Query( apply_filters( 'rl_root_folder_query_args', [ 'rl_folders_root' => true, 'posts_per_page' => -1, 'post_type' => 'attachment', 'post_status' => 'inherit,private', 'fields' => 'ids', 'no_found_rows' => false, 'tax_query' => [ [ 'relation' => 'AND', [ 'taxonomy' => $taxonomy->name, 'field' => 'id', 'terms' => 0, 'include_children' => false, 'operator' => 'NOT EXISTS' ] ] ] ] ) ); // set number of all attachments $counters[-1] = (int) apply_filters( 'rl_count_attachments', 0 ); // set number of root attachments (not categorized) $counters[0] = (int) $root_query->post_count; $html = ' <ul> <li class="cat-item cat-item-all"' . ( $term_id === 0 ? ' data-jstree=\'{ "selected": true }\'' : '' ) . '> <a href="' . esc_url( apply_filters( 'rl_folders_media_folder_url', add_query_arg( [ 'mode' => $this->mode, $taxonomy->name => 0 ] ), null, $this->mode, 0 ) ) . '" data-term_id="all">' . esc_html__( 'All Files', 'responsive-lightbox' ) . ' (' . (int) $counters[-1] . ')</a> </li> <li class="cat-item cat-item-0" data-jstree=\'{ "opened": true' . ( $term_id === -1 ? ', "selected": true ' : '' ) . ' }\'> <a href="' . esc_url( apply_filters( 'rl_folders_media_folder_url', add_query_arg( [ 'mode' => $this->mode, $taxonomy->name => -1 ] ), null, $this->mode, -1 ) ) . '" data-term_id="0">' . esc_html__( 'Root Folder', 'responsive-lightbox' ) . ' (' . (int) $counters[0] . ')</a> <ul>' . $html . '</ul> </li> </ul>'; // register scripts wp_register_script( 'responsive-lightbox-folders-jstree', RESPONSIVE_LIGHTBOX_URL . '/assets/jstree/jstree' . ( ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.min' : '' ) . '.js', [], $rl->defaults['version'], false ); wp_register_script( 'responsive-lightbox-folders-perfect-scrollbar', RESPONSIVE_LIGHTBOX_URL . '/assets/perfect-scrollbar/perfect-scrollbar' . ( ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.min' : '' ) . '.js', [], $rl->defaults['version'], false ); $dependencies[] = 'responsive-lightbox-folders-jstree'; $dependencies[] = 'responsive-lightbox-folders-perfect-scrollbar'; } wp_enqueue_script( 'responsive-lightbox-admin-select2', RESPONSIVE_LIGHTBOX_URL . '/assets/select2/select2.full' . ( ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.min' : '' ) . '.js', [ 'jquery' ], $rl->defaults['version'], false ); wp_enqueue_script( 'responsive-lightbox-folders-admin', RESPONSIVE_LIGHTBOX_URL . '/js/admin-folders.js', $dependencies, $rl->defaults['version'], false ); if ( $page === 'media' ) { // prepare script data $script_data = [ 'taxonomy' => $taxonomy->name, 'page' => $page, 'root' => esc_html__( 'Root Folder', 'responsive-lightbox' ), 'terms' => wp_dropdown_categories( [ 'orderby' => 'name', 'order' => 'asc', 'show_option_all' => esc_html__( 'All Files', 'responsive-lightbox' ), 'show_count' => false, 'hide_empty' => false, 'hierarchical' => true, 'selected' => ( isset( $_GET[$taxonomy->name] ) ? (int) $_GET[$taxonomy->name] : 0 ), 'name' => $taxonomy->name, 'taxonomy' => $taxonomy->name, 'hide_if_empty' => true, 'echo' => false ] ) ]; } else { // prepare script data $script_data = [ 'remove_children' => $rl->options['folders']['folders_removal'], 'wholerow' => $rl->options['folders']['jstree_wholerow'], 'theme' => 'default', 'counters' => $counters, 'no_media_items' => $no_items, 'taxonomy' => $taxonomy->name, 'page' => $page, 'root' => esc_html__( 'Root Folder', 'responsive-lightbox' ), 'all_terms' => esc_html__( 'All Files', 'responsive-lightbox' ), 'new_folder' => esc_html__( 'New Folder', 'responsive-lightbox' ), 'delete_term' => esc_html__( 'Are you sure you want to delete this folder?', 'responsive-lightbox' ), 'delete_terms' => esc_html__( 'Are you sure you want to delete this folder with all subfolders?', 'responsive-lightbox' ), 'nonce' => wp_create_nonce( 'rl-folders-ajax-library-nonce' ), 'terms' => wp_dropdown_categories( [ 'orderby' => 'name', 'order' => 'asc', 'show_option_all' => esc_html__( 'All Files', 'responsive-lightbox' ), 'show_count' => false, 'hide_empty' => false, 'hierarchical' => true, 'selected' => ( isset( $_GET[$taxonomy->name] ) ? (int) $_GET[$taxonomy->name] : 0 ), 'name' => $taxonomy->name, 'taxonomy' => $taxonomy->name, 'hide_if_empty' => true, 'echo' => false ] ), 'template' => ' <div id="rl-folders-tree-container"> <div class="media-toolbar wp-filter"> <div class="view-switch rl-folders-action-links"> <a href="#" title="' . esc_attr( $taxonomy->labels->add_new_item ) . '" class="dashicons dashicons-plus rl-folders-add-new-folder' . ( $this->mode === 'list' && ( $term_id === -1 || $term_id > 0 ) ? '' : ' disabled-link' ) . '"></a> <a href="#" title="' . esc_attr( sprintf( __( 'Save new %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-yes rl-folders-save-new-folder" style="display: none;"></a> <a href="#" title="' . esc_attr( sprintf( __( 'Cancel adding new %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-no rl-folders-cancel-new-folder" style="display: none;"></a> <a href="#" title="' . esc_attr( $taxonomy->labels->edit_item ) . '" class="dashicons dashicons-edit rl-folders-rename-folder' . ( $this->mode === 'list' && $term_id > 0 ? '' : ' disabled-link' ) . '"></a> <a href="#" title="' . esc_attr( sprintf( __( 'Save %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-yes rl-folders-save-folder" style="display: none;"></a> <a href="#" title="' . esc_attr( sprintf( __( 'Cancel renaming %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-no rl-folders-cancel-folder" style="display: none;"></a> <a href="#" title="' . esc_attr( sprintf( __( 'Delete %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-trash rl-folders-delete-folder' . ( $this->mode === 'list' && $term_id > 0 ? '' : ' disabled-link' ) . '"></a> <a href="#" title="' . esc_attr( sprintf( __( 'Expand %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-arrow-down-alt2 rl-folders-expand-folder' . ( $this->mode === 'list' && ! $childless && ( $term_id === -1 || $term_id > 0 ) ? '' : ' disabled-link' ) . '"></a> <a href="#" title="' . esc_attr( sprintf( __( 'Collapse %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-arrow-up-alt2 rl-folders-collapse-folder' . ( $this->mode === 'list' && ! $childless && ( $term_id === -1 || $term_id > 0 ) ? '' : ' disabled-link' ) . '"></a> </div> </div> <div id="rl-folders-tree">' . wp_kses_post( $html ) . '</div> </div>' ]; } wp_add_inline_script( 'responsive-lightbox-folders-admin', 'var rlFoldersArgs = ' . wp_json_encode( $script_data ) . ";\n", 'before' ); add_action( 'admin_print_styles', [ $this, 'admin_print_media_styles' ] ); } /** * CSS fix for media folders checklist. * * @return void */ public function admin_print_media_styles() { echo '<style>.rl_media_folder li .selectit input[type="checkbox"] { margin: 0 3px; }</style>'; } /** * Count attachments. * * @return int */ public function count_attachments() { $count = wp_count_posts( 'attachment' ); return (int) $count->inherit; } /** * Get all previously used media taxonomies. * * @global object $wpdb * * @return array */ public function get_taxonomies() { global $wpdb; // query $fields = $wpdb->get_col( " SELECT DISTINCT tt.taxonomy FROM " . $wpdb->prefix . "term_taxonomy tt LEFT JOIN " . $wpdb->prefix . "term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id LEFT JOIN " . $wpdb->prefix . "posts p ON p.ID = tr.object_id WHERE p.post_type = 'attachment' ORDER BY tt.taxonomy ASC" ); if ( ! empty( $fields ) ) { // remove polylang taxonomy if ( ( $key = array_search( 'language', $fields, true ) ) !== false ) unset( $fields[$key] ); } return $fields; } }