forked from Automattic/fb-instant-articles
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclass-instant-articles-post.php
1006 lines (836 loc) · 27.7 KB
/
class-instant-articles-post.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
/**
* Facebook Instant Articles for WP.
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package default
*/
use Facebook\InstantArticles\Elements\InstantArticle;
use Facebook\InstantArticles\Elements\Header;
use Facebook\InstantArticles\Elements\Time;
use Facebook\InstantArticles\Elements\Ad;
use Facebook\InstantArticles\Elements\Analytics;
use Facebook\InstantArticles\Elements\Author;
use Facebook\InstantArticles\Elements\Image;
use Facebook\InstantArticles\Elements\Video;
use Facebook\InstantArticles\Elements\Caption;
use Facebook\InstantArticles\Elements\Footer;
use Facebook\InstantArticles\Elements\Small;
use Facebook\InstantArticles\Transformer\Transformer;
use Facebook\InstantArticles\Validators\Type;
/**
* Class responsible for constructing our content and preparing it for rendering
*
* @since 0.1
*/
class Instant_Articles_Post {
/**
* The post
*
* @var $_post
*/
protected $_post = null;
/**
* The post cached content
*
* @var string $_content
*/
protected $_content = null;
/** @var string The post’s optional subtitle */
protected $_subtitle = null;
/**
* Instant Article instance object
*
* @var InstantArticle Last built version of the Instant Article object
*/
protected $_instant_article = null;
/**
* Instant Article Transformer instance object
*
* @var Transformer The Transformer object
*/
public $transformer = null;
/**
* Setup data and build the content
*
* @since 0.1
* @param Instant_Article_Post $post ID of the post.
*/
public function __construct( $post ) {
$this->_post = $post;
}
/**
* Get the ID for this post.
*
* @since 0.1
* @return int The post ID.
*/
public function get_the_id() {
return $this->_post->ID;
}
/**
* Get the title for this post.
*
* @since 0.1
* @return string The title.
*/
public function get_the_title() {
$title = $this->_post->post_title;
/**
* Apply the default filter 'the_title' for the post title.
*
* @since 3.1
* @param string $title The current post title.
* @param int $id The current post ID.
*/
$title = apply_filters( 'the_title', $title, $this->_post->ID );
/**
* Filter the post title for use in instant articles.
*
* @since 0.1
*
* @param string $title The current post title.
* @param Instant_Article_Post $instant_article_post The instant article post.
*/
$title = apply_filters( 'instant_articles_title', $title, $this );
return $title;
}
/**
* Get the title for this post.
*
* @since 0.1
* @return string The title.
*/
public function get_the_title_rss() {
$title = $this->get_the_title();
/**
* Apply the default WP Filters for the post title for use in a feed. This ensures proper escaping.
*
* @since 0.1
*
* @param string $title The current post title.
*/
$title = apply_filters( 'the_title_rss', $title );
return $title;
}
/**
* Check if this post has a subtitle
*
* @since 0.2
* @return bool Whether the post has a subtitle or not
*/
public function has_subtitle() {
$has_subtitle = false;
$subtitle = $this->get_the_subtitle();
if ( strlen( $subtitle ) ) {
$has_subtitle = true;
}
return $subtitle;
}
/**
* Get the subtitle for this post
*
* @since 0.2
* @return string The subtitle
*/
public function get_the_subtitle() {
// If we have already been through this function, we’ll have the result stored here
if ( ! is_null( $this->_subtitle ) ) {
return $this->_subtitle;
}
$subtitle = '';
/**
* Filter the subtitle for use in instant articles
*
* @since 0.2
*
* @param string $subtitle The current subtitle for the post.
* @param Instant_Article_Post $instant_article_post The instant article post
*/
$subtitle = apply_filters( 'instant_articles_subtitle', $subtitle, $this );
$this->_subtitle = $subtitle;
return $subtitle;
}
/**
* Get the excerpt for this post.
*
* @since 0.1
* @return string The excerpt.
*/
public function get_the_excerpt() {
$post = get_post( $this->get_the_id() );
// This should ideally not happen, but it may do so if someone tampers with the query.
// Returning the same protected post excerpt as "usual" may help them identify what’s going on.
if ( post_password_required( $this->get_the_ID() ) ) {
return __( 'There is no excerpt because this is a protected post.' );
}
// Make sure no “read more” link is added.
add_filter( 'excerpt_more', '__return_empty_string', 999 );
/**
* Apply the default WP Filters for the post excerpt.
*
* @since 0.1
*
* @param string $post_excerpt The post excerpt.
*/
$excerpt = apply_filters( 'get_the_excerpt', $post->post_excerpt );
/**
* Filter the post excerpt for instant articles.
*
* @since 0.1
*
* @param string $excerpt The current post excerpt.
* @param Instant_Article_Post $instant_article_post The instant article post.
*/
$excerpt = apply_filters( 'instant_articles_excerpt', $excerpt, $this );
return $excerpt;
}
/**
* Get the excerpt for this post
*
* @since 0.1
* @return string The excerpt
*/
public function get_the_excerpt_rss() {
$excerpt = $this->get_the_excerpt();
/**
* Apply the default WP Filters for the post excerpt for use in a feed. This ensures proper escaping.
*
* @since 0.1
*
* @param string $excerpt The current post excerpt.
*/
$excerpt = apply_filters( 'the_excerpt_rss', $excerpt );
return $excerpt;
}
/**
* Get the canonical URL for this post
*
* A little warning here: It is extremely important that this is the same canonical URL as is used on the web site.
* This is the identificator Facebook use to connect the "read" web article with the instant article.
* Do not add any querystring params or URL fragments it. Not any. Not even for tracking.
*
* @since 0.1
* @return string The canonical URL
*/
public function get_canonical_url() {
// If post is draft, clone it to get the eventual permalink,
// see http://wordpress.stackexchange.com/a/42988.
if ( in_array( $this->_post->post_status, array( 'draft', 'pending', 'auto-draft' ), true ) ) {
$post_clone = clone $this->_post;
$post_clone->post_status = 'published';
$post_clone->post_name = sanitize_title( $post_clone->post_name ? $post_clone->post_name : $post_clone->post_title, $post_clone->ID );
$url = get_permalink( $post_clone );
} else {
$url = get_permalink( $this->_post );
}
return $url;
}
/**
* Get the article body for this post
*
* @since 0.1
* @return string The content
*/
public function get_the_content() {
if ( is_null( $this->_content ) ) {
$this->_content = $this->_get_the_content();
}
return $this->_content;
}
/**
* Put together the article body for this post
*
* @since 0.1
* @return string The content
*/
protected function _get_the_content() {
// Try to get the content from a transient, but only if the cached version have the same modtime.
$cache_mod_time = get_transient( 'instantarticles_mod_' . $this->_post->ID );
if ( apply_filters( 'instant_articles_cache_content', true, $this->_post->ID )
&& get_post_modified_time( 'Y-m-d H:i:s', true, $this->_post->ID ) === $cache_mod_time ) {
$content = get_transient( 'instantarticles_content_' . $this->_post->ID );
if ( false !== $content && strlen( $content ) ) {
return $content;
}
}
global $post, $more;
// Force $more.
$orig_more = $more;
$more = 1;
// If we’re not it the loop or otherwise properly setup.
$reset_postdata = false;
if ( empty( $post ) || $this->_post->ID !== $post->ID ) {
$post = get_post( $this->_post->ID );
setup_postdata( $post );
$reset_postdata = true;
}
// Now get the content.
$content = $this->_post->post_content;
/**
* Apply the default filter 'the_content' for the post content.
*
* @since 0.1
* @param string $content The current post content.
*/
// Some people choose to disable wpautop. Due to the Instant Articles spec, we really want it in!
if ( ! has_filter( 'the_content', 'wpautop' ) )
add_filter( 'the_content', 'wpautop' );
$content = apply_filters( 'the_content', $content );
// Maybe cleanup some globals after us?
$more = $orig_more;
if ( $reset_postdata ) {
wp_reset_postdata();
}
// Remove hyperlinks beginning with a # as they cause errors on Facebook (from http://wordpress.stackexchange.com/a/227332/19528)
preg_match_all( '!<a[^>]*? href=[\'"]#[^<]+</a>!i', $content, $matches );
foreach ( $matches[0] as $link ) {
$content = str_replace( $link, strip_tags($link), $content );
}
/**
* Filter the post content for Instant Articles.
*
* @since 0.1
* @param string $content The post content.
* @param int $post_id The instant article post.
*/
$content = apply_filters( 'instant_articles_content', $content, $this->_post->ID );
// Cache the content.
set_transient( 'instantarticles_mod_' . $this->_post->ID, get_post_modified_time( 'Y-m-d H:i:s', true, $this->_post->ID ), WEEK_IN_SECONDS );
set_transient( 'instantarticles_content_' . $this->_post->ID, $content, WEEK_IN_SECONDS );
return $content;
}
/**
* Get the published date for this post (ISO 8601).
*
* @since 0.1
* @return string The published date formatted suitable for use in the RSS feed and the html time elements (ISO 8601).
*/
public function get_the_pubdate_iso() {
$published_date = mysql2date( 'c', get_post_time( 'Y-m-d H:i:s', true, $this->_post->ID ), false );
/**
* Filter the post published date (ISO 8601).
*
* @since 0.1
*
* @param string $published_date The current post published date.
* @param Instant_Article_Post $instant_article_post The instant article post.
*/
$published_date = apply_filters( 'instant_articles_published_date_iso', $published_date, $this );
return $published_date;
}
/**
* Get the modified date for this post (ISO 8601).
*
* @since 0.1
* @return string The modified date formatted suitable for use in the RSS feed and the html time elements (ISO 8601).
*/
public function get_the_moddate_iso() {
$modified_date = mysql2date( 'c', get_post_modified_time( 'Y-m-d H:i:s', true, $this->_post->ID ), false );
/**
* Filter the post modified date (ISO 8601).
*
* @since 0.1
*
* @param string $modified_date The current post modified date.
* @param Instant_Article_Post $instant_article_post The instant article post.
*/
$modified_date = apply_filters( 'instant_articles_modified_date_iso', $modified_date, $this );
return $modified_date;
}
/**
* Get the author(s).
*
* @since 0.1
* @return array $authors The post author(s).
*/
public function get_the_authors() {
$authors = array();
$wp_user = get_userdata( $this->_post->post_author );
if ( is_a( $wp_user, 'WP_User' ) ) {
$author = new stdClass;
$author->ID = $wp_user->ID;
$author->display_name = $wp_user->data->display_name;
$author->first_name = $wp_user->first_name;
$author->last_name = $wp_user->last_name;
$author->user_login = $wp_user->data->user_login;
$author->user_nicename = $wp_user->data->user_nicename;
$author->user_email = $wp_user->data->user_email;
$author->user_url = $wp_user->data->user_url;
$author->bio = $wp_user->description;
$authors[] = $author;
}
/**
* Filter the post author(s).
*
* @since 0.1
*
* @param array $authors The current post author(s).
* @param int $post_id The instant article post.
*/
$authors = apply_filters( 'instant_articles_authors', $authors, $this->_post->ID );
return $authors;
}
/**
* Get featured image for cover.
*
* @since 0.1
* @return array {
* Array containing image source and caption.
*
* @type string $src Image URL.
* @type string $caption Image caption.
* }
*/
public function get_the_featured_image() {
$image_data = array(
'src' => '',
'caption' => '',
);
if ( has_post_thumbnail( $this->_post->ID ) ) {
$image_array = wp_get_attachment_image_src( get_post_thumbnail_id( $this->_post->ID ), 'full' );
$attachment_id = get_post_thumbnail_id( $this->_post->ID );
if ( is_array( $image_array ) ) {
$image_data['src'] = $image_array[0];
$attachment_post = get_post( $attachment_id );
if ( is_a( $attachment_post, 'WP_Post' ) ) {
$image_data['caption'] = $attachment_post->post_excerpt;
}
}
}
/**
* Filter the featured image.
*
* @since 0.1
* @param array $image_data {
* Array containg image source and caption.
*
* @type string $src Image URL.
* @type string $caption Image caption.
* }
* @param int $post_id The post ID.
*/
$image_data = apply_filters( 'instant_articles_featured_image', $image_data, $this->_post->ID );
return $image_data;
}
/**
* Get the cover media.
*
* @since 0.1
* @return Image|Video
*/
public function get_cover_media() {
$cover_media = Image::create();
// If someone else is handling this, let them. Otherwise fall back to us trying to use the featured image.
if ( has_filter( 'instant_articles_cover_media' ) ) {
/**
* Filter the cover media.
*
* @since 0.1
* @param Image $cover_media The cover media object.
* @param int $post_id The current post ID.
*/
$cover_media = apply_filters( 'instant_articles_cover_media', $cover_media, $this->_post->ID );
} else {
$featured_image_data = $this->get_the_featured_image();
if ( isset( $featured_image_data['src'] ) && strlen( $featured_image_data['src'] ) ) {
$cover_media = Image::create()->withURL($featured_image_data['src']);
if( isset( $featured_image_data['caption'] ) && strlen( $featured_image_data['caption'] )) {
$cover_media->withCaption(Caption::create()->withTitle($featured_image_data['caption']));
}
}
}
return $cover_media;
}
/**
* Get kicker text.
*
* @since 0.1
* @return string
*/
public function get_the_kicker() {
$category = '';
if ( has_category() ) {
$categories = get_the_category();
if ( is_array( $categories ) && isset( $categories[0]->name ) && __( 'Uncategorized' ) !== $categories[0]->name ) {
$category = $categories[0]->name;
}
}
/**
* Filter the kicker text.
*
* @since 0.1
*
* @param string $category The first category returned from get_the_category().
* @param int $post_id The post ID.
*/
$category_kicker = apply_filters( 'instant_articles_cover_kicker', $category, $this->_post->ID );
return $category_kicker ? $category_kicker : '';
}
/**
* Get newsfeed cover type, image or video.
*
* @since 0.1
* @return string
*/
public function get_newsfeed_cover() {
$type = 'image';
/**
* Filter the cover type property.
*
* @since 0.1
*
* @param string $type Set to 'video' for video cover. Featured image (image) is default.
* @param int $post_id The post ID.
*/
$type = apply_filters( 'instant_articles_cover_type', $type, $this->_post->ID );
return $type;
}
/**
* Render post
*
* @since 0.1
* @return InstantArticle
*/
public function to_instant_article() {
/**
* Fires before the instant article is rendered.
*
* @since 0.1
* @param Instant_Article_Post $instant_article_post The instant article post.
*/
do_action( 'instant_articles_before_transform_post', $this );
is_transforming_instant_article( true );
// Get time zone configured in WordPress. Default to UTC if no time zone configured.
$date_time_zone = get_option( 'timezone_string' ) ? new DateTimeZone( get_option( 'timezone_string' ) ) : new DateTimeZone( 'UTC' );
// Initialize transformer
$file_path = plugin_dir_path( __FILE__ ) . 'rules-configuration.json';
$file_path = apply_filters( 'instant_articles_transformer_rules_configuration_json_file_path', $file_path );
$configuration = file_get_contents( $file_path );
$transformer = new Transformer();
$this->transformer = $transformer;
$transformer->loadRules( $configuration );
$transformer = apply_filters( 'instant_articles_transformer_rules_loaded', $transformer );
$settings_publishing = Instant_Articles_Option_Publishing::get_option_decoded();
if (
isset( $settings_publishing['custom_rules_enabled'] ) &&
! empty( $settings_publishing['custom_rules_enabled'] ) &&
isset( $settings_publishing['custom_rules'] ) &&
! empty( $settings_publishing['custom_rules'] )
) {
$transformer->loadRules( $settings_publishing['custom_rules'] );
}
$transformer = apply_filters( 'instant_articles_transformer_custom_rules_loaded', $transformer );
$blog_charset = get_option( 'blog_charset' );
$header =
Header::create()
->withPublishTime(
Time::create( Time::PUBLISHED )->withDatetime( new DateTime( $this->_post->post_date, $date_time_zone ) )
)
->withModifyTime(
Time::create( Time::MODIFIED )->withDatetime( new DateTime( $this->_post->post_modified, $date_time_zone ) )
);
$title = $this->get_the_title();
if ( $title ) {
$transformer->transformString( $header, '<h1>' . $title . '</h1>', $blog_charset );
}
if ( $this->has_subtitle() ) {
$header->withSubTitle ( $this->get_the_subtitle() ) ;
}
$authors = $this->get_the_authors();
foreach ( $authors as $author ) {
$author_obj = Author::create();
if ( $author->display_name ) {
$author_obj->withName( $author->display_name );
}
if ( $author->bio ) {
$author_obj->withDescription( $author->bio );
}
if ( $author->user_url ) {
$author_obj->withURL( $author->user_url );
}
$header->addAuthor( $author_obj );
}
$kicker = $this->get_the_kicker();
if ( $kicker ) {
$header->withKicker( $kicker );
}
$cover = $this->get_cover_media();
if ( $cover->getUrl() ) {
$header->withCover( $cover );
}
$this->instant_article =
InstantArticle::create()
->withCanonicalUrl( $this->get_canonical_url() )
->withHeader( $header )
->addMetaProperty( 'op:generator:application', 'facebook-instant-articles-wp' )
->addMetaProperty( 'op:generator:application:version', IA_PLUGIN_VERSION );
$this->set_appearance_from_settings();
$the_content = $this->get_the_content();
if (!Type::isTextEmpty($the_content)) {
$transformer->transformString( $this->instant_article, $the_content, get_option( 'blog_charset' ) );
}
$this->add_ads_from_settings();
$this->add_analytics_from_settings();
$this->instant_article = apply_filters( 'instant_articles_transformed_element', $this->instant_article );
is_transforming_instant_article( false );
/**
* Fires after the instant article is rendered.
*
* @since 0.1
* @param Instant_Article_Post $instant_article_post The instant article post.
*/
do_action( 'instant_articles_after_transform_post', $this );
return $this->instant_article;
}
/**
* Add all ad code(s) to the Header of an InstantArticle.
*
* @since 0.3
*/
public function add_ads_from_settings() {
$header = $this->instant_article->getHeader();
$settings_ads = Instant_Articles_Option_Ads::get_option_decoded();
$width = 300;
$height = 250;
$dimensions_match = array();
$dimensions_raw = isset( $settings_ads['dimensions'] ) ? $settings_ads['dimensions'] : null;
if ( preg_match( '/^(?:\s)*(\d+)x(\d+)(?:\s)*$/', $dimensions_raw, $dimensions_match ) ) {
$width = intval( $dimensions_match[1] );
$height = intval( $dimensions_match[2] );
}
$ad = Ad::create()
->enableDefaultForReuse()
->withWidth( $width )
->withHeight( $height );
$source_of_ad = isset( $settings_ads['ad_source'] ) ? $settings_ads['ad_source'] : 'none';
switch ( $source_of_ad ) {
case 'none':
break;
case 'fan':
if ( ! empty( $settings_ads['fan_placement_id'] ) ) {
$placement_id = $settings_ads['fan_placement_id'];
$ad->withSource(
add_query_arg(
array(
'placement' => $placement_id,
'adtype' => 'banner' . $width . 'x' . $height,
),
'https://www.facebook.com/adnw_request'
)
);
$header->addAd( $ad );
}
break;
case 'iframe':
if ( ! empty( $settings_ads['iframe_url'] ) ) {
$ad->withSource(
$settings_ads['iframe_url']
);
$header->addAd( $ad );
}
break;
case 'embed':
if ( ! empty( $settings_ads['embed_code'] ) ) {
$document = new DOMDocument();
$fragment = $document->createDocumentFragment();
$valid_html = @$fragment->appendXML( $settings_ads['embed_code'] );
if ( $valid_html ) {
$ad->withHTML(
$fragment
);
$header->addAd( $ad );
}
}
break;
default:
if ( ! empty( $source_of_ad ) ) {
$registered_compat_ads = Instant_Articles_Option::get_registered_compat( 'instant_articles_compat_registry_ads' );
foreach ( $registered_compat_ads as $compat_id => $compat_info ) {
if ( array_key_exists( $compat_id, $registered_compat_ads ) ) {
$document = new DOMDocument();
$fragment = $document->createDocumentFragment();
$valid_html = @$fragment->appendXML( $compat_info['payload'] );
if ( $valid_html ) {
$ad = Ad::create()
->enableDefaultForReuse()
->withWidth( $width )
->withHeight( $height )
->withHTML(
$fragment
);
$header->addAd( $ad );
}
}
}
}
break;
}
$this->instant_article->enableAutomaticAdPlacement();
}
/**
* Add all analytic tracking code(s) an InstantArticle.
*
* @since 0.3
*/
public function add_analytics_from_settings() {
$settings_analytics = Instant_Articles_Option_Analytics::get_option_decoded();
if ( isset( $settings_analytics['embed_code_enabled'] ) && ! empty( $settings_analytics['embed_code'] ) ) {
$document = new DOMDocument();
$fragment = $document->createDocumentFragment();
$valid_html = @$fragment->appendXML( $settings_analytics['embed_code'] );
if ( $valid_html ) {
$this->instant_article
->addChild(
Analytics::create()
->withHTML(
$fragment
)
);
}
}
if ( ! empty( $settings_analytics['integrations'] ) ) {
$settings_analytics_compats = $settings_analytics['integrations'];
$registered_compat_analytics = Instant_Articles_Option::get_registered_compat( 'instant_articles_compat_registry_analytics' );
foreach ( $registered_compat_analytics as $compat_id => $compat_info ) {
if ( in_array( $compat_id, $settings_analytics_compats, true ) ) {
$document = new DOMDocument();
$fragment = $document->createDocumentFragment();
$valid_html = @$fragment->appendXML( $compat_info['payload'] );
if ( $valid_html ) {
$this->instant_article
->addChild(
Analytics::create()
->withHTML(
$fragment
)
);
}
}
}
}
}
/**
* Returns whether the transformation results in an empty document
*/
public function is_empty_after_transformation() {
// This post meta is a cache on the calculations made by this function
$cache = get_post_meta( $this->get_the_id(), '_is_empty_after_transformation', true );
if ( $cache ) {
// We use 'yes' or 'no' to avoid booleans because
// get_post_meta() returns false when the key is not found
return ( $cache === 'yes' );
}
$instant_article = $this->to_instant_article();
// Skip empty articles or articles missing title.
// This is important because the save_post action is also triggered by bulk updates, but in this case
// WordPress does not load the content field from DB for performance reasons. In this case, articles
// will be empty here, despite of them actually having content.
if ( count( $instant_article->getChildren() ) === 0 || ! $instant_article->getHeader() || ! $instant_article->getHeader()->getTitle() ) {
update_post_meta( $this->get_the_id(), '_is_empty_after_transformation', 'yes' );
return true;
}
update_post_meta( $this->get_the_id(), '_is_empty_after_transformation', 'no' );
return false;
}
/**
* Returns whether the transformation raises warnings
*/
public function has_warnings_after_transformation() {
// This post meta is a cache on the calculations made by this function
$cache = get_post_meta( $this->get_the_id(), '_has_warnings_after_transformation', true );
if ( $cache ) {
// We use 'yes' or 'no' to avoid booleans because
// get_post_meta() returns false when the key is not found
return ( $cache === 'yes' );
}
$instant_article = $this->to_instant_article();
if ( count( $this->transformer->getWarnings() ) > 0 ) {
update_post_meta( $this->get_the_id(), '_has_warnings_after_transformation', 'yes' );
return true;
}
update_post_meta( $this->get_the_id(), '_has_warnings_after_transformation', 'no' );
return false;
}
/**
* Returns whether the article should be ingested as an Instant Article.
*/
public function should_submit_post() {
$fb_page_settings = Instant_Articles_Option_FB_Page::get_option_decoded();
if ( isset( $fb_page_settings[ 'page_id' ] ) && !$fb_page_settings[ 'page_id' ] ) {
return false;
}
$post = $this->_post;
// Don't process if this is just a revision or an autosave.
if ( wp_is_post_revision( $post ) || wp_is_post_autosave( $post ) ) {
return false;
}
// Don't process if this post is not published
if ( 'publish' !== $post->post_status ) {
return false;
}
// Only process posts
$post_types = apply_filters( 'instant_articles_post_types', array( 'post' ) );
if ( ! in_array( $post->post_type, $post_types ) ) {
return false;
}
// Don't publish posts with password protection
if ( post_password_required( $post ) ) {
return false;
}
// Allow to disable post submit via filter
if ( false === apply_filters( 'instant_articles_should_submit_post', true, $this ) ) {
return false;
}
// Skip empty articles or articles missing title.
// This is important because the save_post action is also triggered by bulk updates, but in this case
// WordPress does not load the content field from DB for performance reasons. In this case, articles
// will be empty here, despite of them actually having content.
if ( $this->is_empty_after_transformation() ) {
return false;
}
// Don't process if contains warnings and blocker flag for transformation warnings is turned on.
$publishing_settings = Instant_Articles_Option_Publishing::get_option_decoded();
$force_submit = get_post_meta( $post->ID, IA_PLUGIN_FORCE_SUBMIT_KEY, true );
if ( $this->has_warnings_after_transformation()
&& ( ! isset( $publishing_settings[ 'publish_with_warnings' ] ) || ! $publishing_settings[ 'publish_with_warnings' ] )
&& ( ! $force_submit )
) {
return false;
}
return true;
}
/**
* Apply appearance settings for an InstantArticle.
*
* @since 3.3
*/
public function set_appearance_from_settings() {
$settings = Instant_Articles_Option_Styles::get_option_decoded();
$article_style = 'default';
if ( isset( $settings['article_style'] ) && ! empty( $settings['article_style'] ) ) {
$article_style = $settings['article_style'];
}
/**
* Filter the article style to use.
*
* @since 0.1
* @param string $template Path to the current (default) template.
* @param Instant_Article_Post $instant_article_post The instant article post.
*/
$article_style = apply_filters( 'instant_articles_style', $article_style, $this );
$this->instant_article->withStyle($article_style);
if ( isset( $settings['copyright'] ) && ! empty( $settings['copyright'] ) ) {
$footer = Footer::create();
$this->transformer->transformString(
$footer,
'<small>' . $settings['copyright'] . '</small>',
get_option( 'blog_charset' ) );
$this->instant_article->withFooter( $footer );
}