f87472ed by Jeff Balicki

stuff

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent 93ceb336
...@@ -54,18 +54,26 @@ namespace :deploy do ...@@ -54,18 +54,26 @@ namespace :deploy do
54 desc 'Install composer packages in msf-child theme' 54 desc 'Install composer packages in msf-child theme'
55 task :install_theme_packages do 55 task :install_theme_packages do
56 on roles(:web), in: :sequence, wait: 5 do 56 on roles(:web), in: :sequence, wait: 5 do
57 execute "cd '#{release_path}/wp-content/themes/msf-child'; /usr/bin/php74 /home/tenzing_www/composer.phar install --no-dev --prefer-dist --no-interaction --quiet --optimize-autoloader" 57 execute "cd '#{release_path}/wp-content/themes/msf-child/'; /usr/bin/php74 /home/tenzing_www/composer.phar install --quiet"
58 end 58 end
59 end 59 end
60 end 60 end
61 61
62 namespace :deploy do
63 desc 'Install composer packages in root'
64 task :install_root_packages do
65 on roles(:web), in: :sequence, wait: 5 do
66 execute "cd '#{release_path}'; /usr/bin/php74 /home/tenzing_www/composer.phar install --no-dev --prefer-dist --no-interaction --quiet --optimize-autoloader"
67 end
68 end
69 end
62 70
63 71
64 namespace :deploy do 72 namespace :deploy do
65 desc 'set file permissions' 73 desc 'set file permissions'
66 task :set_permissions do 74 task :set_permissions do
67 on roles(:web), in: :sequence, wait: 5 do 75 on roles(:web), in: :sequence, wait: 5 do
68 execute "cd '#{release_path}/'; chmod -Rf 777 .htaccess wp-config.php" 76 execute "cd '#{release_path}/'; chmod -Rf 777 .htaccess"
69 end 77 end
70 end 78 end
71 end 79 end
...@@ -99,7 +107,8 @@ end ...@@ -99,7 +107,8 @@ end
99 # Uncomment the following line to run it on deploys if needed 107 # Uncomment the following line to run it on deploys if needed
100 # after 'deploy:publishing', 'deploy:update_option_paths' 108 # after 'deploy:publishing', 'deploy:update_option_paths'
101 109
102 #after 'deploy:updated', 'deploy:install_theme_packages' 110 after 'deploy:updated', 'deploy:install_theme_packages'
111 after 'deploy:updated', 'deploy:install_root_packages'
103 after 'deploy:updated', 'deploy:sync' 112 after 'deploy:updated', 'deploy:sync'
104 #after 'deploy:updated', 'deploy:set_permissions' 113 after 'deploy:updated', 'deploy:set_permissions'
105 after 'deploy:finished', 'deploy:sync_again' 114 after 'deploy:finished', 'deploy:sync_again'
......
1 /* MEDIA FILTERS */
2 .media-modal-content .attachments-browser .media-toolbar {
3 display: flex;
4 }
5
6 .media-modal-content .attachments-browser .media-toolbar .media-toolbar-secondary,
7 .media-modal-content .attachments-browser .media-toolbar .media-toolbar-primary {
8 max-width: none;
9 }
10
11 .media-modal-content .attachments-browser .media-toolbar .media-toolbar-secondary {
12 display: flex;
13 flex: 1;
14 align-items: flex-start;
15 }
16
17 body.block-editor-page .media-modal-content .media-frame select.attachment-filters,
18 .media-modal-content .media-frame .media-toolbar select.attachment-filters {
19 flex: 1;
20 width: 50%;
21 margin-right: 10px;
22 }
23
24 .media-modal-content .media-frame .media-toolbar .spinner {
25 flex: 0 0 20px;
26 align-self: flex-end;
27 margin-right: 20px;
28 margin-bottom: 15px;
29 }
30
31 /* SELECTIZE */
32 .selectize-control.multi .selectize-input > 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
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 taxonomy: taxonomy.slug,
57 query: query
58 },
59 success: function(response) {
60 callback(response.data);
61 }
62 });
63 },
64 create: function(input, callback) {
65 jQuery.ajax({
66 url: ajaxurl,
67 cache: false,
68 data: {
69 action: 'f4-media-taxonomies-add-term',
70 taxonomy: taxonomy.slug,
71 term_label: input
72 },
73 success: function(response) {
74 if(typeof response.new_term !== 'undefined') {
75 callback({
76 value: response.new_term.slug,
77 text: response.new_term.name
78 });
79 } else {
80 callback(false);
81 }
82 }
83 });
84 },
85 render: {
86 option_create: function(data, escape) {
87 return '<div class="create">' + taxonomy.labels.add + ': <strong>' + escape(data.input) + '</strong></div>';
88 },
89 option: function(data, escape) {
90 var label = (typeof data.parents !== 'undefined' && data.parents.length ? '<span class="parent-label">' + escape(data.parents.join(' / ')) + ' /</span> ' : '') + escape(data.text);
91
92 if(typeof data.searchhint === 'undefined') {
93 return '<div>' + label + '</div>';
94 } else {
95 return '<div class="searchhint">' + label + '</div>';
96 }
97 },
98 item: function(data, escape) {
99 var isNewTerm = typeof data.parents === 'undefined';
100
101 if(isNewTerm) {
102 data.parents = [];
103
104 let selectedItems = JSON.parse(this.$input.attr('data-selected-items'));
105 let termSlug = data.text;
106
107 if(typeof selectedItems[termSlug] !== 'undefined') {
108 data.text = selectedItems[termSlug].name;
109 data.parents = selectedItems[termSlug].parents || [];
110 }
111 }
112
113 var sortStringArray = data.parents.slice(0);
114 sortStringArray.push(data.text);
115 var sort_string = sortStringArray.join('-').toLowerCase();
116 var label = (data.parents.length ? '<span class="parent-label">' + escape(data.parents.join(' / ')) + ' /</span> ' : '') + escape(data.text);
117
118 return '<div data-sort-string="' + escape(sort_string) + '">' + label + '</div>';
119 }
120 },
121 onFocus: function() {
122 let items = [];
123
124 if(this.currentResults) {
125 items = this.currentResults.items;
126 }
127
128 if(!items.length) {
129 this.addOption({'text': taxonomy.labels.search_hint, 'value': 'f4-media-searchhint', 'searchhint': true, 'disabled': true});
130 }
131
132 f4MediaTaxonomySelectizeFocus = id;
133 },
134 onItemRemove: function() {
135 f4MediaTaxonomySelectizeFocus = '';
136 },
137 onBlur: function() {
138 this.removeOption('f4-media-searchhint');
139 this.refreshOptions(false);
140 f4MediaTaxonomySelectizeFocus = '';
141 },
142 onItemAdd: function(value, $element) {
143 $element.parent().children(':not(input)').sort(function(a, b) {
144 var upA = jQuery(a).attr('data-sort-string');
145 var upB = jQuery(b).attr('data-sort-string');
146
147 return upA.localeCompare(upB, undefined, {
148 numeric: true,
149 sensitivity: 'base'
150 });
151 }).removeClass('active').insertBefore($element.parent().children('input'));
152 }
153 });
154
155 if(f4MediaTaxonomySelectizeFocus === id) {
156 $selectize[0].selectize.focus();
157 }
158 };
1 (function($){
2 if(typeof f4MediaTaxonomy === 'undefined' || f4MediaTaxonomy.taxonomies.length === 0) {
3 return;
4 }
5
6 setTimeout(function() {
7 // Bulk actions
8 var $bulk = jQuery('[name="action"], [name="action2"]');
9
10 if($bulk.length) {
11 for(var mediaTaxonomyName in f4MediaTaxonomy.taxonomies) {
12 var mediaTaxonomy = f4MediaTaxonomy.taxonomies[mediaTaxonomyName];
13 var $taxonomy = jQuery('<optgroup label="' + mediaTaxonomy.labels.bulk_title + '"></optgroup');
14
15 mediaTaxonomy.terms.forEach(function(mediaTerm) {
16 var $term = jQuery($term);
17 var indent = Array(mediaTerm.level).join('&nbsp;&nbsp;&nbsp;');
18
19 $taxonomy.append('<option value="' + f4MediaTaxonomy.bulk_action_prefix + mediaTerm.term_id + '">' + indent + mediaTerm.name + '</option>');
20 });
21
22 $bulk.append($taxonomy);
23 }
24 }
25 }, 1000);
26 })(jQuery);
1 (function($){
2 if(typeof f4MediaTaxonomy === 'undefined' || f4MediaTaxonomy.taxonomies.length === 0) {
3 return;
4 }
5
6 // Overlay and grid filters
7 if(typeof wp !== 'undefined' && typeof wp.media !== 'undefined') {
8 var media = wp.media;
9
10 if(typeof media.view.AttachmentFilters === 'undefined') {
11 return;
12 }
13
14 var attachmentsBrowser = media.view.AttachmentsBrowser;
15
16 var attachmentFilter = media.view.AttachmentFilters.extend({
17 createFilters: function() {
18 var filters = {};
19 var that = this;
20
21 _.each(that.options.taxonomy.terms, function(mediaTerm, index) {
22 var indent = Array(mediaTerm.level).join('&nbsp;&nbsp;&nbsp;');
23
24 filters[index] = {
25 text: indent + mediaTerm.name,
26 props: {}
27 };
28
29 filters[index].props[that.options.taxonomy.query_var] = mediaTerm.slug;
30 });
31
32 filters.all = {
33 text: that.options.taxonomy.labels.all_items,
34 props: {},
35 priority: 10
36 };
37
38 filters.all.props[that.options.taxonomy.query_var] = '';
39
40 this.filters = filters;
41 }
42 });
43
44 media.view.AttachmentsBrowser = media.view.AttachmentsBrowser.extend({
45 createToolbar: function() {
46 var that = this;
47
48 attachmentsBrowser.prototype.createToolbar.call(that);
49
50 for(var mediaTaxonomyName in f4MediaTaxonomy.taxonomies) {
51 var mediaTaxonomy = f4MediaTaxonomy.taxonomies[mediaTaxonomyName];
52
53 that.toolbar.set('f4-media-taxonomy-' + mediaTaxonomy.slug + '-label', new media.view.Label({
54 value: mediaTaxonomy.labels.singular,
55 attributes: {
56 for: 'f4-media-taxonomy-' + mediaTaxonomy.slug + '-filter'
57 },
58 priority: (-75)
59 }).render());
60
61 that.toolbar.set('f4-media-taxonomy-' + mediaTaxonomy.slug + '-filter', new attachmentFilter({
62 controller: that.controller,
63 model: that.collection.props,
64 priority: (-75),
65 taxonomy: mediaTaxonomy,
66 id: 'f4-media-taxonomy-' + mediaTaxonomy.slug + '-filter',
67 className: 'f4-media-taxonomy-filter attachment-filters'
68 }).render());
69 }
70 }
71 });
72 }
73 })(jQuery);
1 <?php
2
3 /*
4 Plugin Name: F4 Media Taxonomies
5 Plugin URI: https://github.com/faktorvier/f4-media-taxonomies
6 Description: Add filters and bulk actions for attachment categories, tags and custom taxonomies.
7 Version: 1.1.3
8 Author: FAKTOR VIER
9 Author URI: https://www.f4dev.ch
10 License: GPLv2
11 License URI: https://www.gnu.org/licenses/gpl-2.0.html
12 Text Domain: f4-media-taxonomies
13 Domain Path: /languages/
14
15 F4 Media Taxonomies is free software: you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation, either version 2 of the License, or
18 any later version.
19
20 F4 Media Taxonomies is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with F4 Media Taxonomies. If not, see https://www.gnu.org/licenses/gpl-2.0.html.
27 */
28
29 if(!defined('ABSPATH')) exit;
30
31 define('F4_MT_VERSION', '1.1.3');
32
33 define('F4_MT_SLUG', 'f4-media-taxonomies');
34 define('F4_MT_MAIN_FILE', __FILE__);
35 define('F4_MT_BASENAME', plugin_basename(F4_MT_MAIN_FILE));
36 define('F4_MT_PATH', dirname(F4_MT_MAIN_FILE) . DIRECTORY_SEPARATOR);
37 define('F4_MT_URL', plugins_url('/', F4_MT_MAIN_FILE));
38 define('F4_MT_PLUGIN_FILE', basename(F4_MT_BASENAME));
39 define('F4_MT_PLUGIN_FILE_PATH', F4_MT_PATH . F4_MT_PLUGIN_FILE);
40
41 // Add autoloader
42 spl_autoload_register(function($class) {
43 $class = ltrim($class, '\\');
44 $ns_prefix = 'F4\\MT\\';
45
46 if(strpos($class, $ns_prefix) !== 0) {
47 return;
48 }
49
50 $class_name = str_replace($ns_prefix, '', $class);
51 $class_path = str_replace('\\', DIRECTORY_SEPARATOR, $class_name);
52 $class_file = F4_MT_PATH . 'modules' . DIRECTORY_SEPARATOR . $class_path . '.php';
53
54 if(file_exists($class_file)) {
55 require_once $class_file;
56 }
57 });
58
59 // Init modules
60 F4\MT\Core\Hooks::init();
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 <EMAIL@ADDRESS>\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 ""
1 <?php
2
3 namespace F4\MT\Core;
4
5 /**
6 * F4 Media Taxonomies Core Helpers
7 *
8 * All the helpers for the core module
9 *
10 * @since 1.0.0
11 * @package F4\MT\Core
12 */
13 class Helpers {
14 /**
15 * Get terms sorted by hierarchy
16 *
17 * @since 1.0.0
18 * @access public
19 * @static
20 * @param array $args The arguments which should be passed to the get_terms function
21 * @param int $parent The terms parent id (for recursive usage)
22 * @param int $level The current level (for recursive usage)
23 * @param array $parents An array with all the parent terms (for recursive usage)
24 * @return array $terms_all An array with all the terms for this taxonomy
25 */
26 public static function get_terms_hierarchical($args = array(), $parent = 0, $level = 1, $parents = array()) {
27 $terms_all = array();
28
29 $args['parent'] = $args['child_of'] = $parent;
30
31 $terms = get_terms($args);
32 if (is_wp_error($terms)) {
33 return $terms_all;
34 }
35
36 foreach((array) $terms as $term) {
37 $term->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
1 <?php
2
3 namespace F4\MT\Core;
4
5 /**
6 * F4 Media Taxonomies Core Hooks
7 *
8 * All the WordPress hooks for the core module
9 *
10 * @since 1.0.0
11 * @package F4\MT\Core
12 */
13 class Hooks {
14 /**
15 * Initialize the hooks
16 *
17 * @since 1.0.0
18 * @access public
19 * @static
20 */
21 public static function init() {
22 add_action('init', __NAMESPACE__ . '\\Hooks::core_loaded');
23 add_action('init', __NAMESPACE__ . '\\Hooks::load_textdomain');
24 add_action('init', __NAMESPACE__ . '\\Hooks::load_taxonomies', 99);
25 add_action('admin_enqueue_scripts', __NAMESPACE__ . '\\Hooks::load_properties', 50);
26 add_action('F4/MT/Core/set_constants', __NAMESPACE__ . '\\Hooks::set_default_constants', 98);
27
28 add_action('admin_head', __NAMESPACE__ . '\\Hooks::add_custom_js');
29 add_action('customize_controls_print_scripts', __NAMESPACE__ . '\\Hooks::add_custom_js');
30 add_action('admin_enqueue_scripts', __NAMESPACE__ . '\\Hooks::admin_enqueue_scripts', 60);
31 add_action('restrict_manage_posts', __NAMESPACE__ . '\\Hooks::add_media_list_filter');
32 add_action('load-upload.php', __NAMESPACE__ . '\\Hooks::run_bulk_action');
33 add_action('admin_notices', __NAMESPACE__ . '\\Hooks::show_bulk_action_notice');
34 add_filter('attachment_fields_to_edit', __NAMESPACE__ . '\\Hooks::attachment_fields_to_edit', 1, 2);
35 add_filter('update_post_term_count_statuses', __NAMESPACE__ . '\\Hooks::update_post_term_count_statuses', 10, 2);
36 add_action('wp_ajax_f4-media-taxonomies-add-term', __NAMESPACE__ . '\\Hooks::ajax_add_term');
37 add_action('wp_ajax_f4-media-taxonomies-search-terms', __NAMESPACE__ . '\\Hooks::ajax_search_terms');
38
39 add_action('elementor/editor/after_enqueue_scripts', __NAMESPACE__ . '\\Hooks::load_properties', 50);
40 add_action('elementor/editor/after_enqueue_scripts', __NAMESPACE__ . '\\Hooks::add_custom_js', 55);
41 add_action('elementor/editor/after_enqueue_scripts', __NAMESPACE__ . '\\Hooks::admin_enqueue_scripts', 60);
42 }
43
44 /**
45 * Fires once the core module is loaded
46 *
47 * @since 1.0.0
48 * @access public
49 * @static
50 */
51 public static function core_loaded() {
52 do_action('F4/MT/Core/set_constants');
53 do_action('F4/MT/Core/loaded');
54 }
55
56 /**
57 * Load and filter all available attachment taxonomies
58 *
59 * @since 1.0.0
60 * @access public
61 * @static
62 * @return array $attachment_taxonomies An array with all available attachment taxonomies
63 */
64 public static function load_taxonomies() {
65 $attachment_taxonomies_raw = get_taxonomies_for_attachments('objects');
66 $attachment_taxonomies_raw = apply_filters('F4/MT/Core/load_taxonomies', $attachment_taxonomies_raw);
67
68 $attachment_taxonomies = array();
69
70 foreach($attachment_taxonomies_raw as $attachment_taxonomy) {
71 if($attachment_taxonomy->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', true);
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 );
144
145 foreach(Property::$taxonomies as $media_taxonomy) {
146 $media_taxonomy_data['taxonomies'][$media_taxonomy->name] = array(
147 'slug' => $media_taxonomy->name,
148 'terms' => [],
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 '<script>
168 var f4MediaTaxonomy = ' . json_encode($media_taxonomy_data) . '
169 </script>';
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.13.5/js/standalone/selectize.js', array(), '0.13.5');
207 wp_enqueue_style('selectize', 'https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.13.5/css/selectize.min.css', array(), '0.13.5');
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 '<div class="notice notice-success is-dismissible"><p>' . __('Attachment(s) updated.', 'f4-media-taxonomies') . '</p></div>';
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 <input
354 type="text"
355 id="attachments-' . $post->ID .'-' . $media_taxonomy->name . '"
356 name="attachments[' . $post->ID .'][' . $media_taxonomy->name . ']"
357 data-selected-items= "' . esc_attr(json_encode($terms_options)) . '"
358 value="' . implode(',', array_keys($terms_options)) . '"
359 />
360
361 <script>
362 if(typeof f4MediaTaxonomySelectize !== "undefined" && typeof f4MediaTaxonomy !== "undefined") {
363 f4MediaTaxonomySelectize(\'#attachments-' . $post->ID .'-' . $media_taxonomy->name . '\', f4MediaTaxonomy.taxonomies[\'' . $media_taxonomy->name .'\']);
364 }
365 </script>
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 $new_term = wp_insert_term($_REQUEST['term_label'], $_REQUEST['taxonomy']);
406
407 if(is_wp_error($new_term)) {
408 die();
409 }
410
411 $new_term_obj = null;
412
413 if(isset($new_term['term_id'])) {
414 $new_term_obj = get_term($new_term['term_id']);
415 }
416
417 if(!is_wp_error($new_term_obj)) {
418 wp_send_json(array(
419 'new_term' => $new_term_obj
420 ));
421 }
422
423 die();
424 }
425
426 /**
427 * Search terms
428 *
429 * @since 1.1.0
430 * @access public
431 * @static
432 */
433 public static function ajax_search_terms() {
434 $terms_raw = Helpers::get_terms_hierarchical(array(
435 'taxonomy' => $_REQUEST['taxonomy'],
436 'hide_empty' => false
437 ));
438
439 $terms = [];
440
441 foreach($terms_raw as $term) {
442 if(strripos($term->name, trim($_REQUEST['query'])) !== false) {
443 $terms[] = [
444 'value' => $term->slug,
445 'text' => $term->name,
446 'parents' => $term->parents
447 ];
448 }
449 }
450
451 wp_send_json_success($terms);
452 }
453 }
1 <?php
2
3 namespace F4\MT\Core;
4
5 /**
6 * Core properties
7 *
8 * All the properties for the Core module
9 *
10 * @since 1.0.0
11 * @package F4\MT\Core
12 */
13 class Property {
14 public static $taxonomies = array();
15 public static $has_bulk_action = false;
16 public static $has_filter = false;
17 public static $has_assignment = false;
18 }
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.1
7 Stable tag: 1.1.3
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.3 =
116 * Support WordPress 6.1
117
118 = 1.1.2 =
119 * Update www.f4dev.ch links
120
121 = 1.1.1 =
122 * Fix bulk action and taxonomy filter dropdowns
123 * Improve the grid view performance
124
125 = 1.1.0 =
126 * Terms are now lazy loaded with ajax in assignment select
127 * Term assignment styles and scripts optimized
128 * Term assignment sorting fixed
129 * Update selectize to verison 0.13.5
130
131 = 1.0.17 =
132 * Support WordPress 6.0
133
134 = 1.0.16 =
135 * Correctly update post term count (thanks to @nonverbla for the hint)
136 * Support WordPress 5.9
137
138 = 1.0.15 =
139 * Support WordPress 5.8
140
141 = 1.0.14 =
142 * Support WordPress 5.7
143
144 = 1.0.13 =
145 * Fix taxonomy select for new jQuery version
146 * Support WordPress 5.6
147
148 = 1.0.12 =
149 * Fix behaviour after taxonomy selection
150
151 = 1.0.11 =
152 * Support WordPress 5.5
153
154 = 1.0.10 =
155 * Support WordPress 5.4
156
157 = 1.0.9 =
158 * Fix bottom bulk action button in media list
159
160 = 1.0.8 =
161 * Add CMB2 plugin support
162
163 = 1.0.7 =
164 * WordPress 5.3 compatibility fixes
165 * Optimized dropdown width in media modal
166
167 = 1.0.6 =
168 * Update deprecated get_terms function
169
170 = 1.0.5 =
171 * Few PHP and JS code optimisations
172
173 = 1.0.4 =
174 * Fix customizer error
175 * Fix missing dropdowns in media overlay
176
177 = 1.0.3 =
178 * Fix filter error
179
180 = 1.0.2 =
181 * Show only taxonomies with show_ui true
182
183 = 1.0.1 =
184 * Version upgrade for correct repository infos
185
186 = 1.0.0 =
187 * Initial stable release