<?php 
 
/* 
 * This file is part of the PrestaSitemapBundle package. 
 * 
 * (c) PrestaConcept <https://prestaconcept.net> 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
namespace Presta\SitemapBundle\Service; 
 
use Presta\SitemapBundle\Event\SitemapPopulateEvent; 
use Presta\SitemapBundle\Sitemap\Sitemapindex; 
use Presta\SitemapBundle\Sitemap\Url\Url; 
use Presta\SitemapBundle\Sitemap\Url\UrlConcrete; 
use Presta\SitemapBundle\Sitemap\Url\UrlDecorator; 
use Presta\SitemapBundle\Sitemap\Urlset; 
use Symfony\Component\EventDispatcher\EventDispatcherInterface; 
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 
 
/** 
 * Base class for all sitemap generators. 
 * 
 * @phpstan-type Defaults array{ 
 *     lastmod: string|null, 
 *     changefreq: string|null, 
 *     priority: float|string|int|null 
 * } 
 */ 
abstract class AbstractGenerator implements UrlContainerInterface 
{ 
    /** 
     * @var EventDispatcherInterface 
     */ 
    protected $dispatcher; 
 
    /** 
     * @var Sitemapindex|null 
     */ 
    protected $root; 
 
    /** 
     * @var Urlset[] 
     */ 
    protected $urlsets = []; 
 
    /** 
     * The maximum number of item generated in a sitemap 
     * @var int 
     */ 
    protected $itemsBySet; 
 
    /** 
     * @var UrlGeneratorInterface|null 
     */ 
    protected $urlGenerator; 
 
    /** 
     * @var Defaults 
     */ 
    private $defaults; 
 
    /** 
     * @param EventDispatcherInterface   $dispatcher 
     * @param int|null                   $itemsBySet 
     * @param UrlGeneratorInterface|null $urlGenerator 
     */ 
    public function __construct( 
        EventDispatcherInterface $dispatcher, 
        int $itemsBySet = null, 
        UrlGeneratorInterface $urlGenerator = null 
    ) { 
        if (!$urlGenerator) { 
            @trigger_error( 
                'Not injecting the $urlGenerator is deprecated and will be required in 4.0.', 
                \E_USER_DEPRECATED 
            ); 
        } 
 
        $this->dispatcher = $dispatcher; 
        // We add one to LIMIT_ITEMS because it was used as an index, not a quantity 
        $this->itemsBySet = ($itemsBySet === null) ? Sitemapindex::LIMIT_ITEMS + 1 : $itemsBySet; 
        $this->urlGenerator = $urlGenerator; 
 
        $this->defaults = [ 
            'priority' => 1, 
            'changefreq' => UrlConcrete::CHANGEFREQ_DAILY, 
            'lastmod' => 'now', 
        ]; 
    } 
 
    /** 
     * @param Defaults $defaults 
     */ 
    public function setDefaults(array $defaults): void 
    { 
        $this->defaults = $defaults; 
    } 
 
    /** 
     * @inheritdoc 
     */ 
    public function addUrl(Url $url, string $section): void 
    { 
        $urlset = $this->getUrlset($section); 
 
        // Compare the number of items in the urlset against the maximum 
        // allowed and check the maximum of 50k sitemap in sitemapindex 
        $i = 0; 
        while ((count($urlset) >= $this->itemsBySet || $urlset->isFull()) && $i <= Sitemapindex::LIMIT_ITEMS) { 
            $urlset = $this->getUrlset($section . '_' . $i); 
            $i++; 
        } 
 
        if (count($urlset) >= $this->itemsBySet || $urlset->isFull()) { 
            throw new \RuntimeException('The limit of sitemapindex has been exceeded'); 
        } 
 
        $concreteUrl = $this->getUrlConcrete($url); 
        if ($concreteUrl instanceof UrlConcrete) { 
            if (null === $concreteUrl->getLastmod() && null !== $this->defaults['lastmod']) { 
                $concreteUrl->setLastmod(new \DateTimeImmutable($this->defaults['lastmod'])); 
            } 
            if (null === $concreteUrl->getChangefreq()) { 
                $concreteUrl->setChangefreq($this->defaults['changefreq']); 
            } 
            if (null === $concreteUrl->getPriority()) { 
                $concreteUrl->setPriority($this->defaults['priority']); 
            } 
        } 
 
        $urlset->addUrl($url); 
    } 
 
    /** 
     * get or create urlset 
     * 
     * @param string $name 
     * 
     * @return Urlset 
     */ 
    public function getUrlset(string $name): Urlset 
    { 
        if (!isset($this->urlsets[$name])) { 
            $this->urlsets[$name] = $this->newUrlset($name); 
        } 
 
        return $this->urlsets[$name]; 
    } 
 
    /** 
     * Factory method for create Urlsets 
     * 
     * @param string                  $name 
     * @param \DateTimeInterface|null $lastmod 
     * 
     * @return Urlset 
     */ 
    abstract protected function newUrlset(string $name, \DateTimeInterface $lastmod = null): Urlset; 
 
    /** 
     * Dispatches SitemapPopulate Event - the listeners should use it to add their URLs to the sitemap 
     * 
     * @param string|null $section 
     */ 
    protected function populate(string $section = null): void 
    { 
        $event = new SitemapPopulateEvent($this, $section, $this->urlGenerator); 
 
        $this->dispatcher->dispatch($event, SitemapPopulateEvent::ON_SITEMAP_POPULATE); 
    } 
 
    /** 
     * @return Sitemapindex 
     */ 
    protected function getRoot(): Sitemapindex 
    { 
        if (null === $this->root) { 
            $this->root = new Sitemapindex(); 
 
            foreach ($this->urlsets as $urlset) { 
                $this->root->addSitemap($urlset); 
            } 
        } 
 
        return $this->root; 
    } 
 
    /** 
     * @param Url $url 
     * 
     * @return Url|null 
     */ 
    private function getUrlConcrete(Url $url): ?Url 
    { 
        if ($url instanceof UrlConcrete) { 
            return $url; 
        } 
 
        if ($url instanceof UrlDecorator) { 
            return $this->getUrlConcrete($url->getUrlDecorated()); 
        } 
 
        return null; 
    } 
}