<?php
/**
 * WPThemeReview Coding Standard.
 *
 * @package WPTRT\WPThemeReview
 * @link    https://github.com/WPTRT/WPThemeReview
 * @license https://opensource.org/licenses/MIT MIT
 */

namespace WPThemeReview\Sniffs\Templates;

use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;

/**
 * Check if the template file is using a prefix which would cause WP to interpret it as a specialised template
 * meant to apply to only one page on the site.
 *
 * You can check the documentation of each of the functions used in determining the template hierarchy.
 *
 * @link https://developer.wordpress.org/reference/functions/get_category_template
 * @link https://developer.wordpress.org/reference/functions/get_author_template
 * @link https://developer.wordpress.org/reference/functions/get_page_template
 * @link https://developer.wordpress.org/reference/functions/get_tag_template
 *
 * Or you can check the in depth documentation about the template hierarchy.
 *
 * @link https://developer.wordpress.org/themes/basics/template-hierarchy
 *
 * @since 0.2.0
 */
class ReservedFileNamePrefixSniff implements Sniff {

	/**
	 * Error message template.
	 *
	 * @since 0.2.0
	 *
	 * @var string
	 */
	const ERROR_MSG = 'Template files should not be slug specific. The file name used for this template will be interpreted by WP as %1$s-%2$s and only applied when a %1$s with the slug "%2$s" is loaded.';

	/**
	 * Found prefix in a file.
	 *
	 * @since 0.2.0
	 *
	 * @var string
	 */
	protected $prefix;

	/**
	 * File slug.
	 *
	 * @since 0.2.0
	 *
	 * @var string
	 */
	protected $slug;

	/**
	 * List of reserved template file names.
	 *
	 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/
	 * @link https://developer.wordpress.org/themes/template-files-section/partial-and-miscellaneous-template-files/#content-slug-php
	 * @link https://wphierarchy.com/
	 * @link https://en.wikipedia.org/wiki/Media_type#Naming
	 *
	 * @since 0.2.0
	 *
	 * @var array
	 */
	protected $reserved_file_name_prefixes = [
		'author'   => true,
		'category' => true,
		'page'     => true,
		'tag'      => true,
	];

	/**
	 * Returns an array of tokens this test wants to listen for.
	 *
	 * @since 0.2.0
	 *
	 * @return array
	 */
	public function register() {
		return [
			\T_OPEN_TAG,
			\T_OPEN_TAG_WITH_ECHO,
		];
	}

	/**
	 * Processes this test, when one of its tokens is encountered.
	 *
	 * @since 0.2.0
	 *
	 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
	 *                                               token was found.
	 * @param int                         $stackPtr  The position of the current token
	 *                                               in the stack.
	 *
	 * @return int StackPtr to the end of the file, this sniff needs to only
	 *             check each file once.
	 */
	public function process( File $phpcsFile, $stackPtr ) {

		// Usage of `strip_quotes` is to ensure `stdin_path` passed by IDEs does not include quotes.
		$file = $this->strip_quotes( $phpcsFile->getFileName() );

		$fileName = basename( $file );
		$fileName = str_replace( [ '.inc', '.php' ], '', $fileName );

		// Check if the current file has a prefix in the reserved list.
		if ( ! $this->is_reserved_template_name_used( $fileName ) ) {
			return ( $phpcsFile->numTokens + 1 );
		}

		$phpcsFile->addError(
			self::ERROR_MSG,
			0,
			'ReservedTemplatePrefixFound',
			[ $this->prefix, $this->slug ]
		);

		return ( $phpcsFile->numTokens + 1 );
	}

	/**
	 * Checks if the given file name starts with one of the reserved prefixes.
	 *
	 * @since 0.2.0
	 *
	 * @param  string $file File name to check.
	 * @return boolean
	 */
	private function is_reserved_template_name_used( $file ) {
		$file_parts = explode( '-', $file, 2 );

		if ( empty( $file_parts[0] ) || empty( $file_parts[1] ) ) {
			// No dash or dash at start or end of filename, i.e. `-something.php`.
			return false;
		}

		if ( isset( $this->reserved_file_name_prefixes[ strtolower( $file_parts[0] ) ] ) ) {
			$this->prefix = $file_parts[0];
			$this->slug   = $file_parts[1];
			return true;
		}

		return false;
	}

	/**
	 * Strip quotes surrounding an arbitrary string.
	 *
	 * Intended for use with the contents of a T_CONSTANT_ENCAPSED_STRING / T_DOUBLE_QUOTED_STRING.
	 *
	 * Copied from the WordPressCS\WordPress\Sniff abstract class.
	 *
	 * @since 0.2.0
	 *
	 * @param string $string The raw string.
	 * @return string String without quotes around it.
	 */
	private function strip_quotes( $string ) {
		return preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $string );
	}
}