# Copyright 2002-2008 Josh Clark and Global Moxie, LLC. This code cannot be 
# redistributed without permission from globalmoxie.com.  For more
# information, consult your Big Medium license.
#
# $Id: Format.pm 3240 2008-08-23 10:46:47Z josh $

package BigMed::Format;
use strict;
use warnings;
use utf8;
use Carp;
$Carp::Verbose = 1;
use BigMed::Prefs;
use BigMed::DiskUtil qw(bm_file_path);
use BigMed::Filter;
use BigMed::Trigger;


my %formats       = ();
my %Suffix        = ();
my %DirHandler    = ();
my %Format_Class  = ();
my %Template      = ();
my %LevelFilename = ();
my %IsActive      = ();
my %site_flag     = ();
my %section_flag  = ();
my %page_flag     = ();


###########################################################
# FORMAT REGISTRATION AND ACCESS
###########################################################

sub register_format {
    my $class = shift
      or croak "BigMed::Format subclass required to register output format";
    my $name = shift || $class;
    croak 'Usage: $class->register_format($name, %param)' if @_ % 2;
    my %param = @_;
   
    $formats{$class}->{name} = $name;
    $Format_Class{$name} = $class;
    $IsActive{$class} = $param{is_active};
    $Suffix{$class} = $param{suffix}
      or croak 'No suffix specified in register_format';
    croak 'directory_handler not a code reference'
      if $param{directory_handler} && ref $param{directory_handler} ne 'CODE';
    $DirHandler{$class} = $param{directory_handler} || \&_directory_handler;
    $LevelFilename{$class} = $param{level_filename};
    $name;
}

sub name {
    $formats{ $_[0] }
      or croak "No such format $_[0]. Did you forget to register it first?";
    $formats{ $_[0] }->{name};
}

sub level_filename {
    $LevelFilename{ $_[0] };
}

sub suffix {
    $formats{ $_[0] }
      or croak "No such format $_[0]. Did you forget to register it first?";
    $Suffix{ $_[0] };
}

sub is_active {
    my ($self, $context) = @_;
    my $class = ref $self || $self;
    return 0 if !$context->is_active;
    return $IsActive{$class} ? $IsActive{$class}->($context) : 1;
}

sub class_for_name {
    $Format_Class{ $_[1] };
}

sub format_classes {
    sort { lc($a) cmp lc($b) } keys %formats;
}

sub format_names {
    sort { lc($a) cmp lc($b) } map { $_->{name} } values %formats;
}

###########################################################
# BUILDING
###########################################################

sub directory_path {
    my $class = shift;
    $DirHandler{$class}->(@_);
}

sub _directory_handler { #default handler; can be overridden in format reg
    my ($site, $section) = @_;
    return $site->directory_path($section);
}

sub rich_text {
    BigMed::Filter->filter($_[1]);
}

sub inline_rich_text {
    BigMed::Filter->inline_single_graf( BigMed::Filter->filter($_[1]) );
}

sub escape_xml {
    my $text = $_[1];
    return '' if !defined $text;
#had thought that I wanted "smart" escaping but I don't think so...
#   $text =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/&amp;/g;
$text =~ s/&/&amp;/g;
    $text =~ s/</&lt;/g;
    $text =~ s/>/&gt;/g;
    $text =~ s/"/&quot;/g;
    $text =~ s/'/&apos;/g;
    $text;
}

sub strip_html_tags { #simple, but should be good enough for what we need
    my $string = $_[1];
    return '' if !defined $string;
    $string =~ s/<[^>]+>//gs;
    $string =~ s/</&lt;/g;
    $string =~ s/>/&gt;/g;
    $string =~ s/\s+/ /g;
    $string;
}



###########################################################
# TEMPLATES
###########################################################

sub register_template {
    my $class = shift;
    my @templates = @_;
    my $prefix = 'FORMAT_TMPL_' . $class->name . '_';
    foreach my $t (@templates) {
        croak 'Usage: $class->add_template(\%tmpl, \%tmpl2, etc)'
          if ref $t ne 'HASH';
        my %tmpl = %$t;
        $tmpl{name} or croak 'No name param specified in add_template';
        my $label = $tmpl{label} || $prefix . $tmpl{name};
        my @levels;
        if ($tmpl{level}) {
            @levels = ref $tmpl{level} eq 'ARRAY' ? @{ $tmpl{level} }
                    : $tmpl{level}                ? ($tmpl{level})
                    :                               ();
        }
        my @extras;
        if ( $tmpl{level_extras} ) {
            @extras =
              ref $tmpl{level_extras} eq 'ARRAY'
              ? @{ $tmpl{level_extras} }
              : ( $tmpl{level_extras} );
        }

        my %level;
        @level{@levels} = (1) x @levels;
        $Template{$class}->{$tmpl{name}} = {
            custom_sec => $tmpl{custom_sec},
            label => $label,
            description => $tmpl{description},
            level => \%level,
            filename => $tmpl{filename},
            extender => $tmpl{extender},
            suffix => $tmpl{suffix} || $class->suffix,
            option_pref => $tmpl{option_pref},
            level_extras => \@extras,
        };
    }
}

sub templates {
    my $class = shift;
    my $lev = shift || '';
    undef $lev
      if $lev ne 'top'
      && $lev ne 'section'
      && $lev ne 'detail'
      && $lev ne 'extras';
    my %tmpl  = $Template{$class} ? %{ $Template{$class} } : ();
    my @tmpl = map { { name => $_, %{ $tmpl{$_} }, } } sort keys %tmpl;
    return !$lev ? @tmpl
      : $lev ne 'extras' ? ( grep { $_->{level}->{$lev} } @tmpl )
      : ( grep { $_->{level_extras}->[0] } @tmpl );
}

sub template_info {
    my $class = shift;
    my $type = shift;
    my $rall_tmpl = $Template{$class} || {};
    return $rall_tmpl->{$type} ? %{ $rall_tmpl->{$type} } : ();
}


###########################################################
# FORMAT FLAGS
###########################################################

sub add_site_flag {
    shift @_;
    $site_flag{$_} = 1 for @_;
    scalar(@_);
}

sub add_section_flag {
    shift @_;
    $section_flag{$_} = 1 for @_;
    scalar(@_);
}

sub add_page_flag {
    shift @_;
    $page_flag{$_} = 1 for @_;
    scalar(@_);
}

sub site_flags { sort keys %site_flag }

sub section_flags { sort keys %section_flag }

sub page_flags { sort keys %page_flag }

###########################################################
# WIDGET GROUP METHODS
###########################################################

sub add_group {
    my $class = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";
    @_ % 2
      && croak "Poorly formatted add_group request (odd "
      . "number of parameters)";
    my %param = @_;
    $param{name} && $param{name} =~ /^[0-9A-Za-z]/
      or croak "add_group requires group name with all alphanumeric characters";
    if ( $param{prefs} && ref $param{prefs} ne "HASH" ) {
        croak "Group prefs must be hash reference";
    }

    #register and store the preference names in alpha order
    my $prefs = ref $param{prefs} eq "HASH" ? $param{prefs} : {};
    my @pref_names = register_prefs_and_get_names($prefs);
    $formats{$class}->{groups}->{ $param{name} } = { prefs => \@pref_names };
}

sub group_list {
    my $class = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";
    my $rgroups = $formats{$class}->{groups} || {};
    sort { lc($a) cmp lc($b) } keys %$rgroups;
}

sub group_level_pref_names {
    my $class = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";
    my $group_name = shift
      or croak "group_level_pref_names requires a group name";

    $formats{$class}->{groups}->{$group_name}
      or croak "No such group name '$group_name' in $class format class";

    _sort_prefs(@{ $formats{$class}->{groups}->{$group_name}->{prefs} });
}

sub _sort_prefs {
    my @prefs = @_;
    my @prioritized =
        map { $_->[0] }
        sort { $b->[1] <=> $a->[1] || $a->[0] cmp $b->[0] }
        map { [ $_, BigMed::Prefs->pref_priority($_) ] } @prefs;
    @prioritized;
}


###########################################################
# COLLECTOR GROUP CREATION AND ACCESS
###########################################################

sub add_collector_group {
    my $class = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";
    @_ %2 and croak 'usage: $class->add_collector_group(%param);';
    my %param = @_;
    $param{name} or croak "add_collector_group requires name parameter";
    my %collector;

    #do sort order
    if ($param{sort_order_pref}) {
        $collector{sort_order_pref} = $param{sort_order_pref};
    }
    else {
        $collector{sort_order} =
          _assemble_sort_order($param{sort}, $param{order});
    }

    #add it now to register the overflow widget as a member collector
    $formats{$class}->{collectors}->{$param{name}} = \%collector;
    
    if (ref $param{overflow} eq 'HASH') {
        #essentially an enhanced widget definition
        #see the is_overflow portion of add_widget for the details
        #about the params that are overflow-specific.
        my %overflow = ( 
            %{ $param{overflow} },
            name => $param{name} . '___overflow',
            is_overflow => 1,
            collects_for => $param{name},
            format_class => $class,
        );
        $overflow{filename} or croak 'Overflow filename required';
        $overflow{template} or croak 'Overflow template type required';
        $Template{$class} && exists $Template{$class}->{ $overflow{template} }
            or croak "Unknown template type '$overflow{template}'";
        if( ref $overflow{collector} ne 'CODE' ) {
            croak 'Overflow collector must be a coderef';
        }
        if ( $overflow{nav_builder} && ref $overflow{nav_builder} ne 'CODE' ) {
            croak 'Overflow nav_builder must be a coderef';
        }
        if( $overflow{nav_widget} && !$overflow{nav_builder} ) {
            croak 'nav_widget specified without nav_builder coderef';
        }
        $collector{overflow} = $class->_prep_widget_profile(%overflow);
    }
    elsif ($param{overflow}) {
        croak 'Overflow attribute must be a hash reference';
    }
    
    1;
}

sub collector_group {
    my $class = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";
    my $name = shift or croak 'usage: $class->collector_group($name);';
    my $rinfo = $formats{$class}->{collectors}->{$name}
      or croak "No such collector group '$name' in $class";

    #collect the collectors
    my %collector;
    while ( my ( $k, $v ) = each %{ $formats{$class}->{widgets} } ) {
        $collector{$k} = $v->{priority}
          if $v->{collects_for} && $v->{collects_for} eq $name;
    }
    my @prioritized =
      sort { $collector{$b} <=> $collector{$a} || $a cmp $b} keys %collector;

    return (
        %$rinfo,
        widgets => \@prioritized,
    );
}

sub collector_groups {
    my $class = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";
    return $formats{$class}->{collectors}
      ? sort keys %{ $formats{$class}->{collectors} }
      : ();
}

###########################################################
# WIDGET REGISTRATION AND ACCESS
###########################################################

sub add_widget {
    my $class = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";
    my %param = @_;
    $formats{$class}->{widgets}->{ lc( $param{name} ) } =
      $class->_prep_widget_profile(@_);
}

sub _prep_widget_profile {
    my $class = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";
    @_ % 2
      && croak "Poorly formatted _prep_widget_profile request (odd "
      . "number of parameters)";
    my %param = @_;
    ( $param{name} && $param{name} =~ /^[0-9A-Za-z]/ )
      or croak "add_widget requires widget name (alphanumeric characters only)";

    foreach my $h (qw(handler collector assembler)) {
        croak "$h must be coderef in add_widget for '$param{name}'"
          if $param{$h} && ref $param{$h} ne "CODE";
    }
    if ( $param{prefs} && ref $param{prefs} ne "HASH" ) {
        croak "Widget prefs must be hash reference";
    }

    #build accept hash
    my @accept_only =
      ref $param{accept_only_subtypes} eq "ARRAY" ? @{ $param{accept_only_subtypes} }
      : $param{accept_only_subtypes} ? ( $param{accept_only_subtypes} )
      : ();
    my %accept_only_flags;
    @accept_only_flags{@accept_only} = (1) x @accept_only;

    #build reject hash (ignored if accept_only is populated);
    my %reject_type_flags;
    unless (@accept_only) {
        my @reject_subtypes =
            ref $param{reject_subtypes} eq "ARRAY" ? @{ $param{reject_subtypes} }
          : $param{reject_subtypes} ? ( $param{reject_subtypes} )
          : ();
        @reject_type_flags{@reject_subtypes} = (1) x @reject_subtypes;
    }

    #make sure group exists
    if ( defined $param{group} ) {
        exists( $formats{$class}->{groups}->{ $param{group} } )
          or croak
          "Group '$param{group}' does not exist in format class $class";
    }
    
    #make sure collector group and collector handler exists
    if ( defined $param{collects_for} ) {
        exists( $formats{$class}->{collectors}->{ $param{collects_for} } )
          or croak "No such collector group '$param{collects_for}' in $class";
        $param{collector}
          or croak 'No collector coderef supplied for collects_for widget';
    }
    
    #build sort/order info
    my $sort_order_pref = $param{sort_order_pref};
    my $sort_order = _assemble_sort_order($param{sort}, $param{order});
    
    #build prefs hash
    my $prefs      = ref $param{prefs} eq "HASH" ? $param{prefs} : {};
    my @pref_names = register_prefs_and_get_names($prefs);

    my %overflow;
    if ( $param{is_overflow} ) {
        @overflow{
            qw(filename template page_limit_pref page_limit nav_widget
              nav_builder is_overflow)
          } = @param{
            qw(filename template page_limit_pref page_limit nav_widget
              nav_builder is_overflow)
          };
    }

    return {
        name              => lc $param{name},
        group             => $param{group},
        handler           => $param{handler},
        collector         => $param{collector},
        collects_for      => $param{collects_for},
        assembler         => $param{assembler},
        accepts           => \%accept_only_flags,
        rejects           => \%reject_type_flags,
        build_always      => $param{build_always},
        prefs             => \@pref_names,
        sitewide          => $param{sitewide},
        sectionwide       => $param{sitewide} || $param{sectionwide},
        priority          => $param{priority} || 0,
        sort_order        => $sort_order,
        sort_order_pref   => $sort_order_pref,
        limit             => $param{limit},
        limit_pref        => $param{limit_pref},
        format_class      => $class,
        use_handler       => $param{use_handler},
        %overflow
    };
}

sub _assemble_sort_order {
    my ($sort, $order) = @_;
    my @sort = ref $sort eq 'ARRAY' ? @$sort
             : $sort                ? ($sort)
             :                        ('pub_time');
    my @order = ref $order eq 'ARRAY' ? @$order
             : $order                 ? ($order)
             :                          ('descend');
    foreach my $i (0..@sort-1) {
        my $v = $order[$i] ? substr($order[$i],0,1) : 'd';
        $v = 'd' if $v ne 'd' && $v ne 'a';
        $order[$i] = $v;
    }
    join(':',@sort) . '|' . join(':',@order);
}

sub register_prefs_and_get_names {    #INTERNAL ROUTINE, COULD CHANGE
        #accepts a hash reference as argument. Format:
        #  {
        #    'preference_name' => \%pref_attributes,
        #    'preference_nam2' => \%pref_attributes2,
        #    [...]
        #  }
        #
        # returns the array of preference name

    my $prefs = shift;    #preference hash reference
    my @pref_names;
    foreach my $pref_name ( keys %$prefs ) {
        next unless $pref_name;
        ref $$prefs{$pref_name} eq "HASH"
          or croak
          "Attributes for preference '$pref_name' not a hash reference";
        BigMed::Prefs->register_pref( $pref_name, $$prefs{$pref_name} );
        push( @pref_names, $pref_name );
    }
    @pref_names;
}

sub widget_list {
    my $class = shift;
    my $group = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";

    my @all_widgets =
      $formats{$class}->{widgets}
      ? keys %{ $formats{$class}->{widgets} }
      : ();

    my @prioritized = 
        map { $_->[0] }
        sort { $b->[1] <=> $a->[1] || $a->[0] cmp $b->[0] }
        map { [ $_, ( $formats{$class}->{widgets}->{$_}->{priority} || 0 ) ] }
        @all_widgets;

    if ($group) {    #get the widgets for the specific group
        my @group_widgets;
        $formats{$class}->{groups}->{$group}
          or croak "No such group name '$group' in $class format class";
        foreach my $widget_name (@prioritized) {
            my $wgroup = $formats{$class}->{widgets}->{$widget_name}->{group}
              or next;
            push( @group_widgets, $widget_name ) if $wgroup eq $group;
        }
        return @group_widgets;
    }
    @prioritized;
}

sub widget_exists {
    my $class = shift;
    my $name  = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";
    exists $formats{$class}->{widgets}->{lc $name};
}

sub widget {
    my $class = shift;
    my $name  = shift;
    $formats{$class}
      or croak "No such format $class. Did you forget to register it first?";
    my $rparam = $formats{$class}->{widgets}->{lc $name}
      or return undef;            # no such widget
    BigMed::Widget->new($rparam);
}

sub overflow_widget {
    my $class = shift;
    my $cgroup  = shift
      or croak 'collector group name required for overflow_widget';
    my $rinfo = $formats{$class}->{collectors}->{$cgroup}
      or croak "No such collector group '$cgroup' in $class";
    my $roverflow = $rinfo->{overflow}
      or croak "Collector group '$cgroup' is not an overflow group";
      
    if ( !$roverflow->{priority} ) { #figure out priority for the overflow
        my %ginfo = $class->collector_group($cgroup);
        my $lowest;
        foreach my $wname ( @{ $ginfo{widgets} } ) {
            my $w = $class->widget($wname);
            $lowest = $w->priority if !defined $lowest || $w->priority < $lowest;
        }
        $roverflow->{priority} = defined $lowest ? $lowest - 1 : 0;
    }
    BigMed::Widget->new($roverflow);
}

sub empty_widget { #used e.g. to provide placeholders for navbar widgets
    croak 'usage: $class->empty_widget( $name )' if !$_[1];
    BigMed::Widget->new( { name => $_[1], priority => 0 } );
}

sub stash_pref { #get the context's stash value or load it from the site
    my ( $class, $context, $key ) = @_;
    return
      defined $context->stash($key)
      ? $context->stash($key)
      : $context->set_stash( $key,
        $context->site->get_pref_value( $key, $context->section ) );
}

sub allowed_on_home {
    my ( $class, $context, $obj ) = @_;

    #return true if belongs to at least one section allowed on home
    my $rno_home = $context->stash('html_no_home_sections');
    if ( !$rno_home ) {    #find the disallowed sections
        my $site = $context->site;
        my %no_home;
        foreach my $sid ( $site->all_active_descendants_ids ) {
            next if $no_home{$sid};    #already got it
            my $sec      = $site->section_obj_by_id($sid);
            my %sec_flag = $sec->flags;
            next if !$sec_flag{html_nohome};
            foreach my $id ( $sid, $site->all_active_descendants_ids($sec) ) {
                $no_home{$id} = 1;
            }
        }
        $rno_home = \%no_home;
        $context->set_stash( 'html_no_home_sections', $rno_home );
    }
    my $ractive = $context->active_descendants;
    foreach my $sid ( grep { $ractive->{$_} } $obj->sections ) {
        return $sid if !$rno_home->{$sid};
    }
    return 0;
}

sub allowed_on_section {
    my ( $class, $context, $obj ) = @_;
    my $parent = $context->section;
    my $pid    = $parent->id;

    #return true if belongs to the current section or a subsection that
    #allows display on parent (i.e. does not have a html_noparent flag);
    #section pages get a free pass, unless section inactive
    if ( $obj->subtype eq 'section' ) {
        my $sec = $context->site->section_obj_by_id( $obj->sections );
        return 0 if !$sec || !$sec->active;
        return $sec->id;
    }

    return ( $obj->sections )[0]
      if $obj->subtype && $obj->subtype eq 'section';
    my $rno_parent  = $context->stash('html_no_parent_sections');
    my $rdescendant = $context->active_descendants;
    if ( !$rno_parent ) {    #find the disallowed sections
        my $site = $context->site;
        my %no_parent;
        foreach my $sid ( $site->all_active_descendants_ids($parent) ) {
            next if $no_parent{$sid};    #already got it
            my $sec      = $site->section_obj_by_id($sid);
            my %sec_flag = $sec->flags;
            next if !$sec_flag{html_noparent};
            foreach my $id ( $sid, $site->all_active_descendants_ids($sec) ) {
                $no_parent{$id} = 1;
            }
        }
        $rno_parent = \%no_parent;
        $context->set_stash( 'html_no_parent_sections', $rno_parent );
    }
    foreach my $sid ( $obj->sections ) {
        return $sid
          if $sid == $pid || ( $rdescendant->{$sid} && !$rno_parent->{$sid} );
    }
    return 0;
}


###########################################################
# WIDGET OBJECT METHODS
###########################################################

package BigMed::Widget;
use strict;
use warnings;
use utf8;
use Carp;
$Carp::Verbose = 1;

sub new {
    my $class = $_[0];
    my %param = ref $_[1] eq "HASH" ? %{ $_[1] } : (); #dereference
    bless( \%param, $class );
}

sub is_accepted {
    #if there are any accepts values, means the widget accepts only
    #a fixed list of subtypes. return true if this is one of them.
    #otherwise, return true unless it's one that is explicitly rejected
    return ( scalar %{ $_[0]->{accepts} } ) ? $_[0]->{accepts}->{ $_[1] }
      : !$_[0]->{rejects}->{ $_[1] };
}

sub name { $_[0]->{name} }
sub build_always { $_[0]->{build_always} }
sub sitewide { $_[0]->{sitewide} }
sub sectionwide { $_[0]->{sectionwide} }
sub priority { $_[0]->{priority} }
sub group { $_[0]->{group} }
sub is_collector { defined $_[0]->{collector} }
sub is_overflow { defined $_[0]->{is_overflow} }
sub collects_for { $_[0]->{collects_for} }
sub format_class { $_[0]->{format_class} }
sub use_handler { $_[0]->{use_handler} }
sub accepts { keys %{ $_[0]->{accepts} } };
sub rejects { keys %{ $_[0]->{rejects} } };

sub page_string {
    return $_[0]->{handler} ? $_[0]->{handler}->( $_[1], $_[2], $_[3] ) : "";
}

sub collect {
    return $_[0]->{collector} ? $_[0]->{collector}->( @_ ) : ();
}

sub assemble {
    return $_[0]->{assembler} ? $_[0]->{assembler}->( @_ ) : "";
}

sub add_to_collection {
    push @{ $_[0]->{collection} }, $_[1];
    return 1;
}
sub collection { $_[0]->{collection} || []; }
sub count { scalar @{$_[0]->collection} }


#overflow widget methods
sub _overflow_property {
    croak $_[0]->name, ' is not an overflow widget' if !$_[0]->is_overflow;
    $_[0]->{$_[1]};
}
sub nav_widget { $_[0]->_overflow_property('nav_widget') }
sub filename { $_[0]->_overflow_property('filename') }
sub template { $_[0]->_overflow_property('template') }
sub build_nav {
    my $nav_builder = $_[0]->_overflow_property('nav_builder') or return '';
    $nav_builder->( @_ );
}


sub sort_order {
    my $widget  = shift;
    my $context = shift;
    my ( $pref_name, $sort_order );
    if ( $widget->collects_for ) {
        my %cgroup =
          $widget->format_class()->collector_group( $widget->collects_for );
        ( $pref_name, $sort_order ) = @cgroup{qw(sort_order_pref sort_order)};
    }
    else {
        ( $pref_name, $sort_order ) =
          ( $widget->{sort_order_pref}, $widget->{sort_order} );
    }
    if ($pref_name) {
        croak 'context object required to retrieve sort_order preference'
          if !$context;
        my ( $site, $section ) = ( $context->site, $context->section );
        return $site->get_pref_value( $pref_name, $section );
    }
    return $sort_order;
}

sub mark_full {
    $_[0]->{__not_hungry} = 1;
}

sub is_hungry {
    my $widget = shift;
    my $context = shift;
    croak 'context object required in is_hungry' if !$context;
    return 1 if !$widget->is_collector; #non-collectors are always hungry
    return 0 if $widget->{__not_hungry};
    defined ( my $limit = $widget->limit($context) ) or return 1; #no limit
    return 1 if $widget->count < $limit;
    $widget->mark_full();
    return 0;
}

sub limit {
    return $_[0]->{__limit} if defined $_[0]->{__limit};
    return $_[0]->{__limit} = _lookup_limit_pref($_[0],$_[1], 'limit');
}

sub page_limit {
    croak $_[0]->name, " not an overflow widget" if !$_[0]->is_overflow;
    _lookup_limit_pref($_[0],$_[1], 'page_limit');
}

sub _lookup_limit_pref {
    my ($widget, $context, $limit_name) = @_;
    if ($widget->{"${limit_name}_pref"}) {
        croak 'context object required to check widget limit' if !$context;
        return $context->site->get_pref_value(
            $widget->{"${limit_name}_pref"},
            $context->section
        );
    }
    else {
        return $widget->{$limit_name};
    }

}


#pref_names returns an array of the widget's preference names
#(in order of preference priority, then alpha pref name)
sub pref_names {
    my @prefs = $_[0]->{prefs} ? @{ $_[0]->{prefs} } : ();
    BigMed::Format::_sort_prefs(@prefs);
}

1;

__END__

=head1 NAME

BigMed::Format - base class for registering Big Medium output formats
(e.g., HTML, RSS, JavaScript, etc) and their associated widgets.

=head1 DESCRIPTION

The files and pages generated by Big Medium are created by subclasses
of BigMed::Format. Each of these subclasses typically represents a
distinct content format for viewing Big Medium content: HTML, RSS,
JavaScript, iCalendar, etc.

Every format consists essentially of one or more "widget" definitions.
Widgets are the fundamental building blocks of Big Medium-generated
pages and files. They generate the dynamic content that is inserted
into Big Medium templates.

Adding a new format effectively creates a new channel by which
visitors can experience a site's content. WAP, Atom, OPML, XML, CVS,
and other specifications are examples of formats that could be created
to extend sites' reach beyond the web browser.

The term "pages" in this documentation is used generally to mean
files for the format. For example, in the context of a RSS format,
a "page" would really be a RSS feed file.

=head1 METHODS

=head2 Format Registration and Access Methods

An output format class must be registered before groups or widgets
may be added to it. The class should be a subclass of BigMed::Format
and its C<register_format> method should be called to kick things
off.

    package Foo;
    use BigMed::Format;
    use base qw(BigMed::Format);
    
    Foo->register_format(
        'foobar',
        'suffix'            => 'foo',
        'directory_handler' => \&dir_handler
    );
    my $format_name = Foo->name;


    my @format_names = Foo->format_names();
    my @format_classes = Foo->format_classes();

=over 4

=item * Foo->register_format('display_name', %params);

Registers the Foo class as an output format with display name "display_name."
The return value is the display name. If the class has already been
registered, this will change the display name of the class to the
passed display name.

The method also accepts a hash of parameter values after the display name:

=over 4

=item * suffix => 'txt'

Required. The file suffix to be appended by default to the files generated
for the format (this value may be overridden for individual templates, see
C<add_template>).

=item * directory_handler => \&code_ref

An optional reference to a routine to tell BigMed::Builder the file path
where it should build files for the current site level. The routine receives
the current BigMed::Context object as the only argument and it should return
the directory file path as a string.

If no directory_handler is specified, the default handler will be used.
This routine returns the html directory for the current section (or,
for top level pages, the homepage directory).

=item * level_filename => $base_filename

A default file name to use for level/section pages instad of a section's
slug name if a template does not have filename specified.
BigMed::Format::HTML, for example, uses "index" as its level_filename.

=item * is_active => \&coderef

An optional coderef that returns true or false to indicate whether the
format should be built by BigMed::Builder. The routine receives the current
context object. RSS.pm, for example, uses this to check site preferences to
see if the current section has RSS enabled.

If no coderef is provided, the format will be built by BigMed::Builder.

=back

=item * BigMed::Format->format_classes()

Returns an array of the B<class names> of all of the registered
BigMed::Format subclasses, sorted alphabetically. This static class
method can be invoked via BigMed::Format or any BigMed::Format subclass.

=item * BigMed::Format->format_names()

Returns an array of the B<display names> of all of the registered
BigMed::Format subclasses, sorted alphabetically. This static class
method can be invoked via BigMed::Format or any BigMed::Format subclass.

=item * BigMed::Format->class_for_name($name)

Returns the class of the format named in the argument (or undef if that
format name has not been registered).

=item * Foo->name()

Returns the display name of the Foo format class.

=item * Foo->suffix()

Returns the default file suffix for the Foo format class.

=item * Foo->is_active($context)

Returns true if the format should be built for the current context. The
method checks to make sure that the current section is active and, if so,
calls the format's is_active callback if one was registered.

=item * Foo->directory_path($context)

Used by BigMed::Builder to fetch the directory path for the current build
level. Accepts the BigMed::Context object as the only argument and returns
the file path as a string.

=item * Foo->level_filename()

Returns the format's level_filename value, if any.

=back

=head2 Template Registration and Access Methods

Formats require one or more templates in order to generate their output.
The format, names and number of these templates is completely up to the
individual format. The details are registered and accessed as described
below:

=over 4

=item * C<< $class->register_template(\%template1, \%template2, etc) >>

Registers one or more template types for the class. Each argument
should be a hash reference representing one template type. The hash
references may contain the following key/value pairs.

=over 4

=item * name => 'internal name'

Required. The name used internally to refer to this template type.

=item * label => $lexicon_key

The lexicon key to be used for displaying the localized name of the
template to Big Medium users. If not supplied, the label will be
"FORMAT_TMPL_[format name]_[template name]" where [format name] and
[template name] are replaced, you guessed it, by the format and template
names respectively.

    #register a template for the HTML format
    BigMed::Format::HTML->register_template( { name => 'foo' } );
    
    #the label is: FORMAT_TMPL_HTML_foo

=item * level

    #can be a scalar or an array ref of multiple values
    #values can be top, section or detail
    level => $level
    level => \@level 

The level, if any, for which this template will be used. Big Medium
builds pages at three levels:

=over 4

=item * top

A survey of all content on the site, like a homepage or a RSS feed
of the latest content from all sections

=item * section

A survey of all content within a specific section. The section
front page for example

=item * detail

The individual content files assigned to each section

=back

The level attribute determines at which levels, if any, the template
is run through the builder. Some templates may not be assigned any
level at all if they are supporting templates (like the u_utility
template for the HTML format).

=item * filename => 'index'

An optional base filename to use when saving files generated from this
template. If no filename is supplied, the slug name of either the current
page object (for detail pages) or section object (for section pages) is used.

You can also specify an array reference, which will be turned into a 
directory path from the current section directory.

    filename => ['bm~tags', 'tagname', index']
    # if pagedir is the current section directory, will
    # build the resulting page here:
    # http://www.example.com/pagedir/bm~tags/tagname/index

Note that BigMed::Builder treats the C<~> tilde character as a placeholder
for the 'DOT' Big Medium environmental variable.
If BigMed->bigmed->env('DOT') is the "C<.>" character, for example, the
above example would result in a filename of: C<bm.tags>

=item * extender => 'print'

An optional string to append to the base filename of files generated from
this template. BigMed::Builder adds Big Medium's C<DOT> environmental
variable between the base filename and the extender.
So, if BigMed::bigmed->env('DOT') is "." the extender would result in a
filename formatted like so:

    filename.print

=item * suffix => 'txt'

An optional file suffix to append to the filenames of files generated from
this template. If no suffix is specified, the format's default suffix will
be used instead.

=item * custom_sec => 1

If true, this template may be customized for individual sections.

=item * level_extras => \&callback | [\%callback1, \%callback2 ... ]

An optional coderef or array ref of coderefs. These callbacks are called
by BigMed::Builder for each 'top' and 'section' level. They should
return a hash reference with keys as widget names and values the values
of the widgets to be inserted into the template. Any other widgets 
present in the template page will get the values of the current section
page.

The hash reference may also contain a filename key which, if present,
will be used as the base filename for the file (in lieu of the filename
attribute for the template, above).

If the callback returns a false value instead of a hash reference,
no action will be taken and no file will be saved.

=item * option_pref => 'pref_name'

An optional value indicating the name of the site preference to check at each
level to see if the template should be built. The preference should be
a boolean value. If it returns true, the template will be built, otherwise
it will be skipped for that level.

=item * description => $lexicon_key

The lexicon key to be used, if any, for displaying a localized
description of how this template is used to Big Medium users.
If not supplied, no description will be shown.

=back

If a template is registered with a name that already exists, the new
definition will replace the old one.

=item * C<< $class->templates($level) >>

Returns an array of hash references, each representing a template
type for this format class. If the level argument is specified ("top",
"section", "detail" or "extras" ), then only the templates for that level
will be returned. Otherwise, all templates for the format will
be included in the array.

The templates are sorted by their name attribute. The hash references have
the attributes as described above in c<register_template>:

=over 4

=item * name

=item * label

=item * custom_sec

=item * description

=item * level

Level is always a hash reference where the keys are the template's
build levels and the values are some true value.

=back

=item * C<< $class->template_info($name) >>

Returns a hash of the template information for the template type named
in the argument for this format class. The hash has all of the
key/value pairs (except for name) as those described above in
C<templates>.

=back

=head2 Format Flag Methods

Formats can register site-specific, section-specific and content-specific
flags that can be added to the C<flags> column of individual BigMed::Site,
BigMed::Section or BigMed::Content object. These flags can be used to
indicate special handling by widgets at build time.

In the Big Medium control panel, the flags are offered as checkbox options
on the edit page of the specific section or content element. The flags
are offered in alphabetical order by flag name.

These flags are shared across all format subclasses, so it's good practice to
give them names specific to the format in question. For example: html_nohome,
rss_exclude, etc.

    Foo->add_site_flag('foo_nofoosite', 'foo_specialdisplay');
    my @site_flag_names = Foo->site_flags();
    
    Foo->add_section_flag('foo_nofoosection');
    my @section_flag_names = Foo->site_flags();
    
    Foo->add_page_flag('foo_exclude');
    my @page_flag_names = Foo->page_flags();

=over 4

=item * BigMed::Format->add_site_flag(@flag_names)

Creates one or more section flags that can be added to a BigMed::Site object's
C<flags> column. Returns the number of flag names added. This static class
method can be called via the BigMed::Format class or any subclass.

=item * BigMed::Format->site_flags()

Returns an array of the names of all section flags, in alphabetical order.

=item * BigMed::Format->add_section_flag(@flag_names)

Creates one or more section flags that can be added to a BigMed::Section object's
C<flags> column.  Returns the number of flag names added. This static class
method can be called via the BigMed::Format class or any subclass.

=item * BigMed::Format->section_flags()

Returns an array of the names of all section flags, in alphabetical order.

=item * BigMed::Format->add_page_flag(@flag_names)

Creates one or more page-level flags that can be added to a BigMed::Content
object's C<flags> column. Returns the number of flag names added. This static
class method can be called via the BigMed::Format class or any subclass.

=item * BigMed::Format->page_flags

Returns an array of the names of all page flags, in alphabetical order.

=back

=head2 Widget Group Methods

Widgets may be assigned to groups for organization and display in the Big
Medium control panel. Widget preferences can be defined at the group level
(instead of at the widget-specific level) for preferences that are shared
by several widgets, for example.

Groups are not required to create widgets; they are primarily for display
purposes in the Big Medium control panel, and to specify widget preferences
that apply to a whole category of widgets.

    Foo->add_group(
        name  => "Group name",
        prefs => {
            'group_pref_name' => {
                default   => 'default',
                edit_type => 'simple_text',
                sitewide  => 1,
            },
            'group_pref_name2' => {
                default   => 5,
                edit_type => 'number_integer_positive',
                fallback  => 'another_preference',
            },
        }
    );


    my @group_names = Foo->group_list();
    my @group_level_prefs = Foo->group_level_pref_names('Group name');


=over 4

=item * Foo->add_group( name=>$group_name, prefs => { $pref_name => \%prefs } )

Defines a widget group for the Foo output format class. In the
Big Medium control panel, widget preferences are displayed
by group. Widget preferences can also be defined at this
group-wide level (for preferences that are shared by several
widgets, for example). Group preference attributes are the same as
thouse described in the BigMed::Prefs documentation:

=over 4

=item * default => $value
Required if no fallback widget is specified.

=item * edit_type => 'element_type_name'

=item * options => ['value1','value2','value3']

=item * sitewide => 1

=item * fallback => 'widget_preference_name'
Required if no default value is specified.

=back

=item * Foo->group_list()

Returns an array of the names of the widget groups in the Foo output format
class, in alphabetical order.

=item * Foo->group_level_pref_names($group_name)

Returns an array of the names of the group-level preference names, in
order of the preferences' priority, then alphabetical order. These are
only the preference names that have been defined at the group level,
not at the widget-specific level.

=item * Foo->add_collector_group( \%param )

    Foo->add_collector_group(
        name => 'link_collection',
        overflow => {
            filename => 'index',
            template => 'u_utility',
            widget => 'widget',
            collector => \&link_collector,
            limit_pref => 'overflow_links_per_page',
            nav_widget => 'overflow_links',
            nav_builder => \&link_routine,
        },
    );

Top-level pages and section pages can have "collector" widgets that gather
up multiple content elements to present a list (of links, for example).
These widgets can be grouped so that one widget's list begins only after
another is full. In addition, lists can optionally be continued onto a new
page when the list is full but more content members remain.

In order to handle this, formats may have "collector groups" which have
one or more collector widgets as members. A group can also optionally
define what to do in the case of "overflow," when its member widgets
have met their limits for the page but additional member content remains.

If a collector widget is just a standalone widget that does not interact
with other collectors and does not need to overflow onto another page,
it need not be a member of a collection.

C<add_collector_group> accepts two key/value pairs in its hash argument:

=over 4

=item * name

Required. The unique internal name for the group.

=item * sort_order_pref

The name of a site preference from which to retrieve the sort column
and order. This value overrides the individual settins of any member
widgets.

If not supplied, the sort order can alternatively by supplied
by the sort and order parameters below (for when don't want the sort order
to be accessed via preference but instead be hard-coded).

=item * sort

    sort => $column #scalar name of column
    sort => [$col1, $col2, $col3 etc] #array ref of column names

If no sort_order_pref is specified, this parameter determines what
column(s) to sort on. Default is pub_time. This value overrides the
individual settings of any member widgets.

=item * order

    order => 'ascend'
    order => ['ascend', 'descend', 'ascend' etc]

If no sort_order_pref is specified, this parameter determines
sort direction for columns. The default value is descend. This value
overrides the individual settings of any member widgets.

=item * overflow

An optional hash reference which, if present, indicates that the collector
list should continue onto a new page after the member widgets have
filled their quotas for the current page/file. The hash reference
consists of the following key/value pairs:

=over 4

=item * filename => 'root_filename'

Required. The root filename for the overflow files. For example, in the HTML
format, a filename of 'index' will generate a file named index~p2.shtml
for the first overflow file, index~p3.shtml for the second and so on.

Note that BigMed::Builder treats the C<~> tilde character as a placeholder
for the 'DOT' Big Medium environmental variable.
If BigMed->bigmed->env('DOT') is the "C<.>" character, for example, C<bm~file>
would be converted to bm.sitemap so that the first overflow file would be named
bm.file.p2.shtml in the HTML format.

=item * template => 'template'

Required. The name of the template type to use for the overflow page.

=item * collector => \&coderef

Required. A reference to a routine that handles the collection for overflow
items. See the collector attribute in C<add_widget>.

=item * assembler => \&coderef

Required. A reference to a routine that handles the assembly for overflow
items. See the assembler attribute in C<add_widget>.

=item * page_limit_pref => 'preference_name'

The name of the site preference to use to determine how many items
to put on each overflow page. If no preference is specified, page_limit_num
will be used.

=item * page_limit_num => $number

If no page_limit_pref is specified, this is the number of items to include
on each overflow page. If no page_limit_num or page_limit_pref are specified,
all items will be included on the first overflow page.

=item * nav_widget => 'overflow_nav'

The name of the widget tag to use to place the navigation link on the
primary page and on overflow pages. If none is specified, but the
nav_builder widget is supplied, the navigation will be appended to
the bottom of the last widget in the group on the primary page and
to the bottom of the overflow content on the overflow page.

=item * nav_builder => \%coderef

Coderef for the routine that generates the overflow navigation widget.

=back

=back

=item * Foo->collector_groups

Returns an alpha-ordered list of the format's collector groups.

=item * Foo->collector_group($name)

Returns a hash of metadata about the collector group named in the argument.
The key value pairs are:

=over 4

=item * widgets

An array reference of the names of all widgets in the group, sorted by
order of priority.

=item * overflow

If this is an overflow group, this is a hash reference of the key/value
pairs described above for the overflow parameter of the C<add_collector_group>
method. The overflow value is undefined if not an overflow group.

=back

=back


=head2 Widget Creation and Access Methods

Widgets define the individual routines that should be run on individual
content objects as well as collections of content objects at build time.
These routines generate the strings that get inserted into templates to generate
the completed pages and files for the site. Widgets also define preferences
that affect the behavior of the widget's routines.

    Foo->add_widget(
        name         => 'MYWIDGET',
        group        => 'Group name',
        handler      => \&coderef,
        collector    => \&coderef,
        assembler    => \&assembler,
        collects_for => 'collector group name',
        prefs        => {
            'foo_mywidget_prefname' => {
                default   => 'default value',
                edit_type => 'simple_text',
              } 'foo_mywidget_prefname2' => {
                default   => '<p>default value</p>',
                edit_type => 'rich_text',
              }
        },
        sitewide => 1,
        priority => 50,
    );


    my @all_widget_names = Foo->widget_list();
    my @group_widget_names = Foo->widget_list('Group Name');
    
    my $widget_object = Foo->widget('MYWIDGET');

=over 4

=item * Foo->add_widget(%args)

Adds a widget to the Foo output format class. If another widget of the
same name already exists, the new widget definition will replace the old,
an effective method for plugins to replace/update built-in widgets.

The C<add_widget> method accepts a hash of arguments. Only the C<name>
key/value pair is required, but to be useful the widget should also
have at least one of either the C<handler> or
C<collector> key/value pairs as well (often in
conjunction with C<assembler>).

C<add_widget> accepts these parameters in the argument hash:

=over 4

=item * name => "MYWIDGET"

I<Required.> This is the name of the widget, this is also the name of
the widget tag that should appear in Big Medium templates. In the
example above, the widget tag for templates would be: C<++MYWIDGET++>

The name parameter is not case-sensitive, so "myWidget," "myWIDGET"
and "MYWIDGET" are considered identical entries. Widget names can
consist only of alphanumeric characters.

=item * handler => \&coderef

This is a code reference to the routine that generates the text string
for pages at the "detail" builder level and, if the widget does not
have a collector (see below), for "top" and "section" levels, too.
The string replaces the widget tag in the each pass through a template file.

When called, the detail_handler routine receives the current
BigMed::Context and BigMed::Content objects as arguments and a hash
reference of any attributes included in the widget tag in the template
itself.

=item * collector => \&coderef

Setting this value indicates that the widget should be treated as a
"collector" widget. The value is a code reference to the routine that
generates a value that is added to the widget's collection array
for later processing by the assembler routine.

A "collector" widget presents special output for "top" and "section"
build levels, generating output for each of the content elements
in the section. This is useful for generating output based on lists
of items.

The output of a widget might be a simple string, or more commonly,
a data structure of some kind -- it can be anything that you want
to pass to your assembler routine.

When called, the collector routine receives the widget object, the current
BigMed::Context and the collected BigMed::Content object as arguments.

=item * assembler => \&coderef

This is a code reference to the routine that processes the collection
array gathered by the collector routine for a collection widget.

The routine receives the widget object and the current BigMed::Context object as arguments. It should return a string to be placed into collection pages
('top' and 'section' build levels).

=item * use_handler => 1

If true, collector widgets will use the handler routine to generate the
output for section pages instead of the assembler routine which is the
default. The assembler routine still gets run, but it's the handler
that places the code into the page.

This is useful, for example, with collectors that generate server-side
includes (e.g., latest and quicktease) and where the tag can include links
from other sections.  There, the assemblers build and save the server-
side include tags, but the handler processes the tag to include on the
page itself, including (importantly) tags that point to *other*
sections.

=item * sort_order_pref

For collector widgets only: The name of a site preference from which to
retrieve the sort column and order. (For members of a collector group,
this value is overridden by the collector group's sort order).

If not supplied, the sort order can alternatively by supplied
by the sort and order parameters below (for when don't want the sort order
to be accessed via preference but instead be hard-coded).

=item * sort

    sort => $column #scalar name of column
    sort => [$col1, $col2, $col3 etc] #array ref of column names

For collector widgets only: If no sort_order_pref is specified, this
parameter determines what column(s) to sort on. Default is pub_time.
(For members of a collector group, this value is overridden by the collector
group's sort order).

=item * order

    order => 'ascend'
    order => ['ascend', 'descend', 'ascend' etc]

For collector widgets only: If no sort_order_pref is specified, this
parameter determines sort direction for columns. The default value is descend.
(For members of a collector group, this value is overridden by the collector
group's sort order).

=item * priority => $number

Sets the priority of the widget, which determines the order in which it
is processed among all widgets. The higher the number, the earlier it will
be processed. Particularly useful for members of collector groups, since
priority determines the order in which the widgets will be filled.

=item * accept_only_subtypes => ['article']

For collector widgets: A reference to an array of BigMed::Content::Page
subtype names accepted by the widget. Indicates that Big Medium should not
include the widget string on pages for other page classes, and should not
include other page classes in the widget's collections.

C<accept_only_subtypes> is an optional parameter. If left undefined,
the widget will accept all page subtypes, except any indicated
in the C<reject_subtypes> parameter. C<accept_only_subtypes> takes
precedence over C<reject_subtypes>: If C<accept_only_subtypes> is
defined, C<reject_subtypes> will be ignored entirely.

=item * reject_subtypes => ['document','link']

For collector widgets: a reference to an array of BigMed::Content:Page
subtype names not accepted by the widget, telling Big Medium not to include
objects of that subtype in the widget's collections.

This parameter is ignored if C<accept_only_types> is defined.

=item * build_always => 1

If set to a true value, tells Big Medium to process the widget
even if the widget is not included in the current set of section
templates.

By default, Big Medium does not process widgets when those widgets
are not present in a section's templates. However, some widgets
should be processed even when not included in the current set of
templates. For example, the C<QUICKTEASE> widget generates content
that can be used on other sections' templates, even if it's not
used in the current set of templates.

=item * group => 'group name'

The name of the group to which the widget belongs. This is used
in the Big Medium control panel to group preferences of related
widgets together. The group must first be created via the
C<add_group> method before a widget can join it.

=item * sitewide => 1

If true, indicates that the widget has the same value in every
instance across the entire site. The value is cached for improved
performance.

=item * sectionwide => 1

If true, indidcates that the widget has the same value in every
instance of a given section. The value is cached for the section
for improved performance. (This setting is basically moot if the
sitewide setting is selected).

=item * prefs => { pref1 => \%param1, pref2 => \%param2[, ...] }

A reference to a hash containing information about the preferences
that can be set at the site-wide or section-specific level for
the widget.

The keys in the C<prefs> hash reference are the names of each preference,
and the values are hash references to the preference's attributes.
Widget preference attributes are the same as those described in the
BigMed::Prefs documentation:

=over 4

=item * default => $value
Required if no fallback widget is specified.

=item * edit_type => 'element_type_name'

=item * options => ['value1','value2','value3']

=item * sitewide => 1

=item * fallback => 'widget_preference_name'
Required if no default value is specified.

=back

=back

=item * Foo->widget_list($group_name)

Retrieves the list of widgets for the Foo output format, in order of the
widgets' priority values, then alphabetically by widget name.
Optionally accepts a group-name argument to limit the returned list to widgets
assigned to that group.

=item * Foo->widget_exists($widget_name)

Returns true if the widget named in the argument has been registered.

=item * Foo->widget($widget_name)

Retrieves a widget object from the Foo output format.

=item * Foo->empty_widget($widget_name)

Retrieves a "dud" widget object that will return only empty
strings for the assembler and collector methods. BigMed::Builder uses
this to make sure that any unused overflow navigation widgets get
blanked out in the templates.

=item * Foo->overflow_widget($collector_group_name)

Retrieves a collector widget object to handle overflow links for the
collector group named in the argument. The collector group must have
been added as an overflow group to use this method.

=back

=head2 Utility Methods

=over 4

=item * Foo->rich_text($raw_rich_text_value)

Accepts a raw rich-text-formatted data value and returns the final filtered
HTML value.

=item * Foo->inline_rich_text($raw_rich_text_value)

Works like rich_text but additionally snips any leading/trailing paragraph
tags provided that the text has only one paragraph. If it consists of
multiple paragraphs or has other block elements, it's left as-is.

=item * Foo->escape_xml($text)

Returns the escaped text. Escapes any ampersands, quotes and < > symbols.
Smart enough to recognize ampersands that are already part of other escapes.

=item * Foo->strip_html_tags($html)

Fairly simple, naive method for stripping html tags from simple strings of
html.  Not to be used for any heavy lifting but good for removing
C<< <strong> >> and C<< <em> >> tags from inline strings.

Returns the stripped html.

=item * Foo->allowed_on_home($context, $content_obj)

Checks to see if the content object is assigned to a section that is
allowed to appear on the homepage. The method does not check object-
specific flags (e.g. html_no_home); that should be handled by the
BigMed::Format subclass).

Accepts a BigMed::Context object and a BigMed::Content subclass object
(e.g. BigMed::Content::Page) and returns the id of the first active
section to which the object is assigned and which is allowed to be displayed
on the homepage. If none of the object's sections are allowed to be shown
on the homepage, a false value is returned.

    my $sec_id = Foo->allowed_on_home($context, $page)
      or die 'Not allowed on homepage';

=item * Foo->allowed_on_section($context, $content_obj)

Checks to see if the content object is assigned to a section that is
allowed to appear on the $context->section section. The method does not
check object-specific flags (e.g. html_nomain); that should be handled by the
BigMed::Format subclass).

Accepts a BigMed::Context object and a BigMed::Content subclass object
(e.g. BigMed::Content::Page) and returns the id of the first active
section to which the object is assigned and which is allowed to be displayed
in $context->section. If none of the object's sections are allowed to be
shown on the current section, a false value is returned.

    my $sec_id = Foo->allowed_on_section($context, $page)
      or die 'Not allowed on this section';

Section pages are always allowed, unless the section is inactive.

=back

=head2 Widget Methods

    $name              = $widget->name;
    $format_class      = $widget->format_class;
    %accept_only_types = $widget->accept_only_flags;
    %reject_types      = $widget->reject_type_flags;
    $boolean           = $widget->build_always;
    @section_options   = $widget->section_options;
    @page_options      = $widget->page_options;
    $boolean           = $widget->is_accepted($content_class);
    @pref_names        = $widget->pref_names();
    $group_name        = $widget->group;
    $boolean           = $widget->is_collector;
    $collector_group   = $widget->collects_for;
    $sort_order        = $widget->sort_order;

=over

=item * $widget->name

Returns the name of the widget.

=item * $widget->format_class

Returns the name of the BigMed::Format subclass to which the widget belongs. 

=item * $widget->is_accepted($class_name)

Returns a true value if the widget accepts the BigMed::Content class value in
the class_name argument.

=item * $widget->accepts

Returns an array of subtypes, if any, that the widget is exclusively allowed
to accept. These are the subtypes specified in accept_only_subtypes at
registration.

=item * $widget->rejects

Returns an array of subtypes, if any, that the widget is explicitly told
to reject. These are the subtypes specified in reject_subtypes at
registration.

=item * $widget->build_always()

Returns a true value if the widget's C<build_always> flag is set.

=item * $widget->group()

Returns the name of the widget's group.

=item * $widget->is_collector()

Returns true if the widget is a collector widget.

=item * $widget->is_overflow()

Returns true if the widget is the overflow collector for a collector group.

=item * $widget->collects_for()

Returns the name of the widget's collector group if applicable.

=item * $widget->priority()

Returns the widget's priority value.

=item * $widget->use_handler()

Returns the widget's use_handler value.

=item * $widget->sort_order($context)

Returns a stringified version of the sort columns and sort order. Used
internally in BigMed::Builder as a key to cache the sorted driver selection
so that other widgets with the same sort order can use the selection without
hitting the database again.

A BigMed::Context object is required to retrieve the sort order if the
widget relies on a preference value for its sort_order.

=item * $widget->sitewide()

Returns true if the same value should be assigned to the widget sitewide.

=item * $widget->sectionwide()

Returns true if the same value should be assigned to the widget within the
same section level.

=item * $widget->page_string($context,$content);

Returns the string to insert into the content page template when
supplied with the the current context and content objects. If no
C<content_page_handler> routine is defined for the widget, an
empty string is returned.

=item * $widget->collect($context,$content)

Returns an item to add to the current section/collector's array
collection for this widget, provided that the passed
content object meets the criteria in the widget's
C<collector_handler> routine.

Returns undef if the widget has no C<collector> method defined, or if
the content object does not meet the routine's criteria.

=item * $widget->assemble($context,$content)

Returns the string to insert into a collector page template (top or section
build level) when supplied with the the current context and content objects.
If no C<assembler> routine is defined for the widget, an empty string is
returned.

=item * $widget->add_to_collection($value)

Adds the value to a collector widget's collection array.

=item * $widget->count

Returns the number of items in the widget's collection.

=item * $widget->collection

Returns the widget's collection as an array reference.

=item * $widget->limit

Returns the maximum number of items that the collector widget should collect.

=item * $widget->page_limit

Returns the maximum number of items that an overflow collector widget should
display on each overflow page.

=item * $widget->is_hungry

Returns true if the collector widget is not yet full.

=item * $widget->mark_full

Marks that the collector widget should no longer accept items.

=item * $widget->pref_names()

Returns an array of the widget's preference names, if any, sorted
in order by the preferences' priority, then alphabetical order.

=back

=head1 Callback Triggers

BigMed::Format uses the BigMed::Trigger mixin to manage callback hooks
and routines. You may register these callbacks for BigMed::Format or
any subclass. The callbacks are inheritable by any subclasses of the
class for which you register the method.

=head2 Callback methods

=over 4

=item C<< $class->add_trigger($hook_name, \&coderef) >>

    BigMed::Format::HTML->add_trigger(
        'after_saveprefs_navigation',
        sub {
            my ( $class, $site, $section ) = @_;
            require BigMed::CSS;
            BigMed::CSS->build_sheet($site);
        }
    );

The callback should return a defined value on success. An undefined return
value will be treated as an exception: no further callbacks will be
executed for this hook.

=item C<< $class->call_trigger($hook_name) >>

Runs all callback routines for the specified hook name.

=back

=head2 Callback hooks

BigMed::Format currently supplies one built-in set of hooks, although you
can add and call your own. These hooks are called after a group of
preferences have been saved via the BigMed::App::Web::Prefs.pm preferences
app. The hook names are based on the name of the preference group just saved:

    after_saveprefs_GROUPNAME

The callback receives three arguments: the BigMed::Format class name, the
site object and the section object (if any) for which the preferences
were saved.

=head1 AUTHOR & COPYRIGHTS

This module and all Big Medium modules are copyright Josh Clark
and Global Moxie. All rights reserved.

Use of this module and the Big Medium content
management system are governed by Global Moxie's software licenses
and may not be used outside of the terms and conditions outlined
there.

For more information, visit the Global Moxie website at
L<http://globalmoxie.com/>.

Big Medium and Global Moxie are service marks of Global Moxie
and Josh Clark. All rights reserved.

=cut

