HEX
Server: Apache/2.4.65 (Debian)
System: Linux 88f31f35b0b8 6.1.0-38-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.147-1 (2025-08-02) x86_64
User: www-data (33)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/wp-seopress/src/Services/HTMLSitemap/HTMLSitemapService.php
<?php // phpcs:ignore

namespace SEOPress\Services\HTMLSitemap;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use SEOPress\Services\Options\SitemapOption;

/**
 * HTMLSitemapService
 */
class HTMLSitemapService {
	/**
	 * The sitemap option.
	 *
	 * @var SitemapOption
	 */
	private $sitemap_option;

	/**
	 * The constructor.
	 *
	 * @param SitemapOption $sitemap_option The sitemap option.
	 */
	public function __construct( SitemapOption $sitemap_option ) {
		$this->sitemap_option = $sitemap_option;
	}

	/**
	 * The init function.
	 *
	 * @return void
	 */
	public function init() {
		if ( '1' === $this->sitemap_option->getHtmlEnable() ) {
			add_action( 'wp', array( $this, 'display' ) );
			add_shortcode( 'seopress_html_sitemap', array( $this, 'renderSitemap' ) );
		}
	}

	/**
	 * The display function.
	 *
	 * @return void
	 */
	public function display() {
		$html_mapping = $this->sitemap_option->getHtmlMapping();
		if ( ! empty( $html_mapping ) ) {
			if ( is_page( explode( ',', $html_mapping ) ) ) {
				add_filter( 'the_content', array( $this, 'renderSitemap' ) );
			}
		}
	}

	/**
	 * The renderSitemap function.
	 *
	 * @param string $html The html.
	 *
	 * @return string
	 */
	public function renderSitemap( $html = '' ) { // phpcs:ignore -- TODO: check if method is outside this class before renaming.
		$atts = shortcode_atts(
			array(
				'cpt'        => '',
				'terms_only' => false,
			),
			$html,
			'[seopress_html_sitemap]'
		);

		$product_cat_slug = apply_filters( 'seopress_sitemaps_html_product_cat_slug', 'product_cat' );

		$exclude_option = $this->sitemap_option->getHtmlExclude() ?: ''; // phpcs:ignore
		$order_option   = $this->sitemap_option->getHtmlOrder() ?: ''; // phpcs:ignore
		$orderby_option = $this->sitemap_option->getHtmlOrderBy() ?: ''; // phpcs:ignore

		$html = '';

		if ( ! empty( $this->sitemap_option->getPostTypesList() ) ) {
			$html .= '<div class="wrap-html-sitemap sp-html-sitemap">';

			$post_types_list = $this->sitemap_option->getPostTypesList();

			if ( isset( $post_types_list['page'] ) ) {
				$post_types_list = array( 'page' => $post_types_list['page'] ) + $post_types_list;
			}

			if ( ! empty( $atts['cpt'] ) ) {
				unset( $post_types_list );
				$cpt = explode( ',', $atts['cpt'] );
				foreach ( $cpt as $value ) {
					$post_types_list[ $value ] = array( 'include' => '1' );
				}
			}

			$post_types_list = apply_filters( 'seopress_sitemaps_html_cpt', $post_types_list );

			foreach ( $post_types_list as $cpt_key => $cpt_value ) {
				if ( ! empty( $cpt_value ) ) {
					$html .= '<div class="sp-wrap-cpt">';
				}

				$obj = get_post_type_object( $cpt_key );
				if ( $obj ) {
					$cpt_name = apply_filters( 'seopress_sitemaps_html_cpt_name', $obj->labels->name, $obj->name );
					$html    .= '<h2 class="sp-cpt-name">' . $cpt_name . '</h2>';

					// Add archive link if post type has archives enabled.
					if ( $this->hasPostTypeArchive( $cpt_key ) ) {
						$html .= $this->renderArchiveLink( $cpt_key, $obj );
					}
				}

				foreach ( $cpt_value as $_cpt_key => $_cpt_value ) {
					if ( '1' === $_cpt_value ) {
						$args           = $this->getQueryArgs( $cpt_key, $exclude_option, $order_option, $orderby_option );
						$args_cat_query = $this->getCategoryQueryArgs( $exclude_option );

						$cats = $this->getCategories( $cpt_key, $args_cat_query, $product_cat_slug );

						// Check if we should only display terms.
						$display_terms_only = apply_filters( 'seopress_sitemaps_html_display_terms_only', $atts['terms_only'], $cpt_key );

						if ( is_array( $cats ) && ! empty( $cats ) ) {
							if ( $display_terms_only ) {
								$html .= $this->renderTermsOnly( $cats, $cpt_key );
							} elseif ( '1' !== $this->sitemap_option->getHtmlNoHierarchy() ) {
								$html .= $this->renderHierarchicalSitemap( $cats, $cpt_key, $args, $product_cat_slug );
							} else {
								$html .= $this->renderFlatSitemap( $cpt_key, $args );
							}
						} else {
							$html .= $this->renderFlatSitemap( $cpt_key, $args );
						}
					}
				}

				if ( ! empty( $cpt_value ) ) {
					$html .= '</div>';
				}
			}

			$html .= '</div>';
		}

		return $html;
	}

	/**
	 * The renderTermsOnly function.
	 *
	 * @param array  $terms The terms.
	 * @param string $cpt_key The cpt key.
	 *
	 * @return string
	 */
	private function renderTermsOnly( $terms, $cpt_key ) { // phpcs:ignore -- TODO: check if method is outside this class before renaming.
		$html  = '<div class="sp-wrap-terms">';
		$html .= '<ul class="sp-list-terms">';

		foreach ( $terms as $term ) {
			if ( ! is_wp_error( $term ) && is_object( $term ) ) {
				$term_name = apply_filters( 'seopress_sitemaps_html_term_name', $term->name, $term );
				$term_url  = get_term_link( $term );

				if ( ! is_wp_error( $term_url ) ) {
					$html .= sprintf(
						'<li class="sp-term-item"><a href="%s">%s</a></li>',
						esc_url( $term_url ),
						esc_html( $term_name )
					);
				}
			}
		}

		$html .= '</ul>';
		$html .= '</div>';

		return apply_filters( 'seopress_sitemaps_html_terms_output', $html, $terms, $cpt_key );
	}

	/**
	 * The hasPostTypeArchive function.
	 *
	 * @param string $post_type The post type.
	 *
	 * @return bool
	 */
	private function hasPostTypeArchive( $post_type ) { // phpcs:ignore -- TODO: check if method is outside this class before renaming.
		$post_type_obj = get_post_type_object( $post_type );
		return $post_type_obj && $post_type_obj->has_archive;
	}

	/**
	 * The renderArchiveLink function.
	 *
	 * @param string $post_type The post type.
	 * @param object $post_type_obj The post type object.
	 *
	 * @return string
	 */
	private function renderArchiveLink( $post_type, $post_type_obj ) { // phpcs:ignore -- TODO: check if method is outside this class before renaming.
		if ( '1' === $this->sitemap_option->getHtmlPostTypeArchive() ) {
			return '';
		}

		$archive_url = get_post_type_archive_link( $post_type );
		if ( ! $archive_url ) {
			return '';
		}

		$archive_label = sprintf(
			/* translators: %s: post type archive label */
			__( 'View all %s', 'wp-seopress' ),
			strtolower( $post_type_obj->labels->name )
		);

		return sprintf(
			'<div class="sp-archive-link"><a href="%s">%s</a></div>',
			esc_url( $archive_url ),
			esc_html( $archive_label )
		);
	}

	/**
	 * The getQueryArgs function.
	 *
	 * @param string $cpt_key The cpt key.
	 * @param string $exclude_option The exclude option.
	 * @param string $order_option The order option.
	 * @param string $orderby_option The orderby option.
	 *
	 * @return array
	 */
	private function getQueryArgs( $cpt_key, $exclude_option, $order_option, $orderby_option ) { // phpcs:ignore -- TODO: check if method is outside this class before renaming.
		return array(
			'posts_per_page'   => 1000,
			'order'            => $order_option,
			'orderby'          => $orderby_option,
			'post_type'        => $cpt_key,
			'post_status'      => 'publish',
			'meta_query'       => array(
				'relation' => 'OR',
				array(
					'key'     => '_seopress_robots_index',
					'value'   => '',
					'compare' => 'NOT EXISTS',
				),
				array(
					'key'     => '_seopress_robots_index',
					'value'   => 'yes',
					'compare' => '!=',
				),
			),
			'fields'           => 'ids',
			'exclude'          => $exclude_option,
			'suppress_filters' => false,
			'no_found_rows'    => true,
			'nopaging'         => true,
		);
	}

	/**
	 * The getCategoryQueryArgs function.
	 *
	 * @param string $exclude_option The exclude option.
	 *
	 * @return array
	 */
	private function getCategoryQueryArgs( $exclude_option ) { // phpcs:ignore -- TODO: check if method is outside this class before renaming.
		return array(
			'orderby'          => 'name',
			'order'            => 'ASC',
			'meta_query'       => array(
				'relation' => 'OR',
				array(
					'key'     => '_seopress_robots_index',
					'value'   => '',
					'compare' => 'NOT EXISTS',
				),
				array(
					'key'     => '_seopress_robots_index',
					'value'   => 'yes',
					'compare' => '!=',
				),
			),
			'exclude'          => $exclude_option,
			'suppress_filters' => false,
		);
	}

	/**
	 * The getCategories function.
	 *
	 * @param string $cpt_key The cpt key.
	 * @param array  $args_cat_query The args cat query.
	 * @param string $product_cat_slug The product cat slug.
	 *
	 * @return array
	 */
	private function getCategories( $cpt_key, $args_cat_query, $product_cat_slug ) { // phpcs:ignore -- TODO: check if method is outside this class before renaming.
		if ( 'post' === $cpt_key ) {
			$args_cat_query = apply_filters( 'seopress_sitemaps_html_cat_query', $args_cat_query );
			return get_categories( $args_cat_query );
		} elseif ( 'product' === $cpt_key ) {
			$args_cat_query = apply_filters( 'seopress_sitemaps_html_product_cat_query', $args_cat_query );
			return get_terms( $product_cat_slug, $args_cat_query );
		}

		return apply_filters( 'seopress_sitemaps_html_hierarchical_terms_query', $cpt_key, $args_cat_query );
	}

	/**
	 * The renderHierarchicalSitemap function.
	 *
	 * @param array  $cats The cats.
	 * @param string $cpt_key The cpt key.
	 * @param array  $args The args.
	 * @param string $product_cat_slug The product cat slug.
	 *
	 * @return string
	 */
	private function renderHierarchicalSitemap( $cats, $cpt_key, $args, $product_cat_slug ) { // phpcs:ignore -- TODO: check if method is outside this class before renaming.
		$html = '<div class="sp-wrap-cats">';

		foreach ( $cats as $cat ) {
			if ( ! is_wp_error( $cat ) && is_object( $cat ) ) {
				$html .= '<div class="sp-wrap-cat">';
				$html .= '<h3 class="sp-cat-name"><a href="' . get_term_link( $cat->term_id ) . '">' . $cat->name . '</a></h3>';

				if ( 'post' === $cpt_key ) {
					unset( $args['cat'] );
					$args['cat'][] = $cat->term_id;
				} elseif ( 'product' === $cpt_key ) {
					unset( $args['tax_query'] );
					$args['tax_query'] = array(
						array(
							'taxonomy' => $product_cat_slug,
							'field'    => 'term_id',
							'terms'    => $cat->term_id,
						),
					);
				}

				if ( 'post' !== $cpt_key && 'product' !== $cpt_key ) {
					$args['tax_query'] = apply_filters( 'seopress_sitemaps_html_hierarchical_tax_query', $cpt_key, $cat, $args );
				}

				$html .= $this->renderFlatSitemap( $cpt_key, $args );
				$html .= '</div>';
			}
		}

		$html .= '</div>';
		return $html;
	}

	/**
	 * The renderFlatSitemap function.
	 *
	 * @param string $cpt_key The cpt key.
	 * @param array  $args The args.
	 *
	 * @return string
	 */
	private function renderFlatSitemap( $cpt_key, $args ) { // phpcs:ignore -- TODO: check if method is outside this class before renaming.
		$template = new HTMLSitemapTemplate( $this->sitemap_option );
		return $template->render( $cpt_key, $args );
	}
}