Nenhuma Descrição

sitemap-builder.php 39KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469
  1. <?php
  2. /**
  3. * Build the sitemap tree.
  4. *
  5. * @package automattic/jetpack
  6. * @since 4.8.0
  7. * @author Automattic
  8. */
  9. /* Include sitemap subclasses, if not already, and include proper buffer based on phpxml's availability. */
  10. require_once __DIR__ . '/sitemap-constants.php';
  11. require_once __DIR__ . '/sitemap-buffer.php';
  12. if ( ! class_exists( 'DOMDocument' ) ) {
  13. require_once __DIR__ . '/sitemap-buffer-fallback.php';
  14. require_once __DIR__ . '/sitemap-buffer-image-fallback.php';
  15. require_once __DIR__ . '/sitemap-buffer-master-fallback.php';
  16. require_once __DIR__ . '/sitemap-buffer-news-fallback.php';
  17. require_once __DIR__ . '/sitemap-buffer-page-fallback.php';
  18. require_once __DIR__ . '/sitemap-buffer-video-fallback.php';
  19. } else {
  20. require_once __DIR__ . '/sitemap-buffer-image.php';
  21. require_once __DIR__ . '/sitemap-buffer-master.php';
  22. require_once __DIR__ . '/sitemap-buffer-news.php';
  23. require_once __DIR__ . '/sitemap-buffer-page.php';
  24. require_once __DIR__ . '/sitemap-buffer-video.php';
  25. }
  26. require_once __DIR__ . '/sitemap-librarian.php';
  27. require_once __DIR__ . '/sitemap-finder.php';
  28. require_once __DIR__ . '/sitemap-state.php';
  29. if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
  30. require_once __DIR__ . '/sitemap-logger.php';
  31. }
  32. /**
  33. * Simple class for rendering an empty sitemap with a short TTL
  34. */
  35. class Jetpack_Sitemap_Buffer_Empty extends Jetpack_Sitemap_Buffer {
  36. public function __construct() {
  37. parent::__construct( JP_SITEMAP_MAX_ITEMS, JP_SITEMAP_MAX_BYTES, '1970-01-01 00:00:00' );
  38. $this->doc->appendChild(
  39. $this->doc->createComment( "generator='jetpack-" . JETPACK__VERSION . "'" )
  40. );
  41. $this->doc->appendChild(
  42. $this->doc->createProcessingInstruction(
  43. 'xml-stylesheet',
  44. 'type="text/xsl" href="' . $this->finder->construct_sitemap_url( 'sitemap-index.xsl' ) . '"'
  45. )
  46. );
  47. }
  48. protected function get_root_element() {
  49. if ( ! isset( $this->root ) ) {
  50. $this->root = $this->doc->createElement( 'sitemapindex' );
  51. $this->root->setAttribute( 'xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
  52. $this->doc->appendChild( $this->root );
  53. $this->byte_capacity -= strlen( $this->doc->saveXML( $this->root ) );
  54. }
  55. return $this->root;
  56. }
  57. }
  58. /**
  59. * The Jetpack_Sitemap_Builder object handles the construction of
  60. * all sitemap files (except the XSL files, which are handled by
  61. * Jetpack_Sitemap_Stylist.) Other than the constructor, there are
  62. * only two public functions: build_all_sitemaps and news_sitemap_xml.
  63. *
  64. * @since 4.8.0
  65. */
  66. class Jetpack_Sitemap_Builder {
  67. /**
  68. * Librarian object for storing and retrieving sitemap data.
  69. *
  70. * @access private
  71. * @since 4.8.0
  72. * @var $librarian Jetpack_Sitemap_Librarian
  73. */
  74. private $librarian;
  75. /**
  76. * Logger object for reporting debug messages.
  77. *
  78. * @access private
  79. * @since 4.8.0
  80. * @var $logger Jetpack_Sitemap_Logger
  81. */
  82. private $logger = false;
  83. /**
  84. * Finder object for dealing with sitemap URIs.
  85. *
  86. * @access private
  87. * @since 4.8.0
  88. * @var $finder Jetpack_Sitemap_Finder
  89. */
  90. private $finder;
  91. /**
  92. * Construct a new Jetpack_Sitemap_Builder object.
  93. *
  94. * @access public
  95. * @since 4.8.0
  96. */
  97. public function __construct() {
  98. $this->librarian = new Jetpack_Sitemap_Librarian();
  99. $this->finder = new Jetpack_Sitemap_Finder();
  100. if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
  101. $this->logger = new Jetpack_Sitemap_Logger();
  102. }
  103. update_option(
  104. 'jetpack_sitemap_post_types',
  105. /**
  106. * The array of post types to be included in the sitemap.
  107. *
  108. * Add your custom post type name to the array to have posts of
  109. * that type included in the sitemap. The default array includes
  110. * 'page' and 'post'.
  111. *
  112. * The result of this filter is cached in an option, 'jetpack_sitemap_post_types',
  113. * so this filter only has to be applied once per generation.
  114. *
  115. * @since 4.8.0
  116. */
  117. apply_filters(
  118. 'jetpack_sitemap_post_types',
  119. array( 'post', 'page' )
  120. )
  121. );
  122. }
  123. /**
  124. * Update the sitemap.
  125. *
  126. * All we do here is call build_next_sitemap_file a bunch of times.
  127. *
  128. * @since 4.8.0
  129. */
  130. public function update_sitemap() {
  131. if ( $this->logger ) {
  132. $this->logger->report( '-- Updating...' );
  133. if ( ! class_exists( 'DOMDocument' ) ) {
  134. $this->logger->report(
  135. __(
  136. 'Jetpack can not load necessary XML manipulation libraries. Please ask your hosting provider to refer to our server requirements at https://jetpack.com/support/server-requirements/ .',
  137. 'jetpack'
  138. ),
  139. true
  140. );
  141. }
  142. }
  143. for ( $i = 1; $i <= JP_SITEMAP_UPDATE_SIZE; $i++ ) {
  144. if ( true === $this->build_next_sitemap_file() ) {
  145. break; // All finished!
  146. }
  147. }
  148. if ( $this->logger ) {
  149. $this->logger->report( '-- ...done for now.' );
  150. $this->logger->time();
  151. }
  152. }
  153. /**
  154. * Generate the next sitemap file.
  155. *
  156. * Reads the most recent state of the sitemap generation phase,
  157. * constructs the next file, and updates the state.
  158. *
  159. * @since 4.8.0
  160. *
  161. * @return bool True when finished.
  162. */
  163. private function build_next_sitemap_file() {
  164. $finished = false; // Initialize finished flag.
  165. // Get the most recent state, and lock the state.
  166. $state = Jetpack_Sitemap_State::check_out();
  167. // Do nothing if the state was locked.
  168. if ( false === $state ) {
  169. return false;
  170. }
  171. // Otherwise, branch on the sitemap-type key of $state.
  172. switch ( $state['sitemap-type'] ) {
  173. case JP_PAGE_SITEMAP_TYPE:
  174. $this->build_next_sitemap_of_type(
  175. JP_PAGE_SITEMAP_TYPE,
  176. array( $this, 'build_one_page_sitemap' ),
  177. $state
  178. );
  179. break;
  180. case JP_PAGE_SITEMAP_INDEX_TYPE:
  181. $this->build_next_sitemap_index_of_type(
  182. JP_PAGE_SITEMAP_INDEX_TYPE,
  183. JP_IMAGE_SITEMAP_TYPE,
  184. $state
  185. );
  186. break;
  187. case JP_IMAGE_SITEMAP_TYPE:
  188. $this->build_next_sitemap_of_type(
  189. JP_IMAGE_SITEMAP_TYPE,
  190. array( $this, 'build_one_image_sitemap' ),
  191. $state
  192. );
  193. break;
  194. case JP_IMAGE_SITEMAP_INDEX_TYPE:
  195. $this->build_next_sitemap_index_of_type(
  196. JP_IMAGE_SITEMAP_INDEX_TYPE,
  197. JP_VIDEO_SITEMAP_TYPE,
  198. $state
  199. );
  200. break;
  201. case JP_VIDEO_SITEMAP_TYPE:
  202. $this->build_next_sitemap_of_type(
  203. JP_VIDEO_SITEMAP_TYPE,
  204. array( $this, 'build_one_video_sitemap' ),
  205. $state
  206. );
  207. break;
  208. case JP_VIDEO_SITEMAP_INDEX_TYPE:
  209. $this->build_next_sitemap_index_of_type(
  210. JP_VIDEO_SITEMAP_INDEX_TYPE,
  211. JP_MASTER_SITEMAP_TYPE,
  212. $state
  213. );
  214. break;
  215. case JP_MASTER_SITEMAP_TYPE:
  216. $this->build_master_sitemap( $state['max'] );
  217. // Reset the state and quit.
  218. Jetpack_Sitemap_State::reset(
  219. JP_PAGE_SITEMAP_TYPE
  220. );
  221. if ( $this->logger ) {
  222. $this->logger->report( '-- Finished.' );
  223. $this->logger->time();
  224. }
  225. $finished = true;
  226. break;
  227. default:
  228. Jetpack_Sitemap_State::reset(
  229. JP_PAGE_SITEMAP_TYPE
  230. );
  231. $finished = true;
  232. break;
  233. } // End switch.
  234. // Unlock the state.
  235. Jetpack_Sitemap_State::unlock();
  236. return $finished;
  237. }
  238. /**
  239. * Build the next sitemap of a given type and update the sitemap state.
  240. *
  241. * @since 4.8.0
  242. *
  243. * @param string $sitemap_type The type of the sitemap being generated.
  244. * @param callback $build_one A callback which builds a single sitemap file.
  245. * @param array $state A sitemap state.
  246. */
  247. private function build_next_sitemap_of_type( $sitemap_type, $build_one, $state ) {
  248. $index_type = jp_sitemap_index_type_of( $sitemap_type );
  249. // Try to build a sitemap.
  250. $result = call_user_func_array(
  251. $build_one,
  252. array(
  253. $state['number'] + 1,
  254. $state['last-added'],
  255. )
  256. );
  257. if ( false === $result ) {
  258. // If no sitemap was generated, advance to the next type.
  259. Jetpack_Sitemap_State::check_in(
  260. array(
  261. 'sitemap-type' => $index_type,
  262. 'last-added' => 0,
  263. 'number' => 0,
  264. 'last-modified' => '1970-01-01 00:00:00',
  265. )
  266. );
  267. if ( $this->logger ) {
  268. $this->logger->report( "-- Cleaning Up $sitemap_type" );
  269. }
  270. // Clean up old files.
  271. $this->librarian->delete_numbered_sitemap_rows_after(
  272. $state['number'],
  273. $sitemap_type
  274. );
  275. return;
  276. }
  277. // Otherwise, update the state.
  278. Jetpack_Sitemap_State::check_in(
  279. array(
  280. 'sitemap-type' => $state['sitemap-type'],
  281. 'last-added' => $result['last_id'],
  282. 'number' => $state['number'] + 1,
  283. 'last-modified' => $result['last_modified'],
  284. )
  285. );
  286. if ( true === $result['any_left'] ) {
  287. // If there's more work to be done with this type, return.
  288. return;
  289. }
  290. // Otherwise, advance state to the next sitemap type.
  291. Jetpack_Sitemap_State::check_in(
  292. array(
  293. 'sitemap-type' => $index_type,
  294. 'last-added' => 0,
  295. 'number' => 0,
  296. 'last-modified' => '1970-01-01 00:00:00',
  297. )
  298. );
  299. if ( $this->logger ) {
  300. $this->logger->report( "-- Cleaning Up $sitemap_type" );
  301. }
  302. // Clean up old files.
  303. $this->librarian->delete_numbered_sitemap_rows_after(
  304. $state['number'] + 1,
  305. $sitemap_type
  306. );
  307. }
  308. /**
  309. * Build the next sitemap index of a given type and update the state.
  310. *
  311. * @since 4.8.0
  312. *
  313. * @param string $index_type The type of index being generated.
  314. * @param string $next_type The next type to generate after this one.
  315. * @param array $state A sitemap state.
  316. */
  317. private function build_next_sitemap_index_of_type( $index_type, $next_type, $state ) {
  318. $sitemap_type = jp_sitemap_child_type_of( $index_type );
  319. // If only 0 or 1 sitemaps were built, advance to the next type and return.
  320. if ( 1 >= $state['max'][ $sitemap_type ]['number'] ) {
  321. Jetpack_Sitemap_State::check_in(
  322. array(
  323. 'sitemap-type' => $next_type,
  324. 'last-added' => 0,
  325. 'number' => 0,
  326. 'last-modified' => '1970-01-01 00:00:00',
  327. )
  328. );
  329. if ( $this->logger ) {
  330. $this->logger->report( "-- Cleaning Up $index_type" );
  331. }
  332. // There are no indices of this type.
  333. $this->librarian->delete_numbered_sitemap_rows_after(
  334. 0,
  335. $index_type
  336. );
  337. return;
  338. }
  339. // Otherwise, try to build a sitemap index.
  340. $result = $this->build_one_sitemap_index(
  341. $state['number'] + 1,
  342. $state['last-added'],
  343. $state['last-modified'],
  344. $index_type
  345. );
  346. // If no index was built, advance to the next type and return.
  347. if ( false === $result ) {
  348. Jetpack_Sitemap_State::check_in(
  349. array(
  350. 'sitemap-type' => $next_type,
  351. 'last-added' => 0,
  352. 'number' => 0,
  353. 'last-modified' => '1970-01-01 00:00:00',
  354. )
  355. );
  356. if ( $this->logger ) {
  357. $this->logger->report( "-- Cleaning Up $index_type" );
  358. }
  359. // Clean up old files.
  360. $this->librarian->delete_numbered_sitemap_rows_after(
  361. $state['number'],
  362. $index_type
  363. );
  364. return;
  365. }
  366. // Otherwise, check in the state.
  367. Jetpack_Sitemap_State::check_in(
  368. array(
  369. 'sitemap-type' => $index_type,
  370. 'last-added' => $result['last_id'],
  371. 'number' => $state['number'] + 1,
  372. 'last-modified' => $result['last_modified'],
  373. )
  374. );
  375. // If there are still sitemaps left to index, return.
  376. if ( true === $result['any_left'] ) {
  377. return;
  378. }
  379. // Otherwise, advance to the next type.
  380. Jetpack_Sitemap_State::check_in(
  381. array(
  382. 'sitemap-type' => $next_type,
  383. 'last-added' => 0,
  384. 'number' => 0,
  385. 'last-modified' => '1970-01-01 00:00:00',
  386. )
  387. );
  388. if ( $this->logger ) {
  389. $this->logger->report( "-- Cleaning Up $index_type" );
  390. }
  391. // We're done generating indices of this type.
  392. $this->librarian->delete_numbered_sitemap_rows_after(
  393. $state['number'] + 1,
  394. $index_type
  395. );
  396. }
  397. /**
  398. * Builds the master sitemap index.
  399. *
  400. * @param array $max Array of sitemap types with max index and datetime.
  401. *
  402. * @since 4.8.0
  403. */
  404. private function build_master_sitemap( $max ) {
  405. $page = array();
  406. $image = array();
  407. $video = array();
  408. if ( $this->logger ) {
  409. $this->logger->report( '-- Building Master Sitemap.' );
  410. }
  411. $buffer = new Jetpack_Sitemap_Buffer_Master(
  412. JP_SITEMAP_MAX_ITEMS,
  413. JP_SITEMAP_MAX_BYTES
  414. );
  415. 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 );
  418. $page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_TYPE ]['lastmod'] );
  419. } else {
  420. $page['filename'] = jp_sitemap_filename(
  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. $buffer->append(
  427. array(
  428. 'sitemap' => array(
  429. 'loc' => $this->finder->construct_sitemap_url( $page['filename'] ),
  430. 'lastmod' => $page['last_modified'],
  431. ),
  432. )
  433. );
  434. }
  435. if ( 0 < $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
  436. if ( 1 === $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
  437. $image['filename'] = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, 1 );
  438. $image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_TYPE ]['lastmod'] );
  439. } else {
  440. $image['filename'] = jp_sitemap_filename(
  441. JP_IMAGE_SITEMAP_INDEX_TYPE,
  442. $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['number']
  443. );
  444. $image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
  445. }
  446. $buffer->append(
  447. array(
  448. 'sitemap' => array(
  449. 'loc' => $this->finder->construct_sitemap_url( $image['filename'] ),
  450. 'lastmod' => $image['last_modified'],
  451. ),
  452. )
  453. );
  454. }
  455. if ( 0 < $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
  456. if ( 1 === $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
  457. $video['filename'] = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, 1 );
  458. $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_TYPE ]['lastmod'] );
  459. } else {
  460. $video['filename'] = jp_sitemap_filename(
  461. JP_VIDEO_SITEMAP_INDEX_TYPE,
  462. $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['number']
  463. );
  464. $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['lastmod'] );
  465. }
  466. $buffer->append(
  467. array(
  468. 'sitemap' => array(
  469. 'loc' => $this->finder->construct_sitemap_url( $video['filename'] ),
  470. 'lastmod' => $video['last_modified'],
  471. ),
  472. )
  473. );
  474. }
  475. $this->librarian->store_sitemap_data(
  476. 0,
  477. JP_MASTER_SITEMAP_TYPE,
  478. $buffer->contents(),
  479. ''
  480. );
  481. }
  482. /**
  483. * Build and store a single page sitemap. Returns false if no sitemap is built.
  484. *
  485. * Side effect: Create/update a sitemap row.
  486. *
  487. * @access private
  488. * @since 4.8.0
  489. *
  490. * @param int $number The number of the current sitemap.
  491. * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
  492. *
  493. * @return bool|array @args {
  494. * @type int $last_id The ID of the last item to be successfully added to the buffer.
  495. * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
  496. * @type string $last_modified The most recent timestamp to appear on the sitemap.
  497. * }
  498. */
  499. public function build_one_page_sitemap( $number, $from_id ) {
  500. $last_post_id = $from_id;
  501. $any_posts_left = true;
  502. if ( $this->logger ) {
  503. $debug_name = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, $number );
  504. $this->logger->report( "-- Building $debug_name" );
  505. }
  506. $buffer = new Jetpack_Sitemap_Buffer_Page(
  507. JP_SITEMAP_MAX_ITEMS,
  508. JP_SITEMAP_MAX_BYTES
  509. );
  510. // Add entry for the main page (only if we're at the first one) and it isn't already going to be included as a page.
  511. if ( 1 === $number && 'page' !== get_option( 'show_on_front' ) ) {
  512. $item_array = array(
  513. 'url' => array(
  514. 'loc' => home_url( '/' ),
  515. ),
  516. );
  517. /**
  518. * Filter associative array with data to build <url> node
  519. * and its descendants for site home.
  520. *
  521. * @module sitemaps
  522. *
  523. * @since 3.9.0
  524. *
  525. * @param array $blog_home Data to build parent and children nodes for site home.
  526. */
  527. $item_array = apply_filters( 'jetpack_sitemap_url_home', $item_array );
  528. $buffer->append( $item_array );
  529. }
  530. // Add as many items to the buffer as possible.
  531. while ( $last_post_id >= 0 && false === $buffer->is_full() ) {
  532. $posts = $this->librarian->query_posts_after_id(
  533. $last_post_id,
  534. JP_SITEMAP_BATCH_SIZE
  535. );
  536. if ( null == $posts ) { // WPCS: loose comparison ok.
  537. $any_posts_left = false;
  538. break;
  539. }
  540. foreach ( $posts as $post ) {
  541. $current_item = $this->post_to_sitemap_item( $post );
  542. if ( true === $buffer->append( $current_item['xml'] ) ) {
  543. $last_post_id = $post->ID;
  544. $buffer->view_time( $current_item['last_modified'] );
  545. } else {
  546. break;
  547. }
  548. }
  549. }
  550. // Handle other page sitemap URLs.
  551. if ( false === $any_posts_left || $last_post_id < 0 ) {
  552. // Negative IDs are used to track URL indexes.
  553. $last_post_id = min( 0, $last_post_id );
  554. $any_posts_left = true; // Reinitialize.
  555. /**
  556. * Filter other page sitemap URLs.
  557. *
  558. * @module sitemaps
  559. *
  560. * @since 6.1.0
  561. *
  562. * @param array $urls An array of other URLs.
  563. */
  564. $other_urls = apply_filters( 'jetpack_page_sitemap_other_urls', array() );
  565. if ( $other_urls ) { // Start with index [1].
  566. $other_urls = array_values( $other_urls );
  567. array_unshift( $other_urls, $other_urls[0] );
  568. unset( $other_urls[0] );
  569. }
  570. // Add as many items to the buffer as possible.
  571. while ( false === $buffer->is_full() ) {
  572. $last_post_id_index = abs( $last_post_id );
  573. $start_from_post_id_index = $last_post_id_index ? $last_post_id_index + 1 : 0;
  574. $urls = array_slice(
  575. $other_urls,
  576. $start_from_post_id_index,
  577. JP_SITEMAP_BATCH_SIZE,
  578. true
  579. );
  580. if ( ! $urls ) {
  581. $any_posts_left = false;
  582. break;
  583. }
  584. foreach ( $urls as $index => $url ) {
  585. if ( ! is_array( $url ) ) {
  586. $url = array( 'loc' => $url );
  587. }
  588. $item = array( 'xml' => compact( 'url' ) );
  589. if ( true === $buffer->append( $item['xml'] ) ) {
  590. $last_post_id = -$index;
  591. } else {
  592. break;
  593. }
  594. }
  595. }
  596. }
  597. // If no items were added, return false.
  598. if ( true === $buffer->is_empty() ) {
  599. return false;
  600. }
  601. /**
  602. * Filter sitemap before rendering it as XML.
  603. *
  604. * @module sitemaps
  605. *
  606. * @since 3.9.0
  607. * @since 5.3.0 returns an element of DOMDocument type instead of SimpleXMLElement
  608. *
  609. * @param DOMDocument $doc Data tree for sitemap.
  610. * @param string $last_modified Date of last modification.
  611. */
  612. $tree = apply_filters(
  613. 'jetpack_print_sitemap',
  614. $buffer->get_document(),
  615. $buffer->last_modified()
  616. );
  617. // Store the buffer as the content of a sitemap row.
  618. $this->librarian->store_sitemap_data(
  619. $number,
  620. JP_PAGE_SITEMAP_TYPE,
  621. $buffer->contents(),
  622. $buffer->last_modified()
  623. );
  624. /*
  625. * Now report back with the ID of the last post ID to be
  626. * successfully added and whether there are any posts left.
  627. */
  628. return array(
  629. 'last_id' => $last_post_id,
  630. 'any_left' => $any_posts_left,
  631. 'last_modified' => $buffer->last_modified(),
  632. );
  633. }
  634. /**
  635. * Build and store a single image sitemap. Returns false if no sitemap is built.
  636. *
  637. * Side effect: Create/update an image sitemap row.
  638. *
  639. * @access private
  640. * @since 4.8.0
  641. *
  642. * @param int $number The number of the current sitemap.
  643. * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
  644. *
  645. * @return bool|array @args {
  646. * @type int $last_id The ID of the last item to be successfully added to the buffer.
  647. * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
  648. * @type string $last_modified The most recent timestamp to appear on the sitemap.
  649. * }
  650. */
  651. public function build_one_image_sitemap( $number, $from_id ) {
  652. $last_post_id = $from_id;
  653. $any_posts_left = true;
  654. if ( $this->logger ) {
  655. $debug_name = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, $number );
  656. $this->logger->report( "-- Building $debug_name" );
  657. }
  658. $buffer = new Jetpack_Sitemap_Buffer_Image(
  659. JP_SITEMAP_MAX_ITEMS,
  660. JP_SITEMAP_MAX_BYTES
  661. );
  662. // Add as many items to the buffer as possible.
  663. while ( false === $buffer->is_full() ) {
  664. $posts = $this->librarian->query_images_after_id(
  665. $last_post_id,
  666. JP_SITEMAP_BATCH_SIZE
  667. );
  668. if ( null == $posts ) { // WPCS: loose comparison ok.
  669. $any_posts_left = false;
  670. break;
  671. }
  672. foreach ( $posts as $post ) {
  673. $current_item = $this->image_post_to_sitemap_item( $post );
  674. if ( true === $buffer->append( $current_item['xml'] ) ) {
  675. $last_post_id = $post->ID;
  676. $buffer->view_time( $current_item['last_modified'] );
  677. } else {
  678. break;
  679. }
  680. }
  681. }
  682. // If no items were added, return false.
  683. if ( true === $buffer->is_empty() ) {
  684. return false;
  685. }
  686. // Store the buffer as the content of a jp_sitemap post.
  687. $this->librarian->store_sitemap_data(
  688. $number,
  689. JP_IMAGE_SITEMAP_TYPE,
  690. $buffer->contents(),
  691. $buffer->last_modified()
  692. );
  693. /*
  694. * Now report back with the ID of the last post to be
  695. * successfully added and whether there are any posts left.
  696. */
  697. return array(
  698. 'last_id' => $last_post_id,
  699. 'any_left' => $any_posts_left,
  700. 'last_modified' => $buffer->last_modified(),
  701. );
  702. }
  703. /**
  704. * Build and store a single video sitemap. Returns false if no sitemap is built.
  705. *
  706. * Side effect: Create/update an video sitemap row.
  707. *
  708. * @access private
  709. * @since 4.8.0
  710. *
  711. * @param int $number The number of the current sitemap.
  712. * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
  713. *
  714. * @return bool|array @args {
  715. * @type int $last_id The ID of the last item to be successfully added to the buffer.
  716. * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
  717. * @type string $last_modified The most recent timestamp to appear on the sitemap.
  718. * }
  719. */
  720. public function build_one_video_sitemap( $number, $from_id ) {
  721. $last_post_id = $from_id;
  722. $any_posts_left = true;
  723. if ( $this->logger ) {
  724. $debug_name = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, $number );
  725. $this->logger->report( "-- Building $debug_name" );
  726. }
  727. $buffer = new Jetpack_Sitemap_Buffer_Video(
  728. JP_SITEMAP_MAX_ITEMS,
  729. JP_SITEMAP_MAX_BYTES
  730. );
  731. // Add as many items to the buffer as possible.
  732. while ( false === $buffer->is_full() ) {
  733. $posts = $this->librarian->query_videos_after_id(
  734. $last_post_id,
  735. JP_SITEMAP_BATCH_SIZE
  736. );
  737. if ( null == $posts ) { // WPCS: loose comparison ok.
  738. $any_posts_left = false;
  739. break;
  740. }
  741. foreach ( $posts as $post ) {
  742. $current_item = $this->video_post_to_sitemap_item( $post );
  743. if ( true === $buffer->append( $current_item['xml'] ) ) {
  744. $last_post_id = $post->ID;
  745. $buffer->view_time( $current_item['last_modified'] );
  746. } else {
  747. break;
  748. }
  749. }
  750. }
  751. // If no items were added, return false.
  752. if ( true === $buffer->is_empty() ) {
  753. return false;
  754. }
  755. if ( false === $buffer->is_empty() ) {
  756. $this->librarian->store_sitemap_data(
  757. $number,
  758. JP_VIDEO_SITEMAP_TYPE,
  759. $buffer->contents(),
  760. $buffer->last_modified()
  761. );
  762. }
  763. /*
  764. * Now report back with the ID of the last post to be
  765. * successfully added and whether there are any posts left.
  766. */
  767. return array(
  768. 'last_id' => $last_post_id,
  769. 'any_left' => $any_posts_left,
  770. 'last_modified' => $buffer->last_modified(),
  771. );
  772. }
  773. /**
  774. * Build and store a single page sitemap index. Return false if no index is built.
  775. *
  776. * Side effect: Create/update a sitemap index row.
  777. *
  778. * @access private
  779. * @since 4.8.0
  780. *
  781. * @param int $number The number of the current sitemap index.
  782. * @param int $from_id The greatest lower bound of the IDs of the sitemaps to be included.
  783. * @param string $datetime Datetime of previous sitemap in 'YYYY-MM-DD hh:mm:ss' format.
  784. * @param string $index_type Sitemap index type.
  785. *
  786. * @return bool|array @args {
  787. * @type int $last_id The ID of the last item to be successfully added to the buffer.
  788. * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
  789. * @type string $last_modified The most recent timestamp to appear on the sitemap.
  790. * }
  791. */
  792. private function build_one_sitemap_index( $number, $from_id, $datetime, $index_type ) {
  793. $last_sitemap_id = $from_id;
  794. $any_sitemaps_left = true;
  795. // Check the datetime format.
  796. $datetime = jp_sitemap_datetime( $datetime );
  797. $sitemap_type = jp_sitemap_child_type_of( $index_type );
  798. if ( $this->logger ) {
  799. $index_debug_name = jp_sitemap_filename( $index_type, $number );
  800. $this->logger->report( "-- Building $index_debug_name" );
  801. }
  802. $buffer = new Jetpack_Sitemap_Buffer_Master(
  803. JP_SITEMAP_MAX_ITEMS,
  804. JP_SITEMAP_MAX_BYTES,
  805. $datetime
  806. );
  807. // Add pointer to the previous sitemap index (unless we're at the first one).
  808. if ( 1 !== $number ) {
  809. $i = $number - 1;
  810. $prev_index_url = $this->finder->construct_sitemap_url(
  811. jp_sitemap_filename( $index_type, $i )
  812. );
  813. $item_array = array(
  814. 'sitemap' => array(
  815. 'loc' => $prev_index_url,
  816. 'lastmod' => $datetime,
  817. ),
  818. );
  819. $buffer->append( $item_array );
  820. }
  821. // Add as many items to the buffer as possible.
  822. while ( false === $buffer->is_full() ) {
  823. // Retrieve a batch of posts (in order).
  824. $posts = $this->librarian->query_sitemaps_after_id(
  825. $sitemap_type,
  826. $last_sitemap_id,
  827. JP_SITEMAP_BATCH_SIZE
  828. );
  829. // If there were no posts to get, make a note.
  830. if ( null == $posts ) { // WPCS: loose comparison ok.
  831. $any_sitemaps_left = false;
  832. break;
  833. }
  834. // Otherwise, loop through each post in the batch.
  835. foreach ( $posts as $post ) {
  836. // Generate the sitemap XML for the post.
  837. $current_item = $this->sitemap_row_to_index_item( (array) $post );
  838. // Try adding this item to the buffer.
  839. if ( true === $buffer->append( $current_item['xml'] ) ) {
  840. $last_sitemap_id = $post['ID'];
  841. $buffer->view_time( $current_item['last_modified'] );
  842. } else {
  843. // Otherwise stop looping through posts.
  844. break;
  845. }
  846. }
  847. }
  848. // If no items were added, return false.
  849. if ( true === $buffer->is_empty() ) {
  850. return false;
  851. }
  852. $this->librarian->store_sitemap_data(
  853. $number,
  854. $index_type,
  855. $buffer->contents(),
  856. $buffer->last_modified()
  857. );
  858. /*
  859. * Now report back with the ID of the last sitemap post ID to
  860. * be successfully added, whether there are any sitemap posts
  861. * left, and the most recent modification time seen.
  862. */
  863. return array(
  864. 'last_id' => $last_sitemap_id,
  865. 'any_left' => $any_sitemaps_left,
  866. 'last_modified' => $buffer->last_modified(),
  867. );
  868. }
  869. /**
  870. * Construct the sitemap index url entry for a sitemap row.
  871. *
  872. * @link https://www.sitemaps.org/protocol.html#sitemapIndex_sitemap
  873. *
  874. * @access private
  875. * @since 4.8.0
  876. *
  877. * @param array $row The sitemap data to be processed.
  878. *
  879. * @return string An XML fragment representing the post URL.
  880. */
  881. private function sitemap_row_to_index_item( $row ) {
  882. $url = $this->finder->construct_sitemap_url( $row['post_title'] );
  883. $item_array = array(
  884. 'sitemap' => array(
  885. 'loc' => $url,
  886. 'lastmod' => jp_sitemap_datetime( $row['post_date'] ),
  887. ),
  888. );
  889. return array(
  890. 'xml' => $item_array,
  891. 'last_modified' => $row['post_date'],
  892. );
  893. }
  894. /**
  895. * This is served instead of a 404 when the master sitemap is requested
  896. * but not yet generated.
  897. *
  898. * @access public
  899. * @since 6.7.0
  900. *
  901. * @return string The empty sitemap xml.
  902. */
  903. public function empty_sitemap_xml() {
  904. $empty_sitemap = new Jetpack_Sitemap_Buffer_Empty();
  905. return $empty_sitemap->contents();
  906. }
  907. /**
  908. * Build and return the news sitemap xml. Note that the result of this
  909. * function is cached in the transient 'jetpack_news_sitemap_xml'.
  910. *
  911. * @access public
  912. * @since 4.8.0
  913. *
  914. * @return string The news sitemap xml.
  915. */
  916. public function news_sitemap_xml() {
  917. $the_stored_news_sitemap = get_transient( 'jetpack_news_sitemap_xml' );
  918. if ( false === $the_stored_news_sitemap ) {
  919. if ( $this->logger ) {
  920. $this->logger->report( 'Beginning news sitemap generation.' );
  921. }
  922. /**
  923. * Filter limit of entries to include in news sitemap.
  924. *
  925. * @module sitemaps
  926. *
  927. * @since 3.9.0
  928. *
  929. * @param int $count Number of entries to include in news sitemap.
  930. */
  931. $item_limit = apply_filters(
  932. 'jetpack_sitemap_news_sitemap_count',
  933. JP_NEWS_SITEMAP_MAX_ITEMS
  934. );
  935. $buffer = new Jetpack_Sitemap_Buffer_News(
  936. min( $item_limit, JP_NEWS_SITEMAP_MAX_ITEMS ),
  937. JP_SITEMAP_MAX_BYTES
  938. );
  939. $posts = $this->librarian->query_most_recent_posts( JP_NEWS_SITEMAP_MAX_ITEMS );
  940. foreach ( $posts as $post ) {
  941. $current_item = $this->post_to_news_sitemap_item( $post );
  942. if ( false === $buffer->append( $current_item['xml'] ) ) {
  943. break;
  944. }
  945. }
  946. if ( $this->logger ) {
  947. $this->logger->time( 'End news sitemap generation.' );
  948. }
  949. $the_stored_news_sitemap = $buffer->contents();
  950. set_transient(
  951. 'jetpack_news_sitemap_xml',
  952. $the_stored_news_sitemap,
  953. JP_NEWS_SITEMAP_INTERVAL
  954. );
  955. } // End if.
  956. return $the_stored_news_sitemap;
  957. }
  958. /**
  959. * Construct the sitemap url entry for a WP_Post.
  960. *
  961. * @link https://www.sitemaps.org/protocol.html#urldef
  962. * @access private
  963. * @since 4.8.0
  964. *
  965. * @param WP_Post $post The post to be processed.
  966. *
  967. * @return array
  968. * @type array $xml An XML fragment representing the post URL.
  969. * @type string $last_modified Date post was last modified.
  970. */
  971. private function post_to_sitemap_item( $post ) {
  972. /**
  973. * Filter condition to allow skipping specific posts in sitemap.
  974. *
  975. * @module sitemaps
  976. *
  977. * @since 3.9.0
  978. *
  979. * @param bool $skip Current boolean. False by default, so no post is skipped.
  980. * @param object $post Current post in the form of a $wpdb result object. Not WP_Post.
  981. */
  982. if ( true === apply_filters( 'jetpack_sitemap_skip_post', false, $post ) ) {
  983. return array(
  984. 'xml' => null,
  985. 'last_modified' => null,
  986. );
  987. }
  988. $url = esc_url( get_permalink( $post ) );
  989. /*
  990. * Spec requires the URL to be <=2048 bytes.
  991. * In practice this constraint is unlikely to be violated.
  992. */
  993. if ( 2048 < strlen( $url ) ) {
  994. $url = home_url() . '/?p=' . $post->ID;
  995. }
  996. $last_modified = $post->post_modified_gmt;
  997. // Check for more recent comments.
  998. // Note that 'Y-m-d h:i:s' strings sort lexicographically.
  999. if ( 0 < $post->comment_count ) {
  1000. $last_modified = max(
  1001. $last_modified,
  1002. $this->librarian->query_latest_approved_comment_time_on_post( $post->ID )
  1003. );
  1004. }
  1005. $item_array = array(
  1006. 'url' => array(
  1007. 'loc' => $url,
  1008. 'lastmod' => jp_sitemap_datetime( $last_modified ),
  1009. ),
  1010. );
  1011. /**
  1012. * Filter sitemap URL item before rendering it as XML.
  1013. *
  1014. * @module sitemaps
  1015. *
  1016. * @since 3.9.0
  1017. *
  1018. * @param array $tree Associative array representing sitemap URL element.
  1019. * @param int $post_id ID of the post being processed.
  1020. */
  1021. $item_array = apply_filters( 'jetpack_sitemap_url', $item_array, $post->ID );
  1022. return array(
  1023. 'xml' => $item_array,
  1024. 'last_modified' => $last_modified,
  1025. );
  1026. }
  1027. /**
  1028. * Construct the image sitemap url entry for a WP_Post of image type.
  1029. *
  1030. * @link https://www.sitemaps.org/protocol.html#urldef
  1031. *
  1032. * @access private
  1033. * @since 4.8.0
  1034. *
  1035. * @param WP_Post $post The image post to be processed.
  1036. *
  1037. * @return array
  1038. * @type array $xml An XML fragment representing the post URL.
  1039. * @type string $last_modified Date post was last modified.
  1040. */
  1041. private function image_post_to_sitemap_item( $post ) {
  1042. /**
  1043. * Filter condition to allow skipping specific image posts in the sitemap.
  1044. *
  1045. * @module sitemaps
  1046. *
  1047. * @since 4.8.0
  1048. *
  1049. * @param bool $skip Current boolean. False by default, so no post is skipped.
  1050. * @param WP_POST $post Current post object.
  1051. */
  1052. if ( apply_filters( 'jetpack_sitemap_image_skip_post', false, $post ) ) {
  1053. return array(
  1054. 'xml' => null,
  1055. 'last_modified' => null,
  1056. );
  1057. }
  1058. $url = wp_get_attachment_url( $post->ID );
  1059. // Do not include the image if the attached parent is not published.
  1060. // Unattached will be published. Otherwise, will inherit parent status.
  1061. if ( 'publish' !== get_post_status( $post ) ) {
  1062. return array(
  1063. 'xml' => null,
  1064. 'last_modified' => null,
  1065. );
  1066. }
  1067. $parent_url = get_permalink( get_post( $post->post_parent ) );
  1068. if ( '' == $parent_url ) { // WPCS: loose comparison ok.
  1069. $parent_url = get_permalink( $post );
  1070. }
  1071. $item_array = array(
  1072. 'url' => array(
  1073. 'loc' => $parent_url,
  1074. 'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
  1075. 'image:image' => array(
  1076. 'image:loc' => $url,
  1077. ),
  1078. ),
  1079. );
  1080. $item_array['url']['image:image']['image:title'] = $post->post_title;
  1081. $item_array['url']['image:image']['image:caption'] = $post->post_excerpt;
  1082. /**
  1083. * Filter associative array with data to build <url> node
  1084. * and its descendants for current post in image sitemap.
  1085. *
  1086. * @module sitemaps
  1087. *
  1088. * @since 4.8.0
  1089. *
  1090. * @param array $item_array Data to build parent and children nodes for current post.
  1091. * @param int $post_id Current image post ID.
  1092. */
  1093. $item_array = apply_filters(
  1094. 'jetpack_sitemap_image_sitemap_item',
  1095. $item_array,
  1096. $post->ID
  1097. );
  1098. return array(
  1099. 'xml' => $item_array,
  1100. 'last_modified' => $post->post_modified_gmt,
  1101. );
  1102. }
  1103. /**
  1104. * Construct the video sitemap url entry for a WP_Post of video type.
  1105. *
  1106. * @link https://www.sitemaps.org/protocol.html#urldef
  1107. * @link https://developers.google.com/webmasters/videosearch/sitemaps
  1108. *
  1109. * @access private
  1110. * @since 4.8.0
  1111. *
  1112. * @param WP_Post $post The video post to be processed.
  1113. *
  1114. * @return array
  1115. * @type array $xml An XML fragment representing the post URL.
  1116. * @type string $last_modified Date post was last modified.
  1117. */
  1118. private function video_post_to_sitemap_item( $post ) {
  1119. /**
  1120. * Filter condition to allow skipping specific image posts in the sitemap.
  1121. *
  1122. * @module sitemaps
  1123. *
  1124. * @since 4.8.0
  1125. *
  1126. * @param bool $skip Current boolean. False by default, so no post is skipped.
  1127. * @param WP_POST $post Current post object.
  1128. */
  1129. if ( apply_filters( 'jetpack_sitemap_video_skip_post', false, $post ) ) {
  1130. return array(
  1131. 'xml' => null,
  1132. 'last_modified' => null,
  1133. );
  1134. }
  1135. // Do not include the video if the attached parent is not published.
  1136. // Unattached will be published. Otherwise, will inherit parent status.
  1137. if ( 'publish' !== get_post_status( $post ) ) {
  1138. return array(
  1139. 'xml' => null,
  1140. 'last_modified' => null,
  1141. );
  1142. }
  1143. $parent_url = esc_url( get_permalink( get_post( $post->post_parent ) ) );
  1144. if ( '' == $parent_url ) { // WPCS: loose comparison ok.
  1145. $parent_url = esc_url( get_permalink( $post ) );
  1146. }
  1147. // Prepare the content like get_the_content_feed().
  1148. $content = $post->post_content;
  1149. /** This filter is already documented in core/wp-includes/post-template.php */
  1150. $content = apply_filters( 'the_content', $content );
  1151. /** This filter is already documented in core/wp-includes/feed.php */
  1152. $content = apply_filters( 'the_content_feed', $content, 'rss2' );
  1153. // Include thumbnails for VideoPress videos, use blank image for others
  1154. if ( 'complete' === get_post_meta( $post->ID, 'videopress_status', true ) && has_post_thumbnail( $post ) ) {
  1155. $video_thumbnail_url = get_the_post_thumbnail_url( $post );
  1156. } else {
  1157. /**
  1158. * Filter the thumbnail image used in the video sitemap for non-VideoPress videos.
  1159. *
  1160. * @since 7.2.0
  1161. *
  1162. * @param string $str Image URL.
  1163. */
  1164. $video_thumbnail_url = apply_filters( 'jetpack_video_sitemap_default_thumbnail', 'https://s0.wp.com/i/blank.jpg' );
  1165. }
  1166. $item_array = array(
  1167. 'url' => array(
  1168. 'loc' => $parent_url,
  1169. 'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
  1170. 'video:video' => array(
  1171. /** This filter is already documented in core/wp-includes/feed.php */
  1172. 'video:title' => apply_filters( 'the_title_rss', $post->post_title ),
  1173. 'video:thumbnail_loc' => esc_url( $video_thumbnail_url ),
  1174. 'video:description' => $content,
  1175. 'video:content_loc' => esc_url( wp_get_attachment_url( $post->ID ) ),
  1176. ),
  1177. ),
  1178. );
  1179. // TODO: Integrate with VideoPress here.
  1180. // cf. video:player_loc tag in video sitemap spec.
  1181. /**
  1182. * Filter associative array with data to build <url> node
  1183. * and its descendants for current post in video sitemap.
  1184. *
  1185. * @module sitemaps
  1186. *
  1187. * @since 4.8.0
  1188. *
  1189. * @param array $item_array Data to build parent and children nodes for current post.
  1190. * @param int $post_id Current video post ID.
  1191. */
  1192. $item_array = apply_filters(
  1193. 'jetpack_sitemap_video_sitemap_item',
  1194. $item_array,
  1195. $post->ID
  1196. );
  1197. return array(
  1198. 'xml' => $item_array,
  1199. 'last_modified' => $post->post_modified_gmt,
  1200. );
  1201. }
  1202. /**
  1203. * Construct the news sitemap url entry for a WP_Post.
  1204. *
  1205. * @link https://www.sitemaps.org/protocol.html#urldef
  1206. *
  1207. * @access private
  1208. * @since 4.8.0
  1209. *
  1210. * @param WP_Post $post The post to be processed.
  1211. *
  1212. * @return string An XML fragment representing the post URL.
  1213. */
  1214. private function post_to_news_sitemap_item( $post ) {
  1215. /**
  1216. * Filter condition to allow skipping specific posts in news sitemap.
  1217. *
  1218. * @module sitemaps
  1219. *
  1220. * @since 3.9.0
  1221. *
  1222. * @param bool $skip Current boolean. False by default, so no post is skipped.
  1223. * @param WP_POST $post Current post object.
  1224. */
  1225. if ( apply_filters( 'jetpack_sitemap_news_skip_post', false, $post ) ) {
  1226. return array(
  1227. 'xml' => null,
  1228. );
  1229. }
  1230. $url = get_permalink( $post );
  1231. /*
  1232. * Spec requires the URL to be <=2048 bytes.
  1233. * In practice this constraint is unlikely to be violated.
  1234. */
  1235. if ( 2048 < strlen( $url ) ) {
  1236. $url = home_url() . '/?p=' . $post->ID;
  1237. }
  1238. /*
  1239. * Trim the locale to an ISO 639 language code as required by Google.
  1240. * Special cases are zh-cn (Simplified Chinese) and zh-tw (Traditional Chinese).
  1241. * @link https://www.loc.gov/standards/iso639-2/php/code_list.php
  1242. */
  1243. $language = strtolower( get_locale() );
  1244. if ( in_array( $language, array( 'zh_tw', 'zh_cn' ), true ) ) {
  1245. $language = str_replace( '_', '-', $language );
  1246. } else {
  1247. $language = preg_replace( '/(_.*)$/i', '', $language );
  1248. }
  1249. $item_array = array(
  1250. 'url' => array(
  1251. 'loc' => $url,
  1252. 'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
  1253. 'news:news' => array(
  1254. 'news:publication' => array(
  1255. 'news:name' => html_entity_decode( get_bloginfo( 'name' ) ),
  1256. 'news:language' => $language,
  1257. ),
  1258. /** This filter is already documented in core/wp-includes/feed.php */
  1259. 'news:title' => apply_filters( 'the_title_rss', $post->post_title ),
  1260. 'news:publication_date' => jp_sitemap_datetime( $post->post_date_gmt ),
  1261. 'news:genres' => 'Blog',
  1262. ),
  1263. ),
  1264. );
  1265. /**
  1266. * Filter associative array with data to build <url> node
  1267. * and its descendants for current post in news sitemap.
  1268. *
  1269. * @module sitemaps
  1270. *
  1271. * @since 3.9.0
  1272. *
  1273. * @param array $item_array Data to build parent and children nodes for current post.
  1274. * @param int $post_id Current post ID.
  1275. */
  1276. $item_array = apply_filters(
  1277. 'jetpack_sitemap_news_sitemap_item',
  1278. $item_array,
  1279. $post->ID
  1280. );
  1281. return array(
  1282. 'xml' => $item_array,
  1283. );
  1284. }
  1285. }