Completed
Push — kraftbj-patch-2 ( 82c983...ae9d16 )
by
unknown
513:58 queued 503:20
created

Jetpack_Sitemap_Builder::build_one_sitemap_index()   C

Complexity

Conditions 8
Paths 16

Size

Total Lines 105
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 55
nc 16
nop 4
dl 0
loc 105
rs 5.2676
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Build the sitemap tree.
4
 *
5
 * @package Jetpack
6
 * @since 4.7.0
7
 * @author Automattic
8
 */
9
10
require_once dirname( __FILE__ ) . '/sitemap-constants.php';
11
require_once dirname( __FILE__ ) . '/sitemap-buffer.php';
12
require_once dirname( __FILE__ ) . '/sitemap-librarian.php';
13
require_once dirname( __FILE__ ) . '/sitemap-finder.php';
14
require_once dirname( __FILE__ ) . '/sitemap-state.php';
15
16 View Code Duplication
if ( defined( 'WP_DEBUG' ) && ( true === WP_DEBUG ) ) {
17
	require_once dirname( __FILE__ ) . '/sitemap-logger.php';
18
}
19
20
/**
21
 * The Jetpack_Sitemap_Builder object handles the construction of
22
 * all sitemap files (except the XSL files, which are handled by
23
 * Jetpack_Sitemap_Stylist.) Other than the constructor, there are
24
 * only two public functions: build_all_sitemaps and news_sitemap_xml.
25
 *
26
 * @since 4.7.0
27
 */
28
class Jetpack_Sitemap_Builder {
29
30
	/**
31
	 * Librarian object for storing and retrieving sitemap data.
32
	 *
33
	 * @access private
34
	 * @since 4.7.0
35
	 * @var $librarian Jetpack_Sitemap_Librarian
36
	 */
37
	private $librarian;
38
39
	/**
40
	 * Logger object for reporting debug messages.
41
	 *
42
	 * @access private
43
	 * @since 4.7.0
44
	 * @var $logger Jetpack_Sitemap_Logger
45
	 */
46
	private $logger;
47
48
	/**
49
	 * Finder object for dealing with sitemap URIs.
50
	 *
51
	 * @access private
52
	 * @since 4.7.0
53
	 * @var $finder Jetpack_Sitemap_Finder
54
	 */
55
	private $finder;
56
57
	/**
58
	 * Construct a new Jetpack_Sitemap_Builder object.
59
	 *
60
	 * @access public
61
	 * @since 4.7.0
62
	 */
63
	public function __construct() {
64
		$this->librarian = new Jetpack_Sitemap_Librarian();
65
		$this->finder = new Jetpack_Sitemap_Finder();
66
67
		if ( defined( 'WP_DEBUG' ) && ( true === WP_DEBUG ) ) {
68
			$this->logger = new Jetpack_Sitemap_Logger();
69
		}
70
71
		update_option(
72
			'jetpack_sitemap_post_types',
73
			/**
74
			 * The array of post types to be included in the sitemap.
75
			 *
76
			 * Add your custom post type name to the array to have posts of
77
			 * that type included in the sitemap. The default array includes
78
			 * 'page' and 'post'.
79
			 *
80
			 * The result of this filter is cached in an option, 'jetpack_sitemap_post_types',
81
			 * so this filter only has to be applied once per generation.
82
			 *
83
			 * @since 4.7.0
84
			 */
85
			apply_filters(
86
				'jetpack_sitemap_post_types',
87
				array( 'post', 'page' )
88
			)
89
		);
90
91
		return;
92
	}
93
94
	/**
95
	 * Update the sitemap.
96
	 *
97
	 * All we do here is call build_next_sitemap_file a bunch of times.
98
	 *
99
	 * @since 4.7.0
100
	 */
101
	public function update_sitemap() {
102
		if ( $this->logger ) {
103
			$this->logger->report( '-- Updating...' );
104
		}
105
106
		for ( $i = 1; $i <= JP_SITEMAP_UPDATE_SIZE; $i++ ) {
107
			$this->build_next_sitemap_file();
108
		}
109
110
		if ( $this->logger ) {
111
			$this->logger->report( '-- ...done for now.' );
112
			$this->logger->time();
113
		}
114
	}
115
116
	/**
117
	 * Generate the next sitemap file.
118
	 *
119
	 * Reads the most recent state of the sitemap generation phase,
120
	 * constructs the next file, and updates the state.
121
	 *
122
	 * @since 4.7.0
123
	 */
124
	private function build_next_sitemap_file() {
125
		// Get the most recent state, and lock the state.
126
		$state = Jetpack_Sitemap_State::check_out();
127
128
		// Do nothing if the state was locked.
129
		if ( false === $state ) {
130
			return;
131
		}
132
133
		// Otherwise, branch on the sitemap-type key of $state.
134
		switch ( $state['sitemap-type'] ) {
135
			case JP_PAGE_SITEMAP_TYPE:
136
				$this->build_next_sitemap_of_type(
137
					JP_PAGE_SITEMAP_TYPE,
138
					array( $this, 'build_one_page_sitemap' ),
139
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 126 can also be of type boolean; however, Jetpack_Sitemap_Builder:..._next_sitemap_of_type() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
140
				);
141
				break;
142
143
			case JP_PAGE_SITEMAP_INDEX_TYPE:
144
				$this->build_next_sitemap_index_of_type(
145
					JP_PAGE_SITEMAP_INDEX_TYPE,
146
					JP_IMAGE_SITEMAP_TYPE,
147
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 126 can also be of type boolean; however, Jetpack_Sitemap_Builder:...sitemap_index_of_type() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
148
				);
149
				break;
150
151
			case JP_IMAGE_SITEMAP_TYPE:
152
				$this->build_next_sitemap_of_type(
153
					JP_IMAGE_SITEMAP_TYPE,
154
					array( $this, 'build_one_image_sitemap' ),
155
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 126 can also be of type boolean; however, Jetpack_Sitemap_Builder:..._next_sitemap_of_type() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
156
				);
157
				break;
158
159
			case JP_IMAGE_SITEMAP_INDEX_TYPE:
160
				$this->build_next_sitemap_index_of_type(
161
					JP_IMAGE_SITEMAP_INDEX_TYPE,
162
					JP_VIDEO_SITEMAP_TYPE,
163
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 126 can also be of type boolean; however, Jetpack_Sitemap_Builder:...sitemap_index_of_type() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
164
				);
165
				break;
166
167
			case JP_VIDEO_SITEMAP_TYPE:
168
				$this->build_next_sitemap_of_type(
169
					JP_VIDEO_SITEMAP_TYPE,
170
					array( $this, 'build_one_video_sitemap' ),
171
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 126 can also be of type boolean; however, Jetpack_Sitemap_Builder:..._next_sitemap_of_type() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
172
				);
173
				break;
174
175
			case JP_VIDEO_SITEMAP_INDEX_TYPE:
176
				$this->build_next_sitemap_index_of_type(
177
					JP_VIDEO_SITEMAP_INDEX_TYPE,
178
					JP_MASTER_SITEMAP_TYPE,
179
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 126 can also be of type boolean; however, Jetpack_Sitemap_Builder:...sitemap_index_of_type() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
180
				);
181
				break;
182
183
			case JP_MASTER_SITEMAP_TYPE:
184
				$this->build_master_sitemap( $state['max'] );
185
186
				// Reset the state and quit.
187
				Jetpack_Sitemap_State::reset(
188
					JP_PAGE_SITEMAP_TYPE
189
				);
190
191
				if ( $this->logger ) {
192
					$this->logger->report( '-- Finished.' );
193
					$this->logger->time();
194
				}
195
196
				die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method build_next_sitemap_file() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
197
198
			default:
199
				// Otherwise, reset the state.
200
				Jetpack_Sitemap_State::reset(
201
					JP_PAGE_SITEMAP_TYPE
202
				);
203
				die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method build_next_sitemap_file() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
204
		}
205
206
		// Unlock the state.
207
		Jetpack_Sitemap_State::unlock();
208
209
		return;
210
	}
211
212
	/**
213
	 * Build the next sitemap of a given type and update the sitemap state.
214
	 *
215
	 * @since 4.7.0
216
	 *
217
	 * @param string   $sitemap_type The type of the sitemap being generated.
218
	 * @param callback $build_one    A callback which builds a single sitemap file.
219
	 * @param array    $state        A sitemap state.
220
	 */
221
	private function build_next_sitemap_of_type( $sitemap_type, $build_one, $state ) {
222
		$index_type = jp_sitemap_index_type_of( $sitemap_type );
223
224
		// Try to build a sitemap.
225
		$result = call_user_func_array(
226
			$build_one,
227
			array(
228
				$state['number'] + 1,
229
				$state['last-added'],
230
			)
231
		);
232
233 View Code Duplication
		if ( false === $result ) {
234
			// If no sitemap was generated, advance to the next type.
235
			Jetpack_Sitemap_State::check_in( array(
236
				'sitemap-type'  => $index_type,
237
				'last-added'    => 0,
238
				'number'        => 0,
239
				'last-modified' => '1970-01-01 00:00:00',
240
			) );
241
242
			if ( $this->logger ) {
243
				$this->logger->report( "-- Cleaning Up $sitemap_type" );
244
			}
245
246
			// Clean up old files.
247
			$this->librarian->delete_numbered_sitemap_rows_after(
248
				$state['number'], $sitemap_type
249
			);
250
251
			return;
252
		}
253
254
		// Otherwise, update the state.
255
		Jetpack_Sitemap_State::check_in( array(
256
			'sitemap-type'  => $state['sitemap-type'],
257
			'last-added'    => $result['last_id'],
258
			'number'        => $state['number'] + 1,
259
			'last-modified' => $result['last_modified'],
260
		) );
261
262
		if ( true === $result['any_left'] ) {
263
			// If there's more work to be done with this type, return.
264
			return;
265
		}
266
267
		// Otherwise, advance state to the next sitemap type.
268
		Jetpack_Sitemap_State::check_in( array(
269
			'sitemap-type'  => $index_type,
270
			'last-added'    => 0,
271
			'number'        => 0,
272
			'last-modified' => '1970-01-01 00:00:00',
273
		) );
274
275
		if ( $this->logger ) {
276
			$this->logger->report( "-- Cleaning Up $sitemap_type" );
277
		}
278
279
		// Clean up old files.
280
		$this->librarian->delete_numbered_sitemap_rows_after(
281
			$state['number'] + 1, $sitemap_type
282
		);
283
284
		return;
285
	}
286
287
	/**
288
	 * Build the next sitemap index of a given type and update the state.
289
	 *
290
	 * @since 4.7.0
291
	 *
292
	 * @param string $index_type The type of index being generated.
293
	 * @param string $next_type  The next type to generate after this one.
294
	 * @param array  $state      A sitemap state.
295
	 */
296
	private function build_next_sitemap_index_of_type( $index_type, $next_type, $state ) {
297
		$sitemap_type = jp_sitemap_child_type_of( $index_type );
298
299
		// If only 0 or 1 sitemaps were built, advance to the next type and return.
300
		if ( 1 >= $state['max'][ $sitemap_type ]['number'] ) {
301
			Jetpack_Sitemap_State::check_in( array(
302
				'sitemap-type'  => $next_type,
303
				'last-added'    => 0,
304
				'number'        => 0,
305
				'last-modified' => '1970-01-01 00:00:00',
306
			) );
307
308
			if ( $this->logger ) {
309
				$this->logger->report( "-- Cleaning Up $index_type" );
310
			}
311
312
			// There are no indices of this type.
313
			$this->librarian->delete_numbered_sitemap_rows_after(
314
				0, $index_type
315
			);
316
317
			return;
318
		}
319
320
		// Otherwise, try to build a sitemap index.
321
		$result = $this->build_one_sitemap_index(
322
			$state['number'] + 1,
323
			$state['last-added'],
324
			$state['last-modified'],
325
			$index_type
326
		);
327
328
		// If no index was built, advance to the next type and return.
329 View Code Duplication
		if ( false === $result ) {
330
			Jetpack_Sitemap_State::check_in( array(
331
				'sitemap-type'  => $next_type,
332
				'last-added'    => 0,
333
				'number'        => 0,
334
				'last-modified' => '1970-01-01 00:00:00',
335
			) );
336
337
			if ( $this->logger ) {
338
				$this->logger->report( "-- Cleaning Up $index_type" );
339
			}
340
341
			// Clean up old files.
342
			$this->librarian->delete_numbered_sitemap_rows_after(
343
				$state['number'], $index_type
344
			);
345
346
			return;
347
		}
348
349
		// Otherwise, check in the state.
350
		Jetpack_Sitemap_State::check_in( array(
351
			'sitemap-type'  => $index_type,
352
			'last-added'    => $result['last_id'],
353
			'number'        => $state['number'] + 1,
354
			'last-modified' => $result['last_modified'],
355
		) );
356
357
		// If there are still sitemaps left to index, return.
358
		if ( true === $result['any_left'] ) {
359
			return;
360
		}
361
362
		// Otherwise, advance to the next type.
363
		Jetpack_Sitemap_State::check_in( array(
364
			'sitemap-type'  => $next_type,
365
			'last-added'    => 0,
366
			'number'        => 0,
367
			'last-modified' => '1970-01-01 00:00:00',
368
		) );
369
370
		if ( $this->logger ) {
371
			$this->logger->report( "-- Cleaning Up $index_type" );
372
		}
373
374
		// We're done generating indices of this type.
375
		$this->librarian->delete_numbered_sitemap_rows_after(
376
			$state['number'] + 1, $index_type
377
		);
378
379
		return;
380
	}
381
382
	/**
383
	 * Builds the master sitemap index.
384
	 *
385
	 * @param array $max Array of sitemap types with max index and datetime.
386
	 *
387
	 * @since 4.7.0
388
	 */
389
	private function build_master_sitemap( $max ) {
390
		$sitemap_index_xsl_url = $this->finder->construct_sitemap_url( 'sitemap-index.xsl' );
391
		$jetpack_version = JETPACK__VERSION;
392
393
		if ( $this->logger ) {
394
			$this->logger->report( '-- Building Master Sitemap.' );
395
		}
396
397
		$buffer = new Jetpack_Sitemap_Buffer(
398
			JP_SITEMAP_MAX_ITEMS,
399
			JP_SITEMAP_MAX_BYTES,
400
			<<<HEADER
401
<?xml version='1.0' encoding='UTF-8'?>
402
<!-- generator='jetpack-{$jetpack_version}' -->
403
<?xml-stylesheet type='text/xsl' href='{$sitemap_index_xsl_url}'?>
404
<sitemapindex xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>
405
HEADER
406
			,
407
			<<<FOOTER
408
</sitemapindex>\n
409
FOOTER
410
			,
411
			/* epoch */
412
			'1970-01-01 00:00:00'
413
		);
414
415 View Code Duplication
		if ( 0 < $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) {
416
			if ( 1 === $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) {
417
				$page['filename'] = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, 1 );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$page was never initialized. Although not strictly required by PHP, it is generally a good practice to add $page = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
418
				$page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_TYPE ]['lastmod'] );
419
			} else {
420
				$page['filename'] = jp_sitemap_filename(
0 ignored issues
show
Coding Style Comprehensibility introduced by
$page was never initialized. Although not strictly required by PHP, it is generally a good practice to add $page = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
421
					JP_PAGE_SITEMAP_INDEX_TYPE,
422
					$max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['number']
423
				);
424
				$page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
425
			}
426
427
			$buffer->try_to_add_item( Jetpack_Sitemap_Buffer::array_to_xml_string(
428
				array(
429
					'sitemap' => array(
430
						'loc'     => $this->finder->construct_sitemap_url( $page['filename'] ),
431
						'lastmod' => $page['last_modified'],
432
					),
433
				)
434
			) );
435
		}
436
437 View Code Duplication
		if ( 0 < $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
438
			if ( 1 === $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
439
				$image['filename'] = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, 1 );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$image was never initialized. Although not strictly required by PHP, it is generally a good practice to add $image = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
440
				$image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_TYPE ]['lastmod'] );
441
			} else {
442
				$image['filename'] = jp_sitemap_filename(
0 ignored issues
show
Coding Style Comprehensibility introduced by
$image was never initialized. Although not strictly required by PHP, it is generally a good practice to add $image = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
443
					JP_IMAGE_SITEMAP_INDEX_TYPE,
444
					$max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['number']
445
				);
446
				$image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
447
			}
448
449
			$buffer->try_to_add_item( Jetpack_Sitemap_Buffer::array_to_xml_string(
450
				array(
451
					'sitemap' => array(
452
						'loc'     => $this->finder->construct_sitemap_url( $image['filename'] ),
453
						'lastmod' => $image['last_modified'],
454
					),
455
				)
456
			) );
457
		}
458
459 View Code Duplication
		if ( 0 < $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
460
			if ( 1 === $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
461
				$video['filename'] = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, 1 );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$video was never initialized. Although not strictly required by PHP, it is generally a good practice to add $video = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
462
				$video['last_modified'] = $max[ JP_VIDEO_SITEMAP_TYPE ]['lastmod'];
463
			} else {
464
				$video['filename'] = jp_sitemap_filename(
0 ignored issues
show
Coding Style Comprehensibility introduced by
$video was never initialized. Although not strictly required by PHP, it is generally a good practice to add $video = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
465
					JP_VIDEO_SITEMAP_INDEX_TYPE,
466
					$max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['number']
467
				);
468
				$video['last_modified'] = $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['lastmod'];
469
			}
470
471
			$buffer->try_to_add_item( Jetpack_Sitemap_Buffer::array_to_xml_string(
472
				array(
473
					'sitemap' => array(
474
						'loc'     => $this->finder->construct_sitemap_url( $video['filename'] ),
475
						'lastmod' => $video['last_modified'],
476
					),
477
				)
478
			) );
479
		}
480
481
		$this->librarian->store_sitemap_data(
482
			0,
483
			JP_MASTER_SITEMAP_TYPE,
484
			$buffer->contents(),
485
			''
486
		);
487
488
		return;
489
	}
490
491
	/**
492
	 * Build and store a single page sitemap. Returns false if no sitemap is built.
493
	 *
494
	 * Side effect: Create/update a sitemap row.
495
	 *
496
	 * @access private
497
	 * @since 4.7.0
498
	 *
499
	 * @param int $number The number of the current sitemap.
500
	 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
501
	 *
502
	 * @return bool|array @args {
503
	 *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
504
	 *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
505
	 *   @type string $last_modified The most recent timestamp to appear on the sitemap.
506
	 * }
507
	 */
508
	public function build_one_page_sitemap( $number, $from_id ) {
509
		$last_post_id = $from_id;
510
		$any_posts_left = true;
511
512
		if ( $this->logger ) {
513
			$debug_name = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, $number );
514
			$this->logger->report( "-- Building $debug_name" );
515
		}
516
517
		$sitemap_xsl_url = $this->finder->construct_sitemap_url( 'sitemap.xsl' );
518
519
		$jetpack_version = JETPACK__VERSION;
520
521
		$namespaces = Jetpack_Sitemap_Buffer::array_to_xml_attr_string(
522
			/**
523
			 * Filter the attribute value pairs used for namespace and namespace URI mappings.
524
			 *
525
			 * @module sitemaps
526
			 *
527
			 * @since 3.9.0
528
			 *
529
			 * @param array $namespaces Associative array with namespaces and namespace URIs.
530
			 */
531
			apply_filters(
532
				'jetpack_sitemap_ns',
533
				array(
534
					'xmlns:xsi'          => 'http://www.w3.org/2001/XMLSchema-instance',
535
					'xsi:schemaLocation' => 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd',
536
					'xmlns'              => 'http://www.sitemaps.org/schemas/sitemap/0.9',
537
				)
538
			)
539
		);
540
541
		$buffer = new Jetpack_Sitemap_Buffer(
542
			JP_SITEMAP_MAX_ITEMS,
543
			JP_SITEMAP_MAX_BYTES,
544
			<<<HEADER
545
<?xml version='1.0' encoding='UTF-8'?>
546
<!-- generator='jetpack-{$jetpack_version}' -->
547
<?xml-stylesheet type='text/xsl' href='{$sitemap_xsl_url}'?>
548
<urlset{$namespaces}>\n
549
HEADER
550
			,
551
			<<<FOOTER
552
</urlset>\n
553
FOOTER
554
			,
555
			/* epoch */
556
			'1970-01-01 00:00:00'
557
		);
558
559
		// Add entry for the main page (only if we're at the first one).
560
		if ( 1 === $number ) {
561
			$item_array = array(
562
				'url' => array(
563
					'loc' => home_url(),
564
				),
565
			);
566
567
			/**
568
			 * Filter associative array with data to build <url> node
569
			 * and its descendants for site home.
570
			 *
571
			 * @module sitemaps
572
			 *
573
			 * @since 3.9.0
574
			 *
575
			 * @param array $blog_home Data to build parent and children nodes for site home.
576
			 */
577
			$item_array = apply_filters( 'jetpack_sitemap_url_home', $item_array );
578
579
			$buffer->try_to_add_item( Jetpack_Sitemap_Buffer::array_to_xml_string( $item_array ) );
580
		}
581
582
		// Add as many items to the buffer as possible.
583
		while ( false === $buffer->is_full() ) {
584
			$posts = $this->librarian->query_posts_after_id(
585
				$last_post_id, JP_SITEMAP_BATCH_SIZE
586
			);
587
588
			if ( null == $posts ) { // WPCS: loose comparison ok.
589
				$any_posts_left = false;
590
				break;
591
			}
592
593
			foreach ( $posts as $post ) {
594
				$current_item = $this->post_to_sitemap_item( $post );
595
596
				if ( true === $buffer->try_to_add_item( $current_item['xml'] ) ) {
597
					$last_post_id = $post->ID;
598
					$buffer->view_time( $current_item['last_modified'] );
599
				} else {
600
					break;
601
				}
602
			}
603
		}
604
605
		// If no items were added, return false.
606
		if ( true === $buffer->is_empty() ) {
607
			return false;
608
		}
609
610
		/**
611
		 * Filter sitemap before rendering it as XML.
612
		 *
613
		 * @module sitemaps
614
		 *
615
		 * @since 3.9.0
616
		 *
617
		 * @param SimpleXMLElement $tree Data tree for sitemap.
618
		 * @param string           $last_modified Date of last modification.
619
		 */
620
		$tree = apply_filters(
621
			'jetpack_print_sitemap',
622
			simplexml_load_string( $buffer->contents() ),
623
			$buffer->last_modified()
624
		);
625
626
		// Store the buffer as the content of a sitemap row.
627
		$this->librarian->store_sitemap_data(
628
			$number,
629
			JP_PAGE_SITEMAP_TYPE,
630
			$tree->asXML(),
631
			$buffer->last_modified()
632
		);
633
634
		/*
635
		 * Now report back with the ID of the last post ID to be
636
		 * successfully added and whether there are any posts left.
637
		 */
638
		return array(
639
			'last_id'       => $last_post_id,
640
			'any_left'      => $any_posts_left,
641
			'last_modified' => $buffer->last_modified(),
642
		);
643
	}
644
645
	/**
646
	 * Build and store a single image sitemap. Returns false if no sitemap is built.
647
	 *
648
	 * Side effect: Create/update an image sitemap row.
649
	 *
650
	 * @access private
651
	 * @since 4.7.0
652
	 *
653
	 * @param int $number The number of the current sitemap.
654
	 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
655
	 *
656
	 * @return bool|array @args {
657
	 *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
658
	 *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
659
	 *   @type string $last_modified The most recent timestamp to appear on the sitemap.
660
	 * }
661
	 */
662 View Code Duplication
	public function build_one_image_sitemap( $number, $from_id ) {
663
		$last_post_id = $from_id;
664
		$any_posts_left = true;
665
666
		if ( $this->logger ) {
667
			$debug_name = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, $number );
668
			$this->logger->report( "-- Building $debug_name" );
669
		}
670
671
		$image_sitemap_xsl_url = $this->finder->construct_sitemap_url( 'image-sitemap.xsl' );
672
673
		$jetpack_version = JETPACK__VERSION;
674
675
		$namespaces = Jetpack_Sitemap_Buffer::array_to_xml_attr_string(
676
			/**
677
			 * Filter the XML namespaces included in image sitemaps.
678
			 *
679
			 * @module sitemaps
680
			 *
681
			 * @since 4.7.0
682
			 *
683
			 * @param array $namespaces Associative array with namespaces and namespace URIs.
684
			 */
685
			apply_filters(
686
				'jetpack_sitemap_image_ns',
687
				array(
688
					'xmlns:xsi'          => 'http://www.w3.org/2001/XMLSchema-instance',
689
					'xsi:schemaLocation' => 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd',
690
					'xmlns'              => 'http://www.sitemaps.org/schemas/sitemap/0.9',
691
					'xmlns:image'        => 'http://www.google.com/schemas/sitemap-image/1.1',
692
				)
693
			)
694
		);
695
696
		$buffer = new Jetpack_Sitemap_Buffer(
697
			JP_SITEMAP_MAX_ITEMS,
698
			JP_SITEMAP_MAX_BYTES,
699
			<<<HEADER
700
<?xml version='1.0' encoding='UTF-8'?>
701
<!-- generator='jetpack-{$jetpack_version}' -->
702
<?xml-stylesheet type='text/xsl' href='{$image_sitemap_xsl_url}'?>
703
<urlset{$namespaces}>\n
704
HEADER
705
			,
706
			<<<FOOTER
707
</urlset>\n
708
FOOTER
709
			,
710
			/* epoch */
711
			'1970-01-01 00:00:00'
712
		);
713
714
		// Add as many items to the buffer as possible.
715
		while ( false === $buffer->is_full() ) {
716
			$posts = $this->librarian->query_images_after_id(
717
				$last_post_id, JP_SITEMAP_BATCH_SIZE
718
			);
719
720
			if ( null == $posts ) { // WPCS: loose comparison ok.
721
				$any_posts_left = false;
722
				break;
723
			}
724
725
			foreach ( $posts as $post ) {
726
				$current_item = $this->image_post_to_sitemap_item( $post );
727
728
				if ( true === $buffer->try_to_add_item( $current_item['xml'] ) ) {
729
					$last_post_id = $post->ID;
730
					$buffer->view_time( $current_item['last_modified'] );
731
				} else {
732
					break;
733
				}
734
			}
735
		}
736
737
		// If no items were added, return false.
738
		if ( true === $buffer->is_empty() ) {
739
			return false;
740
		}
741
742
		// Store the buffer as the content of a jp_sitemap post.
743
		$this->librarian->store_sitemap_data(
744
			$number,
745
			JP_IMAGE_SITEMAP_TYPE,
746
			$buffer->contents(),
747
			$buffer->last_modified()
748
		);
749
750
		/*
751
		 * Now report back with the ID of the last post to be
752
		 * successfully added and whether there are any posts left.
753
		 */
754
		return array(
755
			'last_id'       => $last_post_id,
756
			'any_left'      => $any_posts_left,
757
			'last_modified' => $buffer->last_modified(),
758
		);
759
	}
760
761
	/**
762
	 * Build and store a single video sitemap. Returns false if no sitemap is built.
763
	 *
764
	 * Side effect: Create/update an video sitemap row.
765
	 *
766
	 * @access private
767
	 * @since 4.7.0
768
	 *
769
	 * @param int $number The number of the current sitemap.
770
	 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
771
	 *
772
	 * @return bool|array @args {
773
	 *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
774
	 *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
775
	 *   @type string $last_modified The most recent timestamp to appear on the sitemap.
776
	 * }
777
	 */
778 View Code Duplication
	public function build_one_video_sitemap( $number, $from_id ) {
779
		$last_post_id = $from_id;
780
		$any_posts_left = true;
781
782
		if ( $this->logger ) {
783
			$debug_name = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, $number );
784
			$this->logger->report( "-- Building $debug_name" );
785
		}
786
787
		$video_sitemap_xsl_url = $this->finder->construct_sitemap_url( 'video-sitemap.xsl' );
788
789
		$jetpack_version = JETPACK__VERSION;
790
791
		$namespaces = Jetpack_Sitemap_Buffer::array_to_xml_attr_string(
792
			/**
793
			 * Filter the XML namespaces included in video sitemaps.
794
			 *
795
			 * @module sitemaps
796
			 *
797
			 * @since 4.7.0
798
			 *
799
			 * @param array $namespaces Associative array with namespaces and namespace URIs.
800
			 */
801
			apply_filters(
802
				'jetpack_sitemap_video_ns',
803
				array(
804
					'xmlns:xsi'          => 'http://www.w3.org/2001/XMLSchema-instance',
805
					'xsi:schemaLocation' => 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd',
806
					'xmlns'              => 'http://www.sitemaps.org/schemas/sitemap/0.9',
807
					'xmlns:video'        => 'http://www.google.com/schemas/sitemap-video/1.1',
808
				)
809
			)
810
		);
811
812
		$buffer = new Jetpack_Sitemap_Buffer(
813
			JP_SITEMAP_MAX_ITEMS,
814
			JP_SITEMAP_MAX_BYTES,
815
			<<<HEADER
816
<?xml version='1.0' encoding='UTF-8'?>
817
<!-- generator='jetpack-{$jetpack_version}' -->
818
<?xml-stylesheet type='text/xsl' href='{$video_sitemap_xsl_url}'?>
819
<urlset{$namespaces}>\n
820
HEADER
821
			,
822
			<<<FOOTER
823
</urlset>\n
824
FOOTER
825
			,
826
			/* epoch */
827
			'1970-01-01 00:00:00'
828
		);
829
830
		// Add as many items to the buffer as possible.
831
		while ( false === $buffer->is_full() ) {
832
			$posts = $this->librarian->query_videos_after_id(
833
				$last_post_id, JP_SITEMAP_BATCH_SIZE
834
			);
835
836
			if ( null == $posts ) { // WPCS: loose comparison ok.
837
				$any_posts_left = false;
838
				break;
839
			}
840
841
			foreach ( $posts as $post ) {
842
				$current_item = $this->video_post_to_sitemap_item( $post );
843
844
				if ( true === $buffer->try_to_add_item( $current_item['xml'] ) ) {
845
					$last_post_id = $post->ID;
846
					$buffer->view_time( $current_item['last_modified'] );
847
				} else {
848
					break;
849
				}
850
			}
851
		}
852
853
		// If no items were added, return false.
854
		if ( true === $buffer->is_empty() ) {
855
			return false;
856
		}
857
858
		if ( false === $buffer->is_empty() ) {
859
			$this->librarian->store_sitemap_data(
860
				$number,
861
				JP_VIDEO_SITEMAP_TYPE,
862
				$buffer->contents(),
863
				$buffer->last_modified()
864
			);
865
		}
866
867
		/*
868
		 * Now report back with the ID of the last post to be
869
		 * successfully added and whether there are any posts left.
870
		 */
871
		return array(
872
			'last_id'       => $last_post_id,
873
			'any_left'      => $any_posts_left,
874
			'last_modified' => $buffer->last_modified(),
875
		);
876
	}
877
878
	/**
879
	 * Build and store a single page sitemap index. Return false if no index is built.
880
	 *
881
	 * Side effect: Create/update a sitemap index row.
882
	 *
883
	 * @access private
884
	 * @since 4.7.0
885
	 *
886
	 * @param int    $number     The number of the current sitemap index.
887
	 * @param int    $from_id    The greatest lower bound of the IDs of the sitemaps to be included.
888
	 * @param string $datetime   Datetime of previous sitemap in 'YYYY-MM-DD hh:mm:ss' format.
889
	 * @param string $index_type Sitemap index type.
890
	 *
891
	 * @return bool|array @args {
892
	 *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
893
	 *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
894
	 *   @type string $last_modified The most recent timestamp to appear on the sitemap.
895
	 * }
896
	 */
897
	private function build_one_sitemap_index( $number, $from_id, $datetime, $index_type ) {
898
		$last_sitemap_id   = $from_id;
899
		$any_sitemaps_left = true;
900
901
		// Check the datetime format.
902
		$datetime = jp_sitemap_datetime( $datetime );
903
904
		$sitemap_type = jp_sitemap_child_type_of( $index_type );
905
906
		if ( $this->logger ) {
907
			$index_debug_name = jp_sitemap_filename( $index_type, $number );
908
			$this->logger->report( "-- Building $index_debug_name" );
909
		}
910
911
		$sitemap_index_xsl_url = $this->finder->construct_sitemap_url( 'sitemap-index.xsl' );
912
913
		$jetpack_version = JETPACK__VERSION;
914
915
		$buffer = new Jetpack_Sitemap_Buffer(
916
			JP_SITEMAP_MAX_ITEMS,
917
			JP_SITEMAP_MAX_BYTES,
918
			<<<HEADER
919
<?xml version='1.0' encoding='UTF-8'?>
920
<!-- generator='jetpack-{$jetpack_version}' -->
921
<?xml-stylesheet type='text/xsl' href='{$sitemap_index_xsl_url}'?>
922
<sitemapindex xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>\n
923
HEADER
924
			,
925
			<<<FOOTER
926
</sitemapindex>\n
927
FOOTER
928
			,
929
			/* initial last_modified value */
930
			$datetime
931
		);
932
933
		// Add pointer to the previous sitemap index (unless we're at the first one).
934
		if ( 1 !== $number ) {
935
			$i = $number - 1;
936
			$prev_index_url = $this->finder->construct_sitemap_url(
937
				jp_sitemap_filename( $index_type, $i )
938
			);
939
940
			$item_array = array(
941
				'sitemap' => array(
942
					'loc'     => $prev_index_url,
943
					'lastmod' => $datetime,
944
				),
945
			);
946
947
			$buffer->try_to_add_item( Jetpack_Sitemap_Buffer::array_to_xml_string( $item_array ) );
948
		}
949
950
		// Add as many items to the buffer as possible.
951
		while ( false === $buffer->is_full() ) {
952
			// Retrieve a batch of posts (in order).
953
			$posts = $this->librarian->query_sitemaps_after_id(
954
				$sitemap_type, $last_sitemap_id, JP_SITEMAP_BATCH_SIZE
955
			);
956
957
			// If there were no posts to get, make a note.
958
			if ( null == $posts ) { // WPCS: loose comparison ok.
959
				$any_sitemaps_left = false;
960
				break;
961
			}
962
963
			// Otherwise, loop through each post in the batch.
964
			foreach ( $posts as $post ) {
965
				// Generate the sitemap XML for the post.
966
				$current_item = $this->sitemap_row_to_index_item( $post );
967
968
				// Try adding this item to the buffer.
969
				if ( true === $buffer->try_to_add_item( $current_item['xml'] ) ) {
970
					$last_sitemap_id = $post['ID'];
971
					$buffer->view_time( $current_item['last_modified'] );
972
				} else {
973
					// Otherwise stop looping through posts.
974
					break;
975
				}
976
			}
977
		}
978
979
		// If no items were added, return false.
980
		if ( true === $buffer->is_empty() ) {
981
			return false;
982
		}
983
984
		$this->librarian->store_sitemap_data(
985
			$number,
986
			$index_type,
987
			$buffer->contents(),
988
			$buffer->last_modified()
989
		);
990
991
		/*
992
		 * Now report back with the ID of the last sitemap post ID to
993
		 * be successfully added, whether there are any sitemap posts
994
		 * left, and the most recent modification time seen.
995
		 */
996
		return array(
997
			'last_id'       => $last_sitemap_id,
998
			'any_left'      => $any_sitemaps_left,
999
			'last_modified' => $buffer->last_modified(),
1000
		);
1001
	}
1002
1003
	/**
1004
	 * Construct the sitemap index url entry for a sitemap row.
1005
	 *
1006
	 * @link http://www.sitemaps.org/protocol.html#sitemapIndex_sitemap
1007
	 *
1008
	 * @access private
1009
	 * @since 4.7.0
1010
	 *
1011
	 * @param array $row The sitemap data to be processed.
1012
	 *
1013
	 * @return string An XML fragment representing the post URL.
1014
	 */
1015
	private function sitemap_row_to_index_item( $row ) {
1016
		$url = $this->finder->construct_sitemap_url( $row['post_title'] );
1017
1018
		$item_array = array(
1019
			'sitemap' => array(
1020
				'loc'     => $url,
1021
				'lastmod' => jp_sitemap_datetime( $row['post_date'] ),
1022
			),
1023
		);
1024
1025
		return array(
1026
			'xml'           => Jetpack_Sitemap_Buffer::array_to_xml_string( $item_array ),
1027
			'last_modified' => $row['post_date'],
1028
		);
1029
	}
1030
1031
	/**
1032
	 * Build and return the news sitemap xml. Note that the result of this
1033
	 * function is cached in the transient 'jetpack_news_sitemap_xml'.
1034
	 *
1035
	 * @access public
1036
	 * @since 4.7.0
1037
	 *
1038
	 * @return string The news sitemap xml.
1039
	 */
1040
	public function news_sitemap_xml() {
1041
		$the_stored_news_sitemap = get_transient( 'jetpack_news_sitemap_xml' );
1042
1043
		if ( false === $the_stored_news_sitemap ) {
1044
1045
			if ( $this->logger ) {
1046
				$this->logger->report( 'Beginning news sitemap generation.' );
1047
			}
1048
1049
			$news_sitemap_xsl_url = $this->finder->construct_sitemap_url( 'news-sitemap.xsl' );
1050
1051
			$jetpack_version = JETPACK__VERSION;
1052
1053
			/**
1054
			 * Filter limit of entries to include in news sitemap.
1055
			 *
1056
			 * @module sitemaps
1057
			 *
1058
			 * @since 3.9.0
1059
			 *
1060
			 * @param int $count Number of entries to include in news sitemap.
1061
			 */
1062
			$item_limit = apply_filters(
1063
				'jetpack_sitemap_news_sitemap_count',
1064
				JP_NEWS_SITEMAP_MAX_ITEMS
1065
			);
1066
1067
			$namespaces = Jetpack_Sitemap_Buffer::array_to_xml_attr_string(
1068
				/**
1069
				 * Filter the attribute value pairs used for namespace and namespace URI mappings.
1070
				 *
1071
				 * @module sitemaps
1072
				 *
1073
				 * @since 4.7.0
1074
				 *
1075
				 * @param array $namespaces Associative array with namespaces and namespace URIs.
1076
				 */
1077
				apply_filters(
1078
					'jetpack_sitemap_news_ns',
1079
					array(
1080
						'xmlns:xsi'          => 'http://www.w3.org/2001/XMLSchema-instance',
1081
						'xsi:schemaLocation' => 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd',
1082
						'xmlns'              => 'http://www.sitemaps.org/schemas/sitemap/0.9',
1083
						'xmlns:news'         => 'http://www.google.com/schemas/sitemap-news/0.9',
1084
					)
1085
				)
1086
			);
1087
1088
			$buffer = new Jetpack_Sitemap_Buffer(
1089
				min( $item_limit, JP_NEWS_SITEMAP_MAX_ITEMS ),
1090
				JP_SITEMAP_MAX_BYTES,
1091
				<<<HEADER
1092
<?xml version='1.0' encoding='UTF-8'?>
1093
<!-- generator='jetpack-{$jetpack_version}' -->
1094
<?xml-stylesheet type='text/xsl' href='{$news_sitemap_xsl_url}'?>
1095
<urlset{$namespaces}>\n
1096
HEADER
1097
				,
1098
				<<<FOOTER
1099
</urlset>\n
1100
FOOTER
1101
				,
1102
				/* epoch */
1103
				'1970-01-01 00:00:00'
1104
			);
1105
1106
			$posts = $this->librarian->query_most_recent_posts( JP_NEWS_SITEMAP_MAX_ITEMS );
1107
1108
			foreach ( $posts as $post ) {
1109
				$current_item = $this->post_to_news_sitemap_item( $post );
1110
1111
				if ( false === $buffer->try_to_add_item( $current_item['xml'] ) ) {
1112
					break;
1113
				}
1114
			}
1115
1116
			if ( $this->logger ) {
1117
				$this->logger->time( 'End news sitemap generation.' );
1118
			}
1119
1120
			$the_stored_news_sitemap = $buffer->contents();
1121
1122
			set_transient(
1123
				'jetpack_news_sitemap_xml',
1124
				$the_stored_news_sitemap,
1125
				JP_NEWS_SITEMAP_INTERVAL
1126
			);
1127
		}
1128
1129
		return $the_stored_news_sitemap;
1130
	}
1131
1132
	/**
1133
	 * Construct the sitemap url entry for a WP_Post.
1134
	 *
1135
	 * @link http://www.sitemaps.org/protocol.html#urldef
1136
	 * @access private
1137
	 * @since 4.7.0
1138
	 *
1139
	 * @param WP_Post $post The post to be processed.
1140
	 *
1141
	 * @return string An XML fragment representing the post URL.
1142
	 */
1143
	private function post_to_sitemap_item( $post ) {
1144
1145
		/**
1146
		 * Filter condition to allow skipping specific posts in sitemap.
1147
		 *
1148
		 * @module sitemaps
1149
		 *
1150
		 * @since 3.9.0
1151
		 *
1152
		 * @param bool    $skip Current boolean. False by default, so no post is skipped.
1153
		 * @param WP_POST $post Current post object.
1154
		 */
1155
		if ( true === apply_filters( 'jetpack_sitemap_skip_post', false, $post ) ) {
1156
			return array(
1157
				'xml'           => null,
1158
				'last_modified' => null,
1159
			);
1160
		}
1161
1162
		$url = get_permalink( $post );
1163
1164
		/*
1165
		 * Spec requires the URL to be <=2048 bytes.
1166
		 * In practice this constraint is unlikely to be violated.
1167
		 */
1168
		if ( 2048 < strlen( $url ) ) {
1169
			$url = home_url() . '/?p=' . $post->ID;
1170
		}
1171
1172
		$last_modified = $post->post_modified_gmt;
1173
1174
		// Check for more recent comments.
1175
		// Note that 'Y-m-d h:i:s' strings sort lexicographically.
1176
		if ( 0 < $post->comment_count ) {
1177
			$last_modified = max(
1178
				$last_modified,
1179
				$this->librarian->query_latest_approved_comment_time_on_post( $post->ID )
1180
			);
1181
		}
1182
1183
		$item_array = array(
1184
			'url' => array(
1185
				'loc'     => $url,
1186
				'lastmod' => jp_sitemap_datetime( $last_modified ),
1187
			),
1188
		);
1189
1190
		/**
1191
		 * Filter sitemap URL item before rendering it as XML.
1192
		 *
1193
		 * @module sitemaps
1194
		 *
1195
		 * @since 3.9.0
1196
		 *
1197
		 * @param array $tree Associative array representing sitemap URL element.
1198
		 * @param int   $post_id ID of the post being processed.
1199
		 */
1200
		$item_array = apply_filters( 'jetpack_sitemap_url', $item_array, $post->ID );
1201
1202
		return array(
1203
			'xml'           => Jetpack_Sitemap_Buffer::array_to_xml_string( $item_array ),
1204
			'last_modified' => $last_modified,
1205
		);
1206
	}
1207
1208
	/**
1209
	 * Construct the image sitemap url entry for a WP_Post of image type.
1210
	 *
1211
	 * @link http://www.sitemaps.org/protocol.html#urldef
1212
	 *
1213
	 * @access private
1214
	 * @since 4.7.0
1215
	 *
1216
	 * @param WP_Post $post The image post to be processed.
1217
	 *
1218
	 * @return string An XML fragment representing the post URL.
1219
	 */
1220
	private function image_post_to_sitemap_item( $post ) {
1221
1222
		/**
1223
		 * Filter condition to allow skipping specific image posts in the sitemap.
1224
		 *
1225
		 * @module sitemaps
1226
		 *
1227
		 * @since 4.7.0
1228
		 *
1229
		 * @param bool    $skip Current boolean. False by default, so no post is skipped.
1230
		 * @param WP_POST $post Current post object.
1231
		 */
1232
		if ( apply_filters( 'jetpack_sitemap_image_skip_post', false, $post ) ) {
1233
			return array(
1234
				'xml'           => null,
1235
				'last_modified' => null,
1236
			);
1237
		}
1238
1239
		$url = wp_get_attachment_url( $post->ID );
1240
1241
		$parent_url = get_permalink( get_post( $post->post_parent ) );
1242
		if ( '' == $parent_url ) { // WPCS: loose comparison ok.
1243
			$parent_url = get_permalink( $post );
1244
		}
1245
1246
		$item_array = array(
1247
			'url' => array(
1248
				'loc'         => $parent_url,
1249
				'lastmod'     => jp_sitemap_datetime( $post->post_modified_gmt ),
1250
				'image:image' => array(
1251
					'image:loc' => $url,
1252
				),
1253
			),
1254
		);
1255
1256
		$title = esc_html( $post->post_title );
1257
		if ( '' !== $title ) {
1258
			$item_array['url']['image:image']['image:title'] = $title;
1259
		}
1260
1261
		$caption = esc_html( $post->post_excerpt );
1262
		if ( '' !== $caption ) {
1263
			$item_array['url']['image:image']['image:caption'] = $caption;
1264
		}
1265
1266
		/**
1267
		 * Filter associative array with data to build <url> node
1268
		 * and its descendants for current post in image sitemap.
1269
		 *
1270
		 * @module sitemaps
1271
		 *
1272
		 * @since 4.7.0
1273
		 *
1274
		 * @param array $item_array Data to build parent and children nodes for current post.
1275
		 * @param int   $post_id Current image post ID.
1276
		 */
1277
		$item_array = apply_filters(
1278
			'jetpack_sitemap_image_sitemap_item',
1279
			$item_array,
1280
			$post->ID
1281
		);
1282
1283
		return array(
1284
			'xml'           => Jetpack_Sitemap_Buffer::array_to_xml_string( $item_array ),
1285
			'last_modified' => $post->post_modified_gmt,
1286
		);
1287
	}
1288
1289
	/**
1290
	 * Construct the video sitemap url entry for a WP_Post of video type.
1291
	 *
1292
	 * @link http://www.sitemaps.org/protocol.html#urldef
1293
	 * @link https://developers.google.com/webmasters/videosearch/sitemaps
1294
	 *
1295
	 * @access private
1296
	 * @since 4.7.0
1297
	 *
1298
	 * @param WP_Post $post The video post to be processed.
1299
	 *
1300
	 * @return string An XML fragment representing the post URL.
1301
	 */
1302
	private function video_post_to_sitemap_item( $post ) {
1303
1304
		/**
1305
		 * Filter condition to allow skipping specific image posts in the sitemap.
1306
		 *
1307
		 * @module sitemaps
1308
		 *
1309
		 * @since 4.7.0
1310
		 *
1311
		 * @param bool    $skip Current boolean. False by default, so no post is skipped.
1312
		 * @param WP_POST $post Current post object.
1313
		 */
1314
		if ( apply_filters( 'jetpack_sitemap_video_skip_post', false, $post ) ) {
1315
			return array(
1316
				'xml'           => null,
1317
				'last_modified' => null,
1318
			);
1319
		}
1320
1321
		$parent_url = get_permalink( get_post( $post->post_parent ) );
1322
		if ( '' == $parent_url ) { // WPCS: loose comparison ok.
1323
			$parent_url = get_permalink( $post );
1324
		}
1325
1326
		$item_array = array(
1327
			'url' => array(
1328
				'loc'         => $parent_url,
1329
				'lastmod'     => jp_sitemap_datetime( $post->post_modified_gmt ),
1330
				'video:video' => array(
1331
					'video:title'         => esc_html( $post->post_title ),
1332
					'video:thumbnail_loc' => '',
1333
					'video:description'   => esc_html( $post->post_content ),
1334
					'video:content_loc'   => wp_get_attachment_url( $post->ID ),
1335
				),
1336
			),
1337
		);
1338
1339
		// TODO: Integrate with VideoPress here.
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...
1340
		// cf. video:player_loc tag in video sitemap spec.
1341
1342
		/**
1343
		 * Filter associative array with data to build <url> node
1344
		 * and its descendants for current post in video sitemap.
1345
		 *
1346
		 * @module sitemaps
1347
		 *
1348
		 * @since 4.7.0
1349
		 *
1350
		 * @param array $item_array Data to build parent and children nodes for current post.
1351
		 * @param int   $post_id Current video post ID.
1352
		 */
1353
		$item_array = apply_filters(
1354
			'jetpack_sitemap_video_sitemap_item',
1355
			$item_array,
1356
			$post->ID
1357
		);
1358
1359
		return array(
1360
			'xml'           => Jetpack_Sitemap_Buffer::array_to_xml_string( $item_array ),
1361
			'last_modified' => $post->post_modified_gmt,
1362
		);
1363
	}
1364
1365
	/**
1366
	 * Construct the news sitemap url entry for a WP_Post.
1367
	 *
1368
	 * @link http://www.sitemaps.org/protocol.html#urldef
1369
	 *
1370
	 * @access private
1371
	 * @since 4.7.0
1372
	 *
1373
	 * @param WP_Post $post The post to be processed.
1374
	 *
1375
	 * @return string An XML fragment representing the post URL.
1376
	 */
1377
	private function post_to_news_sitemap_item( $post ) {
1378
1379
		/**
1380
		 * Filter condition to allow skipping specific posts in news sitemap.
1381
		 *
1382
		 * @module sitemaps
1383
		 *
1384
		 * @since 3.9.0
1385
		 *
1386
		 * @param bool    $skip Current boolean. False by default, so no post is skipped.
1387
		 * @param WP_POST $post Current post object.
1388
		 */
1389
		if ( apply_filters( 'jetpack_sitemap_news_skip_post', false, $post ) ) {
1390
			return array(
1391
				'xml' => null,
1392
			);
1393
		}
1394
1395
		$url = get_permalink( $post );
1396
1397
		/*
1398
		 * Spec requires the URL to be <=2048 bytes.
1399
		 * In practice this constraint is unlikely to be violated.
1400
		 */
1401
		if ( 2048 < strlen( $url ) ) {
1402
			$url = home_url() . '/?p=' . $post->ID;
1403
		}
1404
1405
		/*
1406
		 * Trim the locale to an ISO 639 language code as required by Google.
1407
		 * Special cases are zh-cn (Simplified Chinese) and zh-tw (Traditional Chinese).
1408
		 * @link http://www.loc.gov/standards/iso639-2/php/code_list.php
1409
		 */
1410
		$language = strtolower( get_locale() );
1411
1412
		if ( in_array( $language, array( 'zh_tw', 'zh_cn' ), true ) ) {
1413
			$language = str_replace( '_', '-', $language );
1414
		} else {
1415
			$language = preg_replace( '/(_.*)$/i', '', $language );
1416
		}
1417
1418
		$item_array = array(
1419
			'url' => array(
1420
				'loc' => $url,
1421
				'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
1422
				'news:news' => array(
1423
					'news:publication' => array(
1424
						'news:name'     => esc_html( get_bloginfo( 'name' ) ),
1425
						'news:language' => $language,
1426
					),
1427
					'news:title'            => esc_html( $post->post_title ),
1428
					'news:publication_date' => jp_sitemap_datetime( $post->post_date_gmt ),
1429
					'news:genres'           => 'Blog',
1430
				),
1431
			),
1432
		);
1433
1434
		/**
1435
		 * Filter associative array with data to build <url> node
1436
		 * and its descendants for current post in news sitemap.
1437
		 *
1438
		 * @module sitemaps
1439
		 *
1440
		 * @since 3.9.0
1441
		 *
1442
		 * @param array $item_array Data to build parent and children nodes for current post.
1443
		 * @param int   $post_id Current post ID.
1444
		 */
1445
		$item_array = apply_filters(
1446
			'jetpack_sitemap_news_sitemap_item',
1447
			$item_array,
1448
			$post->ID
1449
		);
1450
1451
		return array(
1452
			'xml' => Jetpack_Sitemap_Buffer::array_to_xml_string( $item_array ),
1453
		);
1454
	}
1455
1456
}
1457