PageRenderTime 59ms CodeModel.GetById 11ms app.highlight 40ms RepoModel.GetById 2ms app.codeStats 0ms

/wp-content/plugins/wordpress-seo/inc/sitemaps/class-post-type-sitemap-provider.php

https://bitbucket.org/carloskikea/helpet
PHP | 641 lines | 301 code | 128 blank | 212 comment | 48 complexity | 6a4519005405420c64050d82c5bf6477 MD5 | raw file
  1<?php
  2/**
  3 * WPSEO plugin file.
  4 *
  5 * @package WPSEO\XML_Sitemaps
  6 */
  7
  8/**
  9 * Sitemap provider for author archives.
 10 */
 11class WPSEO_Post_Type_Sitemap_Provider implements WPSEO_Sitemap_Provider {
 12
 13	/** @var string $home_url Holds the home_url() value. */
 14	protected static $home_url;
 15
 16	/** @var WPSEO_Sitemap_Image_Parser $image_parser Holds image parser instance. */
 17	protected static $image_parser;
 18
 19	/** @var object $classifier Holds instance of classifier for a link. */
 20	protected static $classifier;
 21
 22	/** @var int $page_on_front_id Static front page ID. */
 23	protected static $page_on_front_id;
 24
 25	/** @var int $page_for_posts_id Posts page ID. */
 26	protected static $page_for_posts_id;
 27
 28	/**
 29	 * Set up object properties for data reuse.
 30	 */
 31	public function __construct() {
 32		add_filter( 'save_post', array( $this, 'save_post' ) );
 33	}
 34
 35	/**
 36	 * Get all the options
 37	 *
 38	 * @deprecated 7.0
 39	 */
 40	protected function get_options() {
 41		_deprecated_function( __METHOD__, 'WPSEO 7.0', 'WPSEO_Options::get' );
 42	}
 43
 44	/**
 45	 * Get front page ID
 46	 *
 47	 * @return int
 48	 */
 49	protected function get_page_on_front_id() {
 50		if ( ! isset( self::$page_on_front_id ) ) {
 51			self::$page_on_front_id = (int) get_option( 'page_on_front' );
 52		}
 53
 54		return self::$page_on_front_id;
 55	}
 56
 57	/**
 58	 * Get page for posts ID
 59	 *
 60	 * @return int
 61	 */
 62	protected function get_page_for_posts_id() {
 63		if ( ! isset( self::$page_for_posts_id ) ) {
 64			self::$page_for_posts_id = (int) get_option( 'page_for_posts' );
 65		}
 66
 67		return self::$page_for_posts_id;
 68	}
 69
 70	/**
 71	 * Get the Image Parser
 72	 *
 73	 * @return WPSEO_Sitemap_Image_Parser
 74	 */
 75	protected function get_image_parser() {
 76		if ( ! isset( self::$image_parser ) ) {
 77			self::$image_parser = new WPSEO_Sitemap_Image_Parser();
 78		}
 79
 80		return self::$image_parser;
 81	}
 82
 83	/**
 84	 * Get the Classifier for a link
 85	 *
 86	 * @return WPSEO_Link_Type_Classifier
 87	 */
 88	protected function get_classifier() {
 89		if ( ! isset( self::$classifier ) ) {
 90			self::$classifier = new WPSEO_Link_Type_Classifier( $this->get_home_url() );
 91		}
 92
 93		return self::$classifier;
 94	}
 95
 96	/**
 97	 * Get Home URL
 98	 *
 99	 * This has been moved from the constructor because wp_rewrite is not available on plugins_loaded in multisite.
100	 * It will now be requested on need and not on initialization.
101	 *
102	 * @return string
103	 */
104	protected function get_home_url() {
105		if ( ! isset( self::$home_url ) ) {
106			self::$home_url = WPSEO_Utils::home_url();
107		}
108
109		return self::$home_url;
110	}
111
112	/**
113	 * Check if provider supports given item type.
114	 *
115	 * @param string $type Type string to check for.
116	 *
117	 * @return boolean
118	 */
119	public function handles_type( $type ) {
120
121		return post_type_exists( $type );
122	}
123
124	/**
125	 * @param int $max_entries Entries per sitemap.
126	 *
127	 * @return array
128	 */
129	public function get_index_links( $max_entries ) {
130
131		global $wpdb;
132
133		// Consider using WPSEO_Post_Type::get_accessible_post_types() to filter out any `no-index` post-types.
134		$post_types          = WPSEO_Post_Type::get_accessible_post_types();
135		$post_types          = array_filter( $post_types, array( $this, 'is_valid_post_type' ) );
136		$last_modified_times = WPSEO_Sitemaps::get_last_modified_gmt( $post_types, true );
137		$index               = array();
138
139		foreach ( $post_types as $post_type ) {
140
141			$total_count = $this->get_post_type_count( $post_type );
142
143			if ( $total_count === 0 ) {
144				continue;
145			}
146
147			$max_pages = 1;
148
149			if ( $total_count > $max_entries ) {
150				$max_pages = (int) ceil( $total_count / $max_entries );
151			}
152
153			$all_dates = array();
154
155			if ( $max_pages > 1 ) {
156
157				$sql = "
158				SELECT post_modified_gmt
159				    FROM ( SELECT @rownum:=0 ) init 
160				    JOIN {$wpdb->posts} USE INDEX( type_status_date )
161				    WHERE post_status IN ( 'publish', 'inherit' )
162				      AND post_type = %s
163				      AND ( @rownum:=@rownum+1 ) %% %d = 0
164				    ORDER BY post_modified_gmt ASC
165				";
166
167				$all_dates = $wpdb->get_col( $wpdb->prepare( $sql, $post_type, $max_entries ) );
168			}
169
170			for ( $page_counter = 0; $page_counter < $max_pages; $page_counter++ ) {
171
172				$current_page = ( $max_pages > 1 ) ? ( $page_counter + 1 ) : '';
173				$date         = false;
174
175				if ( empty( $current_page ) || $current_page === $max_pages ) {
176
177					if ( ! empty( $last_modified_times[ $post_type ] ) ) {
178						$date = $last_modified_times[ $post_type ];
179					}
180				}
181				else {
182					$date = $all_dates[ $page_counter ];
183				}
184
185				$index[] = array(
186					'loc'     => WPSEO_Sitemaps_Router::get_base_url( $post_type . '-sitemap' . $current_page . '.xml' ),
187					'lastmod' => $date,
188				);
189			}
190		}
191
192		return $index;
193	}
194
195	/**
196	 * Get set of sitemap link data.
197	 *
198	 * @param string $type         Sitemap type.
199	 * @param int    $max_entries  Entries per sitemap.
200	 * @param int    $current_page Current page of the sitemap.
201	 *
202	 * @return array
203	 */
204	public function get_sitemap_links( $type, $max_entries, $current_page ) {
205
206		$links     = array();
207		$post_type = $type;
208
209		if ( ! $this->is_valid_post_type( $post_type ) ) {
210			return $links;
211		}
212
213		$steps  = min( 100, $max_entries );
214		$offset = ( $current_page > 1 ) ? ( ( $current_page - 1 ) * $max_entries ) : 0;
215		$total  = ( $offset + $max_entries );
216
217		$typecount = $this->get_post_type_count( $post_type );
218
219		if ( $total > $typecount ) {
220			$total = $typecount;
221		}
222
223		if ( $current_page === 1 ) {
224			$links = array_merge( $links, $this->get_first_links( $post_type ) );
225		}
226
227		if ( $typecount === 0 ) {
228
229			return $links;
230		}
231
232		$stacked_urls     = array();
233		$posts_to_exclude = $this->get_excluded_posts();
234
235		while ( $total > $offset ) {
236
237			$posts = $this->get_posts( $post_type, $steps, $offset );
238
239			$offset += $steps;
240
241			if ( empty( $posts ) ) {
242				continue;
243			}
244
245			foreach ( $posts as $post ) {
246
247				if ( in_array( $post->ID, $posts_to_exclude, true ) ) {
248					continue;
249				}
250
251				if ( WPSEO_Meta::get_value( 'meta-robots-noindex', $post->ID ) === '1' ) {
252					continue;
253				}
254
255				$url = $this->get_url( $post );
256
257				if ( ! isset( $url['loc'] ) ) {
258					continue;
259				}
260
261				/**
262				 * Filter URL entry before it gets added to the sitemap.
263				 *
264				 * @param array  $url  Array of URL parts.
265				 * @param string $type URL type.
266				 * @param object $post Data object for the URL.
267				 */
268				$url = apply_filters( 'wpseo_sitemap_entry', $url, 'post', $post );
269
270				if ( empty( $url ) ) {
271					continue;
272				}
273
274				if ( $post->ID === $this->get_page_for_posts_id() || $post->ID === $this->get_page_on_front_id() ) {
275
276					array_unshift( $links, $url );
277					continue;
278				}
279				$links[] = $url;
280			}
281
282			unset( $post, $url );
283		}
284
285		return $links;
286	}
287
288	/**
289	 * Check for relevant post type before invalidation.
290	 *
291	 * @param int $post_id Post ID to possibly invalidate for.
292	 */
293	public function save_post( $post_id ) {
294
295		if ( $this->is_valid_post_type( get_post_type( $post_id ) ) ) {
296			WPSEO_Sitemaps_Cache::invalidate_post( $post_id );
297		}
298	}
299
300	/**
301	 * Check if post type should be present in sitemaps.
302	 *
303	 * @param string $post_type Post type string to check for.
304	 *
305	 * @return bool
306	 */
307	public function is_valid_post_type( $post_type ) {
308		if ( ! WPSEO_Post_Type::is_post_type_indexable( $post_type ) ) {
309			return false;
310		}
311
312		/**
313		 * Filter decision if post type is excluded from the XML sitemap.
314		 *
315		 * @param bool   $exclude   Default false.
316		 * @param string $post_type Post type name.
317		 */
318		if ( apply_filters( 'wpseo_sitemap_exclude_post_type', false, $post_type ) ) {
319			return false;
320		}
321
322		return true;
323	}
324
325	/**
326	 * Retrieves a list with the excluded post ids.
327	 *
328	 * @return array Array with post ids to exclude.
329	 */
330	protected function get_excluded_posts() {
331		/**
332		 * Filter: 'wpseo_exclude_from_sitemap_by_post_ids' - Allow extending and modifying the posts to exclude.
333		 *
334		 * @api array $posts_to_exclude The posts to exclude.
335		 */
336		$excluded_posts_ids = apply_filters( 'wpseo_exclude_from_sitemap_by_post_ids', array() );
337		if ( ! is_array( $excluded_posts_ids ) || $excluded_posts_ids === array() ) {
338			return array();
339		}
340
341		return array_map( 'intval', $excluded_posts_ids );
342	}
343
344	/**
345	 * Get count of posts for post type.
346	 *
347	 * @param string $post_type Post type to retrieve count for.
348	 *
349	 * @return int
350	 */
351	protected function get_post_type_count( $post_type ) {
352
353		global $wpdb;
354
355		/**
356		 * Filter JOIN query part for type count of post type.
357		 *
358		 * @param string $join      SQL part, defaults to empty string.
359		 * @param string $post_type Post type name.
360		 */
361		$join_filter = apply_filters( 'wpseo_typecount_join', '', $post_type );
362
363		/**
364		 * Filter WHERE query part for type count of post type.
365		 *
366		 * @param string $where     SQL part, defaults to empty string.
367		 * @param string $post_type Post type name.
368		 */
369		$where_filter = apply_filters( 'wpseo_typecount_where', '', $post_type );
370
371		$where = $this->get_sql_where_clause( $post_type );
372
373		$sql = "
374			SELECT COUNT({$wpdb->posts}.ID)
375			FROM {$wpdb->posts}
376			{$join_filter}
377			{$where}
378				{$where_filter}
379		";
380
381		return (int) $wpdb->get_var( $sql );
382	}
383
384	/**
385	 * Produces set of links to prepend at start of first sitemap page.
386	 *
387	 * @param string $post_type Post type to produce links for.
388	 *
389	 * @return array
390	 */
391	protected function get_first_links( $post_type ) {
392
393		$links = array();
394
395		$needs_archive = true;
396
397		if ( ! $this->get_page_on_front_id() && ( $post_type === 'post' || $post_type === 'page' ) ) {
398
399			$links[] = array(
400				'loc' => $this->get_home_url(),
401
402				// Deprecated, kept for backwards data compat. R.
403				'chf' => 'daily',
404				'pri' => 1,
405			);
406
407			$needs_archive = false;
408		}
409		elseif ( $this->get_page_on_front_id() && $post_type === 'post' && $this->get_page_for_posts_id() ) {
410
411			$page_for_posts_url = get_permalink( $this->get_page_for_posts_id() );
412
413			$links[] = array(
414				'loc' => $page_for_posts_url,
415
416				// Deprecated, kept for backwards data compat. R.
417				'chf' => 'daily',
418				'pri' => 1,
419			);
420
421			$needs_archive = false;
422		}
423
424		if ( ! $needs_archive ) {
425			return $links;
426		}
427
428		$archive_url = $this->get_post_type_archive_link( $post_type );
429
430		/**
431		 * Filter the URL Yoast SEO uses in the XML sitemap for this post type archive.
432		 *
433		 * @param string $archive_url The URL of this archive
434		 * @param string $post_type   The post type this archive is for.
435		 */
436		$archive_url = apply_filters( 'wpseo_sitemap_post_type_archive_link', $archive_url, $post_type );
437
438		if ( $archive_url ) {
439			/**
440			 * Filter the priority of the URL Yoast SEO uses in the XML sitemap.
441			 *
442			 * @param float  $priority  The priority for this URL, ranging from 0 to 1
443			 * @param string $post_type The post type this archive is for.
444			 */
445			$links[] = array(
446				'loc' => $archive_url,
447				'mod' => WPSEO_Sitemaps::get_last_modified_gmt( $post_type ),
448
449				// Deprecated, kept for backwards data compat. R.
450				'chf' => 'daily',
451				'pri' => 1,
452			);
453		}
454
455		return $links;
456	}
457
458	/**
459	 * Get URL for a post type archive.
460	 *
461	 * @since  5.3
462	 *
463	 * @param  string $post_type Post type.
464	 *
465	 * @return string|bool URL or false if it should be excluded.
466	 */
467	protected function get_post_type_archive_link( $post_type ) {
468
469		if ( WPSEO_Options::get( 'noindex-ptarchive-' . $post_type, false ) ) {
470			return false;
471		}
472
473		// Post archive should be excluded if it isn't front page or posts page.
474		if ( $post_type === 'post' && get_option( 'show_on_front' ) !== 'posts' && ! $this->get_page_for_posts_id() ) {
475			return false;
476		}
477
478		$archive_url = get_post_type_archive_link( $post_type );
479
480		return $archive_url;
481	}
482
483	/**
484	 * Retrieve set of posts with optimized query routine.
485	 *
486	 * @param string $post_type Post type to retrieve.
487	 * @param int    $count     Count of posts to retrieve.
488	 * @param int    $offset    Starting offset.
489	 *
490	 * @return object[]
491	 */
492	protected function get_posts( $post_type, $count, $offset ) {
493
494		global $wpdb;
495
496		static $filters = array();
497
498		if ( ! isset( $filters[ $post_type ] ) ) {
499			// Make sure you're wpdb->preparing everything you throw into this!!
500			$filters[ $post_type ] = array(
501				/**
502				 * Filter JOIN query part for the post type.
503				 *
504				 * @param string $join      SQL part, defaults to false.
505				 * @param string $post_type Post type name.
506				 */
507				'join'  => apply_filters( 'wpseo_posts_join', false, $post_type ),
508
509				/**
510				 * Filter Where query part for the post type.
511				 *
512				 * @param string $where     SQL part, defaults to false.
513				 * @param string $post_type Post type name.
514				 */
515				'where' => apply_filters( 'wpseo_posts_where', false, $post_type ),
516			);
517		}
518
519		$join_filter  = $filters[ $post_type ]['join'];
520		$where_filter = $filters[ $post_type ]['where'];
521		$where        = $this->get_sql_where_clause( $post_type );
522
523		// Optimized query per this thread: http://wordpress.org/support/topic/plugin-wordpress-seo-by-yoast-performance-suggestion.
524		// Also see http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/.
525		$sql = "
526			SELECT l.ID, post_title, post_content, post_name, post_parent, post_author, post_modified_gmt, post_date, post_date_gmt
527			FROM (
528				SELECT {$wpdb->posts}.ID
529				FROM {$wpdb->posts}
530				{$join_filter}
531				{$where}
532					{$where_filter}
533				ORDER BY {$wpdb->posts}.post_modified ASC LIMIT %d OFFSET %d
534			)
535			o JOIN {$wpdb->posts} l ON l.ID = o.ID
536		";
537
538		$posts = $wpdb->get_results( $wpdb->prepare( $sql, $count, $offset ) );
539
540		$post_ids = array();
541
542		foreach ( $posts as $post ) {
543			$post->post_type   = $post_type;
544			$post->post_status = 'publish';
545			$post->filter      = 'sample';
546			$post->ID          = (int) $post->ID;
547			$post->post_parent = (int) $post->post_parent;
548			$post->post_author = (int) $post->post_author;
549			$post_ids[]        = $post->ID;
550		}
551
552		update_meta_cache( 'post', $post_ids );
553
554		return $posts;
555	}
556
557	/**
558	 * @param string $post_type Post type slug.
559	 *
560	 * @return string
561	 */
562	protected function get_sql_where_clause( $post_type ) {
563
564		global $wpdb;
565
566		$join   = '';
567		$status = "{$wpdb->posts}.post_status = 'publish'";
568
569		// Based on WP_Query->get_posts(). R.
570		if ( 'attachment' === $post_type ) {
571			$join   = " LEFT JOIN {$wpdb->posts} AS p2 ON ({$wpdb->posts}.post_parent = p2.ID) ";
572			$status = "p2.post_status = 'publish' AND p2.post_password = ''";
573		}
574
575		$where_clause = "
576		{$join}
577		WHERE {$status}
578			AND {$wpdb->posts}.post_type = %s
579			AND {$wpdb->posts}.post_password = ''
580			AND {$wpdb->posts}.post_date != '0000-00-00 00:00:00'
581		";
582
583		return $wpdb->prepare( $where_clause, $post_type );
584	}
585
586	/**
587	 * Produce array of URL parts for given post object.
588	 *
589	 * @param object $post Post object to get URL parts for.
590	 *
591	 * @return array|bool
592	 */
593	protected function get_url( $post ) {
594
595		$url = array();
596
597		/**
598		 * Filter the URL Yoast SEO uses in the XML sitemap.
599		 *
600		 * Note that only absolute local URLs are allowed as the check after this removes external URLs.
601		 *
602		 * @param string $url  URL to use in the XML sitemap
603		 * @param object $post Post object for the URL.
604		 */
605		$url['loc'] = apply_filters( 'wpseo_xml_sitemap_post_url', get_permalink( $post ), $post );
606
607		/**
608		 * Do not include external URLs.
609		 *
610		 * @see https://wordpress.org/plugins/page-links-to/ can rewrite permalinks to external URLs.
611		 */
612		if ( $this->get_classifier()->classify( $url['loc'] ) === WPSEO_Link::TYPE_EXTERNAL ) {
613			return false;
614		}
615
616		$modified = max( $post->post_modified_gmt, $post->post_date_gmt );
617
618		if ( $modified !== '0000-00-00 00:00:00' ) {
619			$url['mod'] = $modified;
620		}
621
622		$url['chf'] = 'daily'; // Deprecated, kept for backwards data compat. R.
623
624		$canonical = WPSEO_Meta::get_value( 'canonical', $post->ID );
625
626		if ( $canonical !== '' && $canonical !== $url['loc'] ) {
627			/*
628			 * Let's assume that if a canonical is set for this page and it's different from
629			 * the URL of this post, that page is either already in the XML sitemap OR is on
630			 * an external site, either way, we shouldn't include it here.
631			 */
632			return false;
633		}
634		unset( $canonical );
635
636		$url['pri']    = 1; // Deprecated, kept for backwards data compat. R.
637		$url['images'] = $this->get_image_parser()->get_images( $post );
638
639		return $url;
640	}
641}