beardcoder/sitemap_generator

View on GitHub
Classes/Domain/Repository/SitemapRepository.php

Summary

Maintainability
D
2 days
Test Coverage
<?php
namespace Markussom\SitemapGenerator\Domain\Repository;

use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use Exception;
use mysqli_result;
use TYPO3\CMS\Core\Database\DatabaseConnection;
/**
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use Markussom\SitemapGenerator\Domain\Model\GoogleNewsUrlEntry;
use Markussom\SitemapGenerator\Domain\Model\Sitemap;
use Markussom\SitemapGenerator\Domain\Model\UrlEntry;
use Markussom\SitemapGenerator\Service\AdditionalWhereService;
use Markussom\SitemapGenerator\Service\FieldValueService;
use Markussom\SitemapGenerator\Service\LimitService;
use Markussom\SitemapGenerator\Service\OrderByService;
use Markussom\SitemapGenerator\Service\PageUrlService;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Frontend\Page\PageRepository;

/**
 * Class SitemapRepository
 */
class SitemapRepository
{
    /**
     * @var FieldValueService
     */
    protected $fieldValueService = null;

    /**
     * @var array
     */
    protected $pluginConfig = [];

    /**
     * @var string
     */
    protected $pageAdditionalWhere = '';

    /**
     * @var ObjectStorage
     */
    protected $entryStorage = null;

    /**
     * @var PageRepository
     */
    protected $pageRepository = null;

    /**
     * @var FrontendInterface
     */
    protected $cacheInstance;

    /**
     * SitemapRepository constructor.
     *
     * @SuppressWarnings(superglobals)
     */
    public function __construct()
    {
        $this->makeClassInstance();
        $this->cacheInstance = GeneralUtility::makeInstance(CacheManager::class)->getCache('sitemap_generator');

        $this->pluginConfig = GeneralUtility::removeDotsFromTS(
            $GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_sitemapgenerator.']
        );

        $this->pageAdditionalWhere = AdditionalWhereService::getWhereString(
            $this->pluginConfig['urlEntries']['pages']['additionalWhere']
        );
    }

    /**
     * Make instance of needed classes
     */
    protected function makeClassInstance(): void
    {
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
        $this->entryStorage = $objectManager->get(ObjectStorage::class);
        $this->pageRepository = $objectManager->get(PageRepository::class);
        $this->fieldValueService = $objectManager->get(FieldValueService::class);
    }

    /**
     * Generate a sitemap
     *
     * @return Sitemap|null
     */
    public function generateSitemap(): ?object
    {
        if ($this->findAllEntries()) {
            $sitemap = GeneralUtility::makeInstance(Sitemap::class);
            $sitemap->setUrlEntries($this->entryStorage);

            return $sitemap;
        }

        return null;
    }

    /**
     * Find all entries
     *
     * @return bool
     */
    public function findAllEntries(): bool
    {
        $this->findAllPages();
        $this->generateEntriesFromTypoScript();

        return true;
    }

    /**
     * Find all pages
     */
    public function findAllPages(): void
    {
        if (empty($this->pluginConfig['urlEntries']['pages'])) {
            return;
        }
        $pages = $this->hidePagesIfNotTranslated($this->getPages());
        $pages = $this->hidePagesIfHiddenInDefaultTranslation($pages);

        $this->getEntriesFromPages($pages);
    }

    /**
     * Remove page if not translated
     *
     * @param array $pages
     *
     * @return array
     */
    private function hidePagesIfNotTranslated(array $pages): array
    {
        $language = GeneralUtility::_GET('L');
        if ($this->isPageNotTranslated($language)) {
            foreach ($pages as $key => $page) {
                $pageOverlay = $this->pageRepository->getPageOverlay($page, $language);
                if (empty($pageOverlay['_PAGES_OVERLAY'])) {
                    unset($pages[$key]);
                }
            }
        }

        return $pages;
    }

    /**
     * @param $language
     *
     * @return bool
     */
    private function isPageNotTranslated($language): bool
    {
        $ifNotTranslated = $this->pluginConfig['urlEntries']['pages']['hidePagesIfNotTranslated'];

        return (int)$language !== 0 && (int)$ifNotTranslated === 1;
    }

    /**
     * Remove page if hidden in default translation
     *
     * @param array $pages
     *
     * @return array
     */
    private function hidePagesIfHiddenInDefaultTranslation(array $pages): array
    {
        $language = GeneralUtility::_GET('L');

        if ($language != 0) {
            return $pages;
        }

        foreach ($pages as $key => $page) {
            if ($page['l18n_cfg'] === 1) {
                unset($pages[$key]);
            }
        }

        return $pages;
    }

    /**
     * Get pages from Database
     *
     * @return array
     */
    private function getPages(): array
    {
        $rootPageId = $this->pluginConfig['urlEntries']['pages']['rootPageId'];
        $rootPage = $this->pageRepository->getPage($rootPageId);

        $cacheIdentifier = md5($rootPageId . '-pagesForSitemap');
        if ($this->cacheInstance->has($cacheIdentifier)) {
            $pages = $this->cacheInstance->get($cacheIdentifier);
        } else {
            $pages = $this->getSubPagesRecursive($rootPageId);
            $this->cacheInstance->set($cacheIdentifier, $pages, ['pagesForSitemap']);
        }

        return array_merge([$rootPage], $pages);
    }

    /**
     * Get sub pages recursive
     *
     * @param $rootPageId
     *
     * @return array
     */
    private function getSubPagesRecursive($rootPageId): array
    {
        $pages = $this->getSubPages($rootPageId);
        foreach ($pages as $page) {
            if (!$this->isPageTreeLeaf($page)) {
                ArrayUtility::mergeRecursiveWithOverrule(
                    $pages,
                    $this->getSubPagesRecursive($page['uid'])
                );
            }
        }

        return $pages;
    }

    /**
     * Get sub pages
     *
     * @param int $startPageId
     * @return array
     */
    private function getSubPages(int $startPageId): array
    {
        $where = $this->pageRepository->enableFields('pages')
            . ' AND ' . UrlEntry::EXCLUDE_FROM_SITEMAP . '!=1' . $this->pageAdditionalWhere;
        try {
            return $this->pageRepository->getMenu($startPageId, '*', 'sorting', $where);
        } catch (Exception $exception) {
            return [];
        }
    }

    /**
     * @param $pages
     */
    public function getEntriesFromPages($pages): void
    {
        foreach ($pages as $page) {
            if ($this->hasPageAnAllowedDoktype($page)) {
                $urlEntry = GeneralUtility::makeInstance(UrlEntry::class);
                $uri = PageUrlService::generatePageUrl($page['uid']);
                $urlEntry->setLoc($uri);
                $urlEntry->setLastmod(date('Y-m-d', $page['SYS_LASTCHANGED']));
                if (isset($page['sitemap_priority'])) {
                    $urlEntry->setPriority(number_format($page['sitemap_priority'] / 10, 1, '.', ''));
                }
                if (isset($page['sitemap_changefreq'])) {
                    $urlEntry->setChangefreq($page['sitemap_changefreq']);
                }
                $this->entryStorage->attach($urlEntry);
            }
        }
    }

    /**
     * @param $page
     *
     * @return bool
     */
    private function hasPageAnAllowedDoktype($page): bool
    {
        return GeneralUtility::inList(
            $this->pluginConfig['urlEntries']['pages']['allowedDoktypes'],
            $page['doktype']
        );
    }

    /**
     * Determines if the child page tree should not be fetched based on the current page.
     * This is for example a "Backend User Section" or "Recycler" (configurable) or the
     * page has "Stop Page Tree" activated (cannot be deactivated).
     *
     * A leaf is the last element in a tree.
     *
     * @param array $page
     *
     * @return bool
     */
    private function isPageTreeLeaf(array $page): bool
    {
        if ('1' === $page['php_tree_stop']) {
            return true;
        }

        return GeneralUtility::inList(
            $this->pluginConfig['urlEntries']['pages']['stopPageTreeDoktypes'],
            $page['doktype']
        );
    }

    /**
     * Generate entries from TypoScript
     */
    public function generateEntriesFromTypoScript(): void
    {
        $urlEntries = $GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_sitemapgenerator.']['urlEntries.'];
        foreach ($urlEntries as $urlEntry) {
            if (!empty($urlEntry['active'])) {
                $this->mapToEntries($urlEntry);
            }
        }
    }

    /**
     * Map to entries
     *
     * @param array $typoScriptUrlEntry
     * @SuppressWarnings(superglobals)
     */
    protected function mapToEntries(array $typoScriptUrlEntry): void
    {
        if ($typoScriptUrlEntry['table'] && $typoScriptUrlEntry['active'] == 1) {
            $records = $this->getRecordsFromDatabase($typoScriptUrlEntry);
            if ($this->getDatabaseConnection()->sql_num_rows($records) !== 0) {
                while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($records)) {
                    $row = $this->hideRecordIfNotTranslated($typoScriptUrlEntry, $row);
                    if (!empty($row)) {
                        /** @var UrlEntry $urlEntry */
                        $urlEntry = GeneralUtility::makeInstance(UrlEntry::class);
                        $urlEntry->setLoc(
                            $this->fieldValueService->getFieldValue('url', $typoScriptUrlEntry, $row)
                        );
                        if (isset($typoScriptUrlEntry['lastmod'])) {
                            $urlEntry->setLastmod(
                                date(
                                    'Y-m-d',
                                    $this->fieldValueService->getFieldValue('lastmod', $typoScriptUrlEntry, $row)
                                )
                            );
                        }
                        if (isset($typoScriptUrlEntry['changefreq'])) {
                            $urlEntry->setChangefreq(
                                $this->fieldValueService->getFieldValue('changefreq', $typoScriptUrlEntry, $row)
                            );
                        }
                        if (isset($typoScriptUrlEntry['priority'])) {
                            $urlEntry->setPriority(
                                number_format(
                                    $this->fieldValueService->getFieldValue('priority', $typoScriptUrlEntry, $row) / 10,
                                    1,
                                    '.',
                                    ''
                                )
                            );
                        }
                        $this->entryStorage->attach($urlEntry);
                    }
                }
            }
        }
    }

    /**
     * Get records from database
     *
     * @param $typoScriptUrlEntry
     * @SuppressWarnings(superglobals)
     *
     * @return bool|object
     */
    private function getRecordsFromDatabase($typoScriptUrlEntry)
    {
        if (!isset($GLOBALS['TCA'][$typoScriptUrlEntry['table']])
            || !is_array($GLOBALS['TCA'][$typoScriptUrlEntry['table']]['ctrl'])
        ) {
            return false;
        }

        $language = '';
        if ((int)$typoScriptUrlEntry['hideIfNotTranslated'] === 1) {
            $language = 'AND (sys_language_uid=\'-1\' OR sys_language_uid="' . (int)GeneralUtility::_GET('L') . '") ';
        }

        return $this->getDatabaseConnection()->exec_SELECTquery(
            '*',
            $typoScriptUrlEntry['table'],
            'pid!=0 ' . $language . ' ' . AdditionalWhereService::getWhereString(
                $typoScriptUrlEntry['additionalWhere']
            ) . $this->pageRepository->enableFields(
                $typoScriptUrlEntry['table']
            ),
            '',
            OrderByService::getOrderByString(
                $typoScriptUrlEntry['orderBy'],
                $typoScriptUrlEntry['table']
            ),
            LimitService::getLimitString(
                $typoScriptUrlEntry['limit']
            )
        );
    }

    /**
     * Returns the database connection
     *
     * @SuppressWarnings(superglobals)
     *
     * @return DatabaseConnection
     */
    protected function getDatabaseConnection(): DatabaseConnection
    {
        return $GLOBALS['TYPO3_DB'];
    }

    /**
     * @param $recordConfig
     * @param $record
     *
     * @return mixed|null
     */
    private function hideRecordIfNotTranslated($recordConfig, $record)
    {
        $language = GeneralUtility::_GET('L');
        if ($this->isRecordNotTranslated($recordConfig, $record, $language)) {
            $record = $this->pageRepository->getRecordOverlay($recordConfig['table'], $record, $language);
            if ((int)$record['l10n_parent'] !== 0) {
                return $record;
            }

            return null;
        }

        return $record;
    }

    /**
     * @param $recordConfig
     * @param $record
     * @param $language
     *
     * @return bool
     */
    private function isRecordNotTranslated($recordConfig, $record, $language): bool
    {
        return $record['sys_language_uid'] !== '-1' && (int)$language !== 0 && (int)$recordConfig['hideIfNotTranslated'] === 1;
    }

    /**
     * @return bool|object[]
     */
    public function findAllGoogleNewsEntries()
    {
        $googleNewsUrlEntriesConfig = $urlEntries = $GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_sitemapgenerator.'];
        if (!isset($googleNewsUrlEntriesConfig['googleNewsUrlEntry'])
            || !MathUtility::canBeInterpretedAsInteger($googleNewsUrlEntriesConfig['googleNewsUrlEntry'])
            || (int)$googleNewsUrlEntriesConfig['googleNewsUrlEntry'] === 0
        ) {
            return false;
        }

        return $this->mapGoogleNewsEntries($googleNewsUrlEntriesConfig['googleNewsUrlEntry.']);
    }

    /**
     * Map to entries
     *
     * @param array $typoScriptUrlEntry
     * @SuppressWarnings(superglobals)
     *
     * @return object[]
     */
    protected function mapGoogleNewsEntries(array $typoScriptUrlEntry): array
    {
        $records = $this->getRecordsFromDatabase($typoScriptUrlEntry);
        $urlEntries = [];
        if ($this->getDatabaseConnection()->sql_num_rows($records) !== 0) {
            while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($records)) {
                $row = $this->hideRecordIfNotTranslated($typoScriptUrlEntry, $row);

                $urlEntry = GeneralUtility::makeInstance(GoogleNewsUrlEntry::class);
                $urlEntry->setLoc($this->fieldValueService->getFieldValue('url', $typoScriptUrlEntry, $row));
                $urlEntry->setName($row[$typoScriptUrlEntry['name']]);
                $urlEntry->setTitle($row[$typoScriptUrlEntry['title']]);

                if ($typoScriptUrlEntry['language']) {
                    $urlEntry->setLanguage(
                        $this->fieldValueService->getFieldValue('language', $typoScriptUrlEntry, $row)
                    );
                }
                if ($typoScriptUrlEntry['access']) {
                    $urlEntry->setAccess($this->fieldValueService->getFieldValue('access', $typoScriptUrlEntry, $row));
                }
                if ($typoScriptUrlEntry['genres']) {
                    $urlEntry->setGenres($this->fieldValueService->getFieldValue('genres', $typoScriptUrlEntry, $row));
                }
                if ($typoScriptUrlEntry['publicationDate']) {
                    $urlEntry->setPublicationDate(date('Y-m-d', $row[$typoScriptUrlEntry['publicationDate']]));
                }
                if ($typoScriptUrlEntry['keywords']) {
                    $urlEntry->setKeywords($row[$typoScriptUrlEntry['keywords']]);
                }
                if ($typoScriptUrlEntry['stockTickers']) {
                    $urlEntry->setStockTickers($row[$typoScriptUrlEntry['stockTickers']]);
                }
                $urlEntries[] = $urlEntry;
            }
        }

        return $urlEntries;
    }
}