File Coverage

blib/lib/meon/Web/TimelineEntry.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package meon::Web::TimelineEntry;
2              
3 3     3   7099749 use meon::Web::Util;
  0            
  0            
4             use meon::Web::env;
5             use meon::Web::SPc;
6             use DateTime::Format::Strptime;
7             use File::Copy 'copy';
8             use Path::Class qw();
9              
10             use Moose;
11             use MooseX::StrictConstructor;
12             use MooseX::Types::Path::Class;
13             use 5.010;
14             use utf8;
15              
16             has 'xc' => (is=>'ro', isa=>'XML::LibXML::XPathContext',lazy_build=>1,);
17             has 'file' => (is=>'rw', isa=>'Path::Class::File',coerce=>1,lazy_build=>1,predicate=>'has_file');
18             has 'timeline_dir' => (is=>'rw', isa=>'Path::Class::Dir',coerce=>1,lazy_build=>1);
19             has 'xml' => (is=>'rw', isa=>'XML::LibXML::Document', lazy_build => 1);
20             has 'title' => (is=>'ro', isa=>'Str',lazy_build=>1,);
21             has 'created' => (is=>'rw', isa=>'DateTime',lazy_build=>1,);
22             has 'author' => (is=>'ro', isa=>'Maybe[Str]',lazy_build=>1,predicate=>'has_author');
23             has 'intro' => (is=>'ro', isa=>'Maybe[Str]',lazy_build=>1,predicate=>'has_intro');
24             has 'text' => (is=>'ro', isa=>'Maybe[Str]',lazy_build=>1,predicate=>'has_text');
25             has 'comment_to' => (is=>'ro', isa=>'Maybe[Object]',lazy_build=>1,predicate=>'has_parent');
26             has 'category' => (is=>'ro', isa=>'Str',lazy_build=>1,);
27             has 'image' => (is=>'ro', lazy_build=>1,predicate=>'has_image');
28             has 'attachment' => (is=>'ro', lazy_build=>1,predicate=>'has_attachment');
29             has 'link' => (is=>'ro', isa=>'Maybe[Str]',lazy_build=>1,predicate=>'has_link');
30             has 'source_link' => (is=>'ro', isa=>'Maybe[Str]',lazy_build=>1,predicate=>'has_source_link');
31             has 'audio' => (is=>'ro', isa=>'Maybe[Str]',lazy_build=>1,predicate=>'has_audio');
32             has 'video' => (is=>'ro', isa=>'Maybe[Str]',lazy_build=>1,predicate=>'has_video');
33             has 'quote_author' => (is=>'ro', isa=>'Maybe[Str]',lazy_build=>1,predicate=>'has_quote_author');
34              
35             my $strptime_iso8601 = DateTime::Format::Strptime->new(
36             pattern => '%FT%T',
37             time_zone => 'UTC',
38             on_error => 'croak',
39             );
40             my $IDENT = ' 'x4;
41             my $MEON_WEB_NS = "http://web.meon.eu/";
42              
43             sub _build_file {
44             my ($self) = @_;
45              
46             my $year = $self->created->strftime('%Y');
47             my $month = $self->created->strftime('%m');
48             my $filename = meon::Web::Util->filename_cleanup($self->title);
49             while (length($filename) < 5) {
50             $filename .= chr(97+rand(26));
51             }
52             $filename .= ".xml";
53             return $self->timeline_dir->subdir($year)->subdir($month)->file($filename);
54             }
55              
56             sub _build_timeline_dir {
57             my ($self) = @_;
58              
59             return $self->file->dir->parent->parent;
60             }
61              
62             sub _build_xml {
63             my ($self) = @_;
64              
65             return XML::LibXML->load_xml(
66             location => $self->file
67             );
68             }
69              
70             sub _build_xc {
71             my ($self) = @_;
72              
73             my $xml = $self->xml;
74             my $xc = XML::LibXML::XPathContext->new($xml);
75             $xc->registerNs('w', $MEON_WEB_NS);
76             $xc->registerNs('x', 'http://www.w3.org/1999/xhtml');
77             return $xc;
78             }
79              
80             sub _build_title {
81             my ($self) = @_;
82              
83             my $xml = $self->xml;
84             my $xc = $self->xc;
85             my ($title) = $xc->findnodes('/w:page/w:content//w:timeline-entry/w:title');
86             die 'missing title in '.$self->file
87             unless $title;
88              
89             return $title->textContent;
90             }
91              
92             sub _build_created {
93             my ($self) = @_;
94              
95             my $xml = $self->xml;
96             my $xc = $self->xc;
97             my ($created_iso8601) = $xc->findnodes('/w:page/w:content//w:timeline-entry/w:created');
98             die 'missing created in '.$self->file
99             unless $created_iso8601;
100             $created_iso8601 = $created_iso8601->textContent;
101              
102             return $strptime_iso8601->parse_datetime($created_iso8601);
103             }
104              
105             sub _build_author {
106             my ($self) = @_;
107              
108             my $xml = $self->xml;
109             my $xc = $self->xc;
110             my ($author) = $xc->findnodes('/w:page/w:content//w:timeline-entry/w:author');
111             return undef unless $author;
112              
113             return $author->textContent;
114             }
115              
116             sub _build_intro {
117             my ($self) = @_;
118              
119             my $xml = $self->xml;
120             my $xc = $self->xc;
121             my ($intro) = $xc->findnodes('/w:page/w:content//w:timeline-entry/w:intro');
122             return undef unless $intro;
123              
124             return $intro->textContent;
125             }
126              
127             sub _build_text {
128             my ($self) = @_;
129              
130             my $xml = $self->xml;
131             my $xc = $self->xc;
132             my (undef,$text) = $xc->findnodes('/w:page/w:content//w:timeline-entry/w:text');
133             return undef unless $text;
134              
135             return $text->textContent;
136             }
137              
138             sub _build_category {
139             my ($self) = @_;
140              
141             my $category;
142              
143             if ($self->has_file) {
144             my $xml = $self->xml;
145             my $xc = $self->xc;
146             ($category) = $xc->findnodes('/w:page/w:content//w:timeline-entry/@category');
147             }
148             return 'news'
149             unless $category;
150              
151             return $category->textContent;
152             }
153              
154             sub create {
155             my ($self) = @_;
156              
157             my $created = DateTime->now(time_zone=>'UTC');
158             $self->created($created);
159             $created = $created->iso8601;
160              
161             my $title = $self->title;
162              
163             my $xml = XML::LibXML->load_xml(string => qq{<?xml version="1.0" encoding="UTF-8"?>
164             <page
165             xmlns:xhtml="http://www.w3.org/1999/xhtml"
166             xmlns="$MEON_WEB_NS"
167             xmlns:w="$MEON_WEB_NS"
168             >
169              
170             <meta>
171             <title/>
172             <form>
173             <owner-only/>
174             <process>Delete</process>
175             <redirect>../../</redirect>
176             </form>
177             </meta>
178              
179             <content><div xmlns="http://www.w3.org/1999/xhtml">
180              
181             <w:timeline-entry/>
182              
183             <div class="delete-confirmation"><w:form copy-id="form-delete"/></div>
184             </div></content>
185              
186             </page>
187             });
188              
189             $self->xml($xml);
190             my $xc = $self->xc;
191             my ($title_el) = $xc->findnodes('/w:page/w:meta/w:title');
192             $title_el->appendText($title);
193             my ($content_el) = $xc->findnodes('/w:page/w:content/x:div');
194             my ($entry_el) = $xc->findnodes('//w:timeline-entry',$content_el);
195             $entry_el->setAttribute(category => $self->category);
196             $entry_el->appendText("\n");
197             appendTextElement($entry_el,'w:created',$created);
198             if ($self->has_parent) {
199             appendTextElement($entry_el,'w:parent',$self->comment_to->web_uri)
200             ->setAttribute('title' => $self->comment_to->title);
201             }
202              
203             foreach my $el_name (qw(author title intro text image attachment link source_link audio video quote_author)) {
204             my $el_has = 'has_'.$el_name;
205             appendTextElement($entry_el,'w:'.$el_name,$self->$el_name) if $self->$el_has;
206             }
207              
208             appendTextElement($entry_el,'w:timeline',"\n$IDENT")->setAttribute('class' => 'comments');
209             return $self->store;
210             }
211              
212             sub appendTextElement {
213             my ($el,$child_name,$child_text) = @_;
214             $el->appendText($IDENT);
215             my $child = $el->addNewChild($MEON_WEB_NS,$child_name);
216             $child->appendText($child_text);
217             $el->appendText("\n");
218             return $child;
219             }
220              
221             sub store {
222             my $self = shift;
223             my $xml = $self->xml;
224             my $file = $self->file;
225             my $dir = $file->dir;
226             my $timeline_dir = $self->timeline_dir;
227              
228             $dir->mkpath
229             unless -e $dir;
230             unless (-e $dir->file('index.xml')) {
231             $dir->resolve;
232             $timeline_dir->resolve;
233             my $list_index_file = Path::Class::file(
234             meon::Web::SPc->datadir, 'meon-web', 'template', 'xml','timeline-list-index.xml'
235             );
236             my $timeline_index_file = Path::Class::file(
237             meon::Web::SPc->datadir, 'meon-web', 'template', 'xml','timeline-index.xml'
238             );
239             copy($list_index_file, $dir->file('index.xml')) or die 'copy failed: '.$!;
240              
241             while (($dir = $dir->parent) && $timeline_dir->contains($dir) && !-e $dir->file('index.xml')) {
242             copy($timeline_index_file, $dir->file('index.xml')) or die 'copy failed: '.$!;
243             }
244             $dir = $file->dir;
245             }
246              
247             foreach my $upload_name (qw(image attachment)) {
248             my $has = 'has_'.$upload_name;
249             next unless $self->$has;
250             next unless eval { $self->$upload_name->isa('Catalyst::Request::Upload') };
251              
252             my $upload = $self->$upload_name;
253             my $upload_filename = $upload->filename;
254             my $upload_file = $self->non_existing_filename($dir->file($upload_filename));
255             copy($upload->tempname, $upload_file) || die 'failed to copy upload file - '.$!;
256             $upload_filename = $upload_file->basename;
257              
258             my $xc = $self->xc;
259             my ($el) = $xc->findnodes('//w:timeline-entry/w:'.$upload_name.'/text()', $xml);
260             $el->setData($upload_filename);
261             }
262              
263             $file = $self->non_existing_filename($file);
264             $file->spew($xml->toString);
265             if ($self->has_parent) {
266             my $base_dir = meon::Web::env->content_dir;
267             my $path = $file->resolve;
268             $path = '/'.$path->relative($base_dir);
269             $path =~ s/\.xml$//;
270             $self->comment_to->add_comment($path);
271             }
272             }
273              
274             sub non_existing_filename {
275             my ($self,$file) = @_;
276             while (-e $file) {
277             my $ext = ($file =~ m/\.([^.]+)$/ ? $1 : '');
278             if ($file =~ m/^(.+)-(\d{2,})\.$ext/) {
279             $file = $1.'-'.sprintf('%02d', $2+1).'.'.$ext;
280             }
281             else {
282             $file = substr($file,0,-1-length($ext)).'-01.'.$ext;
283             }
284             $file = Path::Class::file($file);
285             }
286             return $file;
287             }
288              
289             sub element {
290             my ($self) = @_;
291              
292             my $xml = $self->xml;
293             my $xc = $self->xc;
294             my ($el) = $xc->findnodes('/w:page/w:content//w:timeline-entry');
295             die 'no timeline entry in '.$self->file
296             unless $el;
297              
298             return $el;
299             }
300              
301             __PACKAGE__->meta->make_immutable;
302              
303             1;