Okay, I’ve had a couple minutes free time, so I wrote up a small plugin. š
The following goes into a new plugin file tf-restrict-categories/tf-restrict-categories.php
:
The introduction
<?php
/**
* Plugin Name: Restrict Categories
* Description: Individually restrict category combinations.
* License: MIT
* License URI: http://opensource.org/licenses/MIT
* Text Domain: tf-restrict-categories
* Domain Path: /languages
*/
if (! class_exists('TFRestrictCategories')) :
/**
* Main (and only) class.
*/
class TFRestrictCategories {
/**
* Plugin instance.
*
* @type object
*/
protected static $instance = null;
/**
* basename() of global $pagenow.
*
* @type string
*/
protected static $page_base;
/**
* Plugin textdomain.
*
* @type string
*/
protected $textdomain = 'tf-restrict-categories';
/**
* Plugin option name.
*
* @type string
*/
protected $option_name="tf_restrict_categories";
/**
* Plugin settings page name.
*
* @type string
*/
protected $settings_page_name="tf-restrict-categories";
/**
* Plugin settings page.
*
* @type string
*/
protected $settings_page;
The basics
/**
* Constructor. Registers activation routine.
*
* @hook wp_loaded
* @return void
*/
public function __construct() {
register_activation_hook(__FILE__, array(__CLASS__, 'activation'));
} // function __construct
/**
* Get plugin instance.
*
* @hook wp_loaded
* @return object TFRestrictCategories
*/
public static function get_instance() {
if (null === self::$instance)
self::$instance = new self;
return self::$instance;
} // function get_instance
/**
* Registers uninstall routine.
*
* @hook activation
* @return void
*/
public static function activation() {
register_uninstall_hook(__FILE__, array(__CLASS__, 'uninstall'));
} // function activation
/**
* Checks if the plugin has to be loaded.
*
* @return boolean
*/
public static function has_to_be_loaded() {
global $pagenow;
if (empty($pagenow))
return false;
self::$page_base = basename($pagenow, '.php');
// Load plugin for all admin pages
return is_admin();
} // function has_to_be_loaded
/**
* Registers plugin actions and filters.
*
* @hook wp_loaded
* @return void
*/
public function init() {
add_action('admin_menu', array($this, 'add_settings_page'));
add_action('wp_insert_post', array($this, 'restrict_categories'));
$pages = array(
'edit',
'post',
'post-new',
);
if (in_array(self::$page_base, $pages))
add_action('admin_print_scripts-posts_page_tf-restrict-categories', array($this, 'enqueue_scripts'));
if ('plugins' === self::$page_base)
add_filter('plugin_action_links_'.plugin_basename(__FILE__), array($this, 'add_settings_link'));
if ('options' === self::$page_base)
add_action('admin_init', array($this, 'register_setting'));
} // function init
/**
* Wrapper for get_option().
*
* @param string $key Option name.
* @param mixed $default Return value for missing key.
* @return mixed|$default Option value.
*/
protected function get_option($key = null, $default = false) {
static $option = null;
if (null === $option) {
$option = get_option($this->option_name, false);
if (false === $option)
$option = array(
);
}
if (null === $key)
return $option;
if (! isset($option[$key]))
return $default;
return $option[$key];
} // function get_option
This is where the action is
/**
* Adds custom settings page to posts settings.
*
* @hook admin_menu
* @return void
*/
public function add_settings_page() {
$this->settings_page = add_posts_page('Restrict Categories', 'Restrict Categories', 'manage_categories', $this->settings_page_name, array($this, 'print_settings_page'));
} // function add_settings_page
/**
* Prints settings page.
*
* @see add_settings_page()
* @return void
*/
public function print_settings_page() {
$this->load_textdomain();
?>
<div class="wrap">
<h2>Restrict Categories</h2>
<div class="tool-box">
<form method="post" action="<?php echo admin_url('options.php'); ?>">
<?php
settings_fields($this->option_name);
$args = array(
'hide_empty' => 0,
);
if (count($categories = get_categories($args))) {
$option = $this->get_option($this->option_name, array());
?>
<table id="tf-restrict-categories" class="widefat">
<thead>
<tr>
<th></th>
<?php
foreach ($categories as $category) {
?>
<th><?php echo $category->name; ?></th>
<?php
}
?>
</tr>
</thead>
<tbody>
<?php
$alternate = true;
foreach ($categories as $category) {
$class = ($alternate) ? ' class="alternate"' : '';
$alternate = ! $alternate;
?>
<tr<?php echo $class; ?>>
<td id=""><?php echo $category->name; ?></td>
<?php
foreach ($categories as $second_category) {
?>
<td id="restrict-category-<?php echo $category->term_id; ?>-<?php echo $second_category->term_id; ?>">
<?php
if ($category->term_id !== $second_category->term_id) {
$checked = (
isset($option[$category->term_id]) && isset($option[$category->term_id][$second_category->term_id])
|| isset($option[$second_category->term_id]) && isset($option[$second_category->term_id][$category->term_id])
);
$checked = ($checked) ? ' checked="checked"' : '';
$disabled = (isset($option[$second_category->term_id]) && isset($option[$second_category->term_id][$category->term_id]));
$disabled = ($disabled) ? ' disabled="disabled"' : '';
?>
<input type="checkbox" id="<?php echo $category->term_id; ?>-<?php echo $second_category->term_id; ?>" name="<?php echo $this->option_name; ?>[<?php echo $category->term_id; ?>][<?php echo $second_category->term_id; ?>]" value="1"<?php echo $checked.$disabled; ?> />
<?php
}
?>
</td>
<?php
}
?>
</tr>
<?php
}
?>
</tbody>
</table>
<div class="submit">
<input type="submit" class="button-primary" value="<?php _e('Save Changes'); ?>" />
</div>
<?php
} else
_e("No categories found.", 'tf-restrict-categories');
?>
</form>
</div>
</div>
<?php
$this->unload_textdomain();
} // function print_settings_page
/**
* Restricts categories according to stored plugin settings.
*
* @hook wp_insert_post
* @param int $id Post ID.
* @return void
*/
public function restrict_categories($id) {
$args = array(
'fields' => 'ids',
'orderby' => 'term_id'
);
if (
count($option = $this->get_option($this->option_name, array()))
&& count($categories = wp_get_object_terms($id, 'category', $args))
) {
foreach ($option as $master_key => $restrict)
foreach($restrict as $restrict_key => $v)
if (in_array($master_key, $categories))
foreach($categories as $key => $category)
if ($category === $restrict_key)
unset($categories[$key]);
wp_set_object_terms($id, $categories, 'category');
}
} // function restrict_categories
/**
* Adds a link to the settings to the plugin list.
*
* @hook plugin_action_links_{$file}
* @param array $links Already existing links.
* @return array
*/
public function add_settings_link($links) {
$settings_link = array(
'<a href="'.admin_url('edit.php?page=".$this->settings_page_name)."">'.__("Settings").'</a>'
);
return array_merge($settings_link, $links);
} // function add_settings_link
/**
* Enqueues necessary script files.
*
* @hook admin_print_scripts-posts_page_tf-restrict-categories
* @return void
*/
public function enqueue_scripts() {
$this->load_textdomain();
wp_enqueue_script('tf-restrict-categories-js', plugin_dir_url(__FILE__).'js/tf-restrict-categories.js', array('jquery'), filemtime(plugin_dir_path(__FILE__).'js/tf-restrict-categories.js'), true);
$this->unload_textdomain();
} // function enqueue_scripts
/**
* Registers setting for custom options page.
*
* @hook admin_init
* @return void
*/
public function register_setting() {
register_setting($this->option_name, $this->option_name, array($this, 'save_setting'));
} // function register_setting
/**
* Prepares option values before they are saved.
*
* @param array $data Original option values.
* @return array Sanitized option values.
*/
public function save_setting($data) {
$sanitized_data = $this->get_option();
if (isset($data) && ! empty($data))
$sanitized_data[$this->option_name] = $data;
else
unset($sanitized_data[$this->option_name]);
return $sanitized_data;
} // function save_setting
The end
/**
* Loads plugin textdomain.
*
* @return boolean
*/
protected function load_textdomain() {
return load_plugin_textdomain($this->textdomain, false, plugin_basename(dirname(__FILE__)).'/languages');
} // function load_textdomain
/**
* Remove translations from memory.
*
* @return void
*/
protected function unload_textdomain() {
unset($GLOBALS['l10n'][$this->textdomain]);
} // function unload_textdomain
/**
* Deletes plugin data on uninstall.
*
* @hook uninstall
* @return void
*/
public static function uninstall() {
delete_option(self::get_instance()->option_name);
} // function uninstall
} // class TFRestrictCategories
if (TFRestrictCategories::has_to_be_loaded())
add_action('wp_loaded', array(TFRestrictCategories::get_instance(), 'init'));
endif; // if (! class_exists('TFRestrictCategories'))
The following goes into a new plugin file tf-restrict-categories/js/tf-restrict-categories.js
:
The JavaScript
jQuery(function($) {
$('#tf-restrict-categories [type="checkbox"]').click(function() {
var $this = $(this);
if (! $this.is(':disabled')) {
var n = this.id.split('-');
$('#tf-restrict-categories [type="checkbox"][name$="\\['+n[1]+'\\]\\['+n[0]+'\\]"]')
.attr('disabled', $this.is(':checked'))
.attr('checked', $this.is(':checked'));
}
});
});
Copy&Paste the code into the two files, upload to your plugins
folder, activate the plugin and find the new settings page.
Happy restricting. š
Okay, okay, some words of explaining…
The plugin works as follows. On the settings page, we define some category combination. Let’s say, we want to have the rule If ‘Cat A’ is present, do NOT allow ‘Cat C’. This can be achieved by checking the checkbox in the row Cat A and column Cat C. This means: the row category is the master category, and the column category will be restricted.
Of course, you can have multiple combinations with Cat A (and multiple other categories).
When saving/updating a post (or, to be more precise: when having saved/updated a post), the categories are checked against the stored plugin settings – and adapted.
Anything else?