Completed
Push — add/amp-pwa-experiment ( efea12 )
by
unknown
11:53
created

AMP_Post_Template::add_data()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
require_once( AMP__ROOT__ . '/includes/utils/class-amp-dom-utils.php' );
4
require_once( AMP__ROOT__ . '/includes/utils/class-amp-html-utils.php' );
5
require_once( AMP__ROOT__ . '/includes/utils/class-amp-string-utils.php' );
6
7
require_once( AMP__ROOT__ . '/includes/class-amp-content.php' );
8
9
require_once( AMP__ROOT__ . '/includes/sanitizers/class-amp-style-sanitizer.php' );
10
require_once( AMP__ROOT__ . '/includes/sanitizers/class-amp-blacklist-sanitizer.php' );
11
require_once( AMP__ROOT__ . '/includes/sanitizers/class-amp-img-sanitizer.php' );
12
require_once( AMP__ROOT__ . '/includes/sanitizers/class-amp-video-sanitizer.php' );
13
require_once( AMP__ROOT__ . '/includes/sanitizers/class-amp-iframe-sanitizer.php' );
14
require_once( AMP__ROOT__ . '/includes/sanitizers/class-amp-audio-sanitizer.php' );
15
16
require_once( AMP__ROOT__ . '/includes/embeds/class-amp-twitter-embed.php' );
17
require_once( AMP__ROOT__ . '/includes/embeds/class-amp-youtube-embed.php' );
18
require_once( AMP__ROOT__ . '/includes/embeds/class-amp-gallery-embed.php' );
19
require_once( AMP__ROOT__ . '/includes/embeds/class-amp-instagram-embed.php' );
20
require_once( AMP__ROOT__ . '/includes/embeds/class-amp-vine-embed.php' );
21
require_once( AMP__ROOT__ . '/includes/embeds/class-amp-facebook-embed.php' );
22
23
class AMP_Post_Template {
24
	const SITE_ICON_SIZE = 32;
25
	const CONTENT_MAX_WIDTH = 600;
26
27
	// Needed for 0.3 back-compat
28
	const DEFAULT_NAVBAR_BACKGROUND = '#0a89c0';
29
	const DEFAULT_NAVBAR_COLOR = '#fff';
30
31
	private $template_dir;
32
	private $data;
33
34
	public function __construct( $post_id ) {
35
		$this->template_dir = apply_filters( 'amp_post_template_dir', AMP__ROOT__ . '/templates' );
36
37
		$this->ID = $post_id;
0 ignored issues
show
Bug introduced by
The property ID does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
38
		$this->post = get_post( $post_id );
0 ignored issues
show
Bug introduced by
The property post does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
39
40
		$content_max_width = self::CONTENT_MAX_WIDTH;
41
		if ( isset( $GLOBALS['content_width'] ) && $GLOBALS['content_width'] > 0 ) {
42
			$content_max_width = $GLOBALS['content_width'];
43
		}
44
		$content_max_width = apply_filters( 'amp_content_max_width', $content_max_width );
45
46
		$this->data = array(
47
			'content_max_width' => $content_max_width,
48
49
			'document_title' => function_exists( 'wp_get_document_title' ) ? wp_get_document_title() : wp_title( '', false ), // back-compat with 4.3
50
			'canonical_url' => get_permalink( $post_id ),
51
			'home_url' => home_url(),
52
			'blog_name' => get_bloginfo( 'name' ),
53
54
			'html_tag_attributes' => array(),
55
			'body_class' => '',
56
57
			'site_icon_url' => apply_filters( 'amp_site_icon_url', function_exists( 'get_site_icon_url' ) ? get_site_icon_url( self::SITE_ICON_SIZE ) : '' ),
58
			'placeholder_image_url' => amp_get_asset_url( 'images/placeholder-icon.png' ),
59
60
			'featured_image' => false,
61
			'comments_link_url' => false,
62
			'comments_link_text' => false,
63
64
			'amp_runtime_script' => 'https://cdn.ampproject.org/v0.js',
65
			'amp_component_scripts' => array(),
66
67
			'customizer_settings' => array(),
68
69
			'font_urls' => array(
70
				'merriweather' => 'https://fonts.googleapis.com/css?family=Merriweather:400,400italic,700,700italic',
71
			),
72
73
			/**
74
			 * Add amp-analytics tags.
75
			 *
76
			 * This filter allows you to easily insert any amp-analytics tags without needing much heavy lifting.
77
			 *
78
			 * @since 0.4
79
			 *.
80
			 * @param	array	$analytics	An associative array of the analytics entries we want to output. Each array entry must have a unique key, and the value should be an array with the following keys: `type`, `attributes`, `script_data`. See readme for more details.
81
			 * @param	object	$post	The current post.
82
			 */
83
			'amp_analytics' => apply_filters( 'amp_post_template_analytics', array(), $this->post ),
84
 		);
85
86
		$this->build_post_content();
87
		$this->build_post_data();
88
		$this->build_customizer_settings();
89
		$this->build_html_tag_attributes();
90
91
		$this->data = apply_filters( 'amp_post_template_data', $this->data, $this->post );
92
	}
93
94
	public function get( $property, $default = null ) {
95
		if ( isset( $this->data[ $property ] ) ) {
96
			return $this->data[ $property ];
97
		} else {
98
			_doing_it_wrong( __METHOD__, sprintf( __( 'Called for non-existant key ("%s").', 'amp' ), esc_html( $property ) ), '0.1' );
99
		}
100
101
		return $default;
102
	}
103
104
	public function get_customizer_setting( $name, $default = null ) {
105
		$settings = $this->get( 'customizer_settings' );
106
		if ( ! empty( $settings[ $name ] ) ) {
107
			return $settings[ $name ];
108
		}
109
110
		return $default;
111
	}
112
113
	public function load() {
114
		$this->load_parts( array( 'single' ) );
115
	}
116
117
	public function load_parts( $templates ) {
118
		foreach ( $templates as $template ) {
119
			$file = $this->get_template_path( $template );
120
			$this->verify_and_include( $file, $template );
121
		}
122
	}
123
124
	private function get_template_path( $template ) {
125
		return sprintf( '%s/%s.php', $this->template_dir, $template );
126
	}
127
128
	private function add_data( $data ) {
129
		$this->data = array_merge( $this->data, $data );
130
	}
131
132
	private function add_data_by_key( $key, $value ) {
133
		$this->data[ $key ] = $value;
134
	}
135
136
	private function merge_data_for_key( $key, $value ) {
137
		if ( is_array( $this->data[ $key ] ) ) {
138
			$this->data[ $key ] = array_merge( $this->data[ $key ], $value );
139
		} else {
140
			$this->add_data_by_key( $key, $value );
141
		}
142
	}
143
144
	private function build_post_data() {
145
		$post_title = get_the_title( $this->ID );
146
		$post_publish_timestamp = get_the_date( 'U', $this->ID );
147
		$post_modified_timestamp = get_post_modified_time( 'U', false, $this->post );
148
		$post_author = get_userdata( $this->post->post_author );
149
150
		$this->add_data( array(
151
			'post' => $this->post,
152
			'post_id' => $this->ID,
153
			'post_title' => $post_title,
154
			'post_publish_timestamp' => $post_publish_timestamp,
155
			'post_modified_timestamp' => $post_modified_timestamp,
156
			'post_author' => $post_author,
157
		) );
158
159
		$metadata = array(
160
			'@context' => 'http://schema.org',
161
			'@type' => 'BlogPosting',
162
			'mainEntityOfPage' => $this->get( 'canonical_url' ),
163
			'publisher' => array(
164
				'@type' => 'Organization',
165
				'name' => $this->get( 'blog_name' ),
166
			),
167
			'headline' => $post_title,
168
			'datePublished' => date( 'c', $post_publish_timestamp ),
169
			'dateModified' => date( 'c', $post_modified_timestamp ),
170
			'author' => array(
171
				'@type' => 'Person',
172
				'name' => $post_author->display_name,
173
			),
174
		);
175
176
		$site_icon_url = $this->get( 'site_icon_url' );
177
		if ( $site_icon_url ) {
178
			$metadata['publisher']['logo'] = array(
179
				'@type' => 'ImageObject',
180
				'url' => $site_icon_url,
181
				'height' => self::SITE_ICON_SIZE,
182
				'width' => self::SITE_ICON_SIZE,
183
			);
184
		}
185
186
		$image_metadata = $this->get_post_image_metadata();
187
		if ( $image_metadata ) {
188
			$metadata['image'] = $image_metadata;
189
		}
190
191
		$this->add_data_by_key( 'metadata', apply_filters( 'amp_post_template_metadata', $metadata, $this->post ) );
192
193
		$this->build_post_featured_image();
194
		$this->build_post_commments_data();
195
	}
196
197
	private function build_post_commments_data() {
198
		if ( ! post_type_supports( $this->post->post_type, 'comments' ) ) {
199
			return;
200
		}
201
202
		$comments_open = comments_open( $this->ID );
203
204
		// Don't show link if close and no comments
205
		if ( ! $comments_open
206
			&& ! $this->post->comment_count ) {
207
			return;
208
		}
209
210
		$comments_link_url = get_comments_link( $this->ID );
211
		$comments_link_text = $comments_open
212
			? __( 'Leave a Comment', 'amp' )
213
			: __( 'View Comments', 'amp' );
214
215
		$this->add_data( array(
216
			'comments_link_url' => $comments_link_url,
217
			'comments_link_text' => $comments_link_text,
218
		) );
219
	}
220
221
	private function build_post_content() {
222
		$amp_content = new AMP_Content( $this->post->post_content,
223
			apply_filters( 'amp_content_embed_handlers', array(
224
				'AMP_Twitter_Embed_Handler' => array(),
225
				'AMP_YouTube_Embed_Handler' => array(),
226
				'AMP_Instagram_Embed_Handler' => array(),
227
				'AMP_Vine_Embed_Handler' => array(),
228
				'AMP_Facebook_Embed_Handler' => array(),
229
				'AMP_Gallery_Embed_Handler' => array(),
230
			), $this->post ),
231
			apply_filters( 'amp_content_sanitizers', array(
232
				 'AMP_Style_Sanitizer' => array(),
233
				 'AMP_Blacklist_Sanitizer' => array(),
234
				 'AMP_Img_Sanitizer' => array(),
235
				 'AMP_Video_Sanitizer' => array(),
236
				 'AMP_Audio_Sanitizer' => array(),
237
				 'AMP_Iframe_Sanitizer' => array(
238
					 'add_placeholder' => true,
239
				 ),
240
			), $this->post ),
241
			array(
242
				'content_max_width' => $this->get( 'content_max_width' ),
243
			)
244
		);
245
246
		$this->add_data_by_key( 'post_amp_content', $amp_content->get_amp_content() );
247
		$this->merge_data_for_key( 'amp_component_scripts', $amp_content->get_amp_scripts() );
248
		$this->add_data_by_key( 'post_amp_styles', $amp_content->get_amp_styles() );
249
	}
250
251
	private function build_post_featured_image() {
252
		$post_id = $this->ID;
253
		$featured_html = get_the_post_thumbnail( $post_id, 'large' );
254
255
		// Skip featured image if no featured image is available.
256
		if ( ! $featured_html ) {
257
			return;
258
		}
259
260
		$featured_id = get_post_thumbnail_id( $post_id );
261
262
		// If an image with the same ID as the featured image exists in the content, skip the featured image markup.
263
		// Prevents duplicate images, which is especially problematic for photo blogs.
264
		// A bit crude but it's fast and should cover most cases.
265
		$post_content = $this->post->post_content;
266
		if ( false !== strpos( $post_content, 'wp-image-' . $featured_id )
267
			|| false !== strpos( $post_content, 'attachment_' . $featured_id ) ) {
268
			return;
269
		}
270
271
		$featured_image = get_post( $featured_id );
272
273
		list( $sanitized_html, $featured_scripts, $featured_styles ) = AMP_Content_Sanitizer::sanitize(
274
			$featured_html,
275
			array( 'AMP_Img_Sanitizer' => array() ),
276
			array(
277
				'content_max_width' => $this->get( 'content_max_width' )
278
			)
279
		);
280
281
		$this->add_data_by_key( 'featured_image', array(
282
			'amp_html' => $sanitized_html,
283
			'caption' => $featured_image->post_excerpt,
284
		) );
285
286
		if ( $featured_scripts ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $featured_scripts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
287
			$this->merge_data_for_key( 'amp_component_scripts', $featured_scripts );
288
		}
289
290
		if ( $featured_styles ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $featured_styles of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
291
			$this->add_data_by_key( 'post_amp_styles', $featured_styles );
292
		}
293
	}
294
295
	private function build_customizer_settings() {
296
		$settings = AMP_Customizer_Settings::get_settings();
297
298
		/**
299
		 * Filter AMP Customizer settings.
300
		 *
301
		 * Inject your Customizer settings here to make them accessible via the getter in your custom style.php template.
302
		 *
303
		 * Example:
304
		 *
305
		 *     echo esc_html( $this->get_customizer_setting( 'your_setting_key', 'your_default_value' ) );
306
		 *
307
		 * @since 0.4
308
		 *
309
		 * @param array   $settings Array of AMP Customizer settings.
310
		 * @param WP_Post $post     Current post object.
311
		 */
312
		$this->add_data_by_key( 'customizer_settings', apply_filters( 'amp_post_template_customizer_settings', $settings, $this->post ) );
313
	}
314
315
	/**
316
	 * Grabs featured image or the first attached image for the post
317
	 *
318
	 * TODO: move to a utils class?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
319
	 */
320
	private function get_post_image_metadata() {
321
		$post_image_meta = null;
322
		$post_image_id = false;
323
324
		if ( has_post_thumbnail( $this->ID ) ) {
325
			$post_image_id = get_post_thumbnail_id( $this->ID );
326
		} else {
327
			$attached_image_ids = get_posts( array(
328
				'post_parent' => $this->ID,
329
				'post_type' => 'attachment',
330
				'post_mime_type' => 'image',
331
				'posts_per_page' => 1,
332
				'orderby' => 'menu_order',
333
				'order' => 'ASC',
334
				'fields' => 'ids',
335
				'suppress_filters' => false,
336
			) );
337
338
			if ( ! empty( $attached_image_ids ) ) {
339
				$post_image_id = array_shift( $attached_image_ids );
340
			}
341
		}
342
343
		if ( ! $post_image_id ) {
344
			return false;
345
		}
346
347
		$post_image_src = wp_get_attachment_image_src( $post_image_id, 'full' );
348
349
		if ( is_array( $post_image_src ) ) {
350
			$post_image_meta = array(
351
				'@type' => 'ImageObject',
352
				'url' => $post_image_src[0],
353
				'width' => $post_image_src[1],
354
				'height' => $post_image_src[2],
355
			);
356
		}
357
358
		return $post_image_meta;
359
	}
360
361
	private function build_html_tag_attributes() {
362
		$attributes = array();
363
364
		if ( function_exists( 'is_rtl' ) && is_rtl() ) {
365
			$attributes['dir'] = 'rtl';
366
		}
367
368
		$lang = get_bloginfo( 'language' );
369
		if ( $lang ) {
370
			$attributes['lang'] = $lang;
371
		}
372
373
		$this->add_data_by_key( 'html_tag_attributes', $attributes );
374
	}
375
376
	private function verify_and_include( $file, $template_type ) {
377
		$located_file = $this->locate_template( $file );
378
		if ( $located_file ) {
379
			$file = $located_file;
380
		}
381
382
		$file = apply_filters( 'amp_post_template_file', $file, $template_type, $this->post );
383
		if ( ! $this->is_valid_template( $file ) ) {
384
			_doing_it_wrong( __METHOD__, sprintf( __( 'Path validation for template (%s) failed. Path cannot traverse and must be located in `%s`.', 'amp' ), esc_html( $file ), 'WP_CONTENT_DIR' ), '0.1' );
385
			return;
386
		}
387
388
		do_action( 'amp_post_template_include_' . $template_type, $this );
389
		include( $file );
390
	}
391
392
393
	private function locate_template( $file ) {
394
		$search_file = sprintf( 'amp/%s', basename( $file ) );
395
		return locate_template( array( $search_file ), false );
396
	}
397
398
	private function is_valid_template( $template ) {
399
		if ( false !== strpos( $template, '..' ) ) {
400
			return false;
401
		}
402
403
		if ( false !== strpos( $template, './' ) ) {
404
			return false;
405
		}
406
407
		if ( ! file_exists( $template ) ) {
408
			return false;
409
		}
410
411
		return true;
412
	}
413
}
414