<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
 * The fallback buffer for users with no XML support.
 *
 * @since 5.3.0
 * @package automattic/jetpack
 */

/**
 * A buffer for constructing master sitemap xml files.
 *
 * @since 5.1.0
 */
abstract class Jetpack_Sitemap_Buffer_Fallback extends Jetpack_Sitemap_Buffer {

	/**
	 * The buffer contents.
	 *
	 * @access protected
	 * @since 5.3.0
	 * @var string The buffer contents.
	 */
	protected $buffer;

	/**
	 * Jetpack_Sitemap_Buffer_Fallback constructor.
	 *
	 * @param int    $item_limit The maximum size of the buffer in items.
	 * @param int    $byte_limit The maximum size of the buffer in bytes.
	 * @param string $time The initial datetime of the buffer. Must be in 'YYYY-MM-DD hh:mm:ss' format.
	 */
	public function __construct( $item_limit, $byte_limit, $time = '1970-01-01 00:00:00' ) {
		$this->is_full_flag  = false;
		$this->is_empty_flag = true;
		$this->timestamp     = $time;

		$this->finder = new Jetpack_Sitemap_Finder();

		$this->item_capacity = max( 1, (int) $item_limit );
		$this->byte_capacity = max( 1, (int) $byte_limit ) - strlen( $this->contents() );
	}

	/**
	 * Append an item to the buffer, if there is room for it,
	 * and set is_empty_flag to false. If there is no room,
	 * we set is_full_flag to true. If $item is null,
	 * don't do anything and report success.
	 *
	 * @since 5.3.0
	 *
	 * @param array $array The item to be added.
	 *
	 * @return bool True if the append succeeded, False if not.
	 */
	public function append( $array ) {
		if ( $array === null ) {
			return true;
		}

		if ( $this->is_full_flag ) {
			return false;
		}

		if ( 0 >= $this->item_capacity || 0 >= $this->byte_capacity ) {
			$this->is_full_flag = true;
			return false;
		} else {
			$this->item_capacity -= 1;
			$added_string         = $this->array_to_xml_string( $array );
			$this->buffer        .= $added_string;
			$this->is_empty_flag  = false;

			mbstring_binary_safe_encoding(); // So we can safely use strlen().
			$this->byte_capacity -= strlen( $added_string );
			reset_mbstring_encoding();

			return true;
		}
	}

	/**
	 * Detect whether the buffer is empty.
	 *
	 * @since 5.3.0
	 *
	 * @return bool True if the buffer is empty, false otherwise.
	 */
	public function is_empty() {
		return $this->is_empty_flag;
	}

	/**
	 * Retrieve the contents of the buffer.
	 *
	 * @since 5.3.0
	 *
	 * @return string The contents of the buffer (with the footer included).
	 */
	public function contents() {
		$root = $this->get_root_element();

		return '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . $root[0] . $this->buffer . $root[1] . PHP_EOL;
	}

	/**
	 * Legacy implementation of array to XML conversion without using DOMDocument.
	 *
	 * @param array       $array Item to append to buffer.
	 * @param DOMElement  $parent (optional) an element to which new children should be added.
	 * @param DOMDocument $root (optional) the parent document.
	 * @return String $result
	 */
	public function array_to_xml_string( $array, $parent = null, $root = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		$string = '';

		foreach ( $array as $key => $value ) {
			// Only allow a-z, A-Z, colon, underscore, and hyphen.
			$tag = preg_replace( '/[^a-zA-Z:_-]/', '_', $key );

			if ( is_array( $value ) ) {
				$string .= "<$tag>";
				$string .= $this->array_to_xml_string( $value );
				$string .= "</$tag>";
			} elseif ( $value === null ) {
				$string .= "<$tag />";
			} else {
				$string .= "<$tag>" . htmlspecialchars( $value ) . "</$tag>";
			}
		}

		return $string;
	}

	/**
	 * Render an associative array of XML attribute key/value pairs.
	 *
	 * @access public
	 * @since 5.3.0
	 *
	 * @param array $array Key/value array of attributes.
	 *
	 * @return string The rendered attribute string.
	 */
	public static function array_to_xml_attr_string( $array ) {
		$string = '';

		foreach ( $array as $key => $value ) {
			$key     = preg_replace( '/[^a-zA-Z:_-]/', '_', $key );
			$string .= ' ' . $key . '="' . esc_attr( $value ) . '"';
		}

		return $string;
	}

}
