Completed
Push — update/sync-sitemaps-with-wpco... ( 3b0737 )
by
unknown
09:30
created

Jetpack_Sitemap_Builder::build_one_page_sitemap()   D

Complexity

Conditions 17
Paths 80

Size

Total Lines 153
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 69
nc 80
nop 2
dl 0
loc 153
rs 4.8361
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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.8.0
7
 * @author Automattic
8
 */
9
10
require_once dirname( __FILE__ ) . '/sitemap-constants.php';
11
require_once dirname( __FILE__ ) . '/sitemap-buffer.php';
12
13
if ( ! class_exists( 'DOMDocument' ) ) {
14
	require_once dirname( __FILE__ ) . '/sitemap-buffer-fallback.php';
15
	require_once dirname( __FILE__ ) . '/sitemap-buffer-image-fallback.php';
16
	require_once dirname( __FILE__ ) . '/sitemap-buffer-master-fallback.php';
17
	require_once dirname( __FILE__ ) . '/sitemap-buffer-news-fallback.php';
18
	require_once dirname( __FILE__ ) . '/sitemap-buffer-page-fallback.php';
19
	require_once dirname( __FILE__ ) . '/sitemap-buffer-video-fallback.php';
20
} else {
21
	require_once dirname( __FILE__ ) . '/sitemap-buffer-image.php';
22
	require_once dirname( __FILE__ ) . '/sitemap-buffer-master.php';
23
	require_once dirname( __FILE__ ) . '/sitemap-buffer-news.php';
24
	require_once dirname( __FILE__ ) . '/sitemap-buffer-page.php';
25
	require_once dirname( __FILE__ ) . '/sitemap-buffer-video.php';
26
}
27
28
require_once dirname( __FILE__ ) . '/sitemap-librarian.php';
29
require_once dirname( __FILE__ ) . '/sitemap-finder.php';
30
require_once dirname( __FILE__ ) . '/sitemap-state.php';
31
32
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
33
	require_once dirname( __FILE__ ) . '/sitemap-logger.php';
34
}
35
36
/**
37
 * The Jetpack_Sitemap_Builder object handles the construction of
38
 * all sitemap files (except the XSL files, which are handled by
39
 * Jetpack_Sitemap_Stylist.) Other than the constructor, there are
40
 * only two public functions: build_all_sitemaps and news_sitemap_xml.
41
 *
42
 * @since 4.8.0
43
 */
44
class Jetpack_Sitemap_Builder {
45
46
	/**
47
	 * Librarian object for storing and retrieving sitemap data.
48
	 *
49
	 * @access private
50
	 * @since 4.8.0
51
	 * @var $librarian Jetpack_Sitemap_Librarian
52
	 */
53
	private $librarian;
54
55
	/**
56
	 * Logger object for reporting debug messages.
57
	 *
58
	 * @access private
59
	 * @since 4.8.0
60
	 * @var $logger Jetpack_Sitemap_Logger
61
	 */
62
	private $logger = false;
63
64
	/**
65
	 * Finder object for dealing with sitemap URIs.
66
	 *
67
	 * @access private
68
	 * @since 4.8.0
69
	 * @var $finder Jetpack_Sitemap_Finder
70
	 */
71
	private $finder;
72
73
	/**
74
	 * Construct a new Jetpack_Sitemap_Builder object.
75
	 *
76
	 * @access public
77
	 * @since 4.8.0
78
	 */
79
	public function __construct() {
80
		$this->librarian = new Jetpack_Sitemap_Librarian();
81
		$this->finder = new Jetpack_Sitemap_Finder();
82
83
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
84
			$this->logger = new Jetpack_Sitemap_Logger();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Jetpack_Sitemap_Logger() of type object<Jetpack_Sitemap_Logger> is incompatible with the declared type boolean of property $logger.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
85
		}
86
87
		update_option(
88
			'jetpack_sitemap_post_types',
89
			/**
90
			 * The array of post types to be included in the sitemap.
91
			 *
92
			 * Add your custom post type name to the array to have posts of
93
			 * that type included in the sitemap. The default array includes
94
			 * 'page' and 'post'.
95
			 *
96
			 * The result of this filter is cached in an option, 'jetpack_sitemap_post_types',
97
			 * so this filter only has to be applied once per generation.
98
			 *
99
			 * @since 4.8.0
100
			 */
101
			apply_filters(
102
				'jetpack_sitemap_post_types',
103
				array( 'post', 'page' )
104
			)
105
		);
106
	}
107
108
	/**
109
	 * Update the sitemap.
110
	 *
111
	 * All we do here is call build_next_sitemap_file a bunch of times.
112
	 *
113
	 * @since 4.8.0
114
	 */
115
	public function update_sitemap() {
116
		if ( $this->logger ) {
117
			$this->logger->report( '-- Updating...' );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
118
			if ( ! class_exists( 'DOMDocument' ) ) {
119
				$this->logger->report(
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
120
					__(
121
						'-- WARNING: Jetpack can not load necessary XML manipulation libraries. '
122
						. 'This can happen if XML support in PHP is not enabled on your server. '
123
						. 'XML support is highly recommended for WordPress and Jetpack, please enable '
124
						. 'it or contact your hosting provider about it.',
125
						'jetpack'
126
					),
127
					true
128
				);
129
			}
130
		}
131
132
		for ( $i = 1; $i <= JP_SITEMAP_UPDATE_SIZE; $i++ ) {
133
			if ( true === $this->build_next_sitemap_file() ) {
134
				break; // All finished!
135
			}
136
		}
137
138
		if ( $this->logger ) {
139
			$this->logger->report( '-- ...done for now.' );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
140
			$this->logger->time();
0 ignored issues
show
Bug introduced by
The method time cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
141
		}
142
	}
143
144
	/**
145
	 * Generate the next sitemap file.
146
	 *
147
	 * Reads the most recent state of the sitemap generation phase,
148
	 * constructs the next file, and updates the state.
149
	 *
150
	 * @since 4.8.0
151
	 *
152
	 * @return bool True when finished.
153
	 */
154
	private function build_next_sitemap_file() {
155
		$finished = false; // Initialize finished flag.
156
157
		// Get the most recent state, and lock the state.
158
		$state = Jetpack_Sitemap_State::check_out();
159
160
		// Do nothing if the state was locked.
161
		if ( false === $state ) {
162
			return false;
163
		}
164
165
		// Otherwise, branch on the sitemap-type key of $state.
166
		switch ( $state['sitemap-type'] ) {
167
			case JP_PAGE_SITEMAP_TYPE:
168
				$this->build_next_sitemap_of_type(
169
					JP_PAGE_SITEMAP_TYPE,
170
					array( $this, 'build_one_page_sitemap' ),
171
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 158 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_PAGE_SITEMAP_INDEX_TYPE:
176
				$this->build_next_sitemap_index_of_type(
177
					JP_PAGE_SITEMAP_INDEX_TYPE,
178
					JP_IMAGE_SITEMAP_TYPE,
179
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 158 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_IMAGE_SITEMAP_TYPE:
184
				$this->build_next_sitemap_of_type(
185
					JP_IMAGE_SITEMAP_TYPE,
186
					array( $this, 'build_one_image_sitemap' ),
187
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 158 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...
188
				);
189
				break;
190
191
			case JP_IMAGE_SITEMAP_INDEX_TYPE:
192
				$this->build_next_sitemap_index_of_type(
193
					JP_IMAGE_SITEMAP_INDEX_TYPE,
194
					JP_VIDEO_SITEMAP_TYPE,
195
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 158 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...
196
				);
197
				break;
198
199
			case JP_VIDEO_SITEMAP_TYPE:
200
				$this->build_next_sitemap_of_type(
201
					JP_VIDEO_SITEMAP_TYPE,
202
					array( $this, 'build_one_video_sitemap' ),
203
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 158 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...
204
				);
205
				break;
206
207
			case JP_VIDEO_SITEMAP_INDEX_TYPE:
208
				$this->build_next_sitemap_index_of_type(
209
					JP_VIDEO_SITEMAP_INDEX_TYPE,
210
					JP_MASTER_SITEMAP_TYPE,
211
					$state
0 ignored issues
show
Bug introduced by
It seems like $state defined by \Jetpack_Sitemap_State::check_out() on line 158 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...
212
				);
213
				break;
214
215
			case JP_MASTER_SITEMAP_TYPE:
216
				$this->build_master_sitemap( $state['max'] );
217
218
				// Reset the state and quit.
219
				Jetpack_Sitemap_State::reset(
220
					JP_PAGE_SITEMAP_TYPE
221
				);
222
223
				if ( $this->logger ) {
224
					$this->logger->report( '-- Finished.' );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
225
					$this->logger->time();
0 ignored issues
show
Bug introduced by
The method time cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
226
				}
227
				$finished = true;
228
229
				break;
230
231
			default:
232
				Jetpack_Sitemap_State::reset(
233
					JP_PAGE_SITEMAP_TYPE
234
				);
235
				$finished = true;
236
237
				break;
238
		} // End switch().
239
240
		// Unlock the state.
241
		Jetpack_Sitemap_State::unlock();
242
243
		return $finished;
244
	}
245
246
	/**
247
	 * Build the next sitemap of a given type and update the sitemap state.
248
	 *
249
	 * @since 4.8.0
250
	 *
251
	 * @param string   $sitemap_type The type of the sitemap being generated.
252
	 * @param callback $build_one    A callback which builds a single sitemap file.
253
	 * @param array    $state        A sitemap state.
254
	 */
255
	private function build_next_sitemap_of_type( $sitemap_type, $build_one, $state ) {
256
		$index_type = jp_sitemap_index_type_of( $sitemap_type );
257
258
		// Try to build a sitemap.
259
		$result = call_user_func_array(
260
			$build_one,
261
			array(
262
				$state['number'] + 1,
263
				$state['last-added'],
264
			)
265
		);
266
267 View Code Duplication
		if ( false === $result ) {
268
			// If no sitemap was generated, advance to the next type.
269
			Jetpack_Sitemap_State::check_in( array(
270
				'sitemap-type'  => $index_type,
271
				'last-added'    => 0,
272
				'number'        => 0,
273
				'last-modified' => '1970-01-01 00:00:00',
274
			) );
275
276
			if ( $this->logger ) {
277
				$this->logger->report( "-- Cleaning Up $sitemap_type" );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
278
			}
279
280
			// Clean up old files.
281
			$this->librarian->delete_numbered_sitemap_rows_after(
282
				$state['number'], $sitemap_type
283
			);
284
285
			return;
286
		}
287
288
		// Otherwise, update the state.
289
		Jetpack_Sitemap_State::check_in( array(
290
			'sitemap-type'  => $state['sitemap-type'],
291
			'last-added'    => $result['last_id'],
292
			'number'        => $state['number'] + 1,
293
			'last-modified' => $result['last_modified'],
294
		) );
295
296
		if ( true === $result['any_left'] ) {
297
			// If there's more work to be done with this type, return.
298
			return;
299
		}
300
301
		// Otherwise, advance state to the next sitemap type.
302
		Jetpack_Sitemap_State::check_in( array(
303
			'sitemap-type'  => $index_type,
304
			'last-added'    => 0,
305
			'number'        => 0,
306
			'last-modified' => '1970-01-01 00:00:00',
307
		) );
308
309
		if ( $this->logger ) {
310
			$this->logger->report( "-- Cleaning Up $sitemap_type" );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
311
		}
312
313
		// Clean up old files.
314
		$this->librarian->delete_numbered_sitemap_rows_after(
315
			$state['number'] + 1, $sitemap_type
316
		);
317
	}
318
319
	/**
320
	 * Build the next sitemap index of a given type and update the state.
321
	 *
322
	 * @since 4.8.0
323
	 *
324
	 * @param string $index_type The type of index being generated.
325
	 * @param string $next_type  The next type to generate after this one.
326
	 * @param array  $state      A sitemap state.
327
	 */
328
	private function build_next_sitemap_index_of_type( $index_type, $next_type, $state ) {
329
		$sitemap_type = jp_sitemap_child_type_of( $index_type );
330
331
		// If only 0 or 1 sitemaps were built, advance to the next type and return.
332
		if ( 1 >= $state['max'][ $sitemap_type ]['number'] ) {
333
			Jetpack_Sitemap_State::check_in( array(
334
				'sitemap-type'  => $next_type,
335
				'last-added'    => 0,
336
				'number'        => 0,
337
				'last-modified' => '1970-01-01 00:00:00',
338
			) );
339
340
			if ( $this->logger ) {
341
				$this->logger->report( "-- Cleaning Up $index_type" );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
342
			}
343
344
			// There are no indices of this type.
345
			$this->librarian->delete_numbered_sitemap_rows_after(
346
				0, $index_type
347
			);
348
349
			return;
350
		}
351
352
		// Otherwise, try to build a sitemap index.
353
		$result = $this->build_one_sitemap_index(
354
			$state['number'] + 1,
355
			$state['last-added'],
356
			$state['last-modified'],
357
			$index_type
358
		);
359
360
		// If no index was built, advance to the next type and return.
361 View Code Duplication
		if ( false === $result ) {
362
			Jetpack_Sitemap_State::check_in( array(
363
				'sitemap-type'  => $next_type,
364
				'last-added'    => 0,
365
				'number'        => 0,
366
				'last-modified' => '1970-01-01 00:00:00',
367
			) );
368
369
			if ( $this->logger ) {
370
				$this->logger->report( "-- Cleaning Up $index_type" );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
371
			}
372
373
			// Clean up old files.
374
			$this->librarian->delete_numbered_sitemap_rows_after(
375
				$state['number'], $index_type
376
			);
377
378
			return;
379
		}
380
381
		// Otherwise, check in the state.
382
		Jetpack_Sitemap_State::check_in( array(
383
			'sitemap-type'  => $index_type,
384
			'last-added'    => $result['last_id'],
385
			'number'        => $state['number'] + 1,
386
			'last-modified' => $result['last_modified'],
387
		) );
388
389
		// If there are still sitemaps left to index, return.
390
		if ( true === $result['any_left'] ) {
391
			return;
392
		}
393
394
		// Otherwise, advance to the next type.
395
		Jetpack_Sitemap_State::check_in( array(
396
			'sitemap-type'  => $next_type,
397
			'last-added'    => 0,
398
			'number'        => 0,
399
			'last-modified' => '1970-01-01 00:00:00',
400
		) );
401
402
		if ( $this->logger ) {
403
			$this->logger->report( "-- Cleaning Up $index_type" );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
404
		}
405
406
		// We're done generating indices of this type.
407
		$this->librarian->delete_numbered_sitemap_rows_after(
408
			$state['number'] + 1, $index_type
409
		);
410
411
		return;
412
	}
413
414
	/**
415
	 * Builds the master sitemap index.
416
	 *
417
	 * @param array $max Array of sitemap types with max index and datetime.
418
	 *
419
	 * @since 4.8.0
420
	 */
421
	private function build_master_sitemap( $max ) {
422
		if ( $this->logger ) {
423
			$this->logger->report( '-- Building Master Sitemap.' );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
424
		}
425
426
		$buffer = new Jetpack_Sitemap_Buffer_Master(
427
			JP_SITEMAP_MAX_ITEMS,
428
			JP_SITEMAP_MAX_BYTES
429
		);
430
431 View Code Duplication
		if ( 0 < $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) {
432
			if ( 1 === $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) {
433
				$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...
434
				$page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_TYPE ]['lastmod'] );
435
			} else {
436
				$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...
437
					JP_PAGE_SITEMAP_INDEX_TYPE,
438
					$max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['number']
439
				);
440
				$page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
441
			}
442
443
			$buffer->append(
444
				array(
445
					'sitemap' => array(
446
						'loc'     => $this->finder->construct_sitemap_url( $page['filename'] ),
447
						'lastmod' => $page['last_modified'],
448
					),
449
				)
450
			);
451
		}
452
453 View Code Duplication
		if ( 0 < $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
454
			if ( 1 === $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
455
				$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...
456
				$image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_TYPE ]['lastmod'] );
457
			} else {
458
				$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...
459
					JP_IMAGE_SITEMAP_INDEX_TYPE,
460
					$max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['number']
461
				);
462
				$image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
463
			}
464
465
			$buffer->append(
466
				array(
467
					'sitemap' => array(
468
						'loc'     => $this->finder->construct_sitemap_url( $image['filename'] ),
469
						'lastmod' => $image['last_modified'],
470
					),
471
				)
472
			);
473
		}
474
475 View Code Duplication
		if ( 0 < $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
476
			if ( 1 === $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
477
				$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...
478
				$video['last_modified'] = $max[ JP_VIDEO_SITEMAP_TYPE ]['lastmod'];
479
			} else {
480
				$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...
481
					JP_VIDEO_SITEMAP_INDEX_TYPE,
482
					$max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['number']
483
				);
484
				$video['last_modified'] = $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['lastmod'];
485
			}
486
487
			$buffer->append(
488
				array(
489
					'sitemap' => array(
490
						'loc'     => $this->finder->construct_sitemap_url( $video['filename'] ),
491
						'lastmod' => $video['last_modified'],
492
					),
493
				)
494
			);
495
		}
496
497
		$this->librarian->store_sitemap_data(
498
			0,
499
			JP_MASTER_SITEMAP_TYPE,
500
			$buffer->contents(),
501
			''
502
		);
503
	}
504
505
	/**
506
	 * Build and store a single page sitemap. Returns false if no sitemap is built.
507
	 *
508
	 * Side effect: Create/update a sitemap row.
509
	 *
510
	 * @access private
511
	 * @since 4.8.0
512
	 *
513
	 * @param int $number The number of the current sitemap.
514
	 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
515
	 *
516
	 * @return bool|array @args {
517
	 *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
518
	 *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
519
	 *   @type string $last_modified The most recent timestamp to appear on the sitemap.
520
	 * }
521
	 */
522
	public function build_one_page_sitemap( $number, $from_id ) {
523
		$last_post_id   = $from_id;
524
		$any_posts_left = true;
525
526
		if ( $this->logger ) {
527
			$debug_name = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, $number );
528
			$this->logger->report( "-- Building $debug_name" );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
529
		}
530
531
		$buffer = new Jetpack_Sitemap_Buffer_Page(
532
			JP_SITEMAP_MAX_ITEMS,
533
			JP_SITEMAP_MAX_BYTES
534
		);
535
536
		// Add entry for the main page (only if we're at the first one).
537
		if ( 1 === $number ) {
538
			$item_array = array(
539
				'url' => array(
540
					'loc' => home_url(),
541
				),
542
			);
543
544
			/**
545
			 * Filter associative array with data to build <url> node
546
			 * and its descendants for site home.
547
			 *
548
			 * @module sitemaps
549
			 *
550
			 * @since 3.9.0
551
			 *
552
			 * @param array $blog_home Data to build parent and children nodes for site home.
553
			 */
554
			$item_array = apply_filters( 'jetpack_sitemap_url_home', $item_array );
555
556
			$buffer->append( $item_array );
557
		}
558
559
		// Add as many items to the buffer as possible.
560
		while ( $last_post_id >= 0 && false === $buffer->is_full() ) {
561
			$posts = $this->librarian->query_posts_after_id(
562
				$last_post_id, JP_SITEMAP_BATCH_SIZE
563
			);
564
565
			if ( null == $posts ) { // WPCS: loose comparison ok.
566
				$any_posts_left = false;
567
				break;
568
			}
569
570
			foreach ( $posts as $post ) {
571
				$current_item = $this->post_to_sitemap_item( $post );
572
573
				if ( true === $buffer->append( $current_item['xml'] ) ) {
0 ignored issues
show
Documentation introduced by
$current_item['xml'] is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
574
					$last_post_id = $post->ID;
575
					$buffer->view_time( $current_item['last_modified'] );
576
				} else {
577
					break;
578
				}
579
			}
580
		}
581
582
		// Handle other page sitemap URLs.
583
		if ( false === $any_posts_left || $last_post_id < 0 ) {
584
			// Negative IDs are used to track URL indexes.
585
			$last_post_id   = min( 0, $last_post_id );
586
			$any_posts_left = true; // Reinitialize.
587
588
			/**
589
			 * Filter other page sitemap URLs.
590
			 *
591
			 * @module sitemaps
592
			 *
593
			 * @since 6.1.0
594
			 *
595
			 * @param array $urls An array of other URLs.
596
			 */
597
			$other_urls = apply_filters( 'jetpack_page_sitemap_other_urls', array() );
598
599
			if ( $other_urls ) { // Start with index [1].
600
				$other_urls = array_values( $other_urls );
601
				array_unshift( $other_urls, $other_urls[0] );
602
				unset( $other_urls[0] );
603
			}
604
605
			// Add as many items to the buffer as possible.
606
			while ( false === $buffer->is_full() ) {
607
				$last_post_id_index = abs( $last_post_id );
608
				$urls               = array_slice(
609
					$other_urls,
610
					$last_post_id_index + 1,
611
					JP_SITEMAP_BATCH_SIZE,
612
					true
613
				);
614
615
				if ( ! $urls ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $urls 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...
616
					$any_posts_left = false;
617
					break;
618
				}
619
620
				foreach ( $urls as $index => $url ) {
621
					if ( ! is_array( $url ) ) {
622
						$url = array( 'loc' => $url );
623
					}
624
					$item = array( 'xml' => compact( 'url' ) );
625
626
					if ( true === $buffer->append( $item['xml'] ) ) {
627
						$last_post_id = -$index;
628
					} else {
629
						break;
630
					}
631
				}
632
			}
633
		}
634
635
		// If no items were added, return false.
636
		if ( true === $buffer->is_empty() ) {
637
			return false;
638
		}
639
640
		/**
641
		 * Filter sitemap before rendering it as XML.
642
		 *
643
		 * @module sitemaps
644
		 *
645
		 * @since 3.9.0
646
		 * @since 5.3.0 returns an element of DOMDocument type instead of SimpleXMLElement
647
		 *
648
		 * @param DOMDocument      $doc Data tree for sitemap.
649
		 * @param string           $last_modified Date of last modification.
650
		 */
651
		$tree = apply_filters(
0 ignored issues
show
Unused Code introduced by
$tree is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
652
			'jetpack_print_sitemap',
653
			$buffer->get_document(),
654
			$buffer->last_modified()
655
		);
656
657
		// Store the buffer as the content of a sitemap row.
658
		$this->librarian->store_sitemap_data(
659
			$number,
660
			JP_PAGE_SITEMAP_TYPE,
661
			$buffer->contents(),
662
			$buffer->last_modified()
663
		);
664
665
		/*
666
		 * Now report back with the ID of the last post ID to be
667
		 * successfully added and whether there are any posts left.
668
		 */
669
		return array(
670
			'last_id'       => $last_post_id,
671
			'any_left'      => $any_posts_left,
672
			'last_modified' => $buffer->last_modified(),
673
		);
674
	}
675
676
	/**
677
	 * Build and store a single image sitemap. Returns false if no sitemap is built.
678
	 *
679
	 * Side effect: Create/update an image sitemap row.
680
	 *
681
	 * @access private
682
	 * @since 4.8.0
683
	 *
684
	 * @param int $number The number of the current sitemap.
685
	 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
686
	 *
687
	 * @return bool|array @args {
688
	 *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
689
	 *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
690
	 *   @type string $last_modified The most recent timestamp to appear on the sitemap.
691
	 * }
692
	 */
693 View Code Duplication
	public function build_one_image_sitemap( $number, $from_id ) {
694
		$last_post_id = $from_id;
695
		$any_posts_left = true;
696
697
		if ( $this->logger ) {
698
			$debug_name = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, $number );
699
			$this->logger->report( "-- Building $debug_name" );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
700
		}
701
702
		$buffer = new Jetpack_Sitemap_Buffer_Image(
703
			JP_SITEMAP_MAX_ITEMS,
704
			JP_SITEMAP_MAX_BYTES
705
		);
706
707
		// Add as many items to the buffer as possible.
708
		while ( false === $buffer->is_full() ) {
709
			$posts = $this->librarian->query_images_after_id(
710
				$last_post_id, JP_SITEMAP_BATCH_SIZE
711
			);
712
713
			if ( null == $posts ) { // WPCS: loose comparison ok.
714
				$any_posts_left = false;
715
				break;
716
			}
717
718
			foreach ( $posts as $post ) {
719
				$current_item = $this->image_post_to_sitemap_item( $post );
720
721
				if ( true === $buffer->append( $current_item['xml'] ) ) {
0 ignored issues
show
Documentation introduced by
$current_item['xml'] is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
722
					$last_post_id = $post->ID;
723
					$buffer->view_time( $current_item['last_modified'] );
724
				} else {
725
					break;
726
				}
727
			}
728
		}
729
730
		// If no items were added, return false.
731
		if ( true === $buffer->is_empty() ) {
732
			return false;
733
		}
734
735
		// Store the buffer as the content of a jp_sitemap post.
736
		$this->librarian->store_sitemap_data(
737
			$number,
738
			JP_IMAGE_SITEMAP_TYPE,
739
			$buffer->contents(),
740
			$buffer->last_modified()
741
		);
742
743
		/*
744
		 * Now report back with the ID of the last post to be
745
		 * successfully added and whether there are any posts left.
746
		 */
747
		return array(
748
			'last_id'       => $last_post_id,
749
			'any_left'      => $any_posts_left,
750
			'last_modified' => $buffer->last_modified(),
751
		);
752
	}
753
754
	/**
755
	 * Build and store a single video sitemap. Returns false if no sitemap is built.
756
	 *
757
	 * Side effect: Create/update an video sitemap row.
758
	 *
759
	 * @access private
760
	 * @since 4.8.0
761
	 *
762
	 * @param int $number The number of the current sitemap.
763
	 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
764
	 *
765
	 * @return bool|array @args {
766
	 *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
767
	 *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
768
	 *   @type string $last_modified The most recent timestamp to appear on the sitemap.
769
	 * }
770
	 */
771 View Code Duplication
	public function build_one_video_sitemap( $number, $from_id ) {
772
		$last_post_id = $from_id;
773
		$any_posts_left = true;
774
775
		if ( $this->logger ) {
776
			$debug_name = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, $number );
777
			$this->logger->report( "-- Building $debug_name" );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
778
		}
779
780
		$buffer = new Jetpack_Sitemap_Buffer_Video(
781
			JP_SITEMAP_MAX_ITEMS,
782
			JP_SITEMAP_MAX_BYTES
783
		);
784
785
		// Add as many items to the buffer as possible.
786
		while ( false === $buffer->is_full() ) {
787
			$posts = $this->librarian->query_videos_after_id(
788
				$last_post_id, JP_SITEMAP_BATCH_SIZE
789
			);
790
791
			if ( null == $posts ) { // WPCS: loose comparison ok.
792
				$any_posts_left = false;
793
				break;
794
			}
795
796
			foreach ( $posts as $post ) {
797
				$current_item = $this->video_post_to_sitemap_item( $post );
798
799
				if ( true === $buffer->append( $current_item['xml'] ) ) {
0 ignored issues
show
Documentation introduced by
$current_item['xml'] is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
800
					$last_post_id = $post->ID;
801
					$buffer->view_time( $current_item['last_modified'] );
802
				} else {
803
					break;
804
				}
805
			}
806
		}
807
808
		// If no items were added, return false.
809
		if ( true === $buffer->is_empty() ) {
810
			return false;
811
		}
812
813
		if ( false === $buffer->is_empty() ) {
814
			$this->librarian->store_sitemap_data(
815
				$number,
816
				JP_VIDEO_SITEMAP_TYPE,
817
				$buffer->contents(),
818
				$buffer->last_modified()
819
			);
820
		}
821
822
		/*
823
		 * Now report back with the ID of the last post to be
824
		 * successfully added and whether there are any posts left.
825
		 */
826
		return array(
827
			'last_id'       => $last_post_id,
828
			'any_left'      => $any_posts_left,
829
			'last_modified' => $buffer->last_modified(),
830
		);
831
	}
832
833
	/**
834
	 * Build and store a single page sitemap index. Return false if no index is built.
835
	 *
836
	 * Side effect: Create/update a sitemap index row.
837
	 *
838
	 * @access private
839
	 * @since 4.8.0
840
	 *
841
	 * @param int    $number     The number of the current sitemap index.
842
	 * @param int    $from_id    The greatest lower bound of the IDs of the sitemaps to be included.
843
	 * @param string $datetime   Datetime of previous sitemap in 'YYYY-MM-DD hh:mm:ss' format.
844
	 * @param string $index_type Sitemap index type.
845
	 *
846
	 * @return bool|array @args {
847
	 *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
848
	 *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
849
	 *   @type string $last_modified The most recent timestamp to appear on the sitemap.
850
	 * }
851
	 */
852
	private function build_one_sitemap_index( $number, $from_id, $datetime, $index_type ) {
853
		$last_sitemap_id   = $from_id;
854
		$any_sitemaps_left = true;
855
856
		// Check the datetime format.
857
		$datetime = jp_sitemap_datetime( $datetime );
858
859
		$sitemap_type = jp_sitemap_child_type_of( $index_type );
860
861
		if ( $this->logger ) {
862
			$index_debug_name = jp_sitemap_filename( $index_type, $number );
863
			$this->logger->report( "-- Building $index_debug_name" );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
864
		}
865
866
		$buffer = new Jetpack_Sitemap_Buffer_Master(
867
			JP_SITEMAP_MAX_ITEMS,
868
			JP_SITEMAP_MAX_BYTES,
869
			$datetime
870
		);
871
872
		// Add pointer to the previous sitemap index (unless we're at the first one).
873
		if ( 1 !== $number ) {
874
			$i = $number - 1;
875
			$prev_index_url = $this->finder->construct_sitemap_url(
876
				jp_sitemap_filename( $index_type, $i )
877
			);
878
879
			$item_array = array(
880
				'sitemap' => array(
881
					'loc'     => $prev_index_url,
882
					'lastmod' => $datetime,
883
				),
884
			);
885
886
			$buffer->append( $item_array );
887
		}
888
889
		// Add as many items to the buffer as possible.
890
		while ( false === $buffer->is_full() ) {
891
			// Retrieve a batch of posts (in order).
892
			$posts = $this->librarian->query_sitemaps_after_id(
893
				$sitemap_type, $last_sitemap_id, JP_SITEMAP_BATCH_SIZE
894
			);
895
896
			// If there were no posts to get, make a note.
897
			if ( null == $posts ) { // WPCS: loose comparison ok.
898
				$any_sitemaps_left = false;
899
				break;
900
			}
901
902
			// Otherwise, loop through each post in the batch.
903
			foreach ( $posts as $post ) {
904
				// Generate the sitemap XML for the post.
905
				$current_item = $this->sitemap_row_to_index_item( (array) $post );
906
907
				// Try adding this item to the buffer.
908
				if ( true === $buffer->append( $current_item['xml'] ) ) {
909
					$last_sitemap_id = $post['ID'];
910
					$buffer->view_time( $current_item['last_modified'] );
911
				} else {
912
					// Otherwise stop looping through posts.
913
					break;
914
				}
915
			}
916
		}
917
918
		// If no items were added, return false.
919
		if ( true === $buffer->is_empty() ) {
920
			return false;
921
		}
922
923
		$this->librarian->store_sitemap_data(
924
			$number,
925
			$index_type,
926
			$buffer->contents(),
927
			$buffer->last_modified()
928
		);
929
930
		/*
931
		 * Now report back with the ID of the last sitemap post ID to
932
		 * be successfully added, whether there are any sitemap posts
933
		 * left, and the most recent modification time seen.
934
		 */
935
		return array(
936
			'last_id'       => $last_sitemap_id,
937
			'any_left'      => $any_sitemaps_left,
938
			'last_modified' => $buffer->last_modified(),
939
		);
940
	}
941
942
	/**
943
	 * Construct the sitemap index url entry for a sitemap row.
944
	 *
945
	 * @link http://www.sitemaps.org/protocol.html#sitemapIndex_sitemap
946
	 *
947
	 * @access private
948
	 * @since 4.8.0
949
	 *
950
	 * @param array $row The sitemap data to be processed.
951
	 *
952
	 * @return string An XML fragment representing the post URL.
953
	 */
954
	private function sitemap_row_to_index_item( $row ) {
955
		$url = $this->finder->construct_sitemap_url( $row['post_title'] );
956
957
		$item_array = array(
958
			'sitemap' => array(
959
				'loc'     => $url,
960
				'lastmod' => jp_sitemap_datetime( $row['post_date'] ),
961
			),
962
		);
963
964
		return array(
965
			'xml'           => $item_array,
966
			'last_modified' => $row['post_date'],
967
		);
968
	}
969
970
	/**
971
	 * Build and return the news sitemap xml. Note that the result of this
972
	 * function is cached in the transient 'jetpack_news_sitemap_xml'.
973
	 *
974
	 * @access public
975
	 * @since 4.8.0
976
	 *
977
	 * @return string The news sitemap xml.
978
	 */
979
	public function news_sitemap_xml() {
980
		$the_stored_news_sitemap = get_transient( 'jetpack_news_sitemap_xml' );
981
982
		if ( false === $the_stored_news_sitemap ) {
983
984
			if ( $this->logger ) {
985
				$this->logger->report( 'Beginning news sitemap generation.' );
0 ignored issues
show
Bug introduced by
The method report cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
986
			}
987
988
			/**
989
			 * Filter limit of entries to include in news sitemap.
990
			 *
991
			 * @module sitemaps
992
			 *
993
			 * @since 3.9.0
994
			 *
995
			 * @param int $count Number of entries to include in news sitemap.
996
			 */
997
			$item_limit = apply_filters(
998
				'jetpack_sitemap_news_sitemap_count',
999
				JP_NEWS_SITEMAP_MAX_ITEMS
1000
			);
1001
1002
			$buffer = new Jetpack_Sitemap_Buffer_News(
1003
				min( $item_limit, JP_NEWS_SITEMAP_MAX_ITEMS ),
1004
				JP_SITEMAP_MAX_BYTES
1005
			);
1006
1007
			$posts = $this->librarian->query_most_recent_posts( JP_NEWS_SITEMAP_MAX_ITEMS );
1008
1009
			foreach ( $posts as $post ) {
1010
				$current_item = $this->post_to_news_sitemap_item( $post );
1011
1012
				if ( false === $buffer->append( $current_item['xml'] ) ) {
0 ignored issues
show
Documentation introduced by
$current_item['xml'] is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1013
					break;
1014
				}
1015
			}
1016
1017
			if ( $this->logger ) {
1018
				$this->logger->time( 'End news sitemap generation.' );
0 ignored issues
show
Bug introduced by
The method time cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1019
			}
1020
1021
			$the_stored_news_sitemap = $buffer->contents();
1022
1023
			set_transient(
1024
				'jetpack_news_sitemap_xml',
1025
				$the_stored_news_sitemap,
1026
				JP_NEWS_SITEMAP_INTERVAL
1027
			);
1028
		} // End if().
1029
1030
		return $the_stored_news_sitemap;
1031
	}
1032
1033
	/**
1034
	 * Construct the sitemap url entry for a WP_Post.
1035
	 *
1036
	 * @link http://www.sitemaps.org/protocol.html#urldef
1037
	 * @access private
1038
	 * @since 4.8.0
1039
	 *
1040
	 * @param WP_Post $post The post to be processed.
1041
	 *
1042
	 * @return array An array representing the post URL.
1043
	 */
1044
	private function post_to_sitemap_item( $post ) {
1045
1046
		/**
1047
		 * Filter condition to allow skipping specific posts in sitemap.
1048
		 *
1049
		 * @module sitemaps
1050
		 *
1051
		 * @since 3.9.0
1052
		 *
1053
		 * @param bool    $skip Current boolean. False by default, so no post is skipped.
1054
		 * @param WP_POST $post Current post object.
1055
		 */
1056
		if ( true === apply_filters( 'jetpack_sitemap_skip_post', false, $post ) ) {
1057
			return array(
1058
				'xml'           => null,
1059
				'last_modified' => null,
1060
			);
1061
		}
1062
1063
		$url = esc_url( get_permalink( $post ) );
1064
1065
		/*
1066
		 * Spec requires the URL to be <=2048 bytes.
1067
		 * In practice this constraint is unlikely to be violated.
1068
		 */
1069
		if ( 2048 < strlen( $url ) ) {
1070
			$url = home_url() . '/?p=' . $post->ID;
1071
		}
1072
1073
		$last_modified = $post->post_modified_gmt;
1074
1075
		// Check for more recent comments.
1076
		// Note that 'Y-m-d h:i:s' strings sort lexicographically.
1077
		if ( 0 < $post->comment_count ) {
1078
			$last_modified = max(
1079
				$last_modified,
1080
				$this->librarian->query_latest_approved_comment_time_on_post( $post->ID )
1081
			);
1082
		}
1083
1084
		$item_array = array(
1085
			'url' => array(
1086
				'loc'     => $url,
1087
				'lastmod' => jp_sitemap_datetime( $last_modified ),
1088
			),
1089
		);
1090
1091
		/**
1092
		 * Filter sitemap URL item before rendering it as XML.
1093
		 *
1094
		 * @module sitemaps
1095
		 *
1096
		 * @since 3.9.0
1097
		 *
1098
		 * @param array $tree Associative array representing sitemap URL element.
1099
		 * @param int   $post_id ID of the post being processed.
1100
		 */
1101
		$item_array = apply_filters( 'jetpack_sitemap_url', $item_array, $post->ID );
1102
1103
		return array(
1104
			'xml'           => $item_array,
1105
			'last_modified' => $last_modified,
1106
		);
1107
	}
1108
1109
	/**
1110
	 * Construct the image sitemap url entry for a WP_Post of image type.
1111
	 *
1112
	 * @link http://www.sitemaps.org/protocol.html#urldef
1113
	 *
1114
	 * @access private
1115
	 * @since 4.8.0
1116
	 *
1117
	 * @param WP_Post $post The image post to be processed.
1118
	 *
1119
	 * @return string An XML fragment representing the post URL.
1120
	 */
1121
	private function image_post_to_sitemap_item( $post ) {
1122
1123
		/**
1124
		 * Filter condition to allow skipping specific image posts in the sitemap.
1125
		 *
1126
		 * @module sitemaps
1127
		 *
1128
		 * @since 4.8.0
1129
		 *
1130
		 * @param bool    $skip Current boolean. False by default, so no post is skipped.
1131
		 * @param WP_POST $post Current post object.
1132
		 */
1133
		if ( apply_filters( 'jetpack_sitemap_image_skip_post', false, $post ) ) {
1134
			return array(
1135
				'xml'           => null,
1136
				'last_modified' => null,
1137
			);
1138
		}
1139
1140
		$url = wp_get_attachment_url( $post->ID );
1141
1142
		$parent_url = get_permalink( get_post( $post->post_parent ) );
1143
		if ( '' == $parent_url ) { // WPCS: loose comparison ok.
1144
			$parent_url = get_permalink( $post );
1145
		}
1146
1147
		$item_array = array(
1148
			'url' => array(
1149
				'loc'         => $parent_url,
1150
				'lastmod'     => jp_sitemap_datetime( $post->post_modified_gmt ),
1151
				'image:image' => array(
1152
					'image:loc' => $url,
1153
				),
1154
			),
1155
		);
1156
1157
		$item_array['url']['image:image']['image:title'] = $post->post_title;
1158
		$item_array['url']['image:image']['image:caption'] = $post->post_excerpt;
1159
1160
		/**
1161
		 * Filter associative array with data to build <url> node
1162
		 * and its descendants for current post in image sitemap.
1163
		 *
1164
		 * @module sitemaps
1165
		 *
1166
		 * @since 4.8.0
1167
		 *
1168
		 * @param array $item_array Data to build parent and children nodes for current post.
1169
		 * @param int   $post_id Current image post ID.
1170
		 */
1171
		$item_array = apply_filters(
1172
			'jetpack_sitemap_image_sitemap_item',
1173
			$item_array,
1174
			$post->ID
1175
		);
1176
1177
		return array(
1178
			'xml'           => $item_array,
1179
			'last_modified' => $post->post_modified_gmt,
1180
		);
1181
	}
1182
1183
	/**
1184
	 * Construct the video sitemap url entry for a WP_Post of video type.
1185
	 *
1186
	 * @link http://www.sitemaps.org/protocol.html#urldef
1187
	 * @link https://developers.google.com/webmasters/videosearch/sitemaps
1188
	 *
1189
	 * @access private
1190
	 * @since 4.8.0
1191
	 *
1192
	 * @param WP_Post $post The video post to be processed.
1193
	 *
1194
	 * @return string An XML fragment representing the post URL.
1195
	 */
1196
	private function video_post_to_sitemap_item( $post ) {
1197
1198
		/**
1199
		 * Filter condition to allow skipping specific image posts in the sitemap.
1200
		 *
1201
		 * @module sitemaps
1202
		 *
1203
		 * @since 4.8.0
1204
		 *
1205
		 * @param bool    $skip Current boolean. False by default, so no post is skipped.
1206
		 * @param WP_POST $post Current post object.
1207
		 */
1208
		if ( apply_filters( 'jetpack_sitemap_video_skip_post', false, $post ) ) {
1209
			return array(
1210
				'xml'           => null,
1211
				'last_modified' => null,
1212
			);
1213
		}
1214
1215
		$parent_url = esc_url( get_permalink( get_post( $post->post_parent ) ) );
1216
		if ( '' == $parent_url ) { // WPCS: loose comparison ok.
1217
			$parent_url = esc_url( get_permalink( $post ) );
1218
		}
1219
1220
		// Prepare the content like get_the_content_feed().
1221
		$content = $post->post_content;
1222
		/** This filter is already documented in core/wp-includes/post-template.php */
1223
		$content = apply_filters( 'the_content', $content );
1224
1225
		/** This filter is already documented in core/wp-includes/feed.php */
1226
		$content = apply_filters( 'the_content_feed', $content, 'rss2' );
1227
1228
		$item_array = array(
1229
			'url' => array(
1230
				'loc'         => $parent_url,
1231
				'lastmod'     => jp_sitemap_datetime( $post->post_modified_gmt ),
1232
				'video:video' => array(
1233
					/** This filter is already documented in core/wp-includes/feed.php */
1234
					'video:title'         => apply_filters( 'the_title_rss', $post->post_title ),
1235
					'video:thumbnail_loc' => '',
1236
					'video:description'   => $content,
1237
					'video:content_loc'   => esc_url( wp_get_attachment_url( $post->ID ) ),
1238
				),
1239
			),
1240
		);
1241
1242
		// 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...
1243
		// cf. video:player_loc tag in video sitemap spec.
1244
1245
		/**
1246
		 * Filter associative array with data to build <url> node
1247
		 * and its descendants for current post in video sitemap.
1248
		 *
1249
		 * @module sitemaps
1250
		 *
1251
		 * @since 4.8.0
1252
		 *
1253
		 * @param array $item_array Data to build parent and children nodes for current post.
1254
		 * @param int   $post_id Current video post ID.
1255
		 */
1256
		$item_array = apply_filters(
1257
			'jetpack_sitemap_video_sitemap_item',
1258
			$item_array,
1259
			$post->ID
1260
		);
1261
1262
		return array(
1263
			'xml'           => $item_array,
1264
			'last_modified' => $post->post_modified_gmt,
1265
		);
1266
	}
1267
1268
	/**
1269
	 * Construct the news sitemap url entry for a WP_Post.
1270
	 *
1271
	 * @link http://www.sitemaps.org/protocol.html#urldef
1272
	 *
1273
	 * @access private
1274
	 * @since 4.8.0
1275
	 *
1276
	 * @param WP_Post $post The post to be processed.
1277
	 *
1278
	 * @return string An XML fragment representing the post URL.
1279
	 */
1280
	private function post_to_news_sitemap_item( $post ) {
1281
1282
		/**
1283
		 * Filter condition to allow skipping specific posts in news sitemap.
1284
		 *
1285
		 * @module sitemaps
1286
		 *
1287
		 * @since 3.9.0
1288
		 *
1289
		 * @param bool    $skip Current boolean. False by default, so no post is skipped.
1290
		 * @param WP_POST $post Current post object.
1291
		 */
1292
		if ( apply_filters( 'jetpack_sitemap_news_skip_post', false, $post ) ) {
1293
			return array(
1294
				'xml' => null,
1295
			);
1296
		}
1297
1298
		$url = get_permalink( $post );
1299
1300
		/*
1301
		 * Spec requires the URL to be <=2048 bytes.
1302
		 * In practice this constraint is unlikely to be violated.
1303
		 */
1304
		if ( 2048 < strlen( $url ) ) {
1305
			$url = home_url() . '/?p=' . $post->ID;
1306
		}
1307
1308
		/*
1309
		 * Trim the locale to an ISO 639 language code as required by Google.
1310
		 * Special cases are zh-cn (Simplified Chinese) and zh-tw (Traditional Chinese).
1311
		 * @link http://www.loc.gov/standards/iso639-2/php/code_list.php
1312
		 */
1313
		$language = strtolower( get_locale() );
1314
1315
		if ( in_array( $language, array( 'zh_tw', 'zh_cn' ), true ) ) {
1316
			$language = str_replace( '_', '-', $language );
1317
		} else {
1318
			$language = preg_replace( '/(_.*)$/i', '', $language );
1319
		}
1320
1321
		$item_array = array(
1322
			'url' => array(
1323
				'loc' => $url,
1324
				'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
1325
				'news:news' => array(
1326
					'news:publication' => array(
1327
						'news:name'     => get_bloginfo( 'name' ),
1328
						'news:language' => $language,
1329
					),
1330
					/** This filter is already documented in core/wp-includes/feed.php */
1331
					'news:title'            => apply_filters( 'the_title_rss', $post->post_title ),
1332
					'news:publication_date' => jp_sitemap_datetime( $post->post_date_gmt ),
1333
					'news:genres'           => 'Blog',
1334
				),
1335
			),
1336
		);
1337
1338
		/**
1339
		 * Filter associative array with data to build <url> node
1340
		 * and its descendants for current post in news sitemap.
1341
		 *
1342
		 * @module sitemaps
1343
		 *
1344
		 * @since 3.9.0
1345
		 *
1346
		 * @param array $item_array Data to build parent and children nodes for current post.
1347
		 * @param int   $post_id Current post ID.
1348
		 */
1349
		$item_array = apply_filters(
1350
			'jetpack_sitemap_news_sitemap_item',
1351
			$item_array,
1352
			$post->ID
1353
		);
1354
1355
		return array(
1356
			'xml' => $item_array,
1357
		);
1358
	}
1359
}
1360