# Copyright 2013-2014 Stefan Goebel. # # This file is part of Newcomen. # # Newcomen is free software: you can redistribute it and/or modify it under the terms of the GNU # General Public License as published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # Newcomen is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along with Newcomen. If not, see # . package Newcomen::Plugin::Blog::Feed::Lists; our $VERSION = 2014101201; use namespace::autoclean; use Data::Rmap (); use Moose '-meta_name' => '_moose_meta'; use MooseX::StrictConstructor; use Newcomen::Collection; use Newcomen::Util::Hash qw( merge ); use Newcomen::Util::String qw( replace ); use Newcomen::Util::UUID; extends 'Newcomen::Plugin::Base'; with 'Newcomen::Role::Attribute::Catalog'; with 'Newcomen::Role::Attribute::Config'; with 'Newcomen::Role::Attribute::URL'; override '_build_hook_order' => sub {{ 'hook_build_collections' => 600 }}; override '_build_default_config' => sub {{ 'blog' => { 'feed' => { 'lists' => {}, }, }, }}; sub hook_build_collections { my $self = shift; my @colls = (); my @keys = grep { $_ ne 'defaults' } keys %{ $self -> _config () -> get (['blog', 'feed', 'lists']) // {} }; for my $coll ($self -> _catalog () -> collections ()) { next unless $coll -> creator () eq 'Blog::Index::Lists'; next unless grep { $coll -> get (['list_id']) eq $_ } @keys; my $id = $coll -> get (['list_id' ]); my $dig = $coll -> get (['list_id_sha1']); my $item = $coll -> get (['list_item' ]); my $collection = Newcomen::Collection -> new ( 'creator' => 'Blog::Feed::Lists', 'id' => "Blog::Feed::Lists/$dig/$item" ); $collection -> push ($_) for $coll -> sources (); my $asc = $self -> _config () -> get (['blog', 'feed', 'lists', $id, 'ascending']) // $self -> _config () -> get (['blog', 'feed', 'lists', 'defaults', 'ascending']) // $self -> _config () -> get (['blog', 'feed', 'defaults', 'ascending']); $collection -> sort ( sub { my $a = $_ [0] -> get (['time', 'modified', 'epoch']) // confess 'No epoch set'; my $b = $_ [1] -> get (['time', 'modified', 'epoch']) // confess 'No epoch set'; return $asc ? ($a <=> $b) : ($b <=> $a); } ); $collection -> meta () -> merge ($coll -> meta ()); $collection -> set (['order_ascending' ], $asc ); $collection -> set (['index_collection'], $coll); push @colls, $collection; } return @colls; } sub hook_build_pages { my $self = shift; my @pages = (); for my $coll ($self -> _catalog () -> collections ()) { next unless $coll -> creator () eq 'Blog::Feed::Lists'; next unless $coll -> count (); my $id = $coll -> get (['list_id' ]); my $item = $coll -> get (['list_item']); my $url = $self -> _config () -> get (['blog', 'feed', 'lists', $id, 'url']); $url = replace ($url, $coll -> meta () -> data ()) if $url; next unless $url; my $entries = $self -> _config () -> get (['blog', 'feed', 'lists', $id, 'entries']) // $self -> _config () -> get (['blog', 'feed', 'lists', 'defaults', 'entries']) // $self -> _config () -> get (['blog', 'feed', 'defaults', 'entries']) // 5; confess 'Invalid entries value' unless $entries =~ /^\d+$/; $entries = $coll -> count () if $entries == 0; my $iterator = $coll -> paginate ($entries); my $page = Newcomen::Page -> new ( 'target' => $url, 'creator' => 'Blog::Feed::Lists', 'content' => [ map { Newcomen::Content -> new ('source' => $_) } $iterator -> () ], 'collection' => $coll, ); $self -> _url () -> set (['Blog::Feed::Lists', $id, $item], $page -> target ()); if (my $index_coll = $coll -> get (['index_collection'])) { $index_coll -> set (['atom_url'], $page -> target ()); } my $info = {}; my @defaults = ( ['blog', 'defaults', 'page_info'], ['blog', 'feed', 'defaults', 'feed_info'], ['blog', 'feed', 'lists', 'defaults', 'feed_info'], ['blog', 'feed', 'lists', $id, 'feed_info'], ); $info = merge ($info, $self -> _config () -> get ($_) // {}) for (@defaults); Data::Rmap::rmap { $_ = replace ($_, $page -> meta () -> data ()) } $info; $page -> set (["atom_$_" ], $info -> {$_}) for keys %$info; unless ($page -> get (['atom_uuid'])) { my $ns = $self -> _config () -> get (['blog', 'feed', 'defaults', 'uuid_ns']); confess 'UUID namespace required' unless $ns; $page -> set (['atom_uuid'], Newcomen::Util::UUID::uuid_v5 ($ns, "Blog::Feed::Lists/$id")); } # Haven't decided yet if empty feeds should be created or not. Right now, if the source # collection is empty, the feed will not be created (see somewhere above). This is the same # behaviour as for the index pages (see U::Plugin::Blog::Index::Lists). The following code # will set an updated value for both non-empty and empty feeds, though. Doesn't hurt to leave # it in... if (not $page -> get (['atom_updated'])) { my $updt; if ($page -> count ()) { my $last = $page -> content ($coll -> get (['order_ascending']) ? -1 : 0); $updt = $last -> get (['time', 'modified', 'iso' ]); $updt .= $last -> get (['time', 'modified', 'offset']); } else { my $time = Newcomen::Util::Time::parse_time (time, '%s'); $updt = $time -> {'iso'} . $time -> {'offset'}; } $page -> set (['atom_updated'], $updt); } push @pages, $page; } return @pages; } sub hook_formatters { my $self = shift; my $content = shift; my $page = shift; my $index = shift; return unless $page -> creator () eq 'Blog::Feed::Lists'; my $id = $page -> get (['list_id']); $content -> formatters () -> add ( $self -> _config () -> get (['blog', 'feed', 'lists', $id, 'formatters']) ); } sub hook_renderer { my $self = shift; my $page = shift; return unless $page -> creator () eq 'Blog::Feed::Lists'; my $id = $page -> get (['list_id']); $page -> set_renderer ( $self -> _config () -> get (['blog', 'feed', 'lists', $id, 'renderer']) ); } sub hook_writer { my $self = shift; my $page = shift; return unless $page -> creator () eq 'Blog::Feed::Lists'; my $id = $page -> get (['list_id']); $page -> set_writer ( $self -> _config () -> get (['blog', 'feed', 'lists', $id, 'writer']) ); } __PACKAGE__ -> _moose_meta () -> make_immutable (); 1; __END__ #################################################################################################### =head1 NAME Newcomen::Plugin::Blog::Feed::Lists - Creates the feeds for the list index pages. =head1 DESCRIPTION This plugin may be used to generate atom feeds for article indexes created by the plugin L. The indexes to be processed can be freely configured, see L. For hierarchical indexes, resulting in more than one index page (that depends on the configuration), one feed will be created for every index page. Please read the documentation of L first! =head2 Collections For every index collection that should be processed a new collection will be created for the feed. The collection's ID will be set to C<< 'Blog::Feed::Lists//' >> (please see the L documentation). The collection's creator will be set to C<'Blog::Feed::Lists'>. The index collection's meta data will be copied to the new collection, and all the sources from the index collection will be added to the feed collection. Sorting of the feed collection is independent from the index collection, see L. The meta data I will be set for the original index collections of every list index. The value will be the page target of the associated feed (the URL of the feed). =head2 Pages One page will be created for every feed collection (see above). The page's creator will be set to C<'Blog::Feed::Lists'>. The target and the number of articles per feed will be set to the configured values, see L. The page's collection attribute will be set to the source collection of the feed page. See L for details on the meta data of the pages. For every feed page an entry in the global URL map (see L) will be created. The key will be set to C<< ['Blog::Feed::Lists', , ] >> (this is similar to the URLs set by the L plugin, please see there for details). =head1 OPTIONS { 'blog' => { 'feed' => { 'lists' => {}, }, }, } These are the default options set by this plugin. They may be overridden by user configuration. By default, no list feed will be created (I is set to an empty hashref). =head2 Example To create a list feed for the categories list, the following configuration may be used (this requires the index ID in I being set to C<'cats'>, as is the case in the example in the L documentation): { 'blog' => { 'feed' => { 'lists' => { 'cats' => { 'url' => '/index.atom', 'ascending' => undef, 'entries' => undef, 'formatters' => undef, 'renderer' => undef, 'writer' => undef, 'feed_info' => { 'title' => 'Category: ', }, }, }, }, }, } For every list feed that should be created, an entry in the I hashref has to be created. The key must be the same key as used in the I hashref (please see L) for the list (in this documentation referred to as I). The value for each key must itself be a hashref, as shown in the example. The example above also lists every possible option recognized by this plugin. The following options may be used: I specifies the page target. Placeholders are supported, every key in the collection meta data may be used, see L below. If the target is empty after all replacements, the page will not be created. I sets the order of the articles in the feed. If set to a true value, articles will be in ascending order, i.e. oldest first. If this is C (the default), the values of the options I and I will be tried, in that order. If none of these are set, it defaults to false. Note that the order is determined by the modification time, not the publication time as is used for the index pages! I specifies the number of articles per feed. If this is C (the default), the values of the options I and I will be tried. If none of these is set, it defaults to C<5>. If I is set to C<0>, all articles for a list will be included in the feed. The I hashref may be used to set arbitrary data that will be included in the page's meta data. The data may contain placeholders (same as the I option). Everything set in this hashref will be included in the meta data, with the original keys being prefixed with C<'atom_'> (so for the example above the page will contain the I key). Before being copied to the meta data, this hashref will be merged with the defaults set in the hashrefs specified by the I, I, I, with more specific data overriding less specific data if necessary. Note that if there is no I key in the I hashref (after merging as described above), an UUID will be created for every feed page, based on the list ID. For this to work, the setting I has to be set, please see L for details. If this options is not set, and there is no UUID in the info hashref, this plugin will I. Also, if there is no I key in the I hashref, the I meta data will be set automatically based on the modification time information of the sources. I, I and I may be used to specify formatter backends for the feed content, and renderer/writer backends for feed pages. The format is the same as described for the options in L, please see there for details. If these are C (the default), no backends will be set, and another plugin may set these. =head1 META DATA =head2 Collections { 'order_ascending' => , 'index_collection' => , 'atom_url' => , } The meta data of the index collection will be copied to the feed collection, please see L for details. Additionally, the I configuration option (determined as described above, see L) will be included, and I will be set to the original index collection (created by the L plugin) the feed collection is created from. The I will be set to the feed URLs (page targets) for the B (created by the L plugin), not the feed collections! =head2 Pages { 'atom_uuid' => , 'atom_updated' => , } All meta data specified in the I hashref (see L) will be set for the pages, keys will be prefixed by C<'atom_'>. Additionally, even if the I hashref does not contain the I and I keys, I and I will be set for every page (if these are included in the I hashref, the configured values will be used). =head1 HOOKS This plugin implements the following hooks (with default priority unless stated otherwise): I (priority C<600>), I, I, I, I. =head1 SEE ALSO L, L, L, L =head1 VERSION This is version C<2014101201>. =head1 AUTHOR Stefan Goebel - newcomen {at} subtype {dot} de =head1 COPYRIGHT AND LICENSE Copyright 2013-2014 Stefan Goebel. This file is part of Newcomen. Newcomen is free software: you can redistribute it and/or modify it under the terms of the L as published by the L, either version 3 of the license, or (at your option) any later version. Newcomen is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the L for more details. You should have received a copy of the L along with Newcomen. If not, see >. =cut #################################################################################################### # :indentSize=3:tabSize=3:noTabs=true:mode=perl:maxLineLen=100: