SeoLinks.php 14.5 KB
<?php
/**
 * ====================================================================================================================|
 * @author artbox Khonko Alex
 * Виджет, полностью копирует \artbox\artbox-core\widgets\SeoWidget
 * Примечание:
 * Я не смог чисто унаследовать виджет, потому что в основу его ложится родительский вариант
 * SeoWidget::run -> parent::run
 * При наследовании у меня выдавалось по 2 одинаковых link prev/link next
 * Поэтому ядро виджета я вернул в исходное состояние, поменял use во всех местах на свой, мой является
 * как минимум на 50% дописанным/переписанным вариантом для линии
 * ====================================================================================================================|
 */

namespace frontend\widgets;

use Yii;
use common\components\Substringer;
use yii\helpers\Url;
use yii\base\Widget;
use yii\data\Pagination;
use yii\helpers\VarDumper as d;

class SeoLinks extends Widget
{

	/**
	 * @var null | Pagination
	 * @var null | \common\components\SeoComponent
	 */
	public $pagination = null;

	public $canonical = true;

	public $firstpageWithoutParameter = false;

	public $seo = null;
	/**
	 * @inheritdoc
	 */

	/**
	 * ============================================================================================================|
	 * Правка от Саши
	 * 1) Если есть параметры в урле, Canonical должен вести на страницу без параметров,
	 * за исключением Пагинации и сортировки. И next/prev чтобы были без этих параметров
	 * Пример страницы
	 * https://www.linija-svitla.ua/catalog/ulichnoe-osveshchenie?page_num=3&fcatlist=3268%2C&sort=test_test&3269%2C3270%2C3272%2C3273%2C3274%2C3275&page=50&per-page=18
	 * ************************************************************************************************************
	 * 2) link prev со 2й страницы должен ссылатся на первую, НО ,
	 * a) там должна отсутствовать пагинация б) должна остатся сортировка
	 * Прим
	 * Было https://www.linija-svitla.ua/catalog/ulichnoe-osveshchenie?page=1&per=page=18&sort=title_asc
	 * Стало https://www.linija-svitla.ua/catalog/ulichnoe-osveshchenie?sort=title_asc
	 * ============================================================================================================|
	 * 3) При 301 редирректе от НеЧПУ продуктам/фильтрам к ЧПУ, теги prev/next ссылаются на старые НеЧПУ
	 * страницы
	 * ============================================================================================================|
	 */
	public function run()
	{

		# 0 изменяем ссылку, оставляя в ней только пагинацию и сортировку
		$firstRegex = '/(\??|&?)page=\d{1,5}&per-page=\d{1,5}/';
		$secondRegex = '/(\?|&)sort=[a-zA-z_]+/';
		$requiredDelimiter = '?';
		$firstConcatenateSymbol = '?';
		$secondConcatenateSymbol = '&';

		$links = $this->pagination->getLinks();

		if ($this->canonical and ($this->seo->getRobots() != 'noindex,nofollow' and $this->seo->getRobots() != 'noindex,follow' and $this->seo->getRobots() != 'noindex, nofollow' and $this->seo->getRobots() != 'noindex, follow')
		) {
			# 0
			$cannonicalLink = Substringer::changeStringByRegex(
				Yii::$app->request->getAbsoluteUrl(),
				$firstRegex,
				$secondRegex,
				$requiredDelimiter,
				$firstConcatenateSymbol,
				$secondConcatenateSymbol
			);
			# 1 В канониклах должна присутствовать только пагинация, удаляю всё, что не есть пагинацией
			if ((strpos($cannonicalLink, '?page'))) {
				$cannonicalLink = Substringer::simpleStringSubstring($cannonicalLink, '&sort');
			} else {
				$cannonicalLink = Substringer::changeStringByRegex(
					Yii::$app->request->getAbsoluteUrl(),
					$firstRegex,
					$secondRegex,
					$requiredDelimiter,
					$firstConcatenateSymbol,
					$secondConcatenateSymbol
				);
				$cannonicalLink = Substringer::simpleStringSubstring($cannonicalLink, '&sort');
				/**
				 * Сейчас ссылка имеет вид: www.siteName.com?page=X&per-page=X
				 */

				# 1.1 Если в результате у нас остается URL вида www.siteName.com?
				# обрезаем этот ?
				if (strpos($cannonicalLink, '?') && !strpos($cannonicalLink, '?page')) {
					$cannonicalLink = stristr($cannonicalLink, '?', true);
				}

				# 1.2 если ссылка содержит в себе пагинацию, НО она по каким-то причинам не первая,
				# удаляем все GET параметры до неё, и заменяем & на ?
				if (strpos($cannonicalLink, '?page') !== false) {
					$cannonicalLinkRigth = stristr($cannonicalLink, '&page');
					$cannonicalLinkRigth = substr($cannonicalLinkRigth, 1);
					$cannonicalLinkRigth = '?' . $cannonicalLinkRigth;

					$cannonicalLink = stristr($cannonicalLink, '?', true);
					$cannonicalLink .= $cannonicalLinkRigth;
				}

			}
			# если нашем очищенном каноникле есть ? в конце(www.siteName.com?page=X&per-page=X?), убираем его
			$cannonicalLink = ($cannonicalLink[strlen($cannonicalLink) - 1] !== '?') ? $cannonicalLink : rtrim(
				$cannonicalLink,
				'?'
			);

			# 1.3 в каноникле должна сохранятся очередность нужных GET параметров
			# берём финальную ссылку и если нужно, перерисовываем её
			$seoCuttedLink = $this->changeLinksToRequestView($cannonicalLink, Yii::$app->request->getAbsoluteUrl());
			$this->view->registerLinkTag(
				[
					'rel' => 'canonical',
					'href' => $cannonicalLink,
				]
			);
		}

		/**
		 * =========================================================================================================
		 * То же самое для тега next
		 * =========================================================================================================
		 */

		if (key_exists('next', $links)) {
			#2.1 Обрезаем ссылку справа по заданным GET regexp-ам
			$nextPageGetQueryString = stristr(
				Substringer::changeStringByRegex(
					$links['next'],
					$firstRegex,
					$secondRegex,
					$requiredDelimiter,
					$firstConcatenateSymbol,
					$secondConcatenateSymbol
				),
				'?'
			);
			# 2.2 сливаем левую и правую часть
			$nextLinkTitle = Substringer::simpleStringSubstring(
					Yii::$app->request->url,
					'?'
				) . $nextPageGetQueryString;

			# 2.3  перестраиваем сслыку согласно входящему стилю
			$nextLinkTitle = $this->changeLinksToRequestView($nextLinkTitle, Yii::$app->request->url);

			$this->view->registerLinkTag(
				[
					'rel' => 'next',
					'href' => $nextLinkTitle,
				]
			);
		}

		if ($this->firstpageWithoutParameter and $this->pagination->page == 1) {
			if (key_exists('prev', $links)) {
				$link = stristr(\Yii::$app->request->url, '?', true);
				/**
				 * @var \artbox\catalog\models\Filter $filter
				 */
				$filter = \Yii::$app->get('filter')->filterObj;
				if (key_exists($link, $filter->getReplacedFilters())) {
					$link = $filter->getReplacedFilters()[$link];
				}

				$seoCuttedLink = $this->changeLinksToRequestView($link, Yii::$app->request->url);
				$this->view->registerLinkTag(
					[
						'rel' => 'prev',
						'href' => $seoCuttedLink,

					]
				);
			}
		} else {
			if (key_exists('prev', $links)) {

				# правка 3  Я не ломаю старых наработок, а просто прокидываю новый(если был редирект) URL prev для
				// предыдущих правок
				$prevPageGetQueryString = stristr(
					Substringer::changeStringByRegex(
						$links['prev'],
						$firstRegex,
						$secondRegex,
						$requiredDelimiter,
						$firstConcatenateSymbol,
						$secondConcatenateSymbol
					),
					'?'
				);
				$prevLinkTitle = Substringer::simpleStringSubstring(
						Yii::$app->request->url,
						'?'
					) . $prevPageGetQueryString;
				# правка 2
				$seoStringStart = $prevLinkTitle;
				$seoCuttedLink = $seoStringStart;
				$seoCuttedLink = Substringer::changeStringByRegex(
					$seoCuttedLink,
					$firstRegex,
					$secondRegex,
					$requiredDelimiter,
					$firstConcatenateSymbol,
					$secondConcatenateSymbol
				);

				if (strpos($seoStringStart, '?page=1')) {

					/**
					 * Правка для категории/блога
					 * У них разное к-во страниц, поэтому на замену конкретной срезки URL(?page=1&per-page=18) как было раньше,
					 * я буду автоматически пересматривать currentPageSizeParam для странички
					 */
					$perPageParam = false;

					# если в $seoCuttredLink уже есть пагинация, то паршу строку и достаю реальное к-во записей для страницы
					if (strpos($seoCuttedLink, 'per-page')) {
						$perPageParam = parse_str($seoCuttedLink, $params);
						$perPageParam = (isset ($params['per-page'])) ? $params['per-page'] : false;
					}
					# или же ставлю размер как в frontend/views/category,
					# потому что большая часть кода здесь заточена именно под currentPerPageSizeParam=18
					$perPageParam = ($perPageParam === false) ? 18 : $perPageParam;
					$seoCuttedLink = str_replace('?page=1&per-page=' . $perPageParam, '?', $seoCuttedLink);
					$seoCuttedLink = str_replace('&', '', $seoCuttedLink);
					$strlen = mb_strlen($seoCuttedLink);
					if ($seoCuttedLink[$strlen - 1] == '?') {
						$seoCuttedLink = str_replace('?', '', $seoCuttedLink);
					}

				} else {
					$seoCuttedLink = $seoStringStart;
				}

				# правка 2

				if (strpos($seoCuttedLink, '?sort')) {
					if (strpos($seoCuttedLink, '&page=1')) {
						$seoCuttedLink = Substringer::simpleStringSubstring($seoCuttedLink, '&page=1&per-page');
					}

				}

				$seoCuttedLink = Substringer::changeStringByRegex(
					$seoCuttedLink,
					$firstRegex,
					$secondRegex,
					$requiredDelimiter,
					$firstConcatenateSymbol,
					$secondConcatenateSymbol
				);
				# поправляем URL выдачи, если он после всего имеет такой вид: <link href="/catName/subCatName?page=Xper-page=Xsort=param1_val1" rel="prev">
				if (preg_match('/\d+sort=/', $seoCuttedLink) != false) {
					$seoCuttedLinkRigth = stristr($seoCuttedLink, 'sort=');
					$seoCuttedLink = stristr($seoCuttedLink, 'sort=', true) . '&' . $seoCuttedLinkRigth;
				}
				# то же самое, если в пагинации URL пропущен & ==>  www.site.com?page=Xper-page=X
				if (preg_match('/\d+per-page=/', $seoCuttedLink) != false) {
					$seoCuttedLinkRigth = stristr($seoCuttedLink, 'per-page=');
					$seoCuttedLink = stristr($seoCuttedLink, 'per-page=', true) . '&' . $seoCuttedLinkRigth;
				}

				$seoCuttedLink = $this->changeLinksToRequestView($seoCuttedLink, Yii::$app->request->url);

				$this->view->registerLinkTag(
					[
						'rel' => 'prev',
						'href' => $seoCuttedLink,
					]
				);
			}
		}

		parent::run();
	}

	/**
	 * @param string $link
	 * @param string $requestUrl
	 *     =========================================================================================================
	 *     Метод, который берет строку(URL), и преобразовывает его в вид, в котором он поступал. Написан для тегов
	 *     next/prev/cannonical
	 *     =========================================================================================================
	 *     К нам приходит сюда(в данный виджет) URL, из которого нужно сформировать данные 3 линка. По всем Саниным
	 *     правкам, они должны быть в одном из форматов:
	 *     + новенькийЧпу?page=X&per-page=X&sort=param_val
	 *     + новенькийЧпу?sort=param_val&page=X&per-page=X
	 *     Метод берет текущий URL, смотрит какая из 2 вариаций пришла, и не меняя уже написанной логики
	 *     возвращает обработанную конечтную строку,идентичную входящей
	 *Если в текущем URL отсутствует погинация, НО присутствует сортировка,
	 *     делает ссылку типа www.siteName.com?sort=var_param&page=X&per-page=X
	 *     =========================================================================================================
	 *
	 * @return string
	 */
	private function changeLinksToRequestView(string $link, string $requestUrl)
	{
		$positionSort = (strpos($requestUrl, 'sort=') !== false) ? strpos($requestUrl, 'sort=') : 0;
		$positionPagination = (strpos($requestUrl, 'page=') !== false) ? strpos($requestUrl, 'page=') : 0;

		$regex1 = '/(\??|&?)page=\d{1,5}&per-page=\d{1,5}/';
		$regex2 = '/(\??|&)sort=[a-zA-z_]+&?/';

		/*
		 * У нас 5 вариантов:
		 *      sort>page ===> пагинация идёт первой
		 *      sort<page ===> перввая сортировка
		 *      !sort
		 *      !page
		 *      !sort !page
		 */
		$option = $positionSort - $positionPagination;
		if ($option > 0 && $positionPagination != 0)// первая пагинация
		{
			$link = Substringer::changeStringByRegex($link, $regex1, $regex2, '?', '?', '&');
		} elseif ($option > 0 && $positionPagination == 0)//есть сортировка, но нету пагинации
		{
			$link = Substringer::changeStringByRegex($link, $regex2, $regex1, '?', '?', '&');
		} elseif ($option < 0)// page>sort
		{
			$link = Substringer::changeStringByRegex($link, $regex2, $regex1, '?', '?', '&');
		} else // нету ни сортировки, ни пагинации
		{
			$link = Substringer::changeStringByRegex($link, $regex2, $regex1, '?', '?', '&');
		}
		$result = $link;
		return $result;
	}
}