Нет описания

sitemap-buffer.php 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <?php
  2. /**
  3. * Sitemaps (per the protocol) are essentially lists of XML fragments;
  4. * lists which are subject to size constraints. The Jetpack_Sitemap_Buffer
  5. * class abstracts the details of constructing these lists while
  6. * maintaining the constraints.
  7. *
  8. * @since 4.8.0
  9. * @package automattic/jetpack
  10. */
  11. /**
  12. * A buffer for constructing sitemap xml files.
  13. *
  14. * Models a list of strings such that
  15. *
  16. * 1. the list must have a bounded number of entries,
  17. * 2. the concatenation of the strings must have bounded
  18. * length (including some header and footer strings), and
  19. * 3. each item has a timestamp, and we need to keep track
  20. * of the most recent timestamp of the items in the list.
  21. *
  22. * @since 4.8.0
  23. */
  24. abstract class Jetpack_Sitemap_Buffer {
  25. /**
  26. * Largest number of items the buffer can hold.
  27. *
  28. * @access protected
  29. * @since 4.8.0
  30. * @var int $item_capacity The item capacity.
  31. */
  32. protected $item_capacity;
  33. /**
  34. * Largest number of bytes the buffer can hold.
  35. *
  36. * @access protected
  37. * @since 4.8.0
  38. * @var int $byte_capacity The byte capacity.
  39. */
  40. protected $byte_capacity;
  41. /**
  42. * Flag which detects when the buffer is full.
  43. *
  44. * @access protected
  45. * @since 4.8.0
  46. * @var bool $is_full_flag The flag value. This flag is set to false on construction and only flipped to true if we've tried to add something and failed.
  47. */
  48. protected $is_full_flag;
  49. /**
  50. * Flag which detects when the buffer is empty.
  51. *
  52. * @access protected
  53. * @since 4.8.0
  54. * @var bool $is_empty_flag The flag value. This flag is set to true on construction and only flipped to false if we've tried to add something and succeeded.
  55. */
  56. protected $is_empty_flag;
  57. /**
  58. * The most recent timestamp seen by the buffer.
  59. *
  60. * @access protected
  61. * @since 4.8.0
  62. * @var string $timestamp Must be in 'YYYY-MM-DD hh:mm:ss' format.
  63. */
  64. protected $timestamp;
  65. /**
  66. * The DOM document object that is currently being used to construct the XML doc.
  67. *
  68. * @access protected
  69. * @since 5.3.0
  70. * @var DOMDocument $doc
  71. */
  72. protected $doc = null;
  73. /**
  74. * The root DOM element object that holds everything inside. Do not use directly, call
  75. * the get_root_element getter method instead.
  76. *
  77. * @access protected
  78. * @since 5.3.0
  79. * @var DOMElement $doc
  80. */
  81. protected $root = null;
  82. /**
  83. * Helper class to construct sitemap paths.
  84. *
  85. * @since 5.3.0
  86. * @protected
  87. * @var Jetpack_Sitemap_Finder
  88. */
  89. protected $finder;
  90. /**
  91. * Construct a new Jetpack_Sitemap_Buffer.
  92. *
  93. * @since 4.8.0
  94. *
  95. * @param int $item_limit The maximum size of the buffer in items.
  96. * @param int $byte_limit The maximum size of the buffer in bytes.
  97. * @param string $time The initial datetime of the buffer. Must be in 'YYYY-MM-DD hh:mm:ss' format.
  98. */
  99. public function __construct( $item_limit, $byte_limit, $time ) {
  100. $this->is_full_flag = false;
  101. $this->timestamp = $time;
  102. $this->finder = new Jetpack_Sitemap_Finder();
  103. $this->doc = new DOMDocument( '1.0', 'UTF-8' );
  104. $this->item_capacity = max( 1, (int) $item_limit );
  105. $this->byte_capacity = max( 1, (int) $byte_limit ) - strlen( $this->doc->saveXML() );
  106. }
  107. /**
  108. * Returns a DOM element that contains all sitemap elements.
  109. *
  110. * @access protected
  111. * @since 5.3.0
  112. * @return DOMElement $root
  113. */
  114. abstract protected function get_root_element();
  115. /**
  116. * Append an item to the buffer, if there is room for it,
  117. * and set is_empty_flag to false. If there is no room,
  118. * we set is_full_flag to true. If $item is null,
  119. * don't do anything and report success.
  120. *
  121. * @since 4.8.0
  122. * @deprecated 5.3.0 Use Jetpack_Sitemap_Buffer::append.
  123. *
  124. * @param string $item The item to be added.
  125. */
  126. public function try_to_add_item( $item ) {
  127. _deprecated_function(
  128. 'Jetpack_Sitemap_Buffer::try_to_add_item',
  129. '5.3.0',
  130. 'Jetpack_Sitemap_Buffer::append'
  131. );
  132. $this->append( $item );
  133. }
  134. /**
  135. * Append an item to the buffer, if there is room for it,
  136. * and set is_empty_flag to false. If there is no room,
  137. * we set is_full_flag to true. If $item is null,
  138. * don't do anything and report success.
  139. *
  140. * @since 5.3.0
  141. *
  142. * @param array $array The item to be added.
  143. *
  144. * @return bool True if the append succeeded, False if not.
  145. */
  146. public function append( $array ) {
  147. if ( is_null( $array ) ) {
  148. return true;
  149. }
  150. if ( $this->is_full_flag ) {
  151. return false;
  152. }
  153. if ( 0 >= $this->item_capacity || 0 >= $this->byte_capacity ) {
  154. $this->is_full_flag = true;
  155. return false;
  156. } else {
  157. $this->item_capacity -= 1;
  158. $added_element = $this->array_to_xml_string( $array, $this->get_root_element(), $this->doc );
  159. $this->byte_capacity -= strlen( $this->doc->saveXML( $added_element ) );
  160. return true;
  161. }
  162. }
  163. /**
  164. * Retrieve the contents of the buffer.
  165. *
  166. * @since 4.8.0
  167. *
  168. * @return string The contents of the buffer (with the footer included).
  169. */
  170. public function contents() {
  171. if ( $this->is_empty() ) {
  172. // The sitemap should have at least the root element added to the DOM.
  173. $this->get_root_element();
  174. }
  175. return $this->doc->saveXML();
  176. }
  177. /**
  178. * Retrieve the document object.
  179. *
  180. * @since 5.3.0
  181. * @return DOMDocument $doc
  182. */
  183. public function get_document() {
  184. return $this->doc;
  185. }
  186. /**
  187. * Detect whether the buffer is full.
  188. *
  189. * @since 4.8.0
  190. *
  191. * @return bool True if the buffer is full, false otherwise.
  192. */
  193. public function is_full() {
  194. return $this->is_full_flag;
  195. }
  196. /**
  197. * Detect whether the buffer is empty.
  198. *
  199. * @since 4.8.0
  200. *
  201. * @return bool True if the buffer is empty, false otherwise.
  202. */
  203. public function is_empty() {
  204. return (
  205. ! isset( $this->root )
  206. || ! $this->root->hasChildNodes()
  207. );
  208. }
  209. /**
  210. * Update the timestamp of the buffer.
  211. *
  212. * @since 4.8.0
  213. *
  214. * @param string $new_time A datetime string in 'YYYY-MM-DD hh:mm:ss' format.
  215. */
  216. public function view_time( $new_time ) {
  217. $this->timestamp = max( $this->timestamp, $new_time );
  218. }
  219. /**
  220. * Retrieve the timestamp of the buffer.
  221. *
  222. * @since 4.8.0
  223. *
  224. * @return string A datetime string in 'YYYY-MM-DD hh:mm:ss' format.
  225. */
  226. public function last_modified() {
  227. return $this->timestamp;
  228. }
  229. /**
  230. * Render an associative array as an XML string. This is needed because
  231. * SimpleXMLElement only handles valid XML, but we sometimes want to
  232. * pass around (possibly invalid) fragments. Note that 'null' values make
  233. * a tag self-closing; this is only sometimes correct (depending on the
  234. * version of HTML/XML); see the list of 'void tags'.
  235. *
  236. * Example:
  237. *
  238. * array(
  239. * 'html' => array( |<html xmlns="foo">
  240. * 'head' => array( | <head>
  241. * 'title' => 'Woo!', | <title>Woo!</title>
  242. * ), | </head>
  243. * 'body' => array( ==> | <body>
  244. * 'h2' => 'Some thing', | <h2>Some thing</h2>
  245. * 'p' => 'it's all up ons', | <p>it's all up ons</p>
  246. * 'br' => null, | <br />
  247. * ), | </body>
  248. * ), |</html>
  249. * )
  250. *
  251. * @access protected
  252. * @since 3.9.0
  253. * @since 4.8.0 Rename, add $depth parameter, and change return type.
  254. * @since 5.3.0 Refactor, remove $depth parameter, add $parent and $root, make access protected.
  255. *
  256. * @param array $array A recursive associative array of tag/child relationships.
  257. * @param DOMElement $parent (optional) an element to which new children should be added.
  258. * @param DOMDocument $root (optional) the parent document.
  259. *
  260. * @return string|DOMDocument The rendered XML string or an object if root element is specified.
  261. */
  262. protected function array_to_xml_string( $array, $parent = null, $root = null ) {
  263. $return_string = false;
  264. if ( null === $parent ) {
  265. $return_string = true;
  266. $parent = $root = new DOMDocument();
  267. }
  268. if ( is_array( $array ) ) {
  269. foreach ( $array as $key => $value ) {
  270. $element = $root->createElement( $key );
  271. $parent->appendChild( $element );
  272. if ( is_array( $value ) ) {
  273. foreach ( $value as $child_key => $child_value ) {
  274. $child = $root->createElement( $child_key );
  275. $element->appendChild( $child );
  276. $child->appendChild( self::array_to_xml_string( $child_value, $child, $root ) );
  277. }
  278. } else {
  279. $element->appendChild(
  280. $root->createTextNode( $value )
  281. );
  282. }
  283. }
  284. } else {
  285. $element = $root->createTextNode( $array );
  286. $parent->appendChild( $element );
  287. }
  288. if ( $return_string ) {
  289. return $root->saveHTML();
  290. } else {
  291. return $element;
  292. }
  293. }
  294. }