Sin descripción

sitemap-librarian.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. <?php
  2. /**
  3. * Sitemaps are stored in the database using a custom table. This class
  4. * provides a small API for storing and retrieving sitemap data so we can
  5. * avoid lots of explicit SQL juggling while building sitemaps. This file
  6. * also includes the SQL used to retrieve posts and images to be included
  7. * in the sitemaps.
  8. *
  9. * @since 4.8.0
  10. * @package automattic/jetpack
  11. */
  12. /* Ensure sitemap constants are available. */
  13. require_once __DIR__ . '/sitemap-constants.php';
  14. /**
  15. * This object handles any database interaction required
  16. * for sitemap generation.
  17. *
  18. * @since 4.8.0
  19. */
  20. class Jetpack_Sitemap_Librarian {
  21. /**
  22. * Retrieve a single sitemap with given name and type.
  23. * Returns null if no such sitemap exists.
  24. *
  25. * @access public
  26. * @since 4.8.0
  27. *
  28. * @param string $name Name of the sitemap to be retrieved.
  29. * @param string $type Type of the sitemap to be retrieved.
  30. *
  31. * @return array $args {
  32. * @type int $id ID number of the sitemap in the database.
  33. * @type string $timestamp Most recent timestamp of the resources pointed to.
  34. * @type string $name Name of the sitemap in the database.
  35. * @type string $type Type of the sitemap in the database.
  36. * @type string $text The content of the sitemap.
  37. * }
  38. */
  39. public function read_sitemap_data( $name, $type ) {
  40. $post_array = get_posts(
  41. array(
  42. 'numberposts' => 1,
  43. 'title' => $name,
  44. 'post_type' => $type,
  45. 'post_status' => 'draft',
  46. )
  47. );
  48. $the_post = array_shift( $post_array );
  49. if ( null === $the_post ) {
  50. return null;
  51. } else {
  52. return array(
  53. 'id' => $the_post->ID,
  54. 'timestamp' => $the_post->post_date,
  55. 'name' => $the_post->post_title,
  56. 'type' => $the_post->post_type,
  57. 'text' => base64_decode( $the_post->post_content ),
  58. );
  59. }
  60. }
  61. /**
  62. * Store a sitemap of given type and index in the database.
  63. * Note that the timestamp is reencoded as 'Y-m-d H:i:s'.
  64. *
  65. * If a sitemap with that type and name does not exist, create it.
  66. * If a sitemap with that type and name does exist, update it.
  67. *
  68. * @access public
  69. * @since 4.8.0
  70. *
  71. * @param string $index Index of the sitemap to be stored.
  72. * @param string $type Type of the sitemap to be stored.
  73. * @param string $contents Contents of the sitemap to be stored.
  74. * @param string $timestamp Timestamp of the sitemap to be stored, in 'YYYY-MM-DD hh:mm:ss' format.
  75. */
  76. public function store_sitemap_data( $index, $type, $contents, $timestamp ) {
  77. $name = jp_sitemap_filename( $type, $index );
  78. $the_post = $this->read_sitemap_data( $name, $type );
  79. if ( null === $the_post ) {
  80. // Post does not exist.
  81. wp_insert_post(
  82. array(
  83. 'post_title' => $name,
  84. 'post_content' => base64_encode( $contents ),
  85. 'post_type' => $type,
  86. 'post_date' => date( 'Y-m-d H:i:s', strtotime( $timestamp ) ),
  87. )
  88. );
  89. } else {
  90. // Post does exist.
  91. wp_insert_post(
  92. array(
  93. 'ID' => $the_post['id'],
  94. 'post_title' => $name,
  95. 'post_content' => base64_encode( $contents ),
  96. 'post_type' => $type,
  97. 'post_date' => date( 'Y-m-d H:i:s', strtotime( $timestamp ) ),
  98. )
  99. );
  100. }
  101. }
  102. /**
  103. * Delete a sitemap by name and type.
  104. *
  105. * @access public
  106. * @since 4.8.0
  107. *
  108. * @param string $name Row name.
  109. * @param string $type Row type.
  110. *
  111. * @return bool 'true' if a row was deleted, 'false' otherwise.
  112. */
  113. public function delete_sitemap_data( $name, $type ) {
  114. $the_post = $this->read_sitemap_data( $name, $type );
  115. if ( null === $the_post ) {
  116. return false;
  117. } else {
  118. wp_delete_post( $the_post['id'] );
  119. return true;
  120. }
  121. }
  122. /**
  123. * Retrieve the contents of a sitemap with given name and type.
  124. * If no such sitemap exists, return the empty string. Note that the
  125. * returned string is run through wp_specialchars_decode.
  126. *
  127. * @access public
  128. * @since 4.8.0
  129. *
  130. * @param string $name Row name.
  131. * @param string $type Row type.
  132. *
  133. * @return string Text of the specified sitemap, or the empty string.
  134. */
  135. public function get_sitemap_text( $name, $type ) {
  136. $row = $this->read_sitemap_data( $name, $type );
  137. if ( null === $row ) {
  138. return '';
  139. } else {
  140. return $row['text'];
  141. }
  142. }
  143. /**
  144. * Delete numbered sitemaps named prefix-(p+1), prefix-(p+2), ...
  145. * until the first nonexistent sitemap is found.
  146. *
  147. * @access public
  148. * @since 4.8.0
  149. *
  150. * @param int $position Number before the first sitemap to be deleted.
  151. * @param string $type Sitemap type.
  152. */
  153. public function delete_numbered_sitemap_rows_after( $position, $type ) {
  154. $any_left = true;
  155. while ( true === $any_left ) {
  156. $position++;
  157. $name = jp_sitemap_filename( $type, $position );
  158. $any_left = $this->delete_sitemap_data( $name, $type );
  159. }
  160. }
  161. /**
  162. * Deletes all stored sitemap data.
  163. *
  164. * @access public
  165. * @since 4.8.0
  166. */
  167. public function delete_all_stored_sitemap_data() {
  168. $this->delete_sitemap_type_data( JP_MASTER_SITEMAP_TYPE );
  169. $this->delete_sitemap_type_data( JP_PAGE_SITEMAP_TYPE );
  170. $this->delete_sitemap_type_data( JP_PAGE_SITEMAP_INDEX_TYPE );
  171. $this->delete_sitemap_type_data( JP_IMAGE_SITEMAP_TYPE );
  172. $this->delete_sitemap_type_data( JP_IMAGE_SITEMAP_INDEX_TYPE );
  173. $this->delete_sitemap_type_data( JP_VIDEO_SITEMAP_TYPE );
  174. $this->delete_sitemap_type_data( JP_VIDEO_SITEMAP_INDEX_TYPE );
  175. }
  176. /**
  177. * Deletes all sitemap data of specific type
  178. *
  179. * @access protected
  180. * @since 5.3.0
  181. *
  182. * @param String $type Type of sitemap.
  183. */
  184. protected function delete_sitemap_type_data( $type ) {
  185. $ids = get_posts(
  186. array(
  187. 'post_type' => $type,
  188. 'post_status' => 'draft',
  189. 'fields' => 'ids',
  190. )
  191. );
  192. foreach ( $ids as $id ) {
  193. wp_trash_post( $id );
  194. }
  195. }
  196. /**
  197. * Retrieve an array of sitemap rows (of a given type) sorted by ID.
  198. *
  199. * Returns the smallest $num_posts sitemap rows (measured by ID)
  200. * of the given type which are larger than $from_id.
  201. *
  202. * @access public
  203. * @since 4.8.0
  204. *
  205. * @param string $type Type of the sitemap rows to retrieve.
  206. * @param int $from_id Greatest lower bound of retrieved sitemap post IDs.
  207. * @param int $num_posts Largest number of sitemap posts to retrieve.
  208. *
  209. * @return array The sitemaps, as an array of associative arrays.
  210. */
  211. public function query_sitemaps_after_id( $type, $from_id, $num_posts ) {
  212. global $wpdb;
  213. return $wpdb->get_results(
  214. $wpdb->prepare(
  215. "SELECT *
  216. FROM $wpdb->posts
  217. WHERE post_type=%s
  218. AND post_status=%s
  219. AND ID>%d
  220. ORDER BY ID ASC
  221. LIMIT %d;",
  222. $type,
  223. 'draft',
  224. $from_id,
  225. $num_posts
  226. ),
  227. ARRAY_A
  228. ); // WPCS: db call ok; no-cache ok.
  229. }
  230. /**
  231. * Retrieve an array of posts sorted by ID.
  232. *
  233. * More precisely, returns the smallest $num_posts posts
  234. * (measured by ID) which are larger than $from_id.
  235. *
  236. * @access public
  237. * @since 4.8.0
  238. *
  239. * @param int $from_id Greatest lower bound of retrieved post IDs.
  240. * @param int $num_posts Largest number of posts to retrieve.
  241. *
  242. * @return array The posts.
  243. */
  244. public function query_posts_after_id( $from_id, $num_posts ) {
  245. global $wpdb;
  246. // Get the list of post types to include and prepare for query.
  247. $post_types = Jetpack_Options::get_option_and_ensure_autoload(
  248. 'jetpack_sitemap_post_types',
  249. array( 'page', 'post' )
  250. );
  251. foreach ( (array) $post_types as $i => $post_type ) {
  252. $post_types[ $i ] = $wpdb->prepare( '%s', $post_type );
  253. }
  254. $post_types_list = join( ',', $post_types );
  255. return $wpdb->get_results(
  256. $wpdb->prepare(
  257. "SELECT *
  258. FROM $wpdb->posts
  259. WHERE post_status='publish'
  260. AND post_type IN ($post_types_list)
  261. AND ID>%d
  262. ORDER BY ID ASC
  263. LIMIT %d;",
  264. $from_id,
  265. $num_posts
  266. )
  267. ); // WPCS: db call ok; no-cache ok.
  268. }
  269. /**
  270. * Get the most recent timestamp among approved comments for the given post_id.
  271. *
  272. * @access public
  273. * @since 4.8.0
  274. *
  275. * @param int $post_id Post identifier.
  276. *
  277. * @return int Timestamp in 'Y-m-d h:i:s' format (UTC) of the most recent comment on the given post, or null if no such comments exist.
  278. */
  279. public function query_latest_approved_comment_time_on_post( $post_id ) {
  280. global $wpdb;
  281. return $wpdb->get_var(
  282. $wpdb->prepare(
  283. "SELECT MAX(comment_date_gmt)
  284. FROM $wpdb->comments
  285. WHERE comment_post_ID = %d AND comment_approved = '1' AND comment_type in ( '', 'comment' )",
  286. $post_id
  287. )
  288. );
  289. }
  290. /**
  291. * Retrieve an array of image posts sorted by ID.
  292. *
  293. * More precisely, returns the smallest $num_posts image posts
  294. * (measured by ID) which are larger than $from_id.
  295. *
  296. * @access public
  297. * @since 4.8.0
  298. *
  299. * @param int $from_id Greatest lower bound of retrieved image post IDs.
  300. * @param int $num_posts Largest number of image posts to retrieve.
  301. *
  302. * @return array The posts.
  303. */
  304. public function query_images_after_id( $from_id, $num_posts ) {
  305. global $wpdb;
  306. return $wpdb->get_results(
  307. $wpdb->prepare(
  308. "SELECT *
  309. FROM $wpdb->posts
  310. WHERE post_type='attachment'
  311. AND post_mime_type LIKE %s
  312. AND ID>%d
  313. ORDER BY ID ASC
  314. LIMIT %d;",
  315. 'image/%',
  316. $from_id,
  317. $num_posts
  318. )
  319. ); // WPCS: db call ok; no-cache ok.
  320. }
  321. /**
  322. * Retrieve an array of video posts sorted by ID.
  323. *
  324. * More precisely, returns the smallest $num_posts video posts
  325. * (measured by ID) which are larger than $from_id.
  326. *
  327. * @access public
  328. * @since 4.8.0
  329. *
  330. * @param int $from_id Greatest lower bound of retrieved video post IDs.
  331. * @param int $num_posts Largest number of video posts to retrieve.
  332. *
  333. * @return array The posts.
  334. */
  335. public function query_videos_after_id( $from_id, $num_posts ) {
  336. global $wpdb;
  337. return $wpdb->get_results(
  338. $wpdb->prepare(
  339. "SELECT *
  340. FROM $wpdb->posts
  341. WHERE post_type='attachment'
  342. AND post_mime_type LIKE %s
  343. AND ID>%d
  344. ORDER BY ID ASC
  345. LIMIT %d;",
  346. 'video/%',
  347. $from_id,
  348. $num_posts
  349. )
  350. ); // WPCS: db call ok; no-cache ok.
  351. }
  352. /**
  353. * Retrieve an array of published posts from the last 2 days.
  354. *
  355. * @access public
  356. * @since 4.8.0
  357. *
  358. * @param int $num_posts Largest number of posts to retrieve.
  359. *
  360. * @return array The posts.
  361. */
  362. public function query_most_recent_posts( $num_posts ) {
  363. global $wpdb;
  364. $two_days_ago = date( 'Y-m-d', strtotime( '-2 days' ) );
  365. /**
  366. * Filter post types to be included in news sitemap.
  367. *
  368. * @module sitemaps
  369. *
  370. * @since 3.9.0
  371. *
  372. * @param array $post_types Array with post types to include in news sitemap.
  373. */
  374. $post_types = apply_filters(
  375. 'jetpack_sitemap_news_sitemap_post_types',
  376. array( 'page', 'post' )
  377. );
  378. foreach ( (array) $post_types as $i => $post_type ) {
  379. $post_types[ $i ] = $wpdb->prepare( '%s', $post_type );
  380. }
  381. $post_types_list = join( ',', $post_types );
  382. return $wpdb->get_results(
  383. $wpdb->prepare(
  384. "SELECT *
  385. FROM $wpdb->posts
  386. WHERE post_status='publish'
  387. AND post_date >= '%s'
  388. AND post_type IN ($post_types_list)
  389. ORDER BY post_date DESC
  390. LIMIT %d;",
  391. $two_days_ago,
  392. $num_posts
  393. )
  394. ); // WPCS: db call ok; no-cache ok.
  395. }
  396. }