Commit e235a8c2 authored by Dominique Feyer's avatar Dominique Feyer
Browse files

[TASK] Refactor to use SitemapBuilder service

This change introduce the SitemapBuilder service and the
SitemapBuilderInterface, so you can implement your own SitemapBuilder.
parent af08304d
Loading
Loading
Loading
Loading
+7 −131
Original line number Diff line number Diff line
<?php
namespace Ttree\Neos\Sitemap\Command;

use Tackk\Cartographer\ChangeFrequency;
use TYPO3\Eel\FlowQuery\FlowQuery;
use Ttree\Neos\Sitemap\Domain\Model\SitemapDefinition;
use Ttree\Neos\Sitemap\Service\SitemapBuilder;
use TYPO3\Flow\Annotations as Flow;
use TYPO3\Flow\Cli\CommandController;
use TYPO3\Flow\Http\Request;
use TYPO3\Flow\Http\Response;
use TYPO3\Flow\Http\Uri;
use TYPO3\Flow\Mvc\ActionRequest;
use TYPO3\Flow\Mvc\Controller\Arguments;
use TYPO3\Flow\Mvc\Controller\ControllerContext;
use TYPO3\Flow\Mvc\Routing\UriBuilder;
use TYPO3\Flow\Utility\Files;
use TYPO3\Neos\Domain\Repository\SiteRepository;
use TYPO3\Neos\Service\LinkingService;
use TYPO3\TYPO3CR\Domain\Model\NodeInterface;
use TYPO3\TYPO3CR\Domain\Service\ContextFactoryInterface;

class SitemapCommandController extends CommandController {

    /**
     * @var ContextFactoryInterface
     * @Flow\Inject
     * @var SitemapBuilder
     */
    protected $contextFactory;

    /**
     * @var SiteRepository
     * @Flow\Inject
     */
    protected $siteRepository;

    /**
     * @Flow\Inject
     * @var LinkingService
     */
    protected $linkingService;

    /**
     * @Flow\Inject
     * @var UriBuilder
     */
    protected $uriBuilder;

    /**
     * @Flow\InjectConfiguration("presets")
     * @var array
     */
    protected $presets;
    protected $sitemapBuilder;

    /**
     * @param string $siteNodeName
@@ -56,101 +21,12 @@ class SitemapCommandController extends CommandController {
     * @param string $preset
     */
    public function generateCommand($siteNodeName, $baseUrl, $preset = 'default') {
        if (!isset($this->presets[$preset])) {
            $this->outputLine('Preset "%s" not found ...', [$preset]);
            $this->sendAndExit(1);
        }
        $preset = $this->presets[$preset];
        $processedUri = [];
        $sitemap = new \Tackk\Cartographer\Sitemap();

        $context = $this->createContext('live', $siteNodeName);
        $controllerContext = $this->createControllerContext($baseUrl);

        foreach ($preset['include'] as $nodeType => $nodeTypeStatus) {
            if ($nodeTypeStatus !== TRUE) {
                continue;
            }
            $flowQuery = new FlowQuery(array($context->getRootNode()));
            /** @var NodeInterface $node */
            foreach ($flowQuery->find(sprintf('[instanceof %s]', $nodeType)) as $node) {
                if ($this->needToSkipNode($preset, $node)) {
                    continue;
                }
                if (trim($node->getProperty('uriPathSegment')) === '') {
                    $this->outputLine('Skip "%s" (%s), uriPathSegment empty', [$node->getLabel(), $node->getPath()]);
                    continue;
                }
                $uri = $this->linkingService->createNodeUri($controllerContext, $node, NULL, 'html', TRUE);
                $hash = md5($uri);
                if (isset($processedUri[$hash])) {
                    $this->outputLine('Skip "%s" (%s), duplicate URI (%s)', [$node->getLabel(), $node->getPath(), $uri]);
                    continue;
                }
                $lastPublicationDateTime = $node->getNodeData()->getLastPublicationDateTime() ?: $node->getNodeData()->getLastModificationDateTime();
                $sitemap->add($uri, $lastPublicationDateTime ? $lastPublicationDateTime->format('Y-m-d') : null, ChangeFrequency::WEEKLY, 1.0);
                $processedUri[$hash] = TRUE;
            }
        }
        $definition = new SitemapDefinition($siteNodeName, $baseUrl, $preset);
        $sitemap = $this->sitemapBuilder->build($definition);

        $pathAndFilename = FLOW_PATH_DATA . '/Persistent/Sitemaps/sitemap.xml';
        $pathAndFilename = FLOW_PATH_DATA . '/Persistent/Sitemaps/Sitemap.xml';
        Files::createDirectoryRecursively(dirname($pathAndFilename));
        file_put_contents($pathAndFilename, (string)$sitemap);
    }

    /**
     * @param array $preset
     * @param NodeInterface $node
     * @return boolean
     */
    protected function needToSkipNode(array $preset, NodeInterface $node) {
        if (isset($preset['skip']) && is_array($preset['skip'])) {
            foreach ($preset['skip'] as $nodeType => $nodeTypeStatus) {
                if ($nodeTypeStatus !== TRUE) {
                    continue;
                }
                if ($node->getNodeType()->isOfType($nodeType) === TRUE) {
                    return TRUE;
                }
            }
        }
        return FALSE;
    }

    /**
     * @param string $baseUrl
     * @return ControllerContext
     */
    protected function createControllerContext($baseUrl) {
        $httpRequest = Request::create(new Uri($baseUrl));
        $response = new Response();
        $arguments = new Arguments([]);
        return new ControllerContext(new ActionRequest($httpRequest), $response, $arguments, $this->uriBuilder);
    }

    /**
     * Creates a content context for given workspace and language identifiers
     *
     * @param string $workspaceName
     * @param string $siteNodeName
     * @param array $languageIdentifiers
     * @return \TYPO3\TYPO3CR\Domain\Service\Context
     */
    protected function createContext($workspaceName, $siteNodeName, array $languageIdentifiers = NULL) {
        $site = $this->siteRepository->findOneByNodeName($siteNodeName);
        $contextProperties = array(
            'workspaceName' => $workspaceName,
            'currentSite' => $site,
            'invisibleContentShown' => TRUE,
            'inaccessibleContentShown' => TRUE
        );
        if ($languageIdentifiers !== NULL) {
            $contextProperties = array_merge($contextProperties, array(
                'dimensions' => array('language' => $languageIdentifiers)
            ));
        }
        return $this->contextFactory->create($contextProperties);
    }


}
 No newline at end of file
+78 −0
Original line number Diff line number Diff line
<?php
namespace Ttree\Neos\Sitemap\Domain\Model;

use TYPO3\Flow\Annotations as Flow;
use TYPO3\Flow\Exception;

/**
 * @api
 */
class SitemapDefinition {

    /**
     * @var string
     */
    protected $siteNodeName;

    /**
     * @var string
     */
    protected $baseUrl;

    /**
     * @var string
     */
    protected $presetName;

    /**
     * @Flow\InjectConfiguration("presets")
     * @var array
     */
    protected $presets;

    /**
     * SitemapDefinition constructor.
     * @param string $siteNodeName
     * @param string $baseUrl
     * @param string $presetName
     * @throws Exception
     */
    public function __construct($siteNodeName, $baseUrl, $presetName) {
        $this->siteNodeName = $siteNodeName;
        $this->baseUrl = $baseUrl;
        $this->presetName = $presetName;
    }

    /**
     * Initialize Object
     *
     * @throws Exception
     */
    public function initializeObject() {
        if (!isset($this->presets[$this->presetName]) || !is_array($this->presets[$this->presetName])) {
            throw new Exception('Preset not found', 1442496982);
        }
    }

    /**
     * @return string
     */
    public function getSiteNodeName() {
        return $this->siteNodeName;
    }

    /**
     * @return string
     */
    public function getBaseUrl() {
        return $this->baseUrl;
    }

    /**
     * @return array
     */
    public function getPreset() {
        return $this->presets[$this->presetName];
    }

}
 No newline at end of file
+145 −0
Original line number Diff line number Diff line
<?php
namespace Ttree\Neos\Sitemap\Service;

use Tackk\Cartographer\ChangeFrequency;
use Tackk\Cartographer\Sitemap;
use Ttree\Neos\Sitemap\Domain\Model\SitemapDefinition;
use TYPO3\Eel\FlowQuery\FlowQuery;
use TYPO3\Flow\Annotations as Flow;
use TYPO3\Flow\Http\Request;
use TYPO3\Flow\Http\Response;
use TYPO3\Flow\Http\Uri;
use TYPO3\Flow\Mvc\ActionRequest;
use TYPO3\Flow\Mvc\Controller\Arguments;
use TYPO3\Flow\Mvc\Controller\ControllerContext;
use TYPO3\Flow\Mvc\Routing\UriBuilder;
use TYPO3\Neos\Domain\Repository\SiteRepository;
use TYPO3\Neos\Service\LinkingService;
use TYPO3\TYPO3CR\Domain\Model\NodeInterface;
use TYPO3\TYPO3CR\Domain\Service\ContextFactoryInterface;

/**
 * @Flow\Scope("singleton")
 * @api
 */
class SitemapBuilder implements SitemapBuilderInterface {

    /**
     * @var ContextFactoryInterface
     * @Flow\Inject
     */
    protected $contextFactory;

    /**
     * @var SiteRepository
     * @Flow\Inject
     */
    protected $siteRepository;

    /**
     * @Flow\Inject
     * @var LinkingService
     */
    protected $linkingService;

    /**
     * @Flow\Inject
     * @var UriBuilder
     */
    protected $uriBuilder;

    /**
     * @param SitemapDefinition $definiton
     * @return string
     */
    public function build(SitemapDefinition $definiton) {
        $preset = $definiton->getPreset();
        $processedUri = [];
        $sitemap = new Sitemap();

        $context = $this->createContext('live', $definiton->getSiteNodeName());
        $controllerContext = $this->createControllerContext($definiton->getBaseUrl());

        foreach ($preset['include'] as $nodeType => $nodeTypeStatus) {
            if ($nodeTypeStatus !== TRUE) {
                continue;
            }
            $flowQuery = new FlowQuery(array($context->getRootNode()));
            /** @var NodeInterface $node */
            foreach ($flowQuery->find(sprintf('[instanceof %s]', $nodeType)) as $node) {
                if ($this->needToSkipNode($preset, $node)) {
                    continue;
                }
                if (trim($node->getProperty('uriPathSegment')) === '') {
                    continue;
                }
                $uri = $this->linkingService->createNodeUri($controllerContext, $node, NULL, 'html', TRUE);
                $hash = md5($uri);
                if (isset($processedUri[$hash])) {
                    continue;
                }
                $lastPublicationDateTime = $node->getNodeData()->getLastPublicationDateTime() ?: $node->getNodeData()->getLastModificationDateTime();
                $sitemap->add($uri, $lastPublicationDateTime ? $lastPublicationDateTime->format('Y-m-d') : null, ChangeFrequency::WEEKLY, 1.0);
                $processedUri[$hash] = TRUE;
            }
        }

        return (string)$sitemap;
    }

    /**
     * @param array $preset
     * @param NodeInterface $node
     * @return boolean
     */
    protected function needToSkipNode(array $preset, NodeInterface $node) {
        if (isset($preset['skip']) && is_array($preset['skip'])) {
            foreach ($preset['skip'] as $nodeType => $nodeTypeStatus) {
                if ($nodeTypeStatus !== TRUE) {
                    continue;
                }
                if ($node->getNodeType()->isOfType($nodeType) === TRUE) {
                    return TRUE;
                }
            }
        }
        return FALSE;
    }

    /**
     * @param string $baseUrl
     * @return ControllerContext
     */
    protected function createControllerContext($baseUrl) {
        $httpRequest = Request::create(new Uri($baseUrl));
        $response = new Response();
        $arguments = new Arguments([]);
        return new ControllerContext(new ActionRequest($httpRequest), $response, $arguments, $this->uriBuilder);
    }

    /**
     * Creates a content context for given workspace and language identifiers
     *
     * @param string $workspaceName
     * @param string $siteNodeName
     * @param array $languageIdentifiers
     * @return \TYPO3\TYPO3CR\Domain\Service\Context
     */
    protected function createContext($workspaceName, $siteNodeName, array $languageIdentifiers = NULL) {
        $site = $this->siteRepository->findOneByNodeName($siteNodeName);
        $contextProperties = array(
            'workspaceName' => $workspaceName,
            'currentSite' => $site,
            'invisibleContentShown' => TRUE,
            'inaccessibleContentShown' => TRUE
        );
        if ($languageIdentifiers !== NULL) {
            $contextProperties = array_merge($contextProperties, array(
                'dimensions' => array('language' => $languageIdentifiers)
            ));
        }
        return $this->contextFactory->create($contextProperties);
    }


}
 No newline at end of file
+18 −0
Original line number Diff line number Diff line
<?php
namespace Ttree\Neos\Sitemap\Service;

use Ttree\Neos\Sitemap\Domain\Model\SitemapDefinition;
use TYPO3\Flow\Annotations as Flow;

/**
 * Sitemap Builder Interface
 */
interface SitemapBuilderInterface {

    /**
     * @param SitemapDefinition $definiton
     * @return string
     */
    public function build(SitemapDefinition $definiton);

}
 No newline at end of file