# 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: RSS.pm 3233 2008-08-21 12:47:26Z josh $

package BigMed::Format::RSS;
use strict;
use warnings;
use utf8;
use Carp;
use base qw(BigMed::Format);
use BigMed;
use File::MMagic;
use BigMed::DiskUtil qw(bm_file_path bm_untaint_filepath);
use BigMed::MD5 qw(md5_hex);

my $RSS = 'BigMed::Format::RSS';
$RSS->register_format( 'RSS', suffix => 'xml', is_active => \&can_build );
$RSS->add_section_flag('rss_disable_feed');
$RSS->add_page_flag('rss_disable_feed');

sub can_build {
    my $context = shift;
    return 0 if !$context->site->get_pref_value('rss_enable_feed');
    my %secflag = $context->section ? $context->section->flags : ();
    return !$secflag{'rss_disable_feed'};
}

$RSS->add_group(
    name  => 'rss_feeds',
    prefs => {
        'rss_enable_feed' => {
            edit_type   => 'boolean',
            default     => 1,
            sitewide    => 1,
            priority    => 100,
            edit_params => { option_label => 'RSS_enablefeed_checkbox', },
        },
        'rss_enable_podcast' => {
            edit_type   => 'boolean',
            default     => 1,
            sitewide    => 1,
            priority    => 95,
            edit_params => { option_label => 'RSS_enablepodcast_checkbox', },
        },
        'rss_full_text' => {
            edit_type   => 'boolean',
            default     => 0,
            sitewide    => 1,
            priority    => 90,
            edit_params => { option_label => 'RSS_fulltext_checkbox', },
        },
        'rss_include_sections' => {
            edit_type   => 'boolean',
            default     => 1,
            sitewide    => 1,
            priority    => 80,
            edit_params =>
              { option_label => 'RSS_includesections_checkbox', },
        },
        'rss_display_num' => {
            edit_type   => 'number_positive_integer',
            default     => 15,
            sitewide    => 1,
            priority    => 75,
            edit_params => {
                description => 'RSS_DESC_displaynum',
                required    => 1,
            }
        },
        'rss_sort_order' => {
            edit_type   => 'sort_order',
            default     => 'pub_time:priority:mod_time|d:d:d',
            sitewide    => 1,
            priority    => 70,
            edit_params => {
                description => 'SORT_DESC_default sort order',
                numfields   => 3,
                required    => 1,
            },
        },
        'rss_fullfeed' => {
            edit_type   => 'simple_text',
            default     => 'Full Feed',
            sitewide    => 1,
            priority    => 60,
            edit_params => {
                container_class => 'bmcpDividerField',
                required        => 1,
            },
        },
        'rss_podcast' => {
            edit_type   => 'simple_text',
            default     => 'Podcast',
            sitewide    => 1,
            priority    => 50,
            edit_params => { required => 1, }
        },
        'rss_feed_linktext' => {
            edit_type   => 'simple_text',
            default     => 'News feeds',
            sitewide    => 1,
            priority    => 40,
            edit_params => {
                container_class => 'bmcpDividerField',
                description     => 'RSS_DESC_linktext',
                required        => 1,
            },
        },
        'rss_feed_title' => {
            edit_type   => 'rich_text_inline',
            default     => 'RSS News Feeds',
            sitewide    => 1,
            priority    => 32,
            edit_params => { required => 1, },
        },
        'rss_section_title' => {
            edit_type => 'rich_text_inline',
            default   => 'Section-Specific Feeds',
            sitewide  => 1,
            priority  => 20,
        },
        'rss_intro' => {
            edit_type => 'rich_text',
            default   => <<'RSS_INTRO',
RichText:Really Simple Syndication (RSS) is a format for sharing headlines
and other online content, allowing you to browse content from many sites at
once with a news reader. To subscribe to this site's news feeds, you need a
news-reader program on your computer or an account at one of the many online
news readers on the web.
RSS_INTRO
            sitewide    => 1,
            priority    => 10,
            edit_params => { container_class => 'bmcpDividerField', },
        },
    }
);

$RSS->register_template(
    {   name        => 'feed',
        description => 'RSS_TMPL_DESC_feed',
        level       => ['top', 'section'],
        filename    => 'bm~feed',
    },
    {   name        => 'podcast',
        description => 'RSS_TMPL_DESC_podcast',
        level       => 'top',
        filename    => 'bm~podcast',
        option_pref => 'rss_enable_podcast',
    },
);

$RSS->add_widget(
    name            => 'items',
    collector       => \&collect_feed,
    assembler       => \&assemble_feed,
    limit_pref      => 'rss_display_num',
    reject_subtypes => 'podcast',
    sort_order_pref => 'rss_sort_order',
);

$RSS->add_widget(
    name                 => 'podcasts',
    collector            => \&collect_feed,
    assembler            => \&assemble_feed,
    limit_pref           => 'rss_display_num',
    accept_only_subtypes => 'podcast',
    sort                 => ['mod_time', 'pub_time', 'priority', 'title'],
    order                => ['descend', 'descend', 'descend', 'ascend'],
);

$RSS->add_widget(
    name     => 'timestamp',
    handler  => sub { _rfc822_date() },
    sitewide => 1,
);

$RSS->add_widget(
    name     => 'rsscss',
    handler  => sub { $_[0]->site->html_url . '/bm.assets/rss.css' },
    sitewide => 1,
);

$RSS->add_widget(
    name     => 'sitename',
    handler  => sub { $RSS->escape_xml( $_[0]->site->name ); },
    sitewide => 1,
);

$RSS->add_widget(
    name        => 'feedtitle',
    handler     => \&wi_feedtitle,
    sectionwide => 1,
);

$RSS->add_widget(
    name     => 'podcasttitle',
    handler  => sub { $RSS->stash_pref( $_[0], 'rss_podcast' ) },
    sitewide => 1,
);

$RSS->add_widget(
    name        => 'description',
    handler     => \&wi_description,
    sectionwide => 1,
);

$RSS->add_widget(
    name        => 'language',
    handler     => \&wi_language,
    sectionwide => 1,
);

$RSS->add_widget(
    name     => 'bmversion',
    handler  => sub { BigMed->version },
    sitewide => 1,
);

$RSS->add_widget(
    name    => 'sectionurl',
    handler =>
      sub { $_[0]->site->directory_url( $_[0]->section ) . '/index.shtml' },
    sectionwide => 1,
);

$RSS->add_widget(
    name    => 'selfurl',
    handler =>
      sub {
        my $dot = BigMed->bigmed->env('DOT');
        $_[0]->site->directory_url( $_[0]->section ) . "/bm${dot}feed.xml";
      },
    sectionwide => 1,
);

$RSS->add_widget(
    name        => 'feedaccess',
    handler     => \&wi_feedaccess,
    sectionwide => 1,
);

sub collect_feed {
    my ( $widget, $context, $obj ) = @_;
    my %flag = $obj->flags;
    return 0
      if $flag{hideall}
      || $flag{rss_disable_feed}
      || ( $obj->subtype eq 'section'
        && !$RSS->stash_pref( $context, 'rss_include_sections' ) );

    my ( $site, $section ) = ( $context->site, $context->section );

    #should not include html, so no need to double-escape it
    my $title = $RSS->strip_html_tags( $obj->title );

    #need unescaped url for description image; escape below
    my $url = $obj->active_page_url(
        $site,
        {   section => $section,
            rcache  => $context->relation_cache,
            rkids   => $context->active_descendants,
        }
    );
    
    my $dfield =
      $RSS->stash_pref( $context, 'rss_full_text' )
      ? 'content'
      : 'description';
    my $description;
    if ( $dfield eq 'content' ) {
        my @alt_path = @{ $context->widget_template_path };    #deref first
        foreach my $p (@alt_path) {
            $p =~ s/RSS$/HTML/;
        }
        $description =
          BigMed::Format::HTML::_content_builder( $context, $obj,
            { alt_path => \@alt_path } );

        #use BigMed::Filter's routine instead of Format's, which calls
        #the original filter and would needlessly run through sanitizer
        $description = BigMed::Filter->inline_single_graf($description);
    }
    if ( !$description ) {
        my $text = $RSS->inline_rich_text( $obj->description );
        if ( $RSS->stash_pref( $context, 'html_content_addtags' ) ) {

            #Even though we use HTML's tags routine, template is in RSS dir
            $text .= BigMed::Format::HTML::wi_tags( $context, $obj );
        }
        
        my ($spotlight, $image) = _format_description_images($context,$obj);
        my $img_ref = $spotlight || $image || {}; #pref to spotlight
        $description = $context->build_markup(
            'wi_short_description.tmpl',
            description => $text,
            url         => $url,
            %{$img_ref},
        );
    }
    $description = $RSS->escape_xml($description);
    $url = $RSS->escape_xml($url) or return 0;    #no inactive pages

    my $base_hash = $context->stash('pageurl_hash');
    if (!$base_hash) {
        $base_hash = md5_hex( $site->html_url );
        $context->set_stash( 'pageurl_hash' );
    }
    my $guid = $base_hash . q{-} . $obj->id;

    my $rcache    = $context->relation_cache;
    my @relations =
      map { { url => $RSS->escape_xml( $_->{url} ) } }
      $obj->related_links( $site, $rcache );

    my @categories;
    my $dot = BigMed->bigmed->env('DOT');
    my $tag_url = $RSS->escape_xml( $site->homepage_url . "/bm${dot}tags" );
    foreach
      my $tag ( map { $_->[1] } $obj->load_related_objects( 'tag', $rcache ) )
    {
        push @categories, {
            category => $tag->name,               #already escaped
            domain   => $tag_url
        };
    }
    foreach my $sid ( $obj->sections ) {
        my $sec = $site->section_obj_by_id($sid);
        next if !$sec || $sec->is_homepage;
        my $cat = _cat_path( $context, $sec ) or next;
        push @categories, { category => $cat }; #section names already escaped
    }

    my $author = $obj->authors;                 #already escaped

    my %enclosure;
    my $enc_type = $obj->subtype || q{};
    if ( $enc_type eq 'download' || $enc_type eq 'podcast' ) {
        my $enc =
          ( $obj->load_related_objects( $enc_type, $context->relation_cache )
          )[0];
        my ( $path, $filename );
        if ( $enc && ( $filename = $enc->[1]->filename ) ) {
            $path =
              bm_untaint_filepath(
                bm_file_path( $site->doc_path, $filename ) );
            if ( $path && -e $path ) {
                $enclosure{enc_url} =
                  $RSS->escape_xml( $site->doc_url . "/$filename" );
                $enclosure{enc_length} = -s $path;
                $enclosure{enc_mime}   = _mime_type($path);
            }
        }
    }

    return $widget->add_to_collection(
        {   title => $title,
            guid    => $guid,
            pub_time    => _rfc822_date( $obj->pub_time ),
            description => $description,
            url         => $url,
            categories  => \@categories,
            author      => $author,
            relations   => \@relations,
            %enclosure,
        }
    );
}

sub assemble_feed {
    my ( $widget, $context ) = @_;
    return $context->build_markup( 'wi_items.tmpl',
        items => $widget->collection, );
}

sub wi_feedaccess {
    my $context = shift;

    #currently use the htmlhead prefs to determine whether search engines
    #should be allowed to index the feed; seems logical that if you're
    #blocking access to html pages, you'll also want to block feeds, but
    #we'll see... may make sense to add a RSS-specific pref.
    my $html_htmlhead_robot =
      $context->site->get_pref_value('html_htmlhead_robot');
    my $allow_index = $html_htmlhead_robot =~ /noindex/ms ? 'deny' : 'allow';
    return qq{<access:restriction relationship="$allow_index" />};
}

sub wi_feedtitle {
    my $sec   = $_[0]->section;
    my $title =
      ( !$sec || $sec->is_homepage )
      ? $_[0]->site->get_pref_value('rss_fullfeed')
      : $sec->name;
    return $RSS->escape_xml( $RSS->strip_html_tags($title) );
}

sub wi_description {
    my $sec = $_[0]->section || $_[0]->site->homepage_obj
      or return q{};
    my $page = $sec->section_page_obj or return q{};
    my $description = $RSS->inline_rich_text( $page->description )
      || $page->meta_description;
    $description = $RSS->strip_html_tags($description);
    return $RSS->escape_xml($description);
}

sub wi_language {
    my ( $site, $sec ) = ( $_[0]->site, $_[0]->section );
    return $RSS->escape_xml(
        $site->get_pref_value( 'html_htmlhead_lang', $sec ) );
}

sub _rfc822_date {
    my $bigmed_time = shift;
    my $time        =
      $bigmed_time
      ? BigMed->time_obj( bigmed_time => $bigmed_time )
      : BigMed->time_obj();
    return $time->wkday_abbr . ', '
      . sprintf( '%02d', $time->day ) . q{ }
      . $time->month_abbr . q{ }
      . $time->year . q{ }
      . sprintf( '%02d', $time->hour ) . q{:}
      . $time->minute . q{:}
      . $time->second . ' UT';
}

sub _cat_path {
    my ( $context, $sec ) = @_;
    return q{} if !$sec || $sec->is_homepage;
    my $site    = $context->site;
    my @parents = $sec->parents;
    shift @parents;    #homepage
    my @cats;
    foreach my $pid (@parents) {
        my $p = $site->section_obj_by_id($pid) or return q{};
        push @cats, $p->name;
    }
    return join( q{/}, @cats, $sec->name );
}

my %MIME = (
    'abw'      => 'application/x-abiword',
    'ai'       => 'application/postscript',
    'aif'      => 'audio/x-aiff',
    'aifc'     => 'audio/x-aiff',
    'aiff'     => 'audio/x-aiff',
    'asc'      => 'text/plain',
    'asf'      => 'video/x-ms-asf',
    'asr'      => 'video/x-ms-asf',
    'asx'      => 'video/x-ms-asf',
    'au'       => 'audio/basic',
    'avi'      => 'video/x-msvideo',
    'bib'      => 'text/x-bibtex',
    'bin'      => 'application/octet-stream',
    'bmp'      => 'image/bmp',
    'c'        => 'text/x-csrc',
    'cc'       => 'text/x-c++src',
    'cdr'      => 'image/x-coreldraw',
    'cdt'      => 'image/x-coreldrawtemplate',
    'chrt'     => 'application/x-kchart',
    'clp'      => 'application/x-msclip',
    'cls'      => 'text/x-tex',
    'cpio'     => 'application/x-cpio',
    'cpt'      => 'image/x-corelphotopaint',
    'crd'      => 'application/x-mscardfile',
    'css'      => 'text/css',
    'csv'      => 'text/comma-separated-values',
    'cu'       => 'application/cu-seeme',
    'cxx'      => 'text/x-c++src',
    'dat'      => 'chemical/x-mopac-input',
    'dcr'      => 'application/x-director',
    'dif'      => 'video/dv',
    'diff'     => 'text/plain',
    'dir'      => 'application/x-director',
    'djv'      => 'image/vnd.djvu',
    'djvu'     => 'image/vnd.djvu',
    'dl'       => 'video/dl',
    'dmg'      => 'application/x-apple-diskimage',
    'doc'      => 'application/msword',
    'dot'      => 'application/msword',
    'dv'       => 'video/dv',
    'dvi'      => 'application/x-dvi',
    'dxr'      => 'application/x-director',
    'eps'      => 'application/postscript',
    'etx'      => 'text/x-setext',
    'evy'      => 'application/envoy',
    'fli'      => 'video/fli',
    'flr'      => 'x-world/x-vrml',
    'gcf'      => 'application/x-graphing-calculator',
    'gf'       => 'application/x-tex-gf',
    'gif'      => 'image/gif',
    'gl'       => 'video/gl',
    'gnumeric' => 'application/x-gnumeric',
    'gsm'      => 'audio/x-gsm',
    'gtar'     => 'application/x-gtar',
    'gz'       => 'application/x-gzip',
    'hqx'      => 'application/mac-binhex40',
    'hs'       => 'text/x-haskell',
    'htm'      => 'text/html',
    'html'     => 'text/html',
    'ice'      => 'x-conference/x-cooltalk',
    'ico'      => 'image/x-icon',
    'ics'      => 'text/calendar',
    'icz'      => 'text/calendar',
    'ief'      => 'image/ief',
    'iges'     => 'model/iges',
    'igs'      => 'model/iges',
    'iii'      => 'application/x-iphone',
    'iso'      => 'application/x-iso9660-image',
    'jfif'     => 'image/pipeg',
    'jpe'      => 'image/jpeg',
    'jpeg'     => 'image/jpeg',
    'jpg'      => 'image/jpeg',
    'js'       => 'application/x-javascript',
    'kar'      => 'audio/midi',
    'kil'      => 'application/x-killustrator',
    'kpr'      => 'application/x-kpresenter',
    'kpt'      => 'application/x-kpresenter',
    'ksp'      => 'application/x-kspread',
    'kwd'      => 'application/x-kword',
    'kwt'      => 'application/x-kword',
    'latex'    => 'application/x-latex',
    'lsf'      => 'video/x-la-asf',
    'lsx'      => 'video/x-la-asf',
    'ltx'      => 'text/x-tex',
    'm13'      => 'application/x-msmediaview',
    'm14'      => 'application/x-msmediaview',
    'm3u'      => 'audio/mpegurl',
    'm3u'      => 'audio/x-mpegurl',
    'm4a'      => 'audio/mpeg',
    'mdb'      => 'application/msaccess',
    'mid'      => 'audio/midi',
    'midi'     => 'audio/midi',
    'mif'      => 'application/x-mif',
    'mm'       => 'application/x-freemind',
    'mml'      => 'text/mathml',
    'mng'      => 'video/x-mng',
    'mny'      => 'application/x-msmoney',
    'mov'      => 'video/quicktime',
    'movie'    => 'video/x-sgi-movie',
    'mp2'      => 'audio/mpeg',
    'mp3'      => 'audio/mpeg',
    'mp4'      => 'video/mp4',
    'mpa'      => 'video/mpeg',
    'mpe'      => 'video/mpeg',
    'mpeg'     => 'video/mpeg',
    'mpega'    => 'audio/mpeg',
    'mpg'      => 'video/mpeg',
    'mpga'     => 'audio/mpeg',
    'mpp'      => 'application/vnd.ms-project',
    'mpv2'     => 'video/mpeg',
    'mxu'      => 'video/vnd.mpegurl',
    'nb'       => 'application/mathematica',
    'odb'      => 'application/vnd.oasis.opendocument.database',
    'odc'      => 'application/vnd.oasis.opendocument.chart',
    'odf'      => 'application/vnd.oasis.opendocument.formula',
    'odg'      => 'application/vnd.oasis.opendocument.graphics',
    'odi'      => 'application/vnd.oasis.opendocument.image',
    'odm'      => 'application/vnd.oasis.opendocument.text-master',
    'odp'      => 'application/vnd.oasis.opendocument.presentation',
    'ods'      => 'application/vnd.oasis.opendocument.spreadsheet',
    'odt'      => 'application/vnd.oasis.opendocument.text',
    'otg'      => 'application/vnd.oasis.opendocument.graphics-template',
    'oth'      => 'application/vnd.oasis.opendocument.text-web',
    'otp'      => 'application/vnd.oasis.opendocument.presentation-template',
    'ots'      => 'application/vnd.oasis.opendocument.spreadsheet-template',
    'ott'      => 'application/vnd.oasis.opendocument.text-template',
    'pat'      => 'image/x-coreldrawpattern',
    'pbm'      => 'image/x-portable-bitmap',
    'pdf'      => 'application/pdf',
    'pgm'      => 'image/x-portable-graymap',
    'php'      => 'text/php',
    'php3'     => 'text/php3',
    'php3p'    => 'application/x-httpd-php3-preprocessed',
    'php4'     => 'application/x-httpd-php4',
    'phps'     => 'application/x-httpd-php-source',
    'pht'      => 'application/x-httpd-php',
    'phtml'    => 'application/x-httpd-php',
    'pk'       => 'application/x-tex-pk',
    'png'      => 'image/png',
    'pnm'      => 'image/x-portable-anymap',
    'pot,'     => 'application/vnd.ms-powerpoint',
    'ppm'      => 'image/x-portable-pixmap',
    'pps'      => 'application/vnd.ms-powerpoint',
    'ppt'      => 'application/vnd.ms-powerpoint',
    'prf'      => 'application/pics-rules',
    'ps'       => 'application/postscript',
    'psd'      => 'image/x-photoshop',
    'pub'      => 'application/x-mspublisher',
    'qt'       => 'video/quicktime',
    'qtl'      => 'application/x-quicktimeplayer',
    'ra'       => 'audio/x-pn-realaudio',
    'ram'      => 'audio/x-pn-realaudio',
    'rar'      => 'application/rar',
    'rdf'      => 'application/rdf+xml',
    'rm'       => 'audio/x-pn-realaudio',
    'rmi'      => 'audio/mid',
    'rss'      => 'application/rss+xml',
    'rtf'      => 'text/rtf',
    'rtx'      => 'text/richtext',
    'scd'      => 'application/x-msschedule',
    'sct'      => 'text/scriptlet',
    'sd2'      => 'audio/x-sd2',
    'sda'      => 'application/vnd.stardivision.draw',
    'sdc'      => 'application/vnd.stardivision.calc',
    'sdd'      => 'application/vnd.stardivision.impress',
    'sdp'      => 'application/vnd.stardivision.impress',
    'sdw'      => 'application/vnd.stardivision.writer',
    'sgl'      => 'application/vnd.stardivision.writer-global',
    'sgm'      => 'text/sgml',
    'sgml'     => 'text/sgml',
    'sh'       => 'application/x-sh',
    'shar'     => 'application/x-shar',
    'shtml'    => 'text/html',
    'sit'      => 'application/x-stuffit',
    'smf'      => 'application/vnd.stardivision.math',
    'smi'      => 'application/smil',
    'smil'     => 'application/smil',
    'snd'      => 'audio/basic',
    'stc'      => 'application/vnd.sun.xml.calc.template',
    'std'      => 'application/vnd.sun.xml.draw.template',
    'sti'      => 'application/vnd.sun.xml.impress.template',
    'stm'      => 'text/html',
    'stw'      => 'application/vnd.sun.xml.writer.template',
    'sty'      => 'text/x-tex',
    'svg'      => 'image/svg+xml',
    'svgz'     => 'image/svg+xml',
    'swf'      => 'application/x-shockwave-flash',
    'swfl'     => 'application/x-shockwave-flash',
    'sxc'      => 'application/vnd.sun.xml.calc',
    'sxd'      => 'application/vnd.sun.xml.draw',
    'sxg'      => 'application/vnd.sun.xml.writer.global',
    'sxi'      => 'application/vnd.sun.xml.impress',
    'sxm'      => 'application/vnd.sun.xml.math',
    'sxw'      => 'application/vnd.sun.xml.writer',
    'tar'      => 'application/x-tar',
    'taz'      => 'application/x-gtar',
    'tex'      => 'text/x-tex',
    'texi'     => 'application/x-texinfo',
    'texinfo'  => 'application/x-texinfo',
    'text'     => 'text/plain',
    'tgz'      => 'application/x-compressed',
    'tgz'      => 'application/x-gtar',
    'tif'      => 'image/tiff',
    'tiff'     => 'image/tiff',
    'tm'       => 'text/texmacs',
    'torrent'  => 'application/x-bittorrent',
    'ts'       => 'text/texmacs',
    'tsv'      => 'text/tab-separated-values',
    'txt'      => 'text/plain',
    'ustar'    => 'application/x-ustar',
    'vcf'      => 'text/x-vcard',
    'vcs'      => 'text/x-vcalendar',
    'vor'      => 'application/vnd.stardivision.writer',
    'vrm'      => 'x-world/x-vrml',
    'vrml'     => 'x-world/x-vrml',
    'wav'      => 'audio/x-wav',
    'wbmp'     => 'image/vnd.wap.wbmp',
    'wbxml'    => 'application/vnd.wap.wbxml',
    'wcm'      => 'application/vnd.ms-works',
    'wdb'      => 'application/vnd.ms-works',
    'wk'       => 'application/x-123',
    'wks'      => 'application/vnd.ms-works',
    'wm'       => 'video/x-ms-wm',
    'wma'      => 'audio/x-ms-wma',
    'wmd'      => 'application/x-ms-wmd',
    'wmf'      => 'application/x-msmetafile',
    'wmv'      => 'video/x-ms-wmv',
    'wmx'      => 'video/x-ms-wmx',
    'wmz'      => 'application/x-ms-wmz',
    'wp5'      => 'application/wordperfect5.1',
    'wpd'      => 'application/wordperfect',
    'wps'      => 'application/vnd.ms-works',
    'wri'      => 'application/x-mswrite',
    'wrl'      => 'x-world/x-vrml',
    'wrz'      => 'x-world/x-vrml',
    'wvx'      => 'video/x-ms-wvx',
    'xaf'      => 'x-world/x-vrml',
    'xbm'      => 'image/x-xbitmap',
    'xht'      => 'application/xhtml+xml',
    'xhtml'    => 'application/xhtml+xml',
    'xla'      => 'application/vnd.ms-excel',
    'xlb'      => 'application/vnd.ms-excel',
    'xlc'      => 'application/vnd.ms-excel',
    'xlm'      => 'application/vnd.ms-excel',
    'xls'      => 'application/vnd.ms-excel',
    'xlt'      => 'application/vnd.ms-excel',
    'xlw'      => 'application/vnd.ms-excel',
    'xml'      => 'text/xml',
    'xof'      => 'x-world/x-vrml',
    'xpm'      => 'image/x-xpixmap',
    'xsl'      => 'text/xml',
    'xul'      => 'application/vnd.mozilla.xul+xml',
    'zip'      => 'application/zip',
);

sub _mime_type {
    my $full_path = shift;
    my ( $mime, $suffix );
    ( $suffix = $full_path ) =~ s/.*[.]([a-z0-9A-Z]+)$/$1/ms;
    if ( !$suffix || !( $mime = $MIME{ lc $suffix } ) ) {
        my $magic = File::MMagic->new();
        $mime = $magic->checktype_filename($full_path);
    }
    return ( $mime || q{} );
}

sub _format_description_images {
    my ($context, $obj) = @_;
    my $rcache = $context->relation_cache;
    my ($spotlight, $image);
    foreach my $pair ( $obj->sorted_related_objects( 'media', $rcache ) ) {
        my %meta = $pair->[0]->metadata;
        my $pos  = $meta{link_position} or next;
        next if $pos ne 'spot' && $pos ne 'links' && $pos ne 'all';
        next if $pos eq 'links' && $image; #already got a link image

        my $img_obj = $pair->[1];
        next if !$img_obj->isa('BigMed::Media::Image');
        my $pref_name = $pos eq 'spot'
          || $pos eq 'all'
          ? 'html_spotlight_imagesize'
          : 'html_link_imagesize';
        my $size = $RSS->stash_pref( $context, $pref_name );
        my %format = $img_obj->formats;
        my $file = $format{$size} or next;

        my $alt_text    = $RSS->strip_html_tags( $img_obj->title );
        my $src =
          index( $file, 'url:' ) == 0 ? substr( $file, 4 ) : $context->site->image_url . "/$file";
        if ($pos eq 'links') {
            $image = { src => $src, alt => $alt_text };
        }
        else {
            $spotlight = { src => $src, alt => $alt_text };
            last; #preference to spotlight
        }
    }
    return ($spotlight, $image);
}


1;

__END__

=head1 BigMed::Format::RSS

=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

