From b2e8b9769b48df1e75eae6d5ea143aca7cdab99b Mon Sep 17 00:00:00 2001 From: ElectricMaxxx Date: Fri, 7 Nov 2014 15:30:12 +0100 Subject: [PATCH] sitemap generation --- CHANGELOG.md | 2 + CmfSeoBundle.php | 2 + Controller/SitemapController.php | 103 ++++++++ DependencyInjection/CmfSeoExtension.php | 39 ++- .../RegisterUrlInformationProviderPass.php | 56 +++++ DependencyInjection/Configuration.php | 9 +- .../Phpcr/SitemapUrlInformationProvider.php | 172 +++++++++++++ Model/UrlInformation.php | 231 ++++++++++++++++++ Resources/config/phpcr-sitemap.xml | 24 ++ Resources/config/routing/sitemap.xml | 12 + Resources/config/sitemap.xml | 24 ++ Resources/views/Sitemap/index.html.twig | 7 + Resources/views/Sitemap/index.xml.twig | 15 ++ SeoPresentation.php | 9 +- Sitemap/ChainProvider.php | 68 ++++++ Sitemap/UrlInformationProviderInterface.php | 27 ++ SitemapElementInterface.php | 23 ++ .../SitemapUrlInformationProviderTest.php | 107 ++++++++ .../DataFixtures/Phpcr/LoadSitemapData.php | 88 +++++++ .../Document/AlternateLocaleContent.php | 1 + .../Document/SitemapAwareContent.php | 126 ++++++++++ ...SitemapAwareWithPublishWorkflowContent.php | 144 +++++++++++ Tests/Resources/Fixtures/sitemap/sitemap.xml | 14 ++ Tests/Resources/app/AppKernel.php | 1 + Tests/Resources/app/config/cmf_seo.phpcr.yml | 5 + Tests/Resources/app/config/cmf_seo.yml | 5 + Tests/Resources/app/config/routing.php | 3 + .../Unit/Controller/SitemapControllerTest.php | 135 ++++++++++ .../CmfSeoExtensionTest.php | 54 +++- ...RegisterUrlInformationProviderPassTest.php | 61 +++++ .../DependencyInjection/ConfigurationTest.php | 4 + Tests/Unit/Model/UrlInformationTest.php | 46 ++++ Tests/Unit/Sitemap/ChainProviderTest.php | 77 ++++++ Tests/WebTest/SitemapTest.php | 81 ++++++ 34 files changed, 1758 insertions(+), 17 deletions(-) create mode 100644 Controller/SitemapController.php create mode 100644 DependencyInjection/Compiler/RegisterUrlInformationProviderPass.php create mode 100644 Doctrine/Phpcr/SitemapUrlInformationProvider.php create mode 100644 Model/UrlInformation.php create mode 100644 Resources/config/phpcr-sitemap.xml create mode 100644 Resources/config/routing/sitemap.xml create mode 100644 Resources/config/sitemap.xml create mode 100644 Resources/views/Sitemap/index.html.twig create mode 100644 Resources/views/Sitemap/index.xml.twig create mode 100644 Sitemap/ChainProvider.php create mode 100644 Sitemap/UrlInformationProviderInterface.php create mode 100644 SitemapElementInterface.php create mode 100644 Tests/Functional/Doctrine/Phpcr/SitemapUrlInformationProviderTest.php create mode 100644 Tests/Resources/DataFixtures/Phpcr/LoadSitemapData.php create mode 100644 Tests/Resources/Document/SitemapAwareContent.php create mode 100644 Tests/Resources/Document/SitemapAwareWithPublishWorkflowContent.php create mode 100644 Tests/Resources/Fixtures/sitemap/sitemap.xml create mode 100644 Tests/Unit/Controller/SitemapControllerTest.php create mode 100644 Tests/Unit/DependencyInjection/Compiler/RegisterUrlInformationProviderPassTest.php create mode 100644 Tests/Unit/Model/UrlInformationTest.php create mode 100644 Tests/Unit/Sitemap/ChainProviderTest.php create mode 100644 Tests/WebTest/SitemapTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 42359c8c..63b8a2e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ Changelog ========= +* **2015-14-02**: implement sitemap generation +* **2015-14-02**: [BC BREAK] Changed method visibility from private to public of SeoPresentation#getSeoMetadata() * **2014-10-04**: Custom exception controller for error handling 1.1.0-RC3 diff --git a/CmfSeoBundle.php b/CmfSeoBundle.php index 1f530965..62953b40 100644 --- a/CmfSeoBundle.php +++ b/CmfSeoBundle.php @@ -14,6 +14,7 @@ use Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass; +use Symfony\Cmf\Bundle\SeoBundle\DependencyInjection\Compiler\RegisterUrlInformationProviderPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -26,6 +27,7 @@ public function build(ContainerBuilder $container) { $container->addCompilerPass(new RegisterExtractorsPass()); $container->addCompilerPass(new RegisterSuggestionProviderPass()); + $container->addCompilerPass(new RegisterUrlInformationProviderPass()); $this->buildPhpcrCompilerPass($container); $this->buildOrmCompilerPass($container); diff --git a/Controller/SitemapController.php b/Controller/SitemapController.php new file mode 100644 index 00000000..2c468d3a --- /dev/null +++ b/Controller/SitemapController.php @@ -0,0 +1,103 @@ + + */ +class SitemapController +{ + /** + * @var UrlInformationProviderInterface + */ + private $urlProvider; + /** + * @var EngineInterface + */ + private $templating; + + /** + * @var array + */ + private $templates; + + /** + * You should provide templates for html and xml. + * + * Json is serialized by default, but can be customized with a template + * + * @param UrlInformationProviderInterface $provider + * @param EngineInterface $templating + * @param array $templates Hash map with key being the format, + * value the name of the twig template + * to render the sitemap in that format + */ + public function __construct( + UrlInformationProviderInterface $provider, + EngineInterface $templating, + array $templates + ) { + $this->urlProvider = $provider; + $this->templating = $templating; + $this->templates = $templates; + } + + /** + * @param string $_format The format of the sitemap. + * + * @return Response + */ + public function indexAction($_format) + { + $supportedFormats = array_merge(array('json'), array_keys($this->templates)); + if (!in_array($_format, $supportedFormats)) { + $text = sprintf( + 'Unknown format %s, use one of %s.', + $_format, + implode(', ', $supportedFormats) + ); + + return new Response($text, 406); + } + + $urls = $this->urlProvider->getUrlInformation(); + if (isset($this->templates[$_format])) { + return new Response($this->templating->render($this->templates[$_format], array('urls' => $urls))); + } + + return $this->createJsonResponse($urls); + } + + /** + * @param array|UrlInformation[] $urls + * + * @return JsonResponse + */ + private function createJsonResponse($urls) + { + $result = array(); + + foreach ($urls as $url) { + $result[] = $url->toArray(); + } + + return new JsonResponse($result); + } +} diff --git a/DependencyInjection/CmfSeoExtension.php b/DependencyInjection/CmfSeoExtension.php index d9aa1a05..87b3f691 100644 --- a/DependencyInjection/CmfSeoExtension.php +++ b/DependencyInjection/CmfSeoExtension.php @@ -14,6 +14,7 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Cmf\Bundle\RoutingBundle\Routing\DynamicRouter; @@ -64,8 +65,6 @@ public function load(array $configs, ContainerBuilder $container) $sonataBundles[] = 'SonataDoctrinePHPCRAdminBundle'; $this->loadPhpcr($config['persistence']['phpcr'], $loader, $container); - - $loader->load('matcher_phpcr.xml'); } if ($this->isConfigEnabled($container, $config['persistence']['orm'])) { @@ -87,6 +86,10 @@ public function load(array $configs, ContainerBuilder $container) $errorConfig = isset($config['error']) ? $config['error'] : array(); $this->loadErrorHandling($errorConfig, $container); + + if ($this->isConfigEnabled($container, $config['sitemap'])) { + $this->loadSitemapHandling($config['sitemap'], $loader, $container); + } } /** @@ -169,6 +172,9 @@ private function loadPhpcr($config, XmlFileLoader $loader, ContainerBuilder $con $this->defaultAlternateLocaleProviderId = 'cmf_seo.alternate_locale.provider_phpcr'; } } + + $loader->load('matcher_phpcr.xml'); + $loader->load('phpcr-sitemap.xml'); } /** @@ -189,13 +195,19 @@ private function loadAlternateLocaleProvider($config, ContainerBuilder $containe if ($alternateLocaleProvider) { - $definition = $container->getDefinition('cmf_seo.event_listener.seo_content'); - $definition + $alternateLocaleProviderDefinition = $container->findDefinition($alternateLocaleProvider); + $container + ->findDefinition('cmf_seo.event_listener.seo_content') + ->addMethodCall( + 'setAlternateLocaleProvider', + array($alternateLocaleProviderDefinition) + ); + $container + ->findDefinition('cmf_seo.sitemap.phpcr_provider') ->addMethodCall( 'setAlternateLocaleProvider', - array($container->getDefinition($alternateLocaleProvider)) - ) - ; + array($alternateLocaleProviderDefinition) + ); } } @@ -210,10 +222,21 @@ private function loadAlternateLocaleProvider($config, ContainerBuilder $containe private function loadErrorHandling($config, ContainerBuilder $container) { foreach (array('parent', 'sibling') as $group) { - $remove = isset($config['enable_'.$group.'_provider']) && !$config['enable_'.$group.'_provider'] ? true : false; + $remove = isset($config['enable_'.$group.'_provider']) + && !$config['enable_'.$group.'_provider'] ? true : false; if ($container->has('cmf_seo.error.suggestion_provider.'.$group) && $remove) { $container->removeDefinition('cmf_seo.error.suggestion_provider.'.$group); } } } + + private function loadSitemapHandling($config, XmlFileLoader $loader, ContainerBuilder $container) + { + $loader->load('sitemap.xml'); + + $container->setParameter( + $this->getAlias().'.sitemap.default_change_frequency', + $config['default_chan_frequency'] + ); + } } diff --git a/DependencyInjection/Compiler/RegisterUrlInformationProviderPass.php b/DependencyInjection/Compiler/RegisterUrlInformationProviderPass.php new file mode 100644 index 00000000..c477fbd7 --- /dev/null +++ b/DependencyInjection/Compiler/RegisterUrlInformationProviderPass.php @@ -0,0 +1,56 @@ + + */ +class RegisterUrlInformationProviderPass implements CompilerPassInterface +{ + /** + * {@inheritDoc} + * + * @throws LogicException If a tagged service is not public. + */ + public function process(ContainerBuilder $container) + { + // feature not activated means nothing to add + if (!$container->hasDefinition('cmf_seo.sitemap.url_information_provider')) { + return; + } + + $chainProviderDefinition = $container->getDefinition('cmf_seo.sitemap.url_information_provider'); + $taggedServices = $container->findTaggedServiceIds('cmf_seo.sitemap.url_information_provider'); + + foreach ($taggedServices as $id => $attributes) { + $priority = null; + foreach ($attributes as $attribute) { + if (isset($attribute['priority'])) { + $priority = $attribute['priority']; + break; + } + } + $priority = $priority ?: 0; + + $chainProviderDefinition->addMethodCall('addProvider', array(new Reference($id), $priority)); + } + } +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 87315b25..330eb8a9 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -60,7 +60,7 @@ public function getConfigTreeBuilder() ->addDefaultsIfNotSet() ->beforeNormalization() ->ifTrue( function ($v) { return is_scalar($v); }) - ->then( function ($v) { + ->then(function ($v) { return array('enabled' => $v); }) ->end() @@ -85,6 +85,13 @@ public function getConfigTreeBuilder() ->scalarNode('enable_sibling_provider')->defaultValue(false)->end() ->end() ->end() + ->arrayNode('sitemap') + ->addDefaultsIfNotSet() + ->canBeEnabled() + ->children() + ->scalarNode('default_chan_frequency')->defaultValue('always')->end() + ->end() + ->end() ->end() ; diff --git a/Doctrine/Phpcr/SitemapUrlInformationProvider.php b/Doctrine/Phpcr/SitemapUrlInformationProvider.php new file mode 100644 index 00000000..07f94194 --- /dev/null +++ b/Doctrine/Phpcr/SitemapUrlInformationProvider.php @@ -0,0 +1,172 @@ + + */ +class SitemapUrlInformationProvider implements UrlInformationProviderInterface +{ + /** + * @var AlternateLocaleProviderInterface + */ + protected $alternateLocaleProvider; + + /** + * @var ExtractorInterface + */ + protected $titleExtractor; + + /** + * @var DocumentManager + */ + private $manager; + + /** + * @var RouterInterface + */ + private $router; + + /** + * @var SecurityContextInterface + */ + private $publishWorkflowChecker; + /** + * @var string + */ + private $defaultChanFrequency; + /** + * @var LoggerInterface + */ + private $logger; + /** + * @var SeoPresentation + */ + private $seoPresentation; + + /** + * @param DocumentManager $manager + * @param RouterInterface $router + * @param string $defaultChanFrequency + * @param PsrLogger $logger + * @param SeoPresentation $seoPresentation + * @param SecurityContextInterface $publishWorkflowChecker + */ + public function __construct( + DocumentManager $manager, + RouterInterface $router, + $defaultChanFrequency, + PsrLogger $logger, + SeoPresentation $seoPresentation, + SecurityContextInterface $publishWorkflowChecker = null + ) { + $this->manager = $manager; + $this->router = $router; + $this->defaultChanFrequency = $defaultChanFrequency; + $this->logger = $logger; + $this->seoPresentation = $seoPresentation; + $this->publishWorkflowChecker = $publishWorkflowChecker; + } + + /** + * {@inheritDocs} + */ + public function getUrlInformation() + { + $contentDocuments = $this->fetchSitemapDocuments(); + $urlInformationList = array(); + + foreach ($contentDocuments as $document) { + if (null != $this->publishWorkflowChecker && + !$this->publishWorkflowChecker->isGranted(array(PublishWorkflowChecker::VIEW_ATTRIBUTE), $document) + ) { + continue; + } + + try { + $urlInformationList[] = $this->computeUrlInformationFromSitemapDocument($document); + } catch (\Exception $e) { + $this->logger->info($e->getMessage()); + } + } + + return $urlInformationList; + } + + /** + * @param AlternateLocaleProviderInterface $alternateLocaleProvider + */ + public function setAlternateLocaleProvider(AlternateLocaleProviderInterface $alternateLocaleProvider) + { + $this->alternateLocaleProvider = $alternateLocaleProvider; + } + + /** + * Wrapper to fetch the sitemap documents from database. + * + * @return object[] + * + * @throws QueryException + */ + protected function fetchSitemapDocuments() + { + // todo rewrite query when https://github.com/symfony-cmf/CoreBundle/issues/126 is ready + return $this->manager->createQuery( + "SELECT * FROM [nt:unstructured] WHERE (visible_for_sitemap = true)", + QueryInterface::JCR_SQL2 + )->execute(); + } + + /** + * Transforms a single sitemap document into url information. + * + * A sitemap document is a document, which should be exposed on a sitemap. + * + * @param object $document + * + * @return UrlInformation + */ + protected function computeUrlInformationFromSitemapDocument($document) + { + $urlInformation = new UrlInformation(); + $urlInformation->setLocation($this->router->generate($document, array(), true)); + $urlInformation->setChangeFrequency($this->defaultChanFrequency); + + if ($this->alternateLocaleProvider) { + $collection = $this->alternateLocaleProvider->createForContent($document); + $urlInformation->setAlternateLocales($collection->toArray()); + } + + $seoMetadata = $this->seoPresentation->getSeoMetadata($document); + if (null !== $seoMetadata->getTitle()) { + $urlInformation->setLabel($seoMetadata->getTitle()); + return $urlInformation; + } + return $urlInformation; + } +} diff --git a/Model/UrlInformation.php b/Model/UrlInformation.php new file mode 100644 index 00000000..45ceb6ae --- /dev/null +++ b/Model/UrlInformation.php @@ -0,0 +1,231 @@ + + */ +class UrlInformation +{ + /** + * Decides whether the content is visible on sitemap. + * + * @var bool + */ + private $visible; + + /** + * @var string + */ + private $location; + + /** + * @var \DateTime + */ + private $lastModification; + + /** + * @var string One of the official/allowed. + */ + private $changeFrequency; + + /** + * @var float + */ + private $priority; + + /** + * @var array + */ + private $allowedChangeFrequencies = array('always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'); + + /** + * @var string $label As a string to display the route i.e. in html views. + */ + private $label; + + /** + * @var AlternateLocale[] + */ + private $alternateLocales; + + public function __construct() + { + $this->alternateLocales = array(); + } + + /** + * @return boolean + */ + public function isVisible() + { + return $this->visible; + } + + /** + * @param boolean $visible + */ + public function setVisible($visible) + { + $this->visible = $visible; + } + + public function toArray() + { + $result = array( + 'loc' => $this->location, + 'label' => $this->label, + 'changefreq' => $this->changeFrequency, + 'lastmod' => $this->lastModification, + 'priority' => $this->priority, + 'alternate_locales' => array() + ); + + foreach ($this->alternateLocales as $locale) { + $result['alternate_locales'][] = array('href' => $locale->href, 'href_locale' => $locale->hrefLocale); + } + + return $result; + } + + /** + * @return string + */ + public function getChangeFrequency() + { + return $this->changeFrequency; + } + + /** + * @param string $changeFrequency One of the official/allowed ones. + * + * @return $this + */ + public function setChangeFrequency($changeFrequency) + { + if (!in_array($changeFrequency, $this->allowedChangeFrequencies)) { + throw new InvalidArgumentException( + sprintf('Invalid change frequency use one of %s.', implode(', ', $this->allowedChangeFrequencies)) + ); + } + + $this->changeFrequency = $changeFrequency; + + return $this; + } + + /** + * @return string + */ + public function getLastModification() + { + return $this->lastModification; + } + + /** + * @param \DateTime $dateTime + * + * @return $this + */ + public function setLastModification(\DateTime $dateTime) + { + $lastmod = $dateTime->format('c'); + $this->lastModification = $lastmod; + + return $this; + } + + /** + * @return string + */ + public function getLocation() + { + return $this->location; + } + + /** + * @param string $location + * + * @return $this + */ + public function setLocation($location) + { + $this->location = $location; + + return $this; + } + + /** + * @return float + */ + public function getPriority() + { + return $this->priority; + } + + /** + * @param float $priority + * + * @return $this + */ + public function setPriority($priority) + { + $this->priority = $priority; + + return $this; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * @param string $label + * + * @return $this + */ + public function setLabel($label) + { + $this->label = $label; + + return $this; + } + + /** + * @return array|AlternateLocale[] + */ + public function getAlternateLocales() + { + return $this->alternateLocales; + } + + /** + * @param array|AlternateLocale[] $alternateLocales + */ + public function setAlternateLocales($alternateLocales) + { + $this->alternateLocales = $alternateLocales; + } + + /** + * @param AlternateLocale $alternateLocale + */ + public function addAlternateLocale(AlternateLocale $alternateLocale) + { + $this->alternateLocales[] = $alternateLocale; + } +} diff --git a/Resources/config/phpcr-sitemap.xml b/Resources/config/phpcr-sitemap.xml new file mode 100644 index 00000000..db7af2ae --- /dev/null +++ b/Resources/config/phpcr-sitemap.xml @@ -0,0 +1,24 @@ + + + + + + Symfony\Cmf\Bundle\SeoBundle\Doctrine\Phpcr\SitemapUrlInformationProvider + + + + + + + %cmf_seo.sitemap.default_change_frequency% + + + + + + + + diff --git a/Resources/config/routing/sitemap.xml b/Resources/config/routing/sitemap.xml new file mode 100644 index 00000000..0ca0c507 --- /dev/null +++ b/Resources/config/routing/sitemap.xml @@ -0,0 +1,12 @@ + + + + + + cmf_seo.sitemap.controller:indexAction + html + + + diff --git a/Resources/config/sitemap.xml b/Resources/config/sitemap.xml new file mode 100644 index 00000000..4a4e51d2 --- /dev/null +++ b/Resources/config/sitemap.xml @@ -0,0 +1,24 @@ + + + + + + Symfony\Cmf\Bundle\SeoBundle\Controller\SitemapController + Symfony\Cmf\Bundle\SeoBundle\Sitemap\ChainProvider + + + + + + + + CmfSeoBundle:Sitemap:index.xml.twig + CmfSeoBundle:Sitemap:index.html.twig + + + + + diff --git a/Resources/views/Sitemap/index.html.twig b/Resources/views/Sitemap/index.html.twig new file mode 100644 index 00000000..a5073d7f --- /dev/null +++ b/Resources/views/Sitemap/index.html.twig @@ -0,0 +1,7 @@ + diff --git a/Resources/views/Sitemap/index.xml.twig b/Resources/views/Sitemap/index.xml.twig new file mode 100644 index 00000000..1d35083e --- /dev/null +++ b/Resources/views/Sitemap/index.xml.twig @@ -0,0 +1,15 @@ + + + {% for url in urls %} + + {{ url.location }} + {{ url.lastModification }} + {{ url.changeFrequency }} + {% if url.alternateLocales is defined and url.alternateLocales|length > 0 %} + {% for locale in url.alternateLocales %} + + {% endfor %} + {% endif %} + + {% endfor %} + diff --git a/SeoPresentation.php b/SeoPresentation.php index d9d9e642..466b857e 100644 --- a/SeoPresentation.php +++ b/SeoPresentation.php @@ -136,14 +136,9 @@ public function addExtractor(ExtractorInterface $extractor, $priority = 0) } /** - * Gets the SeoMetadata based on the content that contains the content. - * - * @param object $content - * - * @throws Exception\InvalidArgumentException - * @return SeoMetadata + * {@inheritDoc} */ - private function getSeoMetadata($content) + public function getSeoMetadata($content) { if ($content instanceof SeoAwareInterface) { $contentSeoMetadata = $content->getSeoMetadata(); diff --git a/Sitemap/ChainProvider.php b/Sitemap/ChainProvider.php new file mode 100644 index 00000000..32ccdc89 --- /dev/null +++ b/Sitemap/ChainProvider.php @@ -0,0 +1,68 @@ + + */ +class ChainProvider implements UrlInformationProviderInterface +{ + /** + * @var array + */ + private $providers = array(); + + /** + * {@inheritDoc} + */ + public function addProvider(UrlInformationProviderInterface $provider, $priority = 0) + { + if (empty($this->providers[$priority])) { + $this->providers[$priority] = array(); + } + $this->providers[$priority][] = $provider; + } + + /** + * @return UrlInformationProviderInterface[] + */ + private function getSortedProviders() + { + $sortedProviders = array(); + ksort($this->providers); + + foreach ($this->providers as $providers) { + $sortedProviders = array_merge($sortedProviders, $providers); + }; + + return $sortedProviders; + } + + /** + * @return UrlInformation[] + */ + public function getUrlInformation() + { + $urlInformation = array(); + + foreach ($this->getSortedProviders() as $provider) { + $urlInformation = array_merge($urlInformation, $provider->getUrlInformation()); + } + + return $urlInformation; + } +} diff --git a/Sitemap/UrlInformationProviderInterface.php b/Sitemap/UrlInformationProviderInterface.php new file mode 100644 index 00000000..1f6f88d8 --- /dev/null +++ b/Sitemap/UrlInformationProviderInterface.php @@ -0,0 +1,27 @@ + + */ +interface UrlInformationProviderInterface +{ + /** + * @return UrlInformation[] + */ + public function getUrlInformation(); +} diff --git a/SitemapElementInterface.php b/SitemapElementInterface.php new file mode 100644 index 00000000..b2bef49f --- /dev/null +++ b/SitemapElementInterface.php @@ -0,0 +1,23 @@ + + */ +interface SitemapElementInterface +{ + /** + * Decision whether a document should be visible + * in sitemap or not. + * + * @return bool + */ + public function isVisibleInSitemap(); +} diff --git a/Tests/Functional/Doctrine/Phpcr/SitemapUrlInformationProviderTest.php b/Tests/Functional/Doctrine/Phpcr/SitemapUrlInformationProviderTest.php new file mode 100644 index 00000000..261338bb --- /dev/null +++ b/Tests/Functional/Doctrine/Phpcr/SitemapUrlInformationProviderTest.php @@ -0,0 +1,107 @@ + + */ +class SitemapUrlInformationProviderTest extends BaseTestCase +{ + /** + * @var DocumentManager + */ + protected $dm; + protected $base; + + /** + * @var SitemapUrlInformationProvider + */ + protected $provider; + protected $alternateLocaleProvider; + protected $logger; + protected $presentation; + + public function setUp() + { + $this->db('PHPCR')->createTestNode(); + $this->dm = $this->db('PHPCR')->getOm(); + $this->base = $this->dm->find(null, '/test'); + + $this->db('PHPCR')->loadFixtures(array( + 'Symfony\Cmf\Bundle\SeoBundle\Tests\Resources\DataFixtures\Phpcr\LoadSitemapData', + )); + + $this->logger = $this->getMock('Psr\Log\LoggerInterface'); + $this->presentation = $this + ->getMockBuilder('\Symfony\Cmf\Bundle\SeoBundle\SeoPresentation') + ->disableOriginalConstructor() + ->getMock(); + + $this->provider = new SitemapUrlInformationProvider( + $this->dm, + $this->getContainer()->get('router'), + 'always', + $this->logger, + $this->presentation, + $this->getContainer()->get('cmf_core.publish_workflow.checker') + ); + $this->alternateLocaleProvider = $this + ->getMock('\Symfony\Cmf\Bundle\SeoBundle\AlternateLocaleProviderInterface'); + $this->provider->setAlternateLocaleProvider($this->alternateLocaleProvider); + + $alternateLocale = new AlternateLocale('test', 'de'); + $this->alternateLocaleProvider + ->expects($this->any()) + ->method('createForContent') + ->will($this->returnValue(new ArrayCollection(array($alternateLocale)))); + } + + public function testRouteGeneration() + { + // expected methods/class + $seoMetadata = $this->getMock('Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface'); + $this->presentation + ->expects($this->any()) + ->method('getSeoMetadata') + ->will($this->returnValue($seoMetadata)); + $seoMetadata->expects($this->any())->method('getTitle')->will($this->returnValue('test-title')); + + $routeInformation = $this->provider->getUrlInformation(); + + $this->assertCount(2, $routeInformation); + $actualValues = array(); + foreach ($routeInformation as $information) { + $actualValues[] = $information->toArray(); + } + $expectedValues = array( + array( + 'loc' => 'http://localhost/sitemap-aware', + 'label' => 'test-title', + 'changefreq' => 'always', + 'lastmod' => '', + 'priority' => '', + 'alternate_locales' => array( + array('href' => 'test', 'href_locale' => 'de'), + ), + ), + array( + 'loc' => 'http://localhost/sitemap-aware-publish', + 'label' => 'test-title', + 'changefreq' => 'always', + 'lastmod' => '', + 'priority' => '', + 'alternate_locales' => array( + array('href' => 'test', 'href_locale' => 'de'), + ), + ), + ); + $this->assertEquals($expectedValues, $actualValues); + } +} diff --git a/Tests/Resources/DataFixtures/Phpcr/LoadSitemapData.php b/Tests/Resources/DataFixtures/Phpcr/LoadSitemapData.php new file mode 100644 index 00000000..e36fd07a --- /dev/null +++ b/Tests/Resources/DataFixtures/Phpcr/LoadSitemapData.php @@ -0,0 +1,88 @@ +getPhpcrSession(), '/test'); + + NodeHelper::createPath($manager->getPhpcrSession(), '/test/content'); + NodeHelper::createPath($manager->getPhpcrSession(), '/test/routes'); + + $contentRoot = $manager->find(null, '/test/content'); + $routeRoot = $manager->find(null, '/test/routes'); + + $sitemapAwareContent = new SitemapAwareContent(); + $sitemapAwareContent + ->setIsVisibleForSitemap(true) + ->setTitle('Sitemap Aware Content') + ->setName('sitemap-aware') + ->setParentDocument($contentRoot) + ->setBody('Content for that is sitemap aware'); + $manager->persist($sitemapAwareContent); + $manager->bindTranslation($sitemapAwareContent, 'en'); + $sitemapAwareContent + ->setTitle('Content für die Sitemap') + ->setBody('Das sollte die Deutsche Version des Contents sein.'); + $manager->bindTranslation($sitemapAwareContent, 'de'); + + $route = new Route(); + $route->setPosition($routeRoot, 'sitemap-aware'); + $route->setContent($sitemapAwareContent); + $manager->persist($route); + + $nonPublishedContent = new SitemapAwareWithPublishWorkflowContent(); + $nonPublishedContent->setIsVisibleForSitemap(true); + $nonPublishedContent->setPublishable(false); + $nonPublishedContent + ->setTitle('Sitemap Aware Content non publish') + ->setName('sitemap-aware-non-publish') + ->setParentDocument($contentRoot) + ->setBody('Content for that is sitemap aware, that is not publish'); + $manager->persist($nonPublishedContent); + + $route = new Route(); + $route->setPosition($routeRoot, 'sitemap-aware-non-publish'); + $route->setContent($nonPublishedContent); + $manager->persist($route); + + $publishedContent = new SitemapAwareWithPublishWorkflowContent(); + $publishedContent->setIsVisibleForSitemap(true); + $publishedContent->setPublishable(true); + $publishedContent + ->setTitle('Sitemap Aware Content publish') + ->setName('sitemap-aware-publish') + ->setParentDocument($contentRoot) + ->setBody('Content for that is sitemap aware, that is publish'); + $manager->persist($publishedContent); + + $route = new Route(); + $route->setPosition($routeRoot, 'sitemap-aware-publish'); + $route->setContent($publishedContent); + $manager->persist($route); + + $manager->flush(); + } +} diff --git a/Tests/Resources/Document/AlternateLocaleContent.php b/Tests/Resources/Document/AlternateLocaleContent.php index 632af4dd..2e7a9ac7 100644 --- a/Tests/Resources/Document/AlternateLocaleContent.php +++ b/Tests/Resources/Document/AlternateLocaleContent.php @@ -52,6 +52,7 @@ public function __construct() { $this->routes = new ArrayCollection(); } + /** * Add a route to the collection. * diff --git a/Tests/Resources/Document/SitemapAwareContent.php b/Tests/Resources/Document/SitemapAwareContent.php new file mode 100644 index 00000000..5f19808f --- /dev/null +++ b/Tests/Resources/Document/SitemapAwareContent.php @@ -0,0 +1,126 @@ + + */ +class SitemapAwareContent extends ContentBase implements + RouteReferrersReadInterface, + TranslatableInterface, + SitemapElementInterface +{ + /** + * @var string + * + * @PHPCRODM\Locale + */ + protected $locale; + + /** + * @var ArrayCollection|Route[] + * + * @PHPCRODM\Referrers( + * referringDocument="Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route", + * referencedBy="content" + * ) + */ + protected $routes; + + /** + * @var bool + * + * @PHPCRODM\Boolean(property="visible_for_sitemap") + */ + private $isVisibleForSitemap; + + /** + * @var string + * + * @PHPCRODM\String(translated=true) + */ + protected $title; + + public function __construct() + { + $this->routes = new ArrayCollection(); + } + + /** + * @return boolean + */ + public function isVisibleInSitemap() + { + return $this->isVisibleForSitemap; + } + + /** + * @param boolean $isVisibleForSitemap + * + * @return SitemapAwareContent + */ + public function setIsVisibleForSitemap($isVisibleForSitemap) + { + $this->isVisibleForSitemap = $isVisibleForSitemap; + + return $this; + } + + /** + * Add a route to the collection. + * + * @param Route $route + */ + public function addRoute($route) + { + $this->routes->add($route); + } + + /** + * Remove a route from the collection. + * + * @param Route $route + */ + public function removeRoute($route) + { + $this->routes->removeElement($route); + } + + /** + * Get the routes that point to this content. + * + * @return Route[] Route instances that point to this content + */ + public function getRoutes() + { + return $this->routes; + } + + + /** + * @return string|boolean The locale of this model or false if + * translations are disabled in this project. + */ + public function getLocale() + { + return $this->locale; + } + + /** + * @param string|boolean $locale The local for this model, or false if + * translations are disabled in this project. + */ + public function setLocale($locale) + { + $this->locale = $locale; + } +} diff --git a/Tests/Resources/Document/SitemapAwareWithPublishWorkflowContent.php b/Tests/Resources/Document/SitemapAwareWithPublishWorkflowContent.php new file mode 100644 index 00000000..dbee0a8b --- /dev/null +++ b/Tests/Resources/Document/SitemapAwareWithPublishWorkflowContent.php @@ -0,0 +1,144 @@ + + */ +class SitemapAwareWithPublishWorkflowContent extends ContentBase implements + PublishableInterface, + PublishTimePeriodInterface, + RouteReferrersReadInterface, + SitemapElementInterface +{ + /** + * @var ArrayCollection|Route[] + * + * @PHPCRODM\Referrers( + * referringDocument="Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route", + * referencedBy="content" + * ) + */ + protected $routes; + + /** + * @var bool + * + * @PHPCRODM\Boolean(property="visible_for_sitemap") + */ + private $isVisibleForSitemap; + + /** + * @var boolean whether this content is publishable + * + * @PHPCRODM\Boolean + */ + protected $publishable = true; + + /** + * @var \DateTime|null publication start time + */ + protected $publishStartDate; + + /** + * @var \DateTime|null publication end time + */ + protected $publishEndDate; + + public function __construct() + { + $this->routes = new ArrayCollection(); + } + + /** + * @return boolean + */ + public function isVisibleInSitemap() + { + return $this->isVisibleForSitemap; + } + + /** + * @param boolean $isVisibleForSitemap + * + * @return SitemapAwareContent + */ + public function setIsVisibleForSitemap($isVisibleForSitemap) + { + $this->isVisibleForSitemap = $isVisibleForSitemap; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setPublishable($publishable) + { + return $this->publishable = (bool) $publishable; + } + + /** + * {@inheritDoc} + */ + public function isPublishable() + { + return $this->publishable; + } + + /** + * {@inheritDoc} + */ + public function getPublishStartDate() + { + return $this->publishStartDate; + } + + /** + * {@inheritDoc} + */ + public function setPublishStartDate(\DateTime $publishStartDate = null) + { + $this->publishStartDate = $publishStartDate; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function getPublishEndDate() + { + return $this->publishEndDate; + } + + /** + * {@inheritDoc} + */ + public function setPublishEndDate(\DateTime $publishEndDate = null) + { + $this->publishEndDate = $publishEndDate; + + return $this; + } + + /** + * Get the routes that point to this content. + * + * @return \Symfony\Component\Routing\Route[] Route instances that point to this content + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/Tests/Resources/Fixtures/sitemap/sitemap.xml b/Tests/Resources/Fixtures/sitemap/sitemap.xml new file mode 100644 index 00000000..6585a8bb --- /dev/null +++ b/Tests/Resources/Fixtures/sitemap/sitemap.xml @@ -0,0 +1,14 @@ + + + + http://www.test-alternate-locale.de + 2014-11-07T00:00:00+01:00 + never + + + + http://www.test-domain.de + 2014-11-06T00:00:00+01:00 + always + + diff --git a/Tests/Resources/app/AppKernel.php b/Tests/Resources/app/AppKernel.php index 9e642be1..0d2a891f 100644 --- a/Tests/Resources/app/AppKernel.php +++ b/Tests/Resources/app/AppKernel.php @@ -23,6 +23,7 @@ public function configure() new \Symfony\Cmf\Bundle\SeoBundle\CmfSeoBundle(), new \Burgov\Bundle\KeyValueFormBundle\BurgovKeyValueFormBundle(), new \Symfony\Cmf\Bundle\RoutingBundle\CmfRoutingBundle(), + new Symfony\Cmf\Bundle\CoreBundle\CmfCoreBundle(), )); } diff --git a/Tests/Resources/app/config/cmf_seo.phpcr.yml b/Tests/Resources/app/config/cmf_seo.phpcr.yml index d0a395ed..888f4811 100644 --- a/Tests/Resources/app/config/cmf_seo.phpcr.yml +++ b/Tests/Resources/app/config/cmf_seo.phpcr.yml @@ -37,3 +37,8 @@ sonata_admin: label: Content items: - sonata.admin.seo_content + +cmf_core: + persistence: + phpcr: + basepath: /test diff --git a/Tests/Resources/app/config/cmf_seo.yml b/Tests/Resources/app/config/cmf_seo.yml index 5dd91075..46ea14fe 100644 --- a/Tests/Resources/app/config/cmf_seo.yml +++ b/Tests/Resources/app/config/cmf_seo.yml @@ -14,6 +14,7 @@ cmf_seo: error: enable_parent_provider: true enable_sibling_provider: true + sitemap: true cmf_routing: chain: @@ -23,3 +24,7 @@ cmf_routing: twig: exception_controller: cmf_seo.error.suggestion_provider.controller:showAction + +cmf_core: + multilang: + locales: [en, de] diff --git a/Tests/Resources/app/config/routing.php b/Tests/Resources/app/config/routing.php index 07d866b9..7b9c4881 100644 --- a/Tests/Resources/app/config/routing.php +++ b/Tests/Resources/app/config/routing.php @@ -6,6 +6,9 @@ $collection->addCollection( $loader->import(CMF_TEST_CONFIG_DIR.'/routing/sonata_routing.yml') ); +$collection->addCollection( + $loader->import(__DIR__.'/../../../../Resources/config/routing/sitemap.xml') +); // $collection->addCollection( // $loader->import(__DIR__.'/routing/my_test_routing.yml') // ); diff --git a/Tests/Unit/Controller/SitemapControllerTest.php b/Tests/Unit/Controller/SitemapControllerTest.php new file mode 100644 index 00000000..831311f4 --- /dev/null +++ b/Tests/Unit/Controller/SitemapControllerTest.php @@ -0,0 +1,135 @@ + + */ +class SitemapControllerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var EngineInterface + */ + protected $templating; + + /** + * @var UrlInformationProviderInterface + */ + private $provider; + + /** + * @var SitemapController + */ + private $controller; + + public function setUp() + { + $this->provider = $this->getMock('Symfony\Cmf\Bundle\SeoBundle\Sitemap\UrlInformationProviderInterface'); + $this->createRoutes(); + + $this->templating = $this->getMock('Symfony\Component\Templating\EngineInterface'); + $this->controller = new SitemapController( + $this->provider, + $this->templating, + array( + 'xml' => 'CmfSeoBundle:Sitemap:index.xml.twig', + 'html' => 'CmfSeoBundle:Sitemap:index.html.twig', + ) + ); + } + + public function testRequestJson() + { + /** @var Response $response */ + $response = $this->controller->indexAction('json'); + $expected = array( + array( + 'loc' => 'http://www.test-alternate-locale.de', + 'label' => 'Test alternate locale', + 'changefreq' => 'never', + 'lastmod' => '2014-11-07T00:00:00+01:00', + 'priority' => 0.85, + 'alternate_locales' => array( + array('href' => 'http://www.test-alternate-locale.com', 'href_locale' => 'en') + ), + ), + array( + 'loc' => 'http://www.test-domain.de', + 'label' => 'Test label', + 'changefreq' => 'always', + 'lastmod' => '2014-11-06T00:00:00+01:00', + 'priority' => 0.85, + 'alternate_locales' => array(), + ), + ); + + $this->assertEquals($expected, json_decode($response->getContent(), true)); + } + + public function testRequestXml() + { + $response = new Response('some-xml-string'); + $this->templating->expects($this->once())->method('render')->will($this->returnValue($response)); + + /** @var Response $response */ + $response = $this->controller->indexAction('xml'); + + $this->assertEquals(new Response('some-xml-string'), $response->getContent()); + } + + public function testRequestHtml() + { + $expectedResponse = new Response('some-html-string'); + $this->templating->expects($this->once())->method('render')->will($this->returnValue($expectedResponse)); + + /** @var Response $response */ + $response = $this->controller->indexAction('html'); + + $this->assertEquals($expectedResponse, $response->getContent()); + } + + private function createRoutes() + { + $urls = array(); + + $simpleUrl = new UrlInformation(); + $simpleUrl + ->setLocation('http://www.test-domain.de') + ->setChangeFrequency('always') + ->setLabel('Test label') + ->setPriority(0.85) + ->setLastModification(new \DateTime('2014-11-06', new \DateTimeZone('Europe/Berlin'))) + ; + + $urlWithAlternateLocale = new UrlInformation(); + $urlWithAlternateLocale + ->setLocation('http://www.test-alternate-locale.de') + ->setChangeFrequency('never') + ->setLabel('Test alternate locale') + ->setPriority(0.85) + ->setLastModification(new \DateTime('2014-11-07', new \DateTimeZone('Europe/Berlin'))) + ; + $alternateLocale = new AlternateLocale('http://www.test-alternate-locale.com', 'en'); + $urlWithAlternateLocale->addAlternateLocale($alternateLocale); + + $urls[] = $urlWithAlternateLocale; + $urls[] = $simpleUrl; + + $this->provider->expects($this->any())->method('getUrlInformation')->will($this->returnValue($urls)); + } + + private function getFileContent($type) + { + $basePath = __DIR__.'/../../Resources/Fixtures/sitemap/sitemap'; + + return file_get_contents($basePath.'.'.$type); + } +} diff --git a/Tests/Unit/DependencyInjection/CmfSeoExtensionTest.php b/Tests/Unit/DependencyInjection/CmfSeoExtensionTest.php index 4ae5f3b8..1dc24070 100644 --- a/Tests/Unit/DependencyInjection/CmfSeoExtensionTest.php +++ b/Tests/Unit/DependencyInjection/CmfSeoExtensionTest.php @@ -51,8 +51,13 @@ public function testPersistencePHPCR() 'description' => 'Default description.', 'persistence' => array( 'phpcr' => true, - ) + ), )); + + $this->assertContainerBuilderHasService( + 'cmf_seo.sitemap.phpcr_provider', + 'Symfony\Cmf\Bundle\SeoBundle\Doctrine\Phpcr\SitemapUrlInformationProvider' + ); } public function testPersistenceORM() @@ -130,11 +135,19 @@ public function testAlternateLocaleWithPhpcr() public function testAlternateLocaleWithCustomProvider() { + $this->container->setParameter( + 'kernel.bundles', + array() + ); $this->container->setDefinition('some_alternate_locale_provider', new Definition()); $this->load(array( + 'persistence' => array( + 'phpcr' => true, + ), 'alternate_locale' => array( 'provider_id' => 'some_alternate_locale_provider' ), + 'sitemap' => true, )); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( @@ -142,6 +155,11 @@ public function testAlternateLocaleWithCustomProvider() 'setAlternateLocaleProvider', array($this->container->getDefinition('some_alternate_locale_provider')) ); + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + 'cmf_seo.sitemap.phpcr_provider', + 'setAlternateLocaleProvider', + array($this->container->getDefinition('some_alternate_locale_provider')) + ); } /** @@ -163,6 +181,7 @@ public function testExceptionForNonExistingBurgovBundleWithAdminExtension() ) )); } + public function testErrorHandlingPHPCR() { $this->container->setParameter( @@ -192,4 +211,37 @@ public function testErrorHandlingPHPCR() array('group' => 'parent') ); } + + public function testSitemapConfiguration() + { + $this->container->setParameter( + 'kernel.bundles', + array() + ); + $this->load(array( + 'sitemap' => array( + 'default_chan_frequency' => 'never', + ), + 'persistence' => array( + 'phpcr' => true, + ), + )); + + $this->assertContainerBuilderHasService( + 'cmf_seo.sitemap.controller', + 'Symfony\Cmf\Bundle\SeoBundle\Controller\SitemapController' + ); + $this->assertContainerBuilderHasService( + 'cmf_seo.sitemap.url_information_provider', + 'Symfony\Cmf\Bundle\SeoBundle\Sitemap\ChainProvider' + ); + $this->assertContainerBuilderHasService( + 'cmf_seo.sitemap.phpcr_provider', + 'Symfony\Cmf\Bundle\SeoBundle\Doctrine\Phpcr\SitemapUrlInformationProvider' + ); + $this->assertContainerBuilderHasServiceDefinitionWithTag( + 'cmf_seo.sitemap.phpcr_provider', + 'cmf_seo.sitemap.url_information_provider' + ); + } } diff --git a/Tests/Unit/DependencyInjection/Compiler/RegisterUrlInformationProviderPassTest.php b/Tests/Unit/DependencyInjection/Compiler/RegisterUrlInformationProviderPassTest.php new file mode 100644 index 00000000..d56cc359 --- /dev/null +++ b/Tests/Unit/DependencyInjection/Compiler/RegisterUrlInformationProviderPassTest.php @@ -0,0 +1,61 @@ + + */ +class RegisterUrlInformationProviderPassTest extends AbstractCompilerPassTestCase +{ + + /** + * Register the compiler pass under test, just like you would do inside a bundle's load() + * method: + * + * $container->addCompilerPass(new MyCompilerPass()); + */ + protected function registerCompilerPass(ContainerBuilder $container) + { + $container->addCompilerPass(new RegisterUrlInformationProviderPass()); + } + + public function testTags() + { + $nonProviderService = new Definition(); + $this->setDefinition('some_service', $nonProviderService); + + $providerService = new Definition(); + $providerService->addTag('cmf_seo.sitemap.url_information_provider'); + $this->setDefinition('provider_service', $providerService); + + $providerServiceWithPriority = new Definition(); + $providerServiceWithPriority->addTag( + 'cmf_seo.sitemap.url_information_provider', + array('priority' => 1) + ); + $this->setDefinition('provider_service_priority', $providerServiceWithPriority); + + $chainProvider = new Definition(); + $this->setDefinition('cmf_seo.sitemap.url_information_provider', $chainProvider); + + $this->compile(); + + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + 'cmf_seo.sitemap.url_information_provider', + 'addProvider', + array(new Reference('provider_service'), 0) + ); + + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + 'cmf_seo.sitemap.url_information_provider', + 'addProvider', + array(new Reference('provider_service_priority'), 1) + ); + } +} diff --git a/Tests/Unit/DependencyInjection/ConfigurationTest.php b/Tests/Unit/DependencyInjection/ConfigurationTest.php index 30cfa70d..5adc9fc6 100644 --- a/Tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/Tests/Unit/DependencyInjection/ConfigurationTest.php @@ -60,6 +60,10 @@ public function testDefaultsForAllConfigFormats() 'enabled' => false, 'provider_id' => null, ), + 'sitemap' => array( + 'enabled' => false, + 'default_chan_frequency' => 'always' + ), ); $sources = array_map(function ($path) { diff --git a/Tests/Unit/Model/UrlInformationTest.php b/Tests/Unit/Model/UrlInformationTest.php new file mode 100644 index 00000000..d355b028 --- /dev/null +++ b/Tests/Unit/Model/UrlInformationTest.php @@ -0,0 +1,46 @@ + + */ +class UrlInformationTest extends \PHPUnit_Framework_Testcase +{ + /** + * @var UrlInformation + */ + private $model; + + public function setUp() + { + $this->model = new UrlInformation(); + } + + /** + * @expectedException Symfony\Cmf\Bundle\SeoBundle\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid change frequency use one of always, hourly, daily, weekly, monthly, yearly, never. + */ + public function testSetChangeFrequencyShouldThrowExceptionForInvalidArguments() + { + $this->model->setChangeFrequency('some one'); + } + + public function testValidChangeFrequency() + { + $this->model->setChangeFrequency('never'); + + $this->assertEquals('never', $this->model->getChangeFrequency()); + } +} diff --git a/Tests/Unit/Sitemap/ChainProviderTest.php b/Tests/Unit/Sitemap/ChainProviderTest.php new file mode 100644 index 00000000..daa2f8ca --- /dev/null +++ b/Tests/Unit/Sitemap/ChainProviderTest.php @@ -0,0 +1,77 @@ + + */ +class ChainProviderTest extends \PHPUnit_Framework_Testcase +{ + /** + * @var ChainProvider + */ + private $chainProvider; + + public function setUp() + { + $this->chainProvider = new ChainProvider(); + } + + public function testInlineInput() + { + $providerOne = $this->getMock('\Symfony\Cmf\Bundle\SeoBundle\Sitemap\UrlInformationProviderInterface'); + $providerTwo = $this->getMock('\Symfony\Cmf\Bundle\SeoBundle\Sitemap\UrlInformationProviderInterface'); + + $this->chainProvider->addProvider($providerOne); + $this->chainProvider->addProvider($providerTwo); + + $providerOne->expects($this->once())->method('getUrlInformation')->will($this->returnValue(array('info-one'))); + $providerTwo->expects($this->once())->method('getUrlInformation')->will($this->returnValue(array('info-two'))); + + $actualList = $this->chainProvider->getUrlInformation(); + $expectedList = array('info-one', 'info-two'); + + $this->assertEquals($expectedList, $actualList); + } + + public function testPrioritisedInput() + { + + $providerBeforeAll = $this->getMock('\Symfony\Cmf\Bundle\SeoBundle\Sitemap\UrlInformationProviderInterface'); + $providerFirst = $this->getMock('\Symfony\Cmf\Bundle\SeoBundle\Sitemap\UrlInformationProviderInterface'); + $providerAfterAll = $this->getMock('\Symfony\Cmf\Bundle\SeoBundle\Sitemap\UrlInformationProviderInterface'); + + $this->chainProvider->addProvider($providerFirst, 1); + $this->chainProvider->addProvider($providerBeforeAll, 0); + $this->chainProvider->addProvider($providerAfterAll, 2); + + $providerFirst + ->expects($this->once()) + ->method('getUrlInformation') + ->will($this->returnValue(array('info-first'))); + $providerBeforeAll + ->expects($this->once()) + ->method('getUrlInformation') + ->will($this->returnValue(array('info-before-all'))); + $providerAfterAll + ->expects($this->once()) + ->method('getUrlInformation') + ->will($this->returnValue(array('info-after-all'))); + + $actualList = $this->chainProvider->getUrlInformation(); + $expectedList = array('info-before-all', 'info-first', 'info-after-all'); + + $this->assertEquals($expectedList, $actualList); + } +} diff --git a/Tests/WebTest/SitemapTest.php b/Tests/WebTest/SitemapTest.php new file mode 100644 index 00000000..950a065f --- /dev/null +++ b/Tests/WebTest/SitemapTest.php @@ -0,0 +1,81 @@ + + */ +class SitemapTest extends BaseTestCase +{ + /** @var Client */ + private $client; + + public function setUp() + { + $this->db('PHPCR')->loadFixtures(array( + 'Symfony\Cmf\Bundle\SeoBundle\Tests\Resources\DataFixtures\Phpcr\LoadSitemapData', + )); + $this->client = $this->createClient(); + } + + /** + * @param $format + * + * @dataProvider getFormats + */ + public function testSitemap($format) + { + $this->client->request('GET', '/sitemap.'.$format); + $res = $this->client->getResponse(); + + $this->assertEquals(200, $res->getStatusCode()); + $content = $res->getContent(); + if ('html' === $format || 'xml' === $format) { + $this->assertXmlStringEqualsXmlString($this->getContent($format), $content); + } else { + $this->assertEquals($this->getContent($format), $content); + } + } + + public function getFormats() + { + return array( + array('html'), array('xml'), array('json') + ); + } + + public function getContent($format) + { + $data = array( + 'html' => '', + 'xml' => ' + + + http://localhost/sitemap-aware + + always + + + + http://localhost/sitemap-aware-publish + + always + + ', + 'json' => '[{"loc":"http:\/\/localhost\/sitemap-aware","label":"Sitemap Aware Content","changefreq":"always","lastmod":null,"priority":null,"alternate_locales":[{"href":"http:\/\/localhost\/sitemap-aware?_locale=de","href_locale":"de"}]},{"loc":"http:\/\/localhost\/sitemap-aware-publish","label":"Sitemap Aware Content publish","changefreq":"always","lastmod":null,"priority":null,"alternate_locales":[]}]', + ); + + return $data[$format]; + } +}