File Coverage

blib/lib/Facebook/InstantArticle.pm
Criterion Covered Total %
statement 117 151 77.4
branch 30 74 40.5
condition 1 3 33.3
subroutine 27 31 87.1
pod 16 16 100.0
total 191 275 69.4


line stmt bran cond sub pod time code
1             package Facebook::InstantArticle;
2 2     2   775552 use Moose;
  2         899090  
  2         17  
3 2     2   16672 use namespace::autoclean;
  2         6869  
  2         20  
4              
5 2     2   1899 use Mojo::Log;
  2         68733  
  2         28  
6 2     2   2140 use XML::Generator;
  2         20352  
  2         14  
7              
8 2     2   1512 use Facebook::InstantArticle::Analytics;
  2         7  
  2         126  
9 2     2   1559 use Facebook::InstantArticle::Author;
  2         8  
  2         137  
10 2     2   1677 use Facebook::InstantArticle::Blockquote;
  2         6  
  2         91  
11 2     2   1097 use Facebook::InstantArticle::Copyright;
  2         5  
  2         115  
12 2     2   1252 use Facebook::InstantArticle::Embed;
  2         8  
  2         139  
13 2     2   1636 use Facebook::InstantArticle::Figure::Image;
  2         9  
  2         122  
14 2     2   1711 use Facebook::InstantArticle::Figure::Video;
  2         6  
  2         106  
15 2     2   1399 use Facebook::InstantArticle::Heading;
  2         5  
  2         94  
16 2     2   1317 use Facebook::InstantArticle::List;
  2         8  
  2         107  
17 2     2   1719 use Facebook::InstantArticle::Map;
  2         8  
  2         99  
18 2     2   1624 use Facebook::InstantArticle::Paragraph;
  2         7  
  2         4289  
19              
20             our $VERSION = '0.12';
21              
22             =encoding utf-8
23              
24             =head1 NAME
25              
26             Facebook::InstantArticle - Helper class for generating Facebook Instant Articles
27             markup.
28              
29             =head1 DESCRIPTION
30              
31             Facebook::InstantArticle is a simple helper class for generating L<Facebook
32             Instant Articles markup|https://developers.facebook.com/docs/instant-articles/reference>.
33              
34             At the moment it doesn't support all of the features, and both the internal and
35             external API are subject to change in upcoming releases, so use with care.
36              
37             =head1 SYNOPSIS
38              
39             use Facebook::InstantArticle;
40             use DateTime;
41              
42             my $now = DateTime->now,
43              
44             my $ia = Facebook::InstantArticle->new(
45             language => 'en',
46             url => 'http://www.example.com/2016/08/17/some-article',
47             title => 'Some title',
48             subtitle => 'Got one?',
49             kicker => 'Nobody needs a kicker, but...',
50             published => "$now",
51             modified => "$now",
52             auto_ad_placement => 0, # defaults to true
53             );
54              
55             $ia->add_author(
56             name => 'Me Myself',
57             description => 'A little bit about myself',
58             );
59              
60             $ia->add_author(
61             name => 'Someone Else',
62             description => 'A little bit about someone else',
63             );
64              
65             $ia->add_lead_asset_image(
66             source => 'http://www.example.com/some_image.png',
67             caption => 'Nice image, eh?',
68             );
69              
70             # or
71              
72             $ia->add_lead_asset_video(
73             source => 'http://www.example.com/some_video.mp4',
74             caption => 'Nice video, eh?',
75             );
76              
77             $ia->add_paragraph(
78             'Will be wrapped in a P element, conversion of inner HTML might be
79             done, explained later in this documentation.'
80             );
81              
82             $ia->add_image(
83             source => 'http://www.example.com/some_image.png',
84             caption => 'Nice picture, eh?',
85             enable_comments => 1, # default false
86             enable_likes => 1, # default false
87             );
88              
89             $ia->add_video(
90             source => 'http://www.example.com/some_video.mp4',
91             caption => 'Nice video, eh?',
92             );
93              
94             say $ia->to_string;
95              
96             =cut
97              
98             has 'language' => ( isa => 'Str', is => 'rw', required => 1 );
99             has 'url' => ( isa => 'Str', is => 'rw', required => 1 );
100             has 'title' => ( isa => 'Str', is => 'rw', required => 1 );
101             has 'subtitle' => ( isa => 'Str', is => 'rw', required => 0 );
102             has 'kicker' => ( isa => 'Str', is => 'rw', required => 0 );
103             has 'published' => ( isa => 'Str', is => 'rw', required => 1 );
104             has 'modified' => ( isa => 'Str', is => 'rw', required => 1 );
105             has 'style' => ( isa => 'Str', is => 'rw', required => 0 );
106             has 'auto_ad_placement' => ( isa => 'Bool', is => 'rw', required => 0, default => 1 );
107              
108             has '_header_elements' => ( isa => 'ArrayRef[Object]', is => 'ro', default => sub { [] } );
109             has '_body_elements' => ( isa => 'ArrayRef[Object]', is => 'ro', default => sub { [] } );
110             has '_footer_elements' => ( isa => 'ArrayRef[Object]', is => 'ro', default => sub { [] } );
111             has '_credit_elements' => ( isa => 'ArrayRef[Object]', is => 'ro', default => sub { [] } );
112              
113             has '_log' => ( isa => 'Mojo::Log', is => 'ro', default => sub { Mojo::Log->new } );
114              
115             =head1 METHODS
116              
117             =head2 add_lead_asset_image
118              
119             Adds a lead asset image to the article.
120              
121             $ia->add_lead_asset_image(
122             source => 'http://www.example.com/lead_image.png',
123             caption => 'Something wicked this way comes...',
124             );
125              
126             =cut
127              
128             sub add_lead_asset_image {
129 1     1 1 1808 my $self = shift;
130              
131 1 50       18 if ( my $e = Facebook::InstantArticle::Figure::Image->new(@_) ) {
132 1 50       2127 if ( $e->is_valid ) {
133 1         2 return push( @{$self->_header_elements}, $e );
  1         36  
134             }
135             }
136              
137 0         0 $self->_log->warn( 'Failed to add lead asset image to article header!' );
138             }
139              
140             =head2 add_lead_asset_video
141              
142             Adds a lead asset video to the article.
143              
144             $ia->add_lead_asset_video(
145             source => 'http://www.example.com/lead_video.mp4',
146             caption => 'Something wicked this way comes...',
147             );
148              
149             =cut
150              
151             sub add_lead_asset_video {
152 0     0 1 0 my $self = shift;
153              
154 0 0       0 if ( my $e = Facebook::InstantArticle::Figure::Video->new(@_) ) {
155 0 0       0 if ( $e->is_valid ) {
156 0         0 return push( @{$self->_header_elements}, $e );
  0         0  
157             }
158             }
159              
160 0         0 $self->_log->warn( 'Failed to add lead asset video to article header!' );
161             }
162              
163             =head2 add_author
164              
165             Adds an author to the article.
166              
167             $ia->add_author(
168             name => 'Oscar Wilde',
169             );
170              
171             =cut
172              
173             sub add_author {
174 1     1 1 7 my $self = shift;
175              
176 1 50       13 if ( my $e = Facebook::InstantArticle::Author->new(@_) ) {
177 1 50       546 if ( $e->is_valid ) {
178 1         2 return push( @{$self->_header_elements}, $e );
  1         31  
179             }
180             }
181              
182 0         0 $self->_log->warn( 'Failed to add author to article header!' );
183             }
184              
185             =head2 add_paragraph
186              
187             Adds a paragraph to the article.
188              
189             $ia->add_paragraph( 'This is a paragraph' );
190              
191             =cut
192              
193             sub add_paragraph {
194 3     3 1 15 my $self = shift;
195              
196 3 50       22 if ( my $e = Facebook::InstantArticle::Paragraph->new(@_) ) {
197 3 100       2277 if ( $e->is_valid ) {
198 2         3 return push( @{$self->_body_elements}, $e );
  2         97  
199             }
200             }
201              
202 1         34 $self->_log->warn( 'Failed to add paragraph to article body!' );
203             }
204              
205             =head2 add_image
206              
207             Adds an image to the article.
208              
209             $ia->add_image(
210             source => 'http://www.example.com/image.png',
211             caption => 'Some caption...',
212             );
213              
214             =cut
215              
216             sub add_image {
217 0     0 1 0 my $self = shift;
218              
219 0 0       0 if ( my $e = Facebook::InstantArticle::Figure::Image->new(@_) ) {
220 0 0       0 if ( $e->is_valid ) {
221 0         0 return push( @{$self->_body_elements}, $e );
  0         0  
222             }
223             }
224              
225 0         0 $self->_log->warn( 'Failed to add image to article body!' );
226             }
227              
228             =head2 add_video
229              
230             Adds a video to the article.
231              
232             $ia->add_video(
233             source => 'http://www.example.com/video.mp4',
234             caption => 'Some caption...',
235             );
236              
237             =cut
238              
239             sub add_video {
240 1     1 1 854 my $self = shift;
241              
242 1 50       15 if ( my $e = Facebook::InstantArticle::Figure::Video->new(@_) ) {
243 1 50       1860 if ( $e->is_valid ) {
244 1         3 return push( @{$self->_body_elements}, $e );
  1         34  
245             }
246             }
247              
248 0         0 $self->_log->warn( 'Failed to add video to article body!' );
249             }
250              
251             =head2 add_slideshow
252              
253             Adds a Facebook::InstantArticle::Slideshow object to the article.
254              
255             my $ss = Facebook::InstantArticle::Slideshow->new;
256              
257             $ss->add_image(
258             source => 'http://www.example.com/image_01.png',
259             caption => 'Image #1',
260             );
261              
262             $ss->add_image(
263             source => 'http://www.example.com/image_02.png',
264             caption => 'Image #2',
265             );
266              
267             $ia->add_slideshow( $ss );
268              
269             =cut
270              
271             sub add_slideshow {
272 1     1 1 8 my $self = shift;
273 1         2 my $slideshow = shift;
274              
275 1 50 33     37 if ( defined $slideshow && $slideshow->is_valid ) {
276 1         5 return push( @{$self->_body_elements}, $slideshow );
  1         35  
277             }
278              
279 0         0 $self->_log->warn( 'Failed to add slideshow to article body!' );
280             }
281              
282             =head2 add_credit
283              
284             Adds a credit to the article.
285              
286             $ia->add_credit( 'Thanks for helping me write this article, someone!' );
287              
288             =cut
289              
290             sub add_credit {
291 1     1 1 7 my $self = shift;
292              
293 1 50       5 if ( my $e = Facebook::InstantArticle::Paragraph->new(@_) ) {
294 1 50       564 if ( $e->is_valid ) {
295 1         3 return push( @{$self->_credit_elements}, $e );
  1         33  
296             }
297             }
298              
299 0         0 $self->_log->warn( 'Failed to add credit to article credits!' );
300             }
301              
302             =head2 add_copyright
303              
304             Adds a copyright to the article.
305              
306             $ia->add_copyright( 'Copyright 2016, Fubar Inc.' );
307              
308             =cut
309              
310             sub add_copyright {
311 1     1 1 6 my $self = shift;
312              
313 1 50       13 if ( my $e = Facebook::InstantArticle::Copyright->new(@_) ) {
314 1 50       555 if ( $e->is_valid ) {
315 1         2 return push( @{$self->_footer_elements}, $e );
  1         35  
316             }
317             }
318              
319 0         0 $self->_log->warn( 'Failed to add copyright to article footer!' );
320             }
321              
322             =head2 add_list
323              
324             Adds a Facebook::InstantArticle::List object to the article.
325              
326             $ia->add_list(
327             ordered => 1, # default 0
328             elements => [ 'Element #1', 'Element #2', 'Element 3' ],
329             );
330              
331             =cut
332              
333             sub add_list {
334 1     1 1 11 my $self = shift;
335              
336 1 50       15 if ( my $e = Facebook::InstantArticle::List->new(@_) ) {
337 1 50       1168 if ( $e->is_valid ) {
338 1         2 return push( @{$self->_body_elements}, $e );
  1         33  
339             }
340             }
341              
342 0         0 $self->_log->warn( 'Failed to add list to article body!' );
343             }
344              
345             =head2 add_blockquote
346              
347             Adds a blockquote to the article.
348              
349             $ia->add_blockquote( 'This is blockquoted.' );
350              
351             =cut
352              
353             sub add_blockquote {
354 1     1 1 6 my $self = shift;
355              
356 1 50       14 if ( my $e = Facebook::InstantArticle::Blockquote->new(@_) ) {
357 1 50       581 if ( $e->is_valid ) {
358 1         6 return push( @{$self->_body_elements}, $e );
  1         34  
359             }
360             }
361              
362 0         0 $self->_log->warn( 'Failed to add blockquote to article body!' );
363             }
364              
365             =head2 add_embed
366              
367             Adds an embed to the article.
368              
369             $ia->add_embed( 'code' );
370              
371             =cut
372              
373             sub add_embed {
374 1     1 1 6 my $self = shift;
375              
376 1 50       12 if ( my $e = Facebook::InstantArticle::Embed->new(@_) ) {
377 1 50       1518 if ( $e->is_valid ) {
378 1         2 return push( @{$self->_body_elements}, $e );
  1         32  
379             }
380             }
381              
382 0         0 $self->_log->warn( 'Failed to add embed to article body!' );
383             }
384              
385             =head2 add_heading
386              
387             Adds a heading to the article BODY.
388              
389             $ia->add_heading(
390             level => 1,
391             text => 'Heading',
392             );
393              
394             =cut
395              
396             sub add_heading {
397 0     0 1 0 my $self = shift;
398              
399 0 0       0 if ( my $e = Facebook::InstantArticle::Heading->new(@_) ) {
400 0 0       0 if ( $e->is_valid ) {
401 0         0 return push( @{$self->_body_elements}, $e );
  0         0  
402             }
403             }
404              
405 0         0 $self->_log->warn( 'Failed to add heading to article body!' );
406             }
407              
408             =head2 add_map
409              
410             Adds a map to the article BODY.
411              
412             $ia->add_map(
413             latitude => 56.1341342,
414             longitude => 23.253474,
415             );
416              
417             =cut
418              
419             sub add_map {
420 1     1 1 10 my $self = shift;
421              
422 1 50       17 if ( my $e = Facebook::InstantArticle::Map->new(@_) ) {
423 1 50       1276 if ( $e->is_valid ) {
424 1         2 return push( @{$self->_body_elements}, $e );
  1         32  
425             }
426             }
427              
428 0         0 $self->_log->warn( 'Failed to add map to article body!' );
429             }
430              
431             =head2 add_analytics
432              
433             Adds an analytics iframe to the article body.
434              
435             =cut
436              
437             sub add_analytics {
438 0     0 1 0 my $self = shift;
439              
440 0 0       0 if ( my $e = Facebook::InstantArticle::Analytics->new(@_) ) {
441 0 0       0 if ( $e->is_valid ) {
442 0         0 return push( @{$self->_body_elements}, $e );
  0         0  
443             }
444             }
445              
446 0         0 $self->_log->warn( 'Failed to add analytics/tracking to article body!' );
447             }
448              
449             =head2 to_string
450              
451             Generates the instant article and returns it as a string.
452              
453             =cut
454              
455             sub to_string {
456 1     1 1 8 my $self = shift;
457              
458             # TODO: Validate
459              
460 1         13 my $gen = XML::Generator->new( ':pretty' );
461              
462             my $xml = $gen->html(
463             { lang => $self->language, prefix => 'op:http://media.facebook.com/op#' },
464              
465             $gen->head(
466             $gen->meta( { charset => 'utf-8' } ),
467             $gen->meta( { property => 'op:markup_version', version => 'v1.0' } ),
468             $gen->meta( { property => 'fb:likes_and_comments', content => 'enable' } ),
469             $gen->meta( { property => 'fb:use_automatic_ad_placement', content => ($self->auto_ad_placement ? 'true' : 'false') } ),
470             ( length $self->style ? $gen->meta( { property => 'fb:article_style', content => $self->style } ) : undef ),
471             $gen->link( { rel => 'canonical', href => $self->url } ),
472             ),
473              
474             $gen->body(
475             $gen->article(
476             $gen->header(
477             $gen->h1( $self->title ),
478             ( length $self->subtitle ? $gen->h2($self->subtitle) : undef ),
479             ( length $self->kicker ? $gen->h3({ class => 'op-kicker' }, $self->kicker) : undef ),
480             $gen->time( { class => 'op-published', datetime => $self->published } ),
481             $gen->time( { class => 'op-modified', datetime => $self->modified } ),
482 1         239 ( @{$self->_header_elements} ? map { $_->as_xml_gen } @{$self->_header_elements} : undef ),
  2         264  
  1         33  
483             ),
484 1         784 ( @{$self->_body_elements} ? map { $_->as_xml_gen } @{$self->_body_elements} : undef ),
  8         458  
  1         56  
485             $gen->footer(
486 1         106 ( @{$self->_credit_elements} ? $gen->aside(map { $_->as_xml_gen } @{$self->_credit_elements}) : undef ),
  1         107  
  1         68  
487 1 50       195 ( @{$self->_footer_elements} ? map { $_->as_xml_gen } @{$self->_footer_elements} : undef ),
  1 50       308  
  1 50       66  
  1 50       69  
    50          
    50          
    50          
    50          
488             ),
489             ),
490             ),
491             );
492              
493 1         2325 return "<!doctype html>\n" . $xml;
494             }
495              
496             1;
497              
498             __END__
499              
500             =head1 AUTHOR
501              
502             Tore Aursand E<lt>toreau@gmail.comE<gt>
503              
504             =head1 COPYRIGHT
505              
506             Copyright 2016 - Tore Aursand
507              
508             =head1 LICENSE
509              
510             This library is free software; you can redistribute it and/or modify it under
511             the same terms as Perl itself.
512              
513             =cut