
# FilterCategories.pl
# Movable Type plugin tags for filtering a category list according to
# specified inclusion or exclusion criteria
# by Kevin Shay
# http://www.staggernation.com/mtplugins/
# last modified July 12, 2004

package MT::Plugin::FilterCategories;
use strict;
use vars qw( $delim $VERSION );
$VERSION = '1.15';

use MT;
use MT::Template::Context;

eval{ require MT::Plugin };
unless ($@) {
    my $plugin = {
        name => "FilterCategories",
        version => $VERSION,
        description => 'Restrict a category listing by specifying the categories to include or exclude.',
        doc_link => 'http://www.staggernation.com/mtplugins/FilterCategories'
    }; 
    MT->add_plugin(new MT::Plugin($plugin));
}

MT::Template::Context->add_container_tag('FilterCategories' => sub{&_hdlr_filter_categories;});
MT::Template::Context->add_container_tag('FilteredEntryCategories' => sub{&_hdlr_filtered_entry_categories;});

$delim = '\|';

sub _hdlr_filter_categories {
# handler for MT container tag to be used within <MTCategories> or <MTArchiveList>;
# prints its contents only if the current category matches the "include" or 
# "exclude" filtering arguments
#	include: list of category labels that should be printed
#	exclude: list of category labels that should not be printed
	my ($ctx, $args, $cond) = @_;
	my $cat = $ctx->stash('category') || return $ctx->error('No category context');
 	$args->{'exclude'} || $args->{'include'} || $args->{'exclude_archive_cat'}
		|| return $ctx->error('No categories specified');
 	if ($args->{'include'}) {
 		$args->{'include'} = build_value($ctx, $args->{'include'});
 		my %include = map { $_ => 1 } split(/$delim/, $args->{'include'});
 		return '' unless ($include{$cat->label});
 	} elsif ($args->{'exclude'}) {
 		$args->{'exclude'} = build_value($ctx, $args->{'exclude'});
  		for (split(/$delim/, $args->{'exclude'})) {
 			return '' if ($cat->label eq $_);
 		}
 	}
 		# can't check _hdlr_archive_category as we can in MTFilteredEntryCategories
 		# since here we're within MTCategories
	if (defined($args->{'exclude_archive_cat'})) {
		if ($ctx->{current_archive_type} eq 'Category') {
			return '' if ($cat->label eq $ctx->stash('archive_category')->label);
		}
	}
 	defined(my $text = $ctx->stash('builder')->build($ctx, $ctx->stash('tokens'), $cond))
 			|| return $ctx->error($ctx->errstr);
 	return $text;
}

sub _hdlr_filtered_entry_categories {
# handler for MT container tag to print the current entry's categories, 
# allowing filtering by category label
# this is a copy of MT::Template::Context::_hdlr_entry_categories with some 
# code added to enable the filtering arguments (can't "filter from within" 
# as with MTCategories, because MTEntryCategories won't know what we've
# filtered out and will insert the "glue" argument between categories that
# aren't displaying.)
# args to tag:
# 	glue: string to join categories, same as in MTEntryCategories
#	include: list of category labels that should be printed
#	exclude: list of category labels that should not be printed
#		(n.b. results will be odd if you pass both include and exclude)
#	exclude_archive_cat: useful only within a category archive template,
#		indicates that we should not print a listing for the category
#		whose archive is being generated
	my ($ctx, $args, $cond) = @_;
	my $e = $ctx->stash('entry')
		or return $ctx->_no_entry_error('MTFilteredEntryCategories');
	my $cats = $e->categories;
	return '' unless $cats && @$cats;
 	$args->{'exclude'} || $args->{'include'} || $args->{'exclude_archive_cat'}
		|| return $ctx->error('No categories specified');
		# BEGIN ADDED CODE
	my %exclude = ();
	my %include = ();
	if ($args->{'include'}) {
		%include = map { $_ => 1 } split(/$delim/, $args->{'include'});
	} elsif ($args->{'exclude'}) {
		%exclude = map { $_ => 1 } split(/$delim/, $args->{'exclude'});
	}
	if ($args->{'exclude_archive_cat'}) {
		if (my $arch = $ctx->_hdlr_archive_category) {
			$exclude{$arch} = 1;
		}
	}
		# END ADDED CODE

	my $builder = $ctx->stash('builder');
	my $tokens = $ctx->stash('tokens');
	my @res;
	for my $cat (@$cats) {

			# BEGIN ADDED CODE
		next if ($exclude{$cat->label});
		next if (%include && !$include{$cat->label});
			# END ADDED CODE
			
		local $ctx->{__stash}->{category} = $cat;
		defined(my $out = $builder->build($ctx, $tokens, $cond))
		or return $ctx->error( $builder->errstr );
		push @res, $out;
	}
	my $sep = $args->{glue} || '';
	join $sep, @res;
}

sub build_value {
# convert and build MT template tags within a passed value.
	my ($ctx, $value) = @_;
		# within a value argument, you can use MT tags, but with
		# square brackets instead of angle brackets and single quotes
		# instead of double quotes; literal square brackets and single
		# quotes must be escaped with a backslash
		# convert non-escaped []'
	$value =~ s/(?<!\\)\[/</g;
	$value =~ s/(?<!\\)\]/>/g;
	$value =~ s/(?<!\\)'/"/g;
		# de-escape escaped []'
	$value =~ s/\\([\[\]'])/$1/g;
		# any MT tags?
	if ($value =~ /<MT/) {
		my $builder = $ctx->stash('builder');
		my $tok = $builder->compile($ctx, $value);
		$value = $builder->build($ctx, $tok);
		return $ctx->error($builder->errstr) unless defined($value);
	}
	return $value;
}

1;
