├── modules └── Core │ ├── Property.php │ ├── Helpers.php │ └── Hooks.php ├── assets ├── js │ ├── bulk.js │ ├── filter.js │ └── assignment.js └── css │ └── styles.css ├── languages └── f4-media-taxonomies.pot ├── f4-media-taxonomies.php └── readme.txt /modules/Core/Property.php: -------------------------------------------------------------------------------- 1 | ' + indent + mediaTerm.name + ''); 20 | }); 21 | 22 | $bulk.append($taxonomy); 23 | } 24 | } 25 | }, 1000); 26 | })(jQuery); 27 | -------------------------------------------------------------------------------- /languages/f4-media-taxonomies.pot: -------------------------------------------------------------------------------- 1 | #, fuzzy 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: F4 Media Taxonomies\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2022-07-05 14:06+0000\n" 7 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 8 | "Last-Translator: FULL NAME \n" 9 | "Language-Team: \n" 10 | "Language: \n" 11 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "X-Generator: Loco https://localise.biz/\n" 16 | "X-Loco-Version: 2.3.3; wp-5.4.1" 17 | 18 | #. Description of the plugin 19 | msgid "" 20 | "Add filters and bulk actions for attachment categories, tags and custom " 21 | "taxonomies." 22 | msgstr "" 23 | 24 | #: modules/Core/Hooks.php:158 25 | msgid "Assign %taxonomy%" 26 | msgstr "" 27 | 28 | #: modules/Core/Hooks.php:310 29 | msgid "Attachment(s) updated." 30 | msgstr "" 31 | 32 | #: modules/Core/Hooks.php:161 33 | #, php-format 34 | msgid "Please enter %chars% or more characters" 35 | msgstr "" 36 | -------------------------------------------------------------------------------- /modules/Core/Helpers.php: -------------------------------------------------------------------------------- 1 | level = $level; 38 | $term->parents = $parents; 39 | 40 | $term_parents = $parents; 41 | $term_parents[] = $term->name; 42 | 43 | $terms_all[] = $term; 44 | 45 | $terms_sub = self::get_terms_hierarchical($args, $term->term_id, $level + 1, $term_parents); 46 | 47 | if(!empty($terms_sub)) { 48 | $terms_all = array_merge($terms_all, $terms_sub); 49 | } 50 | } 51 | 52 | return $terms_all; 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /f4-media-taxonomies.php: -------------------------------------------------------------------------------- 1 | div, 33 | .selectize-control.multi .selectize-input > div.active { 34 | border-width: 1px; 35 | border-radius: 3px; 36 | } 37 | 38 | .compat-item tr.compat-field-selectize { 39 | display: table-row; 40 | overflow: auto; 41 | } 42 | 43 | .compat-item tr.compat-field-selectize .selectize-input { 44 | min-height: 38px; 45 | } 46 | 47 | .compat-item tr.compat-field-selectize > td.field { 48 | margin-bottom: 5px; 49 | } 50 | 51 | .selectize-input { 52 | box-shadow: 0 0 0 transparent; 53 | border-radius: 4px; 54 | border: 1px solid #7e8993; 55 | background-color: #fff; 56 | color: #32373c; 57 | } 58 | 59 | .wp-admin .media-frame .selectize-input.input-active, 60 | .selectize-input.input-active { 61 | border-color: #007cba; 62 | box-shadow: 0 0 0 1px #007cba; 63 | } 64 | 65 | .admin-color-light .selectize-input.input-active { 66 | border-color: #04a4cc; 67 | box-shadow: 0 0 0 1px #04a4cc; 68 | } 69 | 70 | .admin-color-blue .selectize-input.input-active { 71 | border-color: #096484; 72 | box-shadow: 0 0 0 1px #096484; 73 | } 74 | 75 | .admin-color-coffee .selectize-input.input-active { 76 | border-color: #c7a589; 77 | box-shadow: 0 0 0 1px #c7a589; 78 | } 79 | 80 | .admin-color-ectoplasm .selectize-input.input-active { 81 | border-color: #a3b745; 82 | box-shadow: 0 0 0 1px #a3b745; 83 | } 84 | 85 | .admin-color-ectoplasm .selectize-input.input-active { 86 | border-color: #a3b745; 87 | box-shadow: 0 0 0 1px #a3b745; 88 | } 89 | 90 | .admin-color-midnight .selectize-input.input-active { 91 | border-color: #e14d43; 92 | box-shadow: 0 0 0 1px #e14d43; 93 | } 94 | 95 | .admin-color-ocean .selectize-input.input-active { 96 | border-color: #9ebaa0; 97 | box-shadow: 0 0 0 1px #9ebaa0; 98 | } 99 | 100 | .admin-color-sunrise .selectize-input.input-active { 101 | border-color: #dd823b; 102 | box-shadow: 0 0 0 1px #dd823b; 103 | } 104 | 105 | .selectize-input .parent-label, 106 | .selectize-dropdown-content .parent-label { 107 | color: #BBBBBB; 108 | } 109 | 110 | .selectize-input [data-value] { 111 | position: relative; 112 | padding-right: 24px !important; 113 | } 114 | 115 | .selectize-input .remove { 116 | z-index: 1; 117 | position: absolute; 118 | top: 0; 119 | right: 0; 120 | bottom: 0; 121 | width: 17px; 122 | text-align: center; 123 | font-weight: 700; 124 | font-size: 12px; 125 | color: inherit; 126 | text-decoration: none; 127 | vertical-align: middle; 128 | font-family: serif; 129 | padding: 2px 0 0; 130 | border-left: 1px solid #d0d0d0; 131 | -webkit-border-radius: 0 2px 2px 0; 132 | -moz-border-radius: 0 2px 2px 0; 133 | border-radius: 0 2px 2px 0; 134 | box-sizing: border-box; 135 | } 136 | 137 | .selectize-input .remove:hover { 138 | background-color: rgba(0,0,0,.05); 139 | } 140 | 141 | .selectize-dropdown { 142 | z-index: 1000000; 143 | } 144 | 145 | .selectize-dropdown [data-value] { 146 | padding: 5px 8px; 147 | } 148 | 149 | .selectize-dropdown .searchhint { 150 | color: rgba(48,48,48,.5); 151 | } 152 | 153 | /* .upload-php .selectize-dropdown { 154 | margin-top: -33px; 155 | } */ 156 | 157 | -------------------------------------------------------------------------------- /assets/js/assignment.js: -------------------------------------------------------------------------------- 1 | var f4MediaTaxonomySelectizeFocus = ''; 2 | 3 | Selectize.define('silent_remove', function(options){ 4 | var self = this; 5 | 6 | // defang the internal search method when remove has been clicked 7 | this.on('item_remove', function(){ 8 | this.plugin_silent_remove_in_remove = true; 9 | }); 10 | 11 | this.search = (function() { 12 | var original = self.search; 13 | return function() { 14 | if (typeof(this.plugin_silent_remove_in_remove) != "undefined") { 15 | // re-enable normal searching 16 | delete this.plugin_silent_remove_in_remove; 17 | return { 18 | items: {}, 19 | query: [], 20 | tokens: [] 21 | }; 22 | } 23 | else { 24 | return original.apply(this, arguments); 25 | } 26 | }; 27 | })(); 28 | }); 29 | 30 | var f4MediaTaxonomySelectize = function(id, taxonomy) { 31 | var $selectize = jQuery(id); 32 | 33 | $selectize.closest('tr').addClass('compat-field-selectize'); 34 | 35 | $selectize.selectize({ 36 | plugins: ['remove_button', 'silent_remove'], 37 | placeholder: taxonomy.labels.search, 38 | dropdownParent: null, 39 | preload: 'focus', 40 | closeAfterSelect: true, 41 | load: function(query, callback) { 42 | if(!query.length) { 43 | //this.addOption({'text': taxonomy.labels.search_hint, 'value': 'f4-media-searchhint', 'searchhint': true, 'disabled': true}); 44 | //this.refreshOptions(false); 45 | return callback(); 46 | } 47 | 48 | this.removeOption('f4-media-searchhint'); 49 | this.refreshOptions(false); 50 | 51 | jQuery.ajax({ 52 | url: ajaxurl, 53 | cache: false, 54 | data: { 55 | action: 'f4-media-taxonomies-search-terms', 56 | security: f4MediaTaxonomy.ajax_nonce, 57 | taxonomy: taxonomy.slug, 58 | query: query 59 | }, 60 | success: function(response) { 61 | callback(response.data); 62 | } 63 | }); 64 | }, 65 | create: function(input, callback) { 66 | jQuery.ajax({ 67 | url: ajaxurl, 68 | cache: false, 69 | data: { 70 | action: 'f4-media-taxonomies-add-term', 71 | security: f4MediaTaxonomy.ajax_nonce, 72 | taxonomy: taxonomy.slug, 73 | term_label: input 74 | }, 75 | success: function(response) { 76 | if(typeof response.new_term !== 'undefined') { 77 | callback({ 78 | value: response.new_term.slug, 79 | text: response.new_term.name 80 | }); 81 | } else { 82 | callback(false); 83 | } 84 | } 85 | }); 86 | }, 87 | render: { 88 | option_create: function(data, escape) { 89 | return '
' + taxonomy.labels.add + ': ' + escape(data.input) + '
'; 90 | }, 91 | option: function(data, escape) { 92 | var label = (typeof data.parents !== 'undefined' && data.parents.length ? '' + escape(data.parents.join(' / ')) + ' / ' : '') + escape(data.text); 93 | 94 | if(typeof data.searchhint === 'undefined') { 95 | return '
' + label + '
'; 96 | } else { 97 | return '
' + label + '
'; 98 | } 99 | }, 100 | item: function(data, escape) { 101 | var isNewTerm = typeof data.parents === 'undefined'; 102 | 103 | if(isNewTerm) { 104 | data.parents = []; 105 | 106 | let selectedItems = JSON.parse(this.$input.attr('data-selected-items')); 107 | let termSlug = data.text; 108 | 109 | if(typeof selectedItems[termSlug] !== 'undefined') { 110 | data.text = selectedItems[termSlug].name; 111 | data.parents = selectedItems[termSlug].parents || []; 112 | } 113 | } 114 | 115 | var sortStringArray = data.parents.slice(0); 116 | sortStringArray.push(data.text); 117 | var sort_string = sortStringArray.join('-').toLowerCase(); 118 | var label = (data.parents.length ? '' + escape(data.parents.join(' / ')) + ' / ' : '') + escape(data.text); 119 | 120 | return '
' + label + '
'; 121 | } 122 | }, 123 | onFocus: function() { 124 | let items = []; 125 | 126 | if(this.currentResults) { 127 | items = this.currentResults.items; 128 | } 129 | 130 | if(!items.length) { 131 | this.addOption({'text': taxonomy.labels.search_hint, 'value': 'f4-media-searchhint', 'searchhint': true, 'disabled': true}); 132 | } 133 | 134 | f4MediaTaxonomySelectizeFocus = id; 135 | }, 136 | onItemRemove: function() { 137 | f4MediaTaxonomySelectizeFocus = ''; 138 | }, 139 | onBlur: function() { 140 | this.removeOption('f4-media-searchhint'); 141 | this.refreshOptions(false); 142 | f4MediaTaxonomySelectizeFocus = ''; 143 | }, 144 | onItemAdd: function(value, $element) { 145 | $element.parent().children(':not(input)').sort(function(a, b) { 146 | var upA = jQuery(a).attr('data-sort-string'); 147 | var upB = jQuery(b).attr('data-sort-string'); 148 | 149 | return upA.localeCompare(upB, undefined, { 150 | numeric: true, 151 | sensitivity: 'base' 152 | }); 153 | }).removeClass('active').insertBefore($element.parent().children('input')); 154 | } 155 | }); 156 | 157 | if(f4MediaTaxonomySelectizeFocus === id) { 158 | $selectize[0].selectize.focus(); 159 | } 160 | }; 161 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === F4 Media Taxonomies === 2 | Contributors: faktorvier 3 | Donate link: https://www.faktorvier.ch/donate/ 4 | Tags: media, attachments, library, filter, bulk action, categories, tags, taxonomies, custom taxonomies, attachment, category, tag, taxonomy, custom taxonomy 5 | Requires at least: 4.5.0 6 | Tested up to: 6.9 7 | Stable tag: 1.1.6 8 | License: GPLv2 9 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 10 | 11 | Add filters and bulk actions for attachment categories, tags and custom taxonomies. 12 | 13 | == Description == 14 | 15 | [F4 Media Taxonomies](https://www.f4dev.ch) provides the ability to filter the media library by categories, tags and/or custom taxonomies. 16 | You can use the built-in taxonomies (category or post_tag) or any custom taxonomy. 17 | 18 | If a taxonomy is enabled for attachments, you can assign as many of their terms to an attachment as you need. 19 | You can assign them directly in the media library or in every media-selector overlay. 20 | There is also a nifty bulk function in the media library, which allows you to assign a single term to multiple attachments at once. 21 | 22 | Attachments can then be filtered by these terms. The filters are available in the media library and in every media-selector overlay. 23 | 24 | Different than other similar plugins, **F4 Media Taxonomies is 100% free!** 25 | 26 | = Usage = 27 | 28 | See FAQ for a guide how to enable categories, tags and custom taxonomies. 29 | 30 | = Features overview = 31 | 32 | * Use any taxonomy (built-in or custom) 33 | * Assign one or more terms to an attachment in media library/overlay 34 | * Bulk assign terms to multiple attachments at once in media library 35 | * Filter attachments by terms in media library/overlay 36 | * Easy to use 37 | * Lightweight and optimized 38 | * 100% free! 39 | 40 | == Installation == 41 | 42 | 1. Upload the plugin files to the `/wp-content/plugins/f4-media-taxonomies` directory, or install the plugin through the WordPress plugins screen directly 43 | 1. Activate the plugin through the 'Plugins' screen in WordPress 44 | 1. See FAQ for a guide how to enable categories, tags and custom taxonomies 45 | 1. All taxonomies that are assigned to the attachment post-type are automatically enabled 46 | 47 | == Frequently Asked Questions == 48 | 49 | = How to enable categories = 50 | 51 | The built-in taxonomy `category` can be enabled with this snippet. Just put it into your `functions.php`: 52 | 53 | add_action('init', function() { 54 | register_taxonomy_for_object_type('category', 'attachment'); 55 | }); 56 | 57 | = How to enable tags = 58 | 59 | The built-in taxonomy `post_tag` can be enabled with this snippet. Just put it into your `functions.php`: 60 | 61 | add_action('init', function() { 62 | register_taxonomy_for_object_type('post_tag', 'attachment'); 63 | }); 64 | 65 | = How to enable custom taxonomies = 66 | 67 | There are two ways to enable custom taxonomies for attachments: 68 | 69 | **New taxonomy:** 70 | 71 | If the taxonomy does not exist yet and you want to create a new one, you have to set the object_type in the `register_taxonomy()` function to `attachment` ([see WordPress codex](https://codex.wordpress.org/Function_Reference/register_taxonomy#Parameters)). 72 | 73 | add_action('init', function() { 74 | register_taxonomy( 75 | 'media-category', 76 | 'attachment' 77 | ); 78 | }); 79 | 80 | **Existing taxonomy:** 81 | 82 | If the taxonomy is already registered, you can assign it with this snippet. Just put it into your `functions.php` and change `media-category` to your taxonomy: 83 | 84 | add_action('init', function() { 85 | register_taxonomy_for_object_type('media-category', 'attachment'); 86 | }); 87 | 88 | = The filters do not appear in the media overlay = 89 | 90 | For a better performance, we only include the scripts and files when they are needed. Some plugins can cause a problem with this functionality. 91 | For this case we offer a hook, which allows you to enable the filter for special conditions. If this hook returns `true`, the filter is enabled for the current site. 92 | 93 | add_filter('F4/MT/Core/has_filter', function() { 94 | return true; 95 | }); 96 | 97 | = Can I enable taxonomies directly in the backend? = 98 | 99 | No. We simply use the taxonomies that are registered in the code. Maybe in the future, but we want to keep this plugin as lightweight and simple as possible. 100 | 101 | = Is it really free? = 102 | 103 | Yes, absolutely! 104 | 105 | == Screenshots == 106 | 107 | 1. Filter by taxonomies in media library list 108 | 2. Filter by taxonomies in media library grid 109 | 3. Assign one or more taxonomies to an attachment 110 | 4. Hierarchical dropdown menu for taxonomies assignment 111 | 5. Filter by taxonomies in media insert overlay 112 | 113 | == Changelog == 114 | 115 | = 1.1.6 = 116 | * Support WordPress 6.9 117 | 118 | = 1.1.5 = 119 | * Fix broken access control vulnerability 120 | * Update selectize.js to version 0.15.2 and add condition to admin upload for CMB2 121 | * Support WordPress 6.8 122 | 123 | = 1.1.4 = 124 | * Remove double array key 125 | * Support WordPress 6.5 126 | 127 | = 1.1.3 = 128 | * Support WordPress 6.1 129 | 130 | = 1.1.2 = 131 | * Update www.f4dev.ch links 132 | 133 | = 1.1.1 = 134 | * Fix bulk action and taxonomy filter dropdowns 135 | * Improve the grid view performance 136 | 137 | = 1.1.0 = 138 | * Terms are now lazy loaded with ajax in assignment select 139 | * Term assignment styles and scripts optimized 140 | * Term assignment sorting fixed 141 | * Update selectize to verison 0.13.5 142 | 143 | = 1.0.17 = 144 | * Support WordPress 6.0 145 | 146 | = 1.0.16 = 147 | * Correctly update post term count (thanks to @nonverbla for the hint) 148 | * Support WordPress 5.9 149 | 150 | = 1.0.15 = 151 | * Support WordPress 5.8 152 | 153 | = 1.0.14 = 154 | * Support WordPress 5.7 155 | 156 | = 1.0.13 = 157 | * Fix taxonomy select for new jQuery version 158 | * Support WordPress 5.6 159 | 160 | = 1.0.12 = 161 | * Fix behaviour after taxonomy selection 162 | 163 | = 1.0.11 = 164 | * Support WordPress 5.5 165 | 166 | = 1.0.10 = 167 | * Support WordPress 5.4 168 | 169 | = 1.0.9 = 170 | * Fix bottom bulk action button in media list 171 | 172 | = 1.0.8 = 173 | * Add CMB2 plugin support 174 | 175 | = 1.0.7 = 176 | * WordPress 5.3 compatibility fixes 177 | * Optimized dropdown width in media modal 178 | 179 | = 1.0.6 = 180 | * Update deprecated get_terms function 181 | 182 | = 1.0.5 = 183 | * Few PHP and JS code optimisations 184 | 185 | = 1.0.4 = 186 | * Fix customizer error 187 | * Fix missing dropdowns in media overlay 188 | 189 | = 1.0.3 = 190 | * Fix filter error 191 | 192 | = 1.0.2 = 193 | * Show only taxonomies with show_ui true 194 | 195 | = 1.0.1 = 196 | * Version upgrade for correct repository infos 197 | 198 | = 1.0.0 = 199 | * Initial stable release 200 | -------------------------------------------------------------------------------- /modules/Core/Hooks.php: -------------------------------------------------------------------------------- 1 | show_ui) { 72 | $attachment_taxonomies[] = $attachment_taxonomy; 73 | } 74 | } 75 | 76 | Property::$taxonomies = $attachment_taxonomies; 77 | 78 | return $attachment_taxonomies; 79 | } 80 | 81 | /** 82 | * Load properties 83 | * 84 | * @since 1.0.0 85 | * @access public 86 | * @static 87 | */ 88 | public static function load_properties() { 89 | global $pagenow, $mode, $wp_scripts; 90 | 91 | Property::$has_bulk_action = $pagenow === 'upload.php' && $mode !== 'grid'; 92 | Property::$has_filter = wp_script_is('media-views') || wp_script_is('acf-input') || apply_filters('F4/MT/Core/has_filter', false) || ($pagenow === 'upload.php' && $mode === 'grid') || apply_filters('cmb2_enqueue_js', class_exists('CMB2')); 93 | Property::$has_assignment = Property::$has_filter; 94 | 95 | do_action('F4/MT/Core/load_properties'); 96 | } 97 | 98 | /** 99 | * Sets the default constants 100 | * 101 | * @since 1.0.0 102 | * @access public 103 | * @static 104 | */ 105 | public static function set_default_constants() { 106 | if(!defined('DS')) { 107 | define('DS', DIRECTORY_SEPARATOR); 108 | } 109 | 110 | define('F4_MT_BULK_ACTION_PREFIX', 'f4_mt_toggle_'); 111 | } 112 | 113 | /** 114 | * Load plugin textdomain 115 | * 116 | * @since 1.0.0 117 | * @access public 118 | * @static 119 | */ 120 | public static function load_textdomain() { 121 | load_plugin_textdomain('f4-media-taxonomies', false, plugin_basename(F4_MT_PATH . 'languages') . DS); 122 | } 123 | 124 | /** 125 | * Add custom js into admin head 126 | * 127 | * @since 1.0.0 128 | * @access public 129 | * @static 130 | */ 131 | public static function add_custom_js() { 132 | global $pagenow, $mode; 133 | 134 | // Abort if page has no bulk actions or filter 135 | if(!Property::$has_bulk_action && !Property::$has_filter && !Property::$has_assignment) { 136 | return; 137 | } 138 | 139 | // Get available media taxonomies 140 | $media_taxonomy_data = array( 141 | 'taxonomies' => array(), 142 | 'bulk_action_prefix' => F4_MT_BULK_ACTION_PREFIX, 143 | 'ajax_nonce' => wp_create_nonce('f4-mt-ajax') 144 | ); 145 | 146 | foreach(Property::$taxonomies as $media_taxonomy) { 147 | $media_taxonomy_data['taxonomies'][$media_taxonomy->name] = array( 148 | 'slug' => $media_taxonomy->name, 149 | 'terms' => Helpers::get_terms_hierarchical(array( 150 | 'taxonomy' => $media_taxonomy->name, 151 | 'hide_empty' => false 152 | )), 153 | 'query_var' => $media_taxonomy->query_var, 154 | 'labels' => array( 155 | 'all_items' => $media_taxonomy->labels->all_items, 156 | 'singular' => $media_taxonomy->labels->singular_name, 157 | 'plural' => $media_taxonomy->labels->name, 158 | 'bulk_title' => str_replace('%taxonomy%', $media_taxonomy->labels->name, __('Assign %taxonomy%', 'f4-media-taxonomies')), 159 | 'search' => $media_taxonomy->labels->search_items, 160 | 'add' => $media_taxonomy->labels->add_new_item, 161 | 'search_hint' => str_replace('%chars%', '1', __('Please enter %chars% or more characters', 'f4-media-taxonomies')) 162 | ) 163 | ); 164 | } 165 | 166 | // Output media taxonomies as js code 167 | echo ''; 170 | } 171 | 172 | /** 173 | * Enqueue admin script and styles 174 | * 175 | * @since 1.0.0 176 | * @access public 177 | * @static 178 | */ 179 | public static function admin_enqueue_scripts() { 180 | global $pagenow, $mode; 181 | 182 | // Enqueue filters script 183 | if(Property::$has_filter) { 184 | wp_enqueue_script( 185 | 'f4-media-taxonomies-filter', 186 | F4_MT_URL . 'assets/js/filter.js', 187 | array('media-views'), 188 | false, 189 | true 190 | ); 191 | } 192 | 193 | // Enqueue bulk script 194 | if(Property::$has_bulk_action) { 195 | wp_enqueue_script( 196 | 'f4-media-taxonomies-bulk', 197 | F4_MT_URL . 'assets/js/bulk.js', 198 | array(), 199 | false, 200 | true 201 | ); 202 | } 203 | 204 | // Eneuque selecrize 205 | if(Property::$has_assignment) { 206 | wp_enqueue_script('selectize', 'https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.15.2/js/selectize.js', array(), '0.15.2'); 207 | wp_enqueue_style('selectize', 'https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.15.2/css/selectize.min.css', array(), '0.15.2'); 208 | 209 | wp_enqueue_script( 210 | 'f4-media-taxonomies-assignment', 211 | F4_MT_URL . 'assets/js/assignment.js', 212 | array(), 213 | false, 214 | true 215 | ); 216 | } 217 | 218 | // Enqueue styles 219 | if(Property::$has_bulk_action || Property::$has_filter || Property::$has_assignment) { 220 | wp_enqueue_style( 221 | 'f4-media-taxonomies-styles', 222 | F4_MT_URL . 'assets/css/styles.css' 223 | ); 224 | } 225 | } 226 | 227 | /** 228 | * Add taxonomy filters to the media library list 229 | * 230 | * @since 1.0.0 231 | * @access public 232 | * @static 233 | */ 234 | public static function add_media_list_filter() { 235 | global $pagenow; 236 | 237 | if($pagenow === 'upload.php') { 238 | foreach(Property::$taxonomies as $media_taxonomy) { 239 | $dropdown = wp_dropdown_categories(array( 240 | 'taxonomy' => $media_taxonomy->name, 241 | 'name' => $media_taxonomy->query_var, 242 | 'id' => 'f4-media-taxonomies-' . $media_taxonomy->name . '-filter', 243 | 'show_option_none' => $media_taxonomy->labels->all_items, 244 | 'option_none_value' => '', 245 | 'hide_empty' => false, 246 | 'hierarchical' => $media_taxonomy->hierarchical, 247 | 'orderby' => 'name', 248 | 'order' => 'ASC', 249 | 'show_count' => false, 250 | 'value_field' => 'slug', 251 | 'selected' => isset($_REQUEST[$media_taxonomy->query_var]) ? $_REQUEST[$media_taxonomy->query_var] : -1 252 | )); 253 | } 254 | } 255 | } 256 | 257 | /** 258 | * Run bulk action 259 | * 260 | * @since 1.0.0 261 | * @access public 262 | * @static 263 | */ 264 | public static function run_bulk_action() { 265 | $is_bulk_action = isset($_REQUEST['action']) && strpos($_REQUEST['action'], F4_MT_BULK_ACTION_PREFIX) !== false; 266 | $is_bulk_action2 = isset($_REQUEST['action2']) && strpos($_REQUEST['action2'], F4_MT_BULK_ACTION_PREFIX) !== false; 267 | 268 | if(!isset($_REQUEST['media']) || (!$is_bulk_action && !$is_bulk_action2)) { 269 | return; 270 | } 271 | 272 | check_admin_referer('bulk-media'); 273 | 274 | $media_action = $is_bulk_action2 ? $_REQUEST['action2'] : $_REQUEST['action']; 275 | $media_ids = array_map('intval', $_REQUEST['media']); 276 | $media_term_id = (int)substr($media_action, strlen(F4_MT_BULK_ACTION_PREFIX)); 277 | $media_term = get_term($media_term_id); 278 | 279 | if(!is_object($media_term) || !is_a($media_term, 'WP_Term')) { 280 | return; 281 | } 282 | $backlink = remove_query_arg(array('action', 'action2', 'media', '_ajax_nonce', 'filter_action', 'toggle-taxonomy')); 283 | $backlink = add_query_arg('toggle-taxonomy', $media_term->taxonomy, $backlink); 284 | 285 | foreach($media_ids as $media_id) { 286 | $media_has_term = has_term($media_term_id, $media_term->taxonomy, $media_id); 287 | 288 | if($media_has_term) { 289 | wp_remove_object_terms($media_id, $media_term_id, $media_term->taxonomy); 290 | } else { 291 | wp_add_object_terms($media_id, $media_term_id, $media_term->taxonomy); 292 | } 293 | } 294 | 295 | wp_redirect($backlink); 296 | exit(); 297 | } 298 | 299 | /** 300 | * Show bulk action notice after complete 301 | * 302 | * @since 1.0.0 303 | * @access public 304 | * @static 305 | */ 306 | public static function show_bulk_action_notice() { 307 | global $post_type, $pagenow; 308 | 309 | if(Property::$has_bulk_action && isset($_GET['toggle-taxonomy'])) { 310 | echo '

' . __('Attachment(s) updated.', 'f4-media-taxonomies') . '

'; 311 | } 312 | } 313 | 314 | /** 315 | * Add fields to attachment 316 | * 317 | * @since 1.0.0 318 | * @access public 319 | * @static 320 | * @param array $fields An array with all fields to edit 321 | * @param \WP_Post $post An object for the current post 322 | * @return array $fields An array with all fields to edit 323 | */ 324 | public static function attachment_fields_to_edit($fields, $post) { 325 | foreach(Property::$taxonomies as $media_taxonomy) { 326 | $terms = get_the_terms($post, $media_taxonomy->name); 327 | 328 | $terms_options = array(); 329 | 330 | if(is_array($terms) && !empty($terms)) { 331 | foreach($terms as $term) { 332 | $term_parent_ids = get_ancestors($term->term_id, $term->taxonomy, 'taxonomy'); 333 | $term_parents = []; 334 | 335 | foreach($term_parent_ids as $term_parent) { 336 | array_unshift($term_parents, get_term($term_parent)->name); 337 | } 338 | 339 | $terms_options[$term->slug] = [ 340 | 'slug' => $term->slug, 341 | 'name' => $term->name, 342 | 'parents' => $term_parents, 343 | 'sort' => strtolower(!empty($term_parents) ? implode('-', $term_parents) . '-' . $term->name : $term->name) 344 | ]; 345 | } 346 | 347 | uasort($terms_options, function($a, $b) { 348 | return strnatcasecmp($a['sort'], $b['sort']); 349 | }); 350 | } 351 | 352 | $dropdown = ' 353 | 360 | 361 | 366 | '; 367 | 368 | $fields[$media_taxonomy->name] = array( 369 | 'show_in_edit' => false, 370 | 'input' => 'html', 371 | 'html' => $dropdown, 372 | 'label' => $media_taxonomy->labels->name 373 | ); 374 | } 375 | 376 | return $fields; 377 | } 378 | 379 | /** 380 | * Add inherit post status for attachment taxonomy term count 381 | * 382 | * @since 1.0.16 383 | * @access public 384 | * @static 385 | * @param array $statuses List of post statuses to include in the count 386 | * @param \WP_Taxonomy $taxonomy The current taxonomy object 387 | * @return array $statuses List of post statuses to include in the count 388 | */ 389 | public static function update_post_term_count_statuses($statuses, $taxonomy) { 390 | if(in_array('attachment', $taxonomy->object_type)) { 391 | $statuses[] = 'inherit'; 392 | } 393 | 394 | return $statuses; 395 | } 396 | 397 | /** 398 | * Add new term 399 | * 400 | * @since 1.0.0 401 | * @access public 402 | * @static 403 | */ 404 | public static function ajax_add_term() { 405 | if (!current_user_can('manage_categories') || !check_ajax_referer('f4-mt-ajax', 'security')) { 406 | exit; 407 | } 408 | 409 | $new_term = wp_insert_term($_REQUEST['term_label'], $_REQUEST['taxonomy']); 410 | 411 | if(is_wp_error($new_term)) { 412 | die(); 413 | } 414 | 415 | $new_term_obj = null; 416 | 417 | if(isset($new_term['term_id'])) { 418 | $new_term_obj = get_term($new_term['term_id']); 419 | } 420 | 421 | if(!is_wp_error($new_term_obj)) { 422 | wp_send_json(array( 423 | 'new_term' => $new_term_obj 424 | )); 425 | } 426 | 427 | die(); 428 | } 429 | 430 | /** 431 | * Search terms 432 | * 433 | * @since 1.1.0 434 | * @access public 435 | * @static 436 | */ 437 | public static function ajax_search_terms() { 438 | if (!current_user_can('edit_posts') || !check_ajax_referer('f4-mt-ajax', 'security')) { 439 | exit; 440 | } 441 | 442 | $terms_raw = Helpers::get_terms_hierarchical(array( 443 | 'taxonomy' => $_REQUEST['taxonomy'], 444 | 'hide_empty' => false 445 | )); 446 | 447 | $terms = []; 448 | 449 | foreach($terms_raw as $term) { 450 | if(strripos($term->name, trim($_REQUEST['query'])) !== false) { 451 | $terms[] = [ 452 | 'value' => $term->slug, 453 | 'text' => $term->name, 454 | 'parents' => $term->parents 455 | ]; 456 | } 457 | } 458 | 459 | wp_send_json_success($terms); 460 | } 461 | } 462 | --------------------------------------------------------------------------------