package WebGUI::AssetAspect::RssFeed;

=head1 LEGAL

 -------------------------------------------------------------------
  WebGUI is Copyright 2001-2009 Plain Black Corporation.
 -------------------------------------------------------------------
  Please read the legal notices (docs/legal.txt) and the license
  (docs/license.txt) that came with this distribution before using
  this software.
 -------------------------------------------------------------------
  http://www.plainblack.com                     info@plainblack.com
 -------------------------------------------------------------------

=cut

use strict;
use Class::C3;
use WebGUI::Exception;
use WebGUI::Storage;
use XML::FeedPP;
use Path::Class::File;

=head1 NAME

Package WebGUI::AssetAspect::RssFeed

=head1 DESCRIPTION

This is an aspect which exposes an asset's items as an RSS or Atom feed.

=head1 SYNOPSIS

 use Class::C3;
 use base qw(WebGUI::AssetAspect::RssFeed WebGUI::Asset);

And then wherever you would call $self->SUPER::someMethodName call $self->next::method instead.

=head1 METHODS

These methods are available from this class:

=cut

#-------------------------------------------------------------------

=head2 definition

Extends the definition to add the RSS fields.

=cut

sub definition {
	my $class = shift;
	my $session = shift;
	my $definition = shift;
	my $i18n = WebGUI::International->new($session,'AssetAspect_RssFeed');
	my %properties;
	tie %properties, 'Tie::IxHash';
	%properties = (
		itemsPerFeed => {
			noFormPost		=> 0,
			fieldType       => "integer",
			defaultValue    => 25,
    		tab             => "rss",
    		label           => $i18n->get('itemsPerFeed'),
    		hoverHelp       => $i18n->get('itemsPerFeed hoverHelp')
			},
		feedCopyright => {
			noFormPost		=> 0,
			fieldType       => "text",
			defaultValue    => "",
    		tab             => "rss",
    		label           => $i18n->get('feedCopyright'),
    		hoverHelp       => $i18n->get('feedCopyright hoverHelp')
			},
		feedTitle => {
			noFormPost		=> 0,
			fieldType       => "text",
			defaultValue    => "",
    		tab             => "rss",
    		label           => $i18n->get('feedTitle'),
    		hoverHelp       => $i18n->get('feedTitle hoverHelp')
			},
		feedDescription => {
			noFormPost		=> 0,
			fieldType       => "textarea",
			defaultValue    => "",
    		tab             => "rss",
    		label           => $i18n->get('feedDescription'),
    		hoverHelp       => $i18n->get('feedDescription hoverHelp')
			},
		feedImage => {
			noFormPost		=> 0,
			fieldType       => "image",
    		tab             => "rss",
    		label           => $i18n->get('feedImage'),
    		hoverHelp       => $i18n->get('feedImage hoverHelp')
			},
		feedImageLink => {
			noFormPost		=> 0,
			fieldType       => "url",
			defaultValue    => "",
    		tab             => "rss",
    		label           => $i18n->get('feedImageLink'),
    		hoverHelp       => $i18n->get('feedImageLink hoverHelp')
			},
		feedImageDescription => {
			noFormPost		=> 0,
			fieldType       => "text",
			defaultValue    => "",
    		tab             => "rss",
    		label           => $i18n->get('feedImageDescription'),
    		hoverHelp       => $i18n->get('feedImageDescription hoverHelp')
			},
        feedHeaderLinks => {
            fieldType       => "checkList",
            allowEmpty      => 1,
            defaultValue    => "rss\natom",
            tab             => "rss",
            options         => do {
                my %headerLinksOptions;
                tie %headerLinksOptions, 'Tie::IxHash';
                %headerLinksOptions = (
                    rss  => $i18n->get('rssLinkOption'),
                    atom => $i18n->get('atomLinkOption'),
                    rdf  => $i18n->get('rdfLinkOption'),
                );
                \%headerLinksOptions;
            },
            label           => $i18n->get('feedHeaderLinks'),
            hoverHelp       => $i18n->get('feedHeaderLinks hoverHelp')
        },
	    );
	push(@{$definition}, {
		autoGenerateForms   => 1,
		tableName           => 'assetAspectRssFeed',
		className           => 'WebGUI::AssetAspect::RssFeed',
		properties          => \%properties
	    });
	return $class->next::method($session, $definition);
}

#-------------------------------------------------------------------

=head2 dispatch ( )

Extent the base method in Asset.pm to handle RSS feeds.

=cut

sub dispatch {
    my ( $self, $fragment ) = @_;
    if ($fragment eq '.rss') {
        return $self->www_viewRss;
    }
    elsif ($fragment eq '.atom') {
        return $self->www_viewAtom;
    }
    elsif ($fragment eq '.rdf') {
        return $self->www_viewRdf;
    }
    return $self->next::method($fragment);
}

#-------------------------------------------------------------------

=head2 _httpBasicLogin ( )

Set header values and content to show the HTTP Basic Auth login box.

=cut

sub _httpBasicLogin {
    my ( $self ) = @_;
    $self->session->request->headers_out->set(
        'WWW-Authenticate' => 'Basic realm="'.$self->session->setting->get('companyName').'"'
    );
    $self->session->http->setStatus(401,'Unauthorized');
    $self->session->http->sendHeader;
    return '';
}

#-------------------------------------------------------------------

=head2 exportAssetCollateral ()

Extended from WebGUI::Asset and exports the www_viewRss() and
www_viewAtom() methods with filenames generated by
getStaticAtomFeedUrl() and getStaticRssFeedUrl().

This method will be called with the following parameters:

=head3 basePath

A L<Path::Class> object representing the base filesystem path for this
particular asset.

=head3 params

A hashref with the quiet, userId, depth, and indexFileName parameters from
L<WebGUI::Asset/exportAsHtml>.

=head3 session

The session doing the full export.  Can be used to report status messages.

=cut

sub exportAssetCollateral {
    # Lots of copy/paste here from AssetExportHtml.pm, since none of the methods there were
    #   directly useful without ginormous refactoring.
    my $self = shift;
    my $basepath = shift;
    my $args = shift;
    my $reportSession = shift;

    my $reporti18n = WebGUI::International->new($self->session, 'Asset');

    my $basename = $basepath->basename;
    my $filedir;
    my $filenameBase;

    # We want our .rss and .atom files to "appear" at the same level as the asset.
    if ($basename eq 'index.html') {
        # Get the 2nd ancestor, since the asset url had no dot in it (and it therefore
        #   had its own directory created for it).
        $filedir = $basepath->parent->parent->absolute->stringify;
        # Get the parent dir's *path* (essentially the name of the dir) relative to
        #   its own parent dir.
        $filenameBase = $basepath->parent->relative( $basepath->parent->parent )->stringify;
    }
    else {
        # Get the 1st ancestor, since the asset is a file recognized by apache, so
        #   we want our files in the same dir.
        $filedir = $basepath->parent->absolute->stringify;
        # just use the basename.
        $filenameBase = $basename;
    }

    if ( $reportSession && !$args->{quiet} ) {
        $reportSession->output->print('<br />');
    }

    foreach my $ext (qw( rss atom rdf )) {
        my $dest = Path::Class::File->new($filedir, $filenameBase . '.' . $ext);

        # tell the user which asset we're exporting.
        if ( $reportSession && !$args->{quiet} ) {
            my $message = sprintf $reporti18n->get('exporting page'), $dest->absolute->stringify;
            $reportSession->output->print(
                '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $message . '<br />');
        }
        my $exportSession = WebGUI::Session->open(
            $self->session->config->getWebguiRoot,
            $self->session->config->getFilename,
            undef,
            undef,
            $self->session->getId,
        );

        # open another session as the user doing the exporting...
        my $selfdupe = WebGUI::Asset->newByDynamicClass( $exportSession, $self->getId );

        # next, get the contents, open the file, and write the contents to the file.
        my $fh = eval { $dest->open('>:utf8') };
        if($@) {
            $exportSession->close;
            WebGUI::Error->throw(error => "can't open " . $dest->absolute->stringify . " for writing: $!");
        }
        $exportSession->asset($selfdupe);
        $exportSession->output->setHandle($fh);
        my $contents;
        if ($ext eq 'rss') {
            $contents = $selfdupe->www_viewRss;
        }
        else {
            $contents = $selfdupe->www_viewAtom;
        } # add more for more extensions.

        # chunked content is already printed, no need to print it again
        unless($contents eq 'chunked') {
            $exportSession->output->print($contents);
        }

        $exportSession->close;

        # tell the user we did this asset collateral correctly
        if ( $reportSession && !$args->{quiet} ) {
            $reportSession->output->print($reporti18n->get('done'));
        }
    }
    return $self->next::method($basepath, $args, $reportSession);
}

#-------------------------------------------------------------------

=head2 getRssFeedItems ()

This method needs to be overridden by any class that is using it.  To ensure
this, it will throw an exception.

It returns an array reference of hash references.  The list below shows
which ones are required, along with some common keys which are optional.
Other keys may be added, as well.

=head3 Hash reference keys

=head4 title

=head4 description

=head4 link

This is a url to the item.

=head4 date

An epoch date, an RFC 1123 date, or a date in ISO format (referred to as MySQL format
inside WebGUI)

=head4 author

This is optional.

=head4 guid

This is optional.  A unique descriptor for this item.

=cut

sub getRssFeedItems {
    WebGUI::Error::OverrideMe->throw();
}

#-------------------------------------------------------------------

=head2 _getFeedUrl ($extension)

Generic method for returning the URL for a type of feed.  If the special scratch variable
isExporting is true, the static url (url based vs asset function based) will be returned
instead.

=head3 $extension

The kind of feed that is requested.  Valid extensions are "rss", "atom" or "rdf".

=cut

sub _getFeedUrl {
    my $self = shift;
    my $extension = shift;
    if ( $self->session->scratch->get('isExporting') ) {
        return $self->_getStaticFeedUrl($extension);
    }
    return $self->getUrl("func=view\u$extension");
}

#-------------------------------------------------------------------

=head2 _getStaticFeedUrl ($extension)

Generic method for returning the static URL for a type of feed.  The returned URL will be complete,
and absolute, containing the gateway URL for this site.

=head3 $extension

The kind of feed that is requested.  Valid extensions are "rss", "atom" or "rdf".

=cut

sub _getStaticFeedUrl {
    my $self = shift;
    my $extension = shift;
    my $url = $self->get("url") . '.' . $extension;
    $url = $self->session->url->gateway($url);
    if ($self->get("encryptPage")) {
        $url = $self->session->url->getSiteURL . $url;
        $url =~ s/^http:/https:/;
    }
    return $url;
}

#-------------------------------------------------------------------

=head2 getAtomFeedUrl ()

Returns the URL for this asset's feed in Atom format.  If the special scratch variable
isExporting is true, the static url (url based vs asset function based) will be returned
instead.


=cut

sub getAtomFeedUrl {
    my $self = shift;
    return $self->_getFeedUrl('atom');
}

#-------------------------------------------------------------------

=head2 getRdfFeedUrl ()

Returns the URL for this asset's feed in RDF format.  If the special scratch variable
isExporting is true, the static url (url based vs asset function based) will be returned
instead.


=cut

sub getRdfFeedUrl {
    my $self = shift;
    return $self->_getFeedUrl('rdf');
}

#-------------------------------------------------------------------

=head2 getRssFeedUrl ()

Returns the URL for this asset's feed in RSS 2.0 format.  If the special scratch variable
isExporting is true, the static url (url based vs asset function based) will be returned
instead.


=cut

sub getRssFeedUrl {
    my $self = shift;
    return $self->_getFeedUrl('rss');
}

#-------------------------------------------------------------------

=head2 getStaticAtomFeedUrl ()

Returns the URL to use when exporting for this asset's feed in Atom format.

=cut

sub getStaticAtomFeedUrl {
    my $self = shift;
    return $self->_getStaticFeedUrl('atom');
}

#-------------------------------------------------------------------

=head2 getStaticRdfFeedUrl ()

Returns the URL to use when exporting for this asset's feed in RDF format.

=cut

sub getStaticRdfFeedUrl {
    my $self = shift;
    return $self->_getStaticFeedUrl('rdf');
}

#-------------------------------------------------------------------

=head2 getStaticRssFeedUrl ()

Returns the URL to use when exporting for this asset's feed in RSS 2.0 format.

=cut

sub getStaticRssFeedUrl {
    my $self = shift;
    return $self->_getStaticFeedUrl('rss');
}

#-------------------------------------------------------------------

=head2 getFeed ( $feed )

Adds the syndicated items to the feed; returns the stringified edition.

Returns this feed so that XML::FeedPP methods can be chained on it.

TODO: convert dates?

=head3 $feed

An XML::FeedPP sub-object, XML::FeedPP::{Atom,Rss,Rdf} that will be filled
with data from the Asset via the getRssFeedItems method.

=cut

sub getFeed {
    my $self = shift;
    my $feed = shift;
    foreach my $item ( @{ $self->getRssFeedItems } ) {
        my $set_permalink_false = 0;
        my $new_item = $feed->add_item( %{ $item } );
        if (!$new_item->guid) {
            if ($new_item->link) {
                $new_item->guid( $new_item->link );
            }
            else {
                $new_item->guid( $self->session->id->generate );
                $set_permalink_false = 1;
            }
        }
        $new_item->guid( $new_item->guid, isPermaLink => 0 ) if $set_permalink_false;
    }
    $feed->title( $self->get('feedTitle') || $self->get('title') );
    $feed->description( $self->get('feedDescription') || $self->get('synopsis') );
    $feed->pubDate( $self->getContentLastModified );
    $feed->copyright( $self->get('feedCopyright') );
    $feed->link( $self->session->url->getSiteURL . $self->getUrl );
    # $feed->language( $lang );
    if ($self->get('feedImage')) {
        my $storage = WebGUI::Storage->get($self->session, $self->get('feedImage'));
        my @files = @{ $storage->getFiles };
        if (scalar @files) {
            $feed->image(
                $storage->getUrl( $files[0] ),
                $self->get('feedImageDescription') || $self->getTitle,
                $self->get('feedImageUrl') || $self->getUrl,
                $self->get('feedImageDescription') || $self->getTitle,
                ( $storage->getSizeInPixels( $files[0] ) ) # expands to width and height
            );
        }
    }
    return $feed;
}

#-------------------------------------------------------------------

=head2 prepareView ()

Extend the master class to insert head links via addHeaderLinks.

=cut

sub prepareView {
    my $self = shift;
    $self->addHeaderLinks;
    return $self->next::method(@_);
}

#-------------------------------------------------------------------

=head2 addHeaderLinks ()

Add RSS, Atom, or RDF links in the HEAD block of the Asset, depending
on how the Asset has configured feedHeaderLinks.

=cut

sub addHeaderLinks {
    my $self = shift;
    my $style = $self->session->style;
    my $title = $self->get('feedTitle') || $self->get("title");
    my %feeds = map { $_ => 1 } split /\n/, $self->get('feedHeaderLinks');
    my $addType = keys %feeds > 1;
    if ($feeds{rss}) {
        $style->setLink($self->getRssFeedUrl, {
            rel   => 'alternate',
            type  => 'application/rss+xml',
            title => $title . ( $addType ? ' (RSS)' : ''),
        });
    }
    if ($feeds{atom}) {
        $style->setLink($self->getAtomFeedUrl, {
            rel   => 'alternate',
            type  => 'application/atom+xml',
            title => $title . ( $addType ? ' (Atom)' : ''),
        });
    }
    if ($feeds{rdf}) {
        $style->setLink($self->getRdfFeedUrl, {
            rel   => 'alternate',
            type  => 'application/rdf+xml',
            title => $title . ( $addType ? ' (RDF)' : ''),
        });
    }
}

#-------------------------------------------------------------------

=head2 www_viewAtom ()

Return Atom view of the syndicated items.

=cut

sub www_viewAtom {
    my $self = shift;
    return $self->_httpBasicLogin unless $self->canView;
    $self->session->http->setMimeType('application/atom+xml');
    return $self->getFeed( XML::FeedPP::Atom->new )->to_string;
}

#-------------------------------------------------------------------

=head2 www_viewRdf ()

Return Rdf view of the syndicated items.

=cut

sub www_viewRdf {
    my $self = shift;
    return $self->_httpBasicLogin unless $self->canView;
    $self->session->http->setMimeType('application/rdf+xml');
    return $self->getFeed( XML::FeedPP::RDF->new )->to_string;
}

#-------------------------------------------------------------------

=head2 www_viewRss ()

Return RSS view of the syndicated items.

=cut

sub www_viewRss {
    my $self = shift;
    return $self->_httpBasicLogin unless $self->canView;
    $self->session->http->setMimeType('application/rss+xml');
    return $self->getFeed( XML::FeedPP::RSS->new )->to_string;
}

#-------------------------------------------------------------------

=head2 getEditTabs ()

Adds an RSS tab to the Edit Tabs.

=cut

sub getEditTabs {
    my $self = shift;
    my $i18n = WebGUI::International->new($self->session,'AssetAspect_RssFeed');
    return ($self->next::method, ['rss', $i18n->get('RSS tab'), 1]);
}

1;

