├── Makefile
├── composer.json
├── phpunit.xml.dist
├── src
├── AlternateLocaleProviderInterface.php
├── Cache
│ ├── CacheInterface.php
│ ├── ExtractorCollection.php
│ └── FileCache.php
├── CmfSeoBundle.php
├── Controller
│ ├── SitemapController.php
│ └── SuggestionProviderController.php
├── DependencyInjection
│ ├── CmfSeoExtension.php
│ ├── Compiler
│ │ ├── RegisterExtractorsPass.php
│ │ ├── RegisterSuggestionProviderPass.php
│ │ └── RegisterUrlInformationProviderPass.php
│ ├── ConfigValues.php
│ └── Configuration.php
├── Doctrine
│ └── Phpcr
│ │ ├── AlternateLocaleProvider.php
│ │ ├── BaseSuggestionProvider.php
│ │ ├── ParentSuggestionProvider.php
│ │ ├── SeoMetadata.php
│ │ ├── SiblingSuggestionProvider.php
│ │ └── SitemapDocumentProvider.php
├── EventListener
│ ├── ContentListener.php
│ └── LanguageListener.php
├── Exception
│ ├── ExtractorStrategyException.php
│ └── InvalidArgumentException.php
├── Extractor
│ ├── DescriptionExtractor.php
│ ├── DescriptionReadInterface.php
│ ├── ExtractorInterface.php
│ ├── ExtrasExtractor.php
│ ├── ExtrasReadInterface.php
│ ├── KeywordsExtractor.php
│ ├── KeywordsReadInterface.php
│ ├── OriginalRouteExtractor.php
│ ├── OriginalRouteReadInterface.php
│ ├── OriginalUrlExtractor.php
│ ├── OriginalUrlReadInterface.php
│ ├── TitleExtractor.php
│ ├── TitleReadExtractor.php
│ └── TitleReadInterface.php
├── Form
│ └── Type
│ │ └── SeoMetadataType.php
├── Matcher
│ └── ExclusionMatcher.php
├── Model
│ ├── AlternateLocale.php
│ ├── AlternateLocaleCollection.php
│ ├── SeoMetadata.php
│ ├── SeoMetadataInterface.php
│ └── UrlInformation.php
├── Resources
│ ├── config
│ │ ├── content-listener.xml
│ │ ├── doctrine-model
│ │ │ ├── SeoMetadata.orm.xml
│ │ │ └── SeoMetadata.phpcr.xml
│ │ ├── doctrine-phpcr
│ │ │ └── SeoMetadata.phpcr.xml
│ │ ├── extractors.xml
│ │ ├── matcher_phpcr.xml
│ │ ├── phpcr-alternate-locale.xml
│ │ ├── phpcr-sitemap.xml
│ │ ├── routing
│ │ │ └── sitemap.xml
│ │ ├── schema
│ │ │ └── seo-1.0.xsd
│ │ ├── services.xml
│ │ └── sitemap.xml
│ ├── meta
│ │ └── LICENSE
│ └── views
│ │ ├── Exception
│ │ └── error.html.twig
│ │ └── Sitemap
│ │ ├── index.html.twig
│ │ └── index.xml.twig
├── SeoAwareInterface.php
├── SeoAwareTrait.php
├── SeoPresentation.php
├── SeoPresentationInterface.php
├── Sitemap
│ ├── AbstractChain.php
│ ├── AlternateLocalesGuesser.php
│ ├── DefaultChangeFrequencyGuesser.php
│ ├── DepthGuesser.php
│ ├── GuesserChain.php
│ ├── GuesserInterface.php
│ ├── LastModifiedGuesser.php
│ ├── LoaderChain.php
│ ├── LoaderInterface.php
│ ├── LocationGuesser.php
│ ├── PublishWorkflowVoter.php
│ ├── SeoMetadataTitleGuesser.php
│ ├── SitemapAwareDocumentVoter.php
│ ├── UrlInformationProvider.php
│ ├── VoterChain.php
│ └── VoterInterface.php
├── SitemapAwareInterface.php
├── SuggestionProviderInterface.php
└── Twig
│ └── Extension
│ └── CmfSeoExtension.php
├── tests
├── Fixtures
│ ├── App
│ │ ├── Controller
│ │ │ └── TestController.php
│ │ ├── DataFixtures
│ │ │ └── Phpcr
│ │ │ │ ├── LoadContentData.php
│ │ │ │ └── LoadSitemapData.php
│ │ ├── Document
│ │ │ ├── AllStrategiesDocument.php
│ │ │ ├── AlternateLocaleContent.php
│ │ │ ├── ContentBase.php
│ │ │ ├── ContentWithExtractors.php
│ │ │ ├── SeoAwareContent.php
│ │ │ ├── SitemapAwareContent.php
│ │ │ ├── SitemapAwareWithLastModifiedDateContent.php
│ │ │ └── SitemapAwareWithPublishWorkflowContent.php
│ │ ├── Entity
│ │ │ └── SeoAwareOrmContent.php
│ │ ├── Kernel.php
│ │ ├── Model
│ │ │ └── SeoAwareOrmContent.php
│ │ ├── Resources
│ │ │ └── views
│ │ │ │ ├── base.html.twig
│ │ │ │ └── tests
│ │ │ │ ├── exception.html.twig
│ │ │ │ └── index.html.twig
│ │ ├── config
│ │ │ ├── bundles.php
│ │ │ ├── cmf_seo.orm.yml
│ │ │ ├── cmf_seo.phpcr.yml
│ │ │ ├── cmf_seo.yml
│ │ │ ├── config.php
│ │ │ ├── config_orm.php
│ │ │ ├── config_phpcr.php
│ │ │ ├── routing.php
│ │ │ ├── tree_browser_1.x.yml
│ │ │ └── tree_browser_2.x.yml
│ │ └── translations
│ │ │ ├── mesages.de.xliff
│ │ │ └── mesages.en.xliff
│ └── fixtures
│ │ └── config
│ │ ├── config.php
│ │ ├── config.xml
│ │ ├── config.yml
│ │ ├── config1.xml
│ │ ├── config2.xml
│ │ ├── config3.xml
│ │ ├── config4.xml
│ │ ├── config_error.xml
│ │ └── config_sitemap.xml
├── Functional
│ └── Doctrine
│ │ ├── Orm
│ │ └── SeoMetadataTest.php
│ │ └── Phpcr
│ │ ├── SeoMetadataTest.php
│ │ └── SitemapDocumentProviderTest.php
├── Unit
│ ├── Cache
│ │ └── FileCacheTest.php
│ ├── Controller
│ │ └── SitemapControllerTest.php
│ ├── DependencyInjection
│ │ ├── CmfSeoExtensionTest.php
│ │ ├── Compiler
│ │ │ ├── RegisterExtractorsPassTest.php
│ │ │ ├── RegisterSuggestionProviderPassTest.php
│ │ │ └── RegisterUrlInformationProviderPassTest.php
│ │ ├── ConfigValuesTest.php
│ │ ├── ConfigurationTest.php
│ │ └── XmlSchemaTest.php
│ ├── EventListener
│ │ ├── ContentListenerTest.php
│ │ └── LanguageListenerTest.php
│ ├── Extractor
│ │ ├── BaseTestCase.php
│ │ ├── DescriptionExtractorTest.php
│ │ ├── ExtrasExtractorTest.php
│ │ ├── KeywordsExtractorTest.php
│ │ ├── OriginalRouteExtractorTest.php
│ │ ├── OriginalUrlExtractorTest.php
│ │ ├── TitleExtractorTest.php
│ │ └── TitleReadExtractorTest.php
│ ├── Form
│ │ └── Type
│ │ │ └── SeoMetadataTypeTest.php
│ ├── Matcher
│ │ └── ExclusionMatcherTest.php
│ ├── Model
│ │ └── UrlInformationTest.php
│ ├── SeoPresentationTest.php
│ └── Sitemap
│ │ ├── AbstractChainTest.php
│ │ ├── AlternateLocalesGuesserTest.php
│ │ ├── DefaultChangeFrequencyGuesserTest.php
│ │ ├── DepthGuesserTest.php
│ │ ├── GuesserTestCase.php
│ │ ├── LastModifiedGuesserTest.php
│ │ ├── LocationGuesserTest.php
│ │ ├── SeoMetadataTitleGuesserTest.php
│ │ ├── SitemapAwareDocumentVoterTest.php
│ │ └── UrlInformationProviderTest.php
├── WebTest
│ ├── SeoFrontendTest.php
│ └── SitemapTest.php
└── bootstrap.php
└── travis.php.ini
/Makefile:
--------------------------------------------------------------------------------
1 | #######################################################
2 | # DO NOT EDIT THIS FILE! #
3 | # #
4 | # It's auto-generated by symfony-cmf/dev-kit package. #
5 | #######################################################
6 |
7 | ############################################################################
8 | # This file is part of the Symfony CMF package. #
9 | # #
10 | # (c) 2011-2017 Symfony CMF #
11 | # #
12 | # For the full copyright and license information, please view the LICENSE #
13 | # file that was distributed with this source code. #
14 | ############################################################################
15 |
16 | TESTING_SCRIPTS_DIR=vendor/symfony-cmf/testing/bin
17 | CONSOLE=${TESTING_SCRIPTS_DIR}/console
18 | VERSION=dev-master
19 | ifdef BRANCH
20 | VERSION=dev-${BRANCH}
21 | endif
22 | PACKAGE=symfony-cmf/seo-bundle
23 | export KERNEL_CLASS=Symfony\Cmf\Bundle\SeoBundle\Tests\Fixtures\App\Kernel
24 | list:
25 | @echo 'test: will run all tests'
26 | @echo 'unit_tests: will run unit tests only'
27 | @echo 'functional_tests_phpcr: will run functional tests with PHPCR'
28 | @echo 'functional_tests_orm: will run functional tests with ORM'
29 |
30 | include ${TESTING_SCRIPTS_DIR}/make/unit_tests.mk
31 | include ${TESTING_SCRIPTS_DIR}/make/functional_tests_phpcr.mk
32 | include ${TESTING_SCRIPTS_DIR}/make/functional_tests_orm.mk
33 |
34 | .PHONY: test
35 | test: unit_tests functional_tests_phpcr functional_tests_orm
36 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "symfony-cmf/seo-bundle",
3 | "type": "symfony-bundle",
4 | "description": "Symfony CMF Search Engine Optimization Bundle",
5 | "keywords": [
6 | "Symfony CMF",
7 | "SEO"
8 | ],
9 | "homepage": "http://cmf.symfony.com",
10 | "license": "MIT",
11 | "authors": [
12 | {
13 | "name": "Symfony CMF Community",
14 | "homepage": "https://github.com/symfony-cmf/SeoBundle/contributors"
15 | }
16 | ],
17 | "require": {
18 | "php": "^7.2",
19 | "symfony/framework-bundle": "^3.4 || ^4.0",
20 | "sonata-project/seo-bundle": "^2.2",
21 | "doctrine/collections": "^1.0"
22 | },
23 | "require-dev": {
24 | "doctrine/phpcr-odm": "^1.4.2 || ^2.0",
25 | "doctrine/orm": "^2.4",
26 | "symfony-cmf/core-bundle": "^2.1",
27 | "symfony-cmf/routing-bundle": "^1.4.0 || ^2.1",
28 | "matthiasnoback/symfony-dependency-injection-test": "^1.1",
29 | "matthiasnoback/symfony-config-test": "^2.1",
30 | "symfony/phpunit-bridge": "^3.4 || ^4.0",
31 | "burgov/key-value-form-bundle": "^1.0",
32 | "symfony-cmf/testing": "^2.1.9",
33 | "symfony/security-bundle": "^3.4 || ^4.0",
34 | "symfony/translation": "^3.4 || ^4.0",
35 | "symfony/validator": "^3.4 || ^4.0",
36 | "symfony/asset": "^3.4 || ^4.0",
37 | "symfony/templating": "^3.4 || ^4.0",
38 | "symfony/browser-kit": "^3.4 || ^4.0",
39 | "symfony/css-selector": "^3.4 || ^4.0"
40 | },
41 | "suggest": {
42 | "symfony-cmf/core-bundle": "When using the provided PHPCR-ODM document",
43 | "symfony-cmf/routing-bundle": "To make use of the alternate locale provider.",
44 | "doctrine/phpcr-bundle": "To persist the metadata in PHPCR ODM documents",
45 | "doctrine/doctrine-bundle": "To persist the metadata in ORM entities",
46 | "doctrine/orm": "To persist the metadata in ORM entities, ~2.4",
47 | "burgov/key-value-form-bundle": "To have advanced editing options in the SEO form type, ^1.0",
48 | "symfony-cmf/sonata-admin-integration-bundle": "To provide an admin interface for the PHPCR ODM documents."
49 | },
50 | "autoload": {
51 | "psr-4": {
52 | "Symfony\\Cmf\\Bundle\\SeoBundle\\": "src/"
53 | }
54 | },
55 | "autoload-dev": {
56 | "psr-4": {
57 | "Symfony\\Cmf\\Bundle\\SeoBundle\\Tests\\": "tests/"
58 | }
59 | },
60 | "extra": {
61 | "branch-alias": {
62 | "dev-master": "2.2-dev"
63 | }
64 | },
65 | "conflict": {
66 | "phpcr/phpcr-utils": "<1.3.0",
67 | "sebastian/exporter": "<2.0.0",
68 | "burgov/key-value-form-bundle": "<1.3.1"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 | ./tests/Unit
10 |
11 |
12 |
13 | ./tests/WebTest
14 | ./tests/Functional/Doctrine/Phpcr
15 |
16 |
17 |
18 | ./tests/Functional/Doctrine/Orm
19 |
20 |
21 |
22 |
23 |
24 | .
25 |
26 | Resources/
27 | tests
28 | vendor/
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/AlternateLocaleProviderInterface.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | interface AlternateLocaleProviderInterface
22 | {
23 | /**
24 | * Creates a collection of AlternateLocales for one content object.
25 | *
26 | * @param object $content
27 | *
28 | * @return AlternateLocaleCollection
29 | */
30 | public function createForContent($content);
31 |
32 | /**
33 | * Creates a collection of AlternateLocales for many content object.
34 | *
35 | * @param array|object[] $contents
36 | *
37 | * @return AlternateLocaleCollection[]
38 | */
39 | public function createForContents(array $contents);
40 | }
41 |
--------------------------------------------------------------------------------
/src/Cache/CacheInterface.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | interface CacheInterface
21 | {
22 | /**
23 | * Fetches extractors from the cache.
24 | *
25 | * @param string $class
26 | *
27 | * @return ExtractorCollection|null
28 | */
29 | public function loadExtractorsFromCache($class);
30 |
31 | /**
32 | * Saves extractors into the cache.
33 | *
34 | * @param string $class
35 | * @param array $extractors
36 | */
37 | public function putExtractorsInCache($class, array $extractors);
38 | }
39 |
--------------------------------------------------------------------------------
/src/Cache/ExtractorCollection.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class ExtractorCollection implements \IteratorAggregate, \Serializable
20 | {
21 | /**
22 | * @var array
23 | */
24 | private $extractors;
25 |
26 | /**
27 | * @var null|string
28 | */
29 | private $resource;
30 |
31 | /**
32 | * @var int
33 | */
34 | private $createdAt;
35 |
36 | /**
37 | * @param array $extractors
38 | * @param null|string $resource The path to the file of the content object, this is
39 | * used to determine if the cache needs to be updated
40 | */
41 | public function __construct(array $extractors, $resource = null)
42 | {
43 | $this->extractors = $extractors;
44 | $this->resource = $resource;
45 | $this->createdAt = time();
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | public function getIterator()
52 | {
53 | return new \ArrayIterator($this->extractors);
54 | }
55 |
56 | /**
57 | * Checks if the cache needs to be updated or not.
58 | *
59 | * @param null|int $timestamp
60 | *
61 | * @return bool whether cache needs to be updated
62 | */
63 | public function isFresh($timestamp = null)
64 | {
65 | if (null === $timestamp) {
66 | $timestamp = $this->createdAt;
67 | }
68 |
69 | if (!file_exists($this->resource)) {
70 | return false;
71 | }
72 |
73 | if ($timestamp < filemtime($this->resource)) {
74 | return false;
75 | }
76 |
77 | return true;
78 | }
79 |
80 | /**
81 | * {@inheritdoc}
82 | */
83 | public function serialize()
84 | {
85 | return serialize([
86 | $this->extractors,
87 | $this->resource,
88 | $this->createdAt,
89 | ]);
90 | }
91 |
92 | /**
93 | * {@inheritdoc}
94 | */
95 | public function unserialize($data)
96 | {
97 | list(
98 | $this->extractors,
99 | $this->resource,
100 | $this->createdAt
101 | ) = unserialize($data);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/CmfSeoBundle.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new RegisterExtractorsPass());
27 | $container->addCompilerPass(new RegisterSuggestionProviderPass());
28 | $container->addCompilerPass(new RegisterUrlInformationProviderPass());
29 |
30 | $this->buildPhpcrCompilerPass($container);
31 | $this->buildOrmCompilerPass($container);
32 | }
33 |
34 | /**
35 | * Creates and registers compiler passes for PHPCR-ODM mapping if both the
36 | * phpcr-odm and the phpcr-bundle are present.
37 | *
38 | * @param ContainerBuilder $container
39 | */
40 | private function buildPhpcrCompilerPass(ContainerBuilder $container)
41 | {
42 | if (!class_exists('Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass')
43 | || !class_exists('Doctrine\ODM\PHPCR\Version')
44 | ) {
45 | return;
46 | }
47 |
48 | $container->addCompilerPass(
49 | DoctrinePhpcrMappingsPass::createXmlMappingDriver(
50 | [
51 | $this->getPath().'/Resources/config/doctrine-model' => 'Symfony\Cmf\Bundle\SeoBundle\Model',
52 | $this->getPath().'/Resources/config/doctrine-phpcr' => 'Symfony\Cmf\Bundle\SeoBundle\Doctrine\Phpcr',
53 | ],
54 | ['cmf_seo.dynamic.persistence.phpcr.manager_name'],
55 | 'cmf_seo.backend_type_phpcr',
56 | ['CmfSeoBundle' => 'Symfony\Cmf\Bundle\SeoBundle\Doctrine\Phpcr']
57 | )
58 | );
59 | }
60 |
61 | /**
62 | * Creates and registers compiler passes for ORM mappings if both doctrine
63 | * ORM and a suitable compiler pass implementation are available.
64 | *
65 | * @param ContainerBuilder $container
66 | */
67 | private function buildOrmCompilerPass(ContainerBuilder $container)
68 | {
69 | if (!class_exists('Doctrine\ORM\Version')
70 | || !class_exists('Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterMappingsPass')
71 | || !class_exists('Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass')
72 | ) {
73 | return;
74 | }
75 |
76 | $container->addCompilerPass(
77 | DoctrineOrmMappingsPass::createXmlMappingDriver(
78 | [
79 | $this->getPath().'/Resources/config/doctrine-model' => 'Symfony\Cmf\Bundle\SeoBundle\Model',
80 | ],
81 | ['cmf_seo.dynamic.persistence.orm.manager_name'],
82 | 'cmf_seo.backend_type_orm',
83 | ['CmfSeoBundle' => 'Symfony\Cmf\Bundle\SeoBundle\Model']
84 | )
85 | );
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Controller/SitemapController.php:
--------------------------------------------------------------------------------
1 |
25 | */
26 | class SitemapController
27 | {
28 | /**
29 | * @var EngineInterface
30 | */
31 | private $templating;
32 |
33 | /**
34 | * The complete configurations for all sitemap with its
35 | * definitions for their templates.
36 | *
37 | * @var array
38 | */
39 | private $configurations;
40 |
41 | /**
42 | * @var UrlInformationProvider
43 | */
44 | private $sitemapProvider;
45 |
46 | /**
47 | * You should provide templates for html and xml.
48 | *
49 | * Json is serialized by default, but can be customized with a template
50 | *
51 | * @param UrlInformationProvider $sitemapProvider
52 | * @param EngineInterface $templating
53 | * @param array $configurations list of available sitemap configurations
54 | */
55 | public function __construct(
56 | UrlInformationProvider $sitemapProvider,
57 | EngineInterface $templating,
58 | array $configurations
59 | ) {
60 | $this->templating = $templating;
61 | $this->configurations = $configurations;
62 | $this->sitemapProvider = $sitemapProvider;
63 | }
64 |
65 | /**
66 | * @param string $_format the format of the sitemap
67 | * @param string $sitemap the sitemap to show
68 | *
69 | * @return Response
70 | */
71 | public function indexAction($_format, $sitemap)
72 | {
73 | if (!isset($this->configurations[$sitemap])) {
74 | throw new NotFoundHttpException(sprintf('Unknown sitemap "%s"', $sitemap));
75 | }
76 |
77 | $templates = $this->configurations[$sitemap]['templates'];
78 |
79 | $supportedFormats = array_merge(['json'], array_keys($templates));
80 | if (!in_array($_format, $supportedFormats)) {
81 | $text = sprintf(
82 | 'Unknown format %s, use one of %s.',
83 | $_format,
84 | implode(', ', $supportedFormats)
85 | );
86 |
87 | return new Response($text, 406);
88 | }
89 |
90 | $urlInformation = $this->sitemapProvider->getUrlInformation($sitemap);
91 |
92 | if (isset($templates[$_format])) {
93 | return new Response($this->templating->render($templates[$_format], ['urls' => $urlInformation]));
94 | }
95 |
96 | return $this->createJsonResponse($urlInformation);
97 | }
98 |
99 | /**
100 | * @param array|UrlInformation[] $urls
101 | *
102 | * @return JsonResponse
103 | */
104 | private function createJsonResponse($urls)
105 | {
106 | $result = [];
107 |
108 | foreach ($urls as $url) {
109 | $result[] = $url->toArray();
110 | }
111 |
112 | return new JsonResponse($result);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Compiler/RegisterExtractorsPass.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class RegisterExtractorsPass implements CompilerPassInterface
26 | {
27 | /**
28 | * {@inheritdoc}
29 | *
30 | * @throws LogicException if a tagged service is not public
31 | */
32 | public function process(ContainerBuilder $container)
33 | {
34 | if (!$container->hasDefinition('cmf_seo.presentation')) {
35 | return;
36 | }
37 |
38 | $strategyDefinition = $container->getDefinition('cmf_seo.presentation');
39 | $taggedServices = $container->findTaggedServiceIds('cmf_seo.extractor');
40 |
41 | foreach ($taggedServices as $id => $attributes) {
42 | $definition = $container->getDefinition($id);
43 | if (!$definition->isPublic()) {
44 | throw new LogicException(sprintf('Strategy "%s" must be public.', $id));
45 | }
46 |
47 | $priority = 0;
48 | foreach ($attributes as $attribute) {
49 | if (isset($attribute['priority'])) {
50 | $priority = $attribute['priority'];
51 |
52 | break;
53 | }
54 | }
55 |
56 | $strategyDefinition->addMethodCall('addExtractor', [
57 | new Reference($id),
58 | $priority,
59 | ]);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Compiler/RegisterSuggestionProviderPass.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class RegisterSuggestionProviderPass implements CompilerPassInterface
23 | {
24 | /**
25 | * {@inheritdoc}
26 | *
27 | * @throws LogicException if a tagged service is not public
28 | */
29 | public function process(ContainerBuilder $container)
30 | {
31 | if (!$container->hasDefinition('cmf_seo.error.suggestion_provider.controller')) {
32 | return;
33 | }
34 |
35 | $presentationDefinition = $container->getDefinition('cmf_seo.error.suggestion_provider.controller');
36 | $taggedServices = $container->findTaggedServiceIds('cmf_seo.suggestion_provider');
37 | $provider = [];
38 | foreach ($taggedServices as $id => $attributes) {
39 | $definition = $container->getDefinition($id);
40 | if (!$definition->isPublic()) {
41 | throw new LogicException(sprintf('Matcher "%s" must be public.', $id));
42 | }
43 |
44 | $group = null;
45 | foreach ($attributes as $attribute) {
46 | if (isset($attribute['group'])) {
47 | $group = $attribute['group'];
48 |
49 | break;
50 | }
51 | }
52 | $group = $group ?: 'default';
53 | $provider[] = ['provider' => new Reference($id), 'group' => $group];
54 | }
55 |
56 | $presentationDefinition->replaceArgument(4, $provider);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/DependencyInjection/ConfigValues.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class ConfigValues
24 | {
25 | /**
26 | * Specific translation domain to use for title and description.
27 | *
28 | * @var string
29 | */
30 | private $translationDomain;
31 |
32 | /**
33 | * The title translation key. The extracted title will be passed to the
34 | * translator as %content_title%.
35 | *
36 | * @var string
37 | */
38 | private $title;
39 |
40 | /**
41 | * The description translation key. The extracted description will be
42 | * passed to the translator as %content_description%.
43 | *
44 | * @var string
45 | */
46 | private $description;
47 |
48 | /**
49 | * The original URL behaviour decides on how to handle content with several URLs.
50 | *
51 | * This value needs to be one of the ORIGINAL_URL_* constants in SeoPresentation.
52 | *
53 | * @var string
54 | */
55 | private $originalUrlBehaviour;
56 |
57 | /**
58 | * @param string $description
59 | */
60 | public function setDescription($description)
61 | {
62 | $this->description = $description;
63 | }
64 |
65 | /**
66 | * @return string
67 | */
68 | public function getDescription()
69 | {
70 | return $this->description;
71 | }
72 |
73 | /**
74 | * @param string $behaviour one of the constants from SeoPresentation
75 | *
76 | * @throws ExtractorStrategyException if $behaviour is not supported
77 | */
78 | public function setOriginalUrlBehaviour($behaviour)
79 | {
80 | if (!in_array($behaviour, SeoPresentation::$originalUrlBehaviours)) {
81 | throw new ExtractorStrategyException(
82 | sprintf('Behaviour "%s" not supported by SeoPresentation.', $behaviour)
83 | );
84 | }
85 | $this->originalUrlBehaviour = $behaviour;
86 | }
87 |
88 | /**
89 | * @return string
90 | */
91 | public function getOriginalUrlBehaviour()
92 | {
93 | return $this->originalUrlBehaviour;
94 | }
95 |
96 | /**
97 | * @param string $title
98 | */
99 | public function setTitle($title)
100 | {
101 | $this->title = $title;
102 | }
103 |
104 | /**
105 | * @return string
106 | */
107 | public function getTitle()
108 | {
109 | return $this->title;
110 | }
111 |
112 | /**
113 | * @param string $translationDomain
114 | */
115 | public function setTranslationDomain($translationDomain)
116 | {
117 | $this->translationDomain = $translationDomain;
118 | }
119 |
120 | /**
121 | * @return string
122 | */
123 | public function getTranslationDomain()
124 | {
125 | return $this->translationDomain;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Doctrine/Phpcr/BaseSuggestionProvider.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | abstract class BaseSuggestionProvider implements SuggestionProviderInterface
26 | {
27 | /**
28 | * @var ManagerRegistry
29 | */
30 | protected $managerRegistry;
31 |
32 | /**
33 | * By concatenating the routeBasePaths and the url
34 | * we will get the absolute path a route document
35 | * should be persisted with.
36 | *
37 | * @var array
38 | */
39 | protected $routeBasePaths;
40 |
41 | /**
42 | * @param ManagerRegistry $managerRegistry
43 | * @param array $routeBasePath
44 | */
45 | public function __construct(ManagerRegistry $managerRegistry, $routeBasePaths)
46 | {
47 | $this->managerRegistry = $managerRegistry;
48 | $this->routeBasePaths = (array) $routeBasePaths;
49 | }
50 |
51 | /**
52 | * @param string $class
53 | *
54 | * @return null|DocumentManager
55 | */
56 | public function getManagerForClass($class)
57 | {
58 | return $this->managerRegistry->getManagerForClass($class);
59 | }
60 |
61 | /**
62 | * Finds the parent route document by concatenating the basepaths with the
63 | * requested path.
64 | *
65 | * @return null|object
66 | */
67 | protected function findParentRoute($requestedPath)
68 | {
69 | $manager = $this->getManagerForClass('Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route');
70 | $parentPaths = [];
71 |
72 | foreach ($this->routeBasePaths as $basepath) {
73 | $parentPaths[] = PathHelper::getParentPath($basepath.$requestedPath);
74 | }
75 |
76 | $parentRoutes = $manager->findMany(null, $parentPaths);
77 | if (0 === count($parentRoutes)) {
78 | return;
79 | }
80 |
81 | return $parentRoutes->first();
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Doctrine/Phpcr/ParentSuggestionProvider.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class ParentSuggestionProvider extends BaseSuggestionProvider
23 | {
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function create(Request $request)
28 | {
29 | $routes = [];
30 | $parentRoute = $this->findParentRoute($request->getPathInfo());
31 |
32 | if (null === $parentRoute) {
33 | return [];
34 | }
35 |
36 | if ($parentRoute instanceof Route) {
37 | $routes[$parentRoute->getName()] = $parentRoute;
38 | }
39 |
40 | return $routes;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Doctrine/Phpcr/SeoMetadata.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class SeoMetadata extends SeoMetadataModel implements HierarchyInterface, TranslatableInterface
22 | {
23 | /**
24 | * @var string
25 | */
26 | protected $name;
27 |
28 | /**
29 | * @var object
30 | */
31 | protected $parentDocument;
32 |
33 | /**
34 | * @param string $name
35 | *
36 | * @return SeoMetadata
37 | */
38 | public function setName($name)
39 | {
40 | $this->name = $name;
41 |
42 | return $this;
43 | }
44 |
45 | /**
46 | * @return string
47 | */
48 | public function getName()
49 | {
50 | return $this->name;
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | public function setParentDocument($parentDocument)
57 | {
58 | $this->parentDocument = $parentDocument;
59 |
60 | return $this;
61 | }
62 |
63 | /**
64 | * {@inheritdoc}
65 | */
66 | public function getParentDocument()
67 | {
68 | return $this->parentDocument;
69 | }
70 |
71 | /**
72 | * @deprecated For compatibility with Doctrine PHPCR ODM 1.4, to be removed in version 3.0.
73 | * @see setParentDocument
74 | */
75 | public function setParent($parent)
76 | {
77 | @trigger_error('The '.__METHOD__.'() method is deprecated and will be removed in version 3.0. Use setParentDocument() instead.', E_USER_DEPRECATED);
78 |
79 | return $this->setParentDocument($parent);
80 | }
81 |
82 | /**
83 | * @deprecated For compatibility with Doctrine PHPCR ODM 1.4, to be removed in version 3.0.
84 | * @see getParentDocument
85 | */
86 | public function getParent()
87 | {
88 | @trigger_error('The '.__METHOD__.'() method is deprecated and will be removed in version 3.0. Use getParentDocument() instead.', E_USER_DEPRECATED);
89 |
90 | return $this->getParentDocument();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Doctrine/Phpcr/SiblingSuggestionProvider.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class SiblingSuggestionProvider extends BaseSuggestionProvider
24 | {
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function create(Request $request)
29 | {
30 | $parentRoute = $this->findParentRoute($request->getPathInfo());
31 |
32 | if (null === $parentRoute) {
33 | return [];
34 | }
35 |
36 | $routes = [];
37 | $manager = $this->getManagerForClass('Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route');
38 | $childRoutes = $manager->getChildren($parentRoute);
39 |
40 | foreach ($childRoutes->toArray() as $childRoute) {
41 | if ($childRoute instanceof Route) {
42 | $routes[$childRoute->getName()] = $childRoute;
43 | }
44 | }
45 |
46 | return $routes;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Doctrine/Phpcr/SitemapDocumentProvider.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class SitemapDocumentProvider implements LoaderInterface
24 | {
25 | /**
26 | * @var DocumentManager
27 | */
28 | private $manager;
29 |
30 | /**
31 | * @param DocumentManager $manager
32 | */
33 | public function __construct(DocumentManager $manager)
34 | {
35 | $this->manager = $manager;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}.
40 | */
41 | public function load($sitemap)
42 | {
43 | // todo rewrite query when https://github.com/symfony-cmf/CoreBundle/issues/126 is ready
44 | $documentsCollection = $this->manager->createQuery(
45 | 'SELECT * FROM [nt:unstructured] WHERE (visible_for_sitemap = true) ORDER BY [jcr:path]',
46 | QueryInterface::JCR_SQL2
47 | )->execute();
48 |
49 | $documents = [];
50 | // the chain provider does not like collections as we array_merge in there
51 | foreach ($documentsCollection as $document) {
52 | $documents[] = $document;
53 | }
54 |
55 | return $documents;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/EventListener/ContentListener.php:
--------------------------------------------------------------------------------
1 |
27 | */
28 | class ContentListener
29 | {
30 | /**
31 | * @var SeoPresentationInterface
32 | */
33 | private $seoPresentation;
34 |
35 | /**
36 | * @var string The key to look up the content in the request attributes
37 | */
38 | private $requestKey;
39 |
40 | /**
41 | * @var AlternateLocaleProviderInterface|null
42 | */
43 | private $alternateLocaleProvider;
44 |
45 | /**
46 | * @param SeoPresentationInterface $seoPage service Handling SEO information
47 | * @param string $requestKey the key to look up the content in the request attributes
48 | */
49 | public function __construct(SeoPresentationInterface $seoPage, $requestKey)
50 | {
51 | $this->seoPresentation = $seoPage;
52 | $this->requestKey = $requestKey;
53 | }
54 |
55 | /**
56 | * @param GetResponseEvent $event
57 | */
58 | public function onKernelRequest(GetResponseEvent $event)
59 | {
60 | if ($event->getRequest()->attributes->has($this->requestKey)) {
61 | $content = $event->getRequest()->attributes->get($this->requestKey);
62 | $this->seoPresentation->updateSeoPage($content);
63 |
64 | // look if the strategy is redirectResponse and if there is a route to redirectResponse to
65 | $response = $this->seoPresentation->getRedirectResponse();
66 | if (false !== $response && $this->canBeRedirected($event->getRequest(), $response)) {
67 | $event->setResponse($response);
68 | }
69 |
70 | if (null !== $this->alternateLocaleProvider) {
71 | $this->seoPresentation->updateAlternateLocales(
72 | $this->alternateLocaleProvider->createForContent($content)
73 | );
74 | }
75 | }
76 | }
77 |
78 | protected function canBeRedirected(Request $request, RedirectResponse $response)
79 | {
80 | $targetRequest = Request::create($response->getTargetUrl());
81 | $stripUrl = function ($path) {
82 | return preg_replace('/#.+$/', '', $path);
83 | };
84 | $targetPath = $stripUrl($targetRequest->getBaseUrl().$targetRequest->getPathInfo());
85 | $currentPath = $stripUrl($request->getBaseUrl().$request->getPathInfo());
86 |
87 | return $targetPath !== $currentPath;
88 | }
89 |
90 | /**
91 | * @param AlternateLocaleProviderInterface $alternateLocaleProvider
92 | */
93 | public function setAlternateLocaleProvider($alternateLocaleProvider)
94 | {
95 | $this->alternateLocaleProvider = $alternateLocaleProvider;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/EventListener/LanguageListener.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class LanguageListener implements EventSubscriberInterface
24 | {
25 | public static function getSubscribedEvents()
26 | {
27 | return [
28 | KernelEvents::RESPONSE => 'onKernelResponse',
29 | ];
30 | }
31 |
32 | public function onKernelResponse(FilterResponseEvent $event)
33 | {
34 | if ($event->getResponse()->headers->has('Content-Language')) {
35 | return;
36 | }
37 |
38 | $locale = $event->getRequest()->getLocale();
39 | $language = current(explode('_', $locale, 2));
40 | $event->getResponse()->headers->set('Content-Language', $language);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Exception/ExtractorStrategyException.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class ExtractorStrategyException extends \RuntimeException
20 | {
21 | }
22 |
--------------------------------------------------------------------------------
/src/Exception/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class InvalidArgumentException extends \RuntimeException
18 | {
19 | }
20 |
--------------------------------------------------------------------------------
/src/Extractor/DescriptionExtractor.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class DescriptionExtractor implements ExtractorInterface
23 | {
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function supports($content)
28 | {
29 | return $content instanceof DescriptionReadInterface;
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | *
35 | * @param DescriptionReadInterface $content
36 | */
37 | public function updateMetadata($content, SeoMetadataInterface $seoMetadata)
38 | {
39 | $seoMetadata->setMetaDescription($content->getSeoDescription());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Extractor/DescriptionReadInterface.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | interface DescriptionReadInterface
25 | {
26 | /**
27 | * Provide a description of this page to be used in SEO context.
28 | *
29 | * @return string
30 | */
31 | public function getSeoDescription();
32 | }
33 |
--------------------------------------------------------------------------------
/src/Extractor/ExtractorInterface.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | interface ExtractorInterface
22 | {
23 | /**
24 | * Check whether the strategy supports this content.
25 | *
26 | * The decision could be based on the content implementing
27 | * an interface or being instance of a specific class,
28 | * or introspection to see if a certain method exists.
29 | *
30 | * @param object $content
31 | *
32 | * @return bool
33 | */
34 | public function supports($content);
35 |
36 | /**
37 | * Update the metadata with information from this content.
38 | *
39 | * It is up to the strategy to check if certain fields
40 | * are already set by previous strategies and decide on a merge strategy.
41 | *
42 | * This method should only be called if supports returned true.
43 | *
44 | * @param object $content
45 | * @param SeoMetadataInterface $seoMetadata
46 | */
47 | public function updateMetadata($content, SeoMetadataInterface $seoMetadata);
48 | }
49 |
--------------------------------------------------------------------------------
/src/Extractor/ExtrasExtractor.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class ExtrasExtractor implements ExtractorInterface
24 | {
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function supports($content)
29 | {
30 | return $content instanceof ExtrasReadInterface;
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | *
36 | * @param ExtrasReadInterface $content
37 | */
38 | public function updateMetadata($content, SeoMetadataInterface $seoMetadata)
39 | {
40 | $allowedTypesMethodMapping = [
41 | 'property' => 'addExtraProperty',
42 | 'name' => 'addExtraName',
43 | 'http-equiv' => 'addExtraHttp',
44 | ];
45 |
46 | $contentExtras = $content->getSeoExtras();
47 |
48 | foreach ($contentExtras as $type => $extras) {
49 | if (!array_key_exists($type, $allowedTypesMethodMapping)) {
50 | throw new InvalidArgumentException(
51 | printf(
52 | 'Extras type %s not in the list of allowed ones %s.',
53 | $type,
54 | implode(', ', $allowedTypesMethodMapping)
55 | )
56 | );
57 | }
58 |
59 | foreach ($extras as $key => $value) {
60 | $seoMetadata->{$allowedTypesMethodMapping[$type]}($key, $value);
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Extractor/ExtrasReadInterface.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | interface ExtrasReadInterface
26 | {
27 | /**
28 | * Provides a list of extras as key-values pairs
29 | * for this page's SEO context and meta type property.
30 | *
31 | * Different types should be grouped in different arrays.
32 | *
33 | * Example return:
34 | * array(
35 | * 'property' => array('og:title' => 'Extra Title'),
36 | * 'name' => array('robots' => 'index, follow'),
37 | * 'http-equiv' => array('Content-Type' => 'text/html; charset=utf-8'),
38 | * )
39 | *
40 | * @return array
41 | */
42 | public function getSeoExtras();
43 | }
44 |
--------------------------------------------------------------------------------
/src/Extractor/KeywordsExtractor.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class KeywordsExtractor implements ExtractorInterface
23 | {
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function supports($object)
28 | {
29 | return $object instanceof KeywordsReadInterface;
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | *
35 | * @param KeywordsReadInterface $content
36 | */
37 | public function updateMetadata($content, SeoMetadataInterface $seoMetadata)
38 | {
39 | $keywords = $content->getSeoKeywords();
40 | if (is_array($keywords)) {
41 | $keywords = implode(', ', $keywords);
42 | }
43 |
44 | $seoMetadata->setMetaKeywords($keywords);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Extractor/KeywordsReadInterface.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | interface KeywordsReadInterface
25 | {
26 | /**
27 | * Provides a list of keywords for this page to be
28 | * used in SEO context.
29 | *
30 | * @return string|array
31 | */
32 | public function getSeoKeywords();
33 | }
34 |
--------------------------------------------------------------------------------
/src/Extractor/OriginalRouteExtractor.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class OriginalRouteExtractor implements ExtractorInterface
26 | {
27 | /**
28 | * @var UrlGeneratorInterface
29 | */
30 | private $urlGenerator;
31 |
32 | public function __construct(UrlGeneratorInterface $urlGenerator)
33 | {
34 | $this->urlGenerator = $urlGenerator;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | public function supports($content)
41 | {
42 | return $content instanceof OriginalRouteReadInterface;
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | *
48 | * @param OriginalRouteReadInterface $content
49 | */
50 | public function updateMetadata($content, SeoMetadataInterface $seoMetadata)
51 | {
52 | $route = $content->getSeoOriginalRoute();
53 |
54 | try {
55 | $seoMetadata->setOriginalUrl($this->urlGenerator->generate($route));
56 | } catch (RouteNotFoundException $e) {
57 | throw new ExtractorStrategyException('Unable to create a url.', 0, $e);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Extractor/OriginalRouteReadInterface.php:
--------------------------------------------------------------------------------
1 |
25 | */
26 | interface OriginalRouteReadInterface
27 | {
28 | /**
29 | * Returns something that can be used to generate an absolute URL.
30 | *
31 | * This may be a Symfony route name or - when using the Symfony CMF
32 | * DynamicRouter - a Route object.
33 | *
34 | * @return Route|string
35 | */
36 | public function getSeoOriginalRoute();
37 | }
38 |
--------------------------------------------------------------------------------
/src/Extractor/OriginalUrlExtractor.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class OriginalUrlExtractor implements ExtractorInterface
24 | {
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function supports($content)
29 | {
30 | return $content instanceof OriginalUrlReadInterface;
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function updateMetadata($content, SeoMetadataInterface $seoMetadata)
37 | {
38 | $seoMetadata->setOriginalUrl($content->getSeoOriginalUrl());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Extractor/OriginalUrlReadInterface.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | interface OriginalUrlReadInterface
24 | {
25 | /**
26 | * The method returns the absolute URL as a string to redirect to or set to
27 | * the canonical link.
28 | *
29 | * @return string an absolute URL
30 | */
31 | public function getSeoOriginalUrl();
32 | }
33 |
--------------------------------------------------------------------------------
/src/Extractor/TitleExtractor.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class TitleExtractor implements ExtractorInterface
23 | {
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function supports($content)
28 | {
29 | return $content instanceof TitleReadInterface;
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | *
35 | * @param TitleReadInterface $content
36 | */
37 | public function updateMetadata($content, SeoMetadataInterface $seoMetadata)
38 | {
39 | $seoMetadata->setTitle($content->getSeoTitle());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Extractor/TitleReadExtractor.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class TitleReadExtractor implements ExtractorInterface
22 | {
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function supports($content)
27 | {
28 | return method_exists($content, 'getTitle');
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public function updateMetadata($content, SeoMetadataInterface $seoMetadata)
35 | {
36 | if (!$seoMetadata->getTitle()) {
37 | $seoMetadata->setTitle($content->getTitle());
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Extractor/TitleReadInterface.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | interface TitleReadInterface
24 | {
25 | /**
26 | * Provides a title of this page to be used in SEO context.
27 | *
28 | * @return string
29 | */
30 | public function getSeoTitle();
31 | }
32 |
--------------------------------------------------------------------------------
/src/Matcher/ExclusionMatcher.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class ExclusionMatcher implements RequestMatcherInterface
23 | {
24 | /**
25 | * @var RequestMatcherInterface[] a list of rule matchers
26 | */
27 | private $matchersMap = [];
28 |
29 | /**
30 | * @param RequestMatcherInterface $ruleMatcher
31 | */
32 | public function addRequestMatcher(RequestMatcherInterface $ruleMatcher)
33 | {
34 | $this->matchersMap[] = $ruleMatcher;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | public function matches(Request $request)
41 | {
42 | foreach ($this->matchersMap as $matcher) {
43 | if ($matcher->matches($request)) {
44 | return true;
45 | }
46 | }
47 |
48 | return false;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Model/AlternateLocale.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class AlternateLocale
20 | {
21 | const REL = 'alternate';
22 |
23 | /**
24 | * @var string the absolute url for that locale
25 | */
26 | public $href;
27 |
28 | /**
29 | * @var string The locale/language in the following formats: de, de-DE
30 | */
31 | public $hrefLocale;
32 |
33 | public function __construct($href, $hrefLocale)
34 | {
35 | $this->href = $href;
36 | $this->hrefLocale = $hrefLocale;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Model/AlternateLocaleCollection.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class AlternateLocaleCollection extends ArrayCollection
22 | {
23 | }
24 |
--------------------------------------------------------------------------------
/src/Model/SeoMetadataInterface.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | interface SeoMetadataInterface
20 | {
21 | /**
22 | * Updates the description.
23 | *
24 | * @param string $metaDescription
25 | */
26 | public function setMetaDescription($metaDescription);
27 |
28 | /**
29 | * Gets the description for the meta tag.
30 | *
31 | * @return string
32 | */
33 | public function getMetaDescription();
34 |
35 | /**
36 | * Sets the keywords.
37 | *
38 | * @param string $metaKeywords
39 | */
40 | public function setMetaKeywords($metaKeywords);
41 |
42 | /**
43 | * Gets the Keywords for the meta tag.
44 | *
45 | * @return string
46 | */
47 | public function getMetaKeywords();
48 |
49 | /**
50 | * Sets the original URL for content that has several URLs.
51 | *
52 | * @param string $originalUrl
53 | */
54 | public function setOriginalUrl($originalUrl);
55 |
56 | /**
57 | * Gets the original URL of this content.
58 | *
59 | * This will be used for the canonical link or to redirect to the original
60 | * URL, depending on your settings.
61 | *
62 | * @return string
63 | */
64 | public function getOriginalUrl();
65 |
66 | /**
67 | * Sets the title.
68 | *
69 | * @param string $title
70 | */
71 | public function setTitle($title);
72 |
73 | /**
74 | * Gets the title.
75 | *
76 | * @return string
77 | */
78 | public function getTitle();
79 |
80 | /**
81 | * @param array|\Traversable|KeyValueContainer
82 | */
83 | public function setExtraProperties($extraProperties);
84 |
85 | /**
86 | * @param array|\Traversable|KeyValueContainer
87 | */
88 | public function setExtraNames($extraNames);
89 |
90 | /**
91 | * @param array|\Traversable|KeyValueContainer
92 | */
93 | public function setExtraHttp($extraHttp);
94 |
95 | /**
96 | * @return array
97 | */
98 | public function getExtraProperties();
99 |
100 | /**
101 | * @return array
102 | */
103 | public function getExtraNames();
104 |
105 | /**
106 | * @return array
107 | */
108 | public function getExtraHttp();
109 |
110 | /**
111 | * Add a key-value pair for meta attribute property.
112 | *
113 | * @param string $key
114 | * @param string $value
115 | */
116 | public function addExtraProperty($key, $value);
117 |
118 | /**
119 | * Add a key-value pair for meta attribute name.
120 | *
121 | * @param string $key
122 | * @param string $value
123 | */
124 | public function addExtraName($key, $value);
125 |
126 | /**
127 | * Add a key-value pair for meta attribute http-equiv.
128 | *
129 | * @param string $key
130 | * @param string $value
131 | */
132 | public function addExtraHttp($key, $value);
133 | }
134 |
--------------------------------------------------------------------------------
/src/Resources/config/content-listener.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 | %cmf_seo.content_key%
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Resources/config/doctrine-model/SeoMetadata.orm.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Resources/config/doctrine-model/SeoMetadata.phpcr.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Resources/config/doctrine-phpcr/SeoMetadata.phpcr.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/Resources/config/extractors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
19 |
20 |
23 |
24 |
27 |
28 |
31 |
32 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/Resources/config/matcher_phpcr.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 | %cmf_routing.dynamic.persistence.phpcr.route_basepaths%
11 |
12 |
13 |
14 |
15 |
16 | %cmf_routing.dynamic.persistence.phpcr.route_basepaths%
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Resources/config/phpcr-alternate-locale.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Resources/config/phpcr-sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | %cmf_seo.persistence.phpcr.content_basepath%
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/Resources/config/routing/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 | cmf_seo.sitemap.controller:indexAction
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Resources/config/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 | %cmf_seo.title%
11 | %cmf_seo.description%
12 | %cmf_seo.translation_domain%
13 | %cmf_seo.original_route_pattern%
14 |
15 |
16 |
17 | %cmf_seo.form.data_class.seo_metadata%
18 | %cmf_seo.form.options%
19 |
20 |
21 |
22 |
23 | %kernel.cache_dir%
24 | cmf_seo
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | %kernel.debug%
42 |
43 | %cmf_seo.error.templates%
44 |
45 |
46 |
47 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/Resources/config/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 | %cmf_seo.sitemap.configurations%
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | %cmf_seo.sitemap.default_change_frequency%
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/Resources/meta/LICENSE:
--------------------------------------------------------------------------------
1 | Symfony CMF Seo Bundle
2 |
3 | The MIT License
4 |
5 | Copyright (c) 2011-2017 Symfony CMF
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in
15 | all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/src/Resources/views/Exception/error.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | An Error Occurred: {{ status_text }}
6 |
7 |
8 | Oops! An Error Occurred
9 | The server returned a "{{ status_code }} {{ status_text }}".
10 |
11 |
12 | Something is broken. Please let us know what you were doing when this error occurred.
13 | We will fix it as soon as possible. Sorry for any inconvenience caused.
14 |
15 |
16 | {% if best_matches not is empty %}
17 | Suggested pages
18 |
19 | {% for group, matches in best_matches %}
20 | {{ group|capitalize }}
21 |
26 | {% endfor %}
27 | {% endif %}
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/Resources/views/Sitemap/index.html.twig:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/Resources/views/Sitemap/index.xml.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% for url in urls %}
4 |
5 | {{ url.location }}
6 | {% if url.lastModification %}
7 | {{ url.lastModification }}
8 | {% endif %}
9 | {{ url.changeFrequency }}
10 | {% if url.alternateLocales is defined and url.alternateLocales|length > 0 %}
11 | {% for locale in url.alternateLocales %}
12 |
13 | {% endfor %}
14 | {% endif %}
15 |
16 | {% endfor %}
17 |
18 |
--------------------------------------------------------------------------------
/src/SeoAwareInterface.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | interface SeoAwareInterface
23 | {
24 | /**
25 | * Gets the SEO metadata for this content.
26 | *
27 | * @return SeoMetadataInterface
28 | */
29 | public function getSeoMetadata();
30 |
31 | /**
32 | * Sets the SEO metadata for this content.
33 | *
34 | * This method is used by a listener, which converts the metadata to a
35 | * plain array in order to persist it and converts it back when the content
36 | * is fetched.
37 | *
38 | * @param array|SeoMetadataInterface $metadata
39 | */
40 | public function setSeoMetadata($metadata);
41 | }
42 |
--------------------------------------------------------------------------------
/src/SeoAwareTrait.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | trait SeoAwareTrait
23 | {
24 | /**
25 | * @PHPCRODM\Child
26 | */
27 | private $seoMetadata;
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function getSeoMetadata()
33 | {
34 | return $this->seoMetadata;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | public function setSeoMetadata($metadata)
41 | {
42 | $this->seoMetadata = $metadata;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/SeoPresentationInterface.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | interface SeoPresentationInterface
24 | {
25 | /**
26 | * Updates the Sonata SeoPage service with the data retrieved from the $content.
27 | *
28 | * @param object $content the content to load data from
29 | */
30 | public function updateSeoPage($content);
31 |
32 | /**
33 | * Returns the redirect response if the bundle is configured to redirect to
34 | * the canonical URL and this content provided a canonical URL different
35 | * from the current URL. Returns false in all other cases.
36 | *
37 | * @return bool|RedirectResponse
38 | */
39 | public function getRedirectResponse();
40 |
41 | /**
42 | * Updates alternate locale information on the Sonata SeoPage service.
43 | *
44 | * @param AlternateLocaleCollection $collection
45 | */
46 | public function updateAlternateLocales(AlternateLocaleCollection $collection);
47 | }
48 |
--------------------------------------------------------------------------------
/src/Sitemap/AbstractChain.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | abstract class AbstractChain
20 | {
21 | /**
22 | * The list of entries by sitemap name and priority.
23 | *
24 | * @var array
25 | */
26 | private $items = [];
27 |
28 | /**
29 | * The list of default entries by priority.
30 | *
31 | * @var array
32 | */
33 | private $defaultItems = [];
34 |
35 | /**
36 | * Add an entry to the chain.
37 | *
38 | * An entry can be added to a specific sitemap or to all of them. The
39 | * higher the priority, the earlier the entry is in the chain. Sitemap
40 | * specific entries come before general entries of the same priority.
41 | *
42 | * @param object $item the entry
43 | * @param int $priority the higher the value, the earlier the item comes in the chain
44 | * @param string $sitemap Name of the sitemap to add the entry for. Null adds the entry to all sitemaps.
45 | */
46 | public function addItem($item, $priority = 0, $sitemap = null)
47 | {
48 | if ($sitemap) {
49 | if (!isset($this->items[$sitemap])) {
50 | $this->items[$sitemap] = [];
51 | }
52 | $entries = &$this->items[$sitemap];
53 | } else {
54 | $entries = &$this->defaultItems;
55 | }
56 |
57 | if (!isset($entries[$priority])) {
58 | $entries[$priority] = [];
59 | }
60 |
61 | $entries[$priority][] = $item;
62 | }
63 |
64 | /**
65 | * Get the sorted list of chain entries for the specified sitemap.
66 | *
67 | * @param string $sitemap Name of the sitemap
68 | *
69 | * @return object[] priority sorted list of chain entries
70 | */
71 | protected function getSortedEntriesForSitemap($sitemap)
72 | {
73 | $priorities = array_keys($this->defaultItems);
74 | if (isset($this->items[$sitemap])) {
75 | $priorities = array_unique(array_merge($priorities, array_keys($this->items[$sitemap])));
76 | }
77 |
78 | rsort($priorities);
79 |
80 | $sortedItems = [];
81 | foreach ($priorities as $priority) {
82 | if (isset($this->items[$sitemap][$priority])) {
83 | // never happens if the sitemap has no specific entries at all
84 | $sortedItems = array_merge($sortedItems, $this->items[$sitemap][$priority]);
85 | }
86 | if (isset($this->defaultItems[$priority])) {
87 | $sortedItems = array_merge($sortedItems, $this->defaultItems[$priority]);
88 | }
89 | }
90 |
91 | return $sortedItems;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Sitemap/AlternateLocalesGuesser.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class AlternateLocalesGuesser implements GuesserInterface
23 | {
24 | /**
25 | * @var AlternateLocaleProviderInterface
26 | */
27 | private $alternateLocaleProvider;
28 |
29 | public function __construct(AlternateLocaleProviderInterface $alternateLocaleProvider)
30 | {
31 | $this->alternateLocaleProvider = $alternateLocaleProvider;
32 | }
33 |
34 | /**
35 | * {@inheritdoc}.
36 | */
37 | public function guessValues(UrlInformation $urlInformation, $object, $sitemap)
38 | {
39 | if ($urlInformation->getAlternateLocales()) {
40 | return;
41 | }
42 |
43 | $collection = $this->alternateLocaleProvider->createForContent($object);
44 | $urlInformation->setAlternateLocales($collection->toArray());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Sitemap/DefaultChangeFrequencyGuesser.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class DefaultChangeFrequencyGuesser implements GuesserInterface
22 | {
23 | /**
24 | * @var string
25 | */
26 | private $defaultChangeFrequency;
27 |
28 | /**
29 | * @param string $defaultChangeFrequency
30 | *
31 | * @see UrlInformation::setChangeFrequency for information on this parameter.
32 | */
33 | public function __construct($defaultChangeFrequency)
34 | {
35 | $this->defaultChangeFrequency = $defaultChangeFrequency;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}.
40 | */
41 | public function guessValues(UrlInformation $urlInformation, $object, $sitemap)
42 | {
43 | if ($urlInformation->getChangeFrequency()) {
44 | return;
45 | }
46 |
47 | $urlInformation->setChangeFrequency($this->defaultChangeFrequency);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Sitemap/DepthGuesser.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | class DepthGuesser implements GuesserInterface
25 | {
26 | /**
27 | * @var ManagerRegistry
28 | */
29 | private $managerRegistry;
30 |
31 | /**
32 | * @var int the depth of the content base path as the depth offset
33 | */
34 | private $offset;
35 |
36 | /**
37 | * DepthGuesser constructor.
38 | *
39 | * @param ManagerRegistry $managerRegistry
40 | * @param $contentBasePath
41 | */
42 | public function __construct(ManagerRegistry $managerRegistry, $contentBasePath)
43 | {
44 | $this->managerRegistry = $managerRegistry;
45 | $this->offset = ('/' === $contentBasePath) ? 1 : substr_count($contentBasePath, '/') + 1;
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | public function guessValues(UrlInformation $urlInformation, $object, $sitemap)
52 | {
53 | if (null !== $urlInformation->getDepth()) {
54 | return;
55 | }
56 |
57 | $manager = $this->managerRegistry->getManagerForClass(ClassUtils::getRealClass(get_class($object)));
58 | if (!$manager instanceof DocumentManager) {
59 | return;
60 | }
61 |
62 | $node = $manager->getNodeForDocument($object);
63 | if (null === $node) {
64 | return;
65 | }
66 | $urlInformation->setDepth($node->getDepth() - $this->offset);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Sitemap/GuesserChain.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class GuesserChain extends AbstractChain
22 | {
23 | /**
24 | * {@inheritdoc}.
25 | */
26 | public function guessValues(UrlInformation $urlInformation, $object, $sitemap)
27 | {
28 | /** @var $guesser GuesserInterface */
29 | foreach ($this->getSortedEntriesForSitemap($sitemap) as $guesser) {
30 | $guesser->guessValues($urlInformation, $object, $sitemap);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Sitemap/GuesserInterface.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | interface GuesserInterface
25 | {
26 | /**
27 | * Updates UrlInformation with new values if they are not already set.
28 | *
29 | * @param UrlInformation $urlInformation the value object to update
30 | * @param object $object the sitemap element to get values from
31 | * @param string $sitemap name of the sitemap being built
32 | */
33 | public function guessValues(UrlInformation $urlInformation, $object, $sitemap);
34 | }
35 |
--------------------------------------------------------------------------------
/src/Sitemap/LastModifiedGuesser.php:
--------------------------------------------------------------------------------
1 |
25 | */
26 | class LastModifiedGuesser implements GuesserInterface
27 | {
28 | /**
29 | * @var ManagerRegistry
30 | */
31 | private $managerRegistry;
32 |
33 | /**
34 | * LastModifiedGuesser constructor.
35 | *
36 | * @param ManagerRegistry $manager
37 | */
38 | public function __construct(ManagerRegistry $manager)
39 | {
40 | $this->managerRegistry = $manager;
41 | }
42 |
43 | /**
44 | * Updates UrlInformation with new values if they are not already set.
45 | *
46 | * @param UrlInformation $urlInformation the value object to update
47 | * @param object $object the sitemap element to get values from
48 | * @param string $sitemap name of the sitemap being built
49 | */
50 | public function guessValues(UrlInformation $urlInformation, $object, $sitemap)
51 | {
52 | if (null !== $urlInformation->getLastModification()) {
53 | return;
54 | }
55 |
56 | $className = ClassUtils::getRealClass(get_class($object));
57 | $manager = $this->managerRegistry->getManagerForClass($className);
58 | if (!$manager instanceof DocumentManager) {
59 | return;
60 | }
61 |
62 | /** @var ClassMetadata $metadata */
63 | $metadata = $manager->getClassMetadata($className);
64 | $mixins = $metadata->getMixins();
65 |
66 | if (!in_array('mix:lastModified', $mixins)) {
67 | return;
68 | }
69 |
70 | $fieldName = $this->getFieldName($metadata);
71 | if (null === $fieldName) {
72 | return;
73 | }
74 |
75 | $urlInformation->setLastModification($metadata->getFieldValue($object, $fieldName));
76 | }
77 |
78 | private function getFieldName(ClassMetadata $metadata)
79 | {
80 | foreach ($metadata->getFieldNames() as $fieldName) {
81 | $field = $metadata->getFieldMapping($fieldName);
82 | if ('jcr:lastModified' === $field['property']) {
83 | return $fieldName;
84 | }
85 | }
86 |
87 | return;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Sitemap/LoaderChain.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class LoaderChain extends AbstractChain
20 | {
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function load($sitemap)
25 | {
26 | $documents = [];
27 |
28 | /** @var $loader LoaderInterface */
29 | foreach ($this->getSortedEntriesForSitemap($sitemap) as $loader) {
30 | $documents = array_merge($documents, $loader->load($sitemap));
31 | }
32 |
33 | return $documents;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Sitemap/LoaderInterface.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | interface LoaderInterface
22 | {
23 | /**
24 | * @param string $sitemap the name of the sitemap
25 | *
26 | * @return Route[]
27 | */
28 | public function load($sitemap);
29 | }
30 |
--------------------------------------------------------------------------------
/src/Sitemap/LocationGuesser.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | class LocationGuesser implements GuesserInterface
25 | {
26 | /**
27 | * @var UrlGeneratorInterface
28 | */
29 | private $urlGenerator;
30 |
31 | public function __construct(UrlGeneratorInterface $router)
32 | {
33 | $this->urlGenerator = $router;
34 | }
35 |
36 | /**
37 | * {@inheritdoc}.
38 | */
39 | public function guessValues(UrlInformation $urlInformation, $object, $sitemap)
40 | {
41 | if ($urlInformation->getLocation()) {
42 | return;
43 | }
44 |
45 | $urlInformation->setLocation($this->urlGenerator->generate($object, [], UrlGeneratorInterface::ABSOLUTE_URL));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Sitemap/PublishWorkflowVoter.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class PublishWorkflowVoter implements VoterInterface
24 | {
25 | /**
26 | * @var AuthorizationCheckerInterface
27 | */
28 | private $publishWorkflowChecker;
29 |
30 | public function __construct(AuthorizationCheckerInterface $publishWorkflowChecker)
31 | {
32 | $this->publishWorkflowChecker = $publishWorkflowChecker;
33 | }
34 |
35 | /**
36 | * {@inheritdoc}
37 | */
38 | public function exposeOnSitemap($content, $sitemap)
39 | {
40 | return $this->publishWorkflowChecker->isGranted(PublishWorkflowChecker::VIEW_ATTRIBUTE, $content);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Sitemap/SeoMetadataTitleGuesser.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class SeoMetadataTitleGuesser implements GuesserInterface
23 | {
24 | /**
25 | * @var SeoPresentation
26 | */
27 | private $seoPresentation;
28 |
29 | public function __construct(
30 | SeoPresentation $seoPresentation
31 | ) {
32 | $this->seoPresentation = $seoPresentation;
33 | }
34 |
35 | /**
36 | * {@inheritdoc}.
37 | */
38 | public function guessValues(UrlInformation $urlInformation, $object, $sitemap)
39 | {
40 | if ($urlInformation->getLabel()) {
41 | return;
42 | }
43 |
44 | $seoMetadata = $this->seoPresentation->getSeoMetadata($object);
45 | if ($seoMetadata->getTitle()) {
46 | $urlInformation->setLabel($seoMetadata->getTitle());
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Sitemap/SitemapAwareDocumentVoter.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class SitemapAwareDocumentVoter implements VoterInterface
23 | {
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function exposeOnSitemap($content, $sitemap)
28 | {
29 | if (!$content instanceof SitemapAwareInterface) {
30 | return true;
31 | }
32 |
33 | return $content->isVisibleInSitemap($sitemap);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Sitemap/UrlInformationProvider.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | class UrlInformationProvider
25 | {
26 | /**
27 | * @var LoaderChain
28 | */
29 | private $loader;
30 |
31 | /**
32 | * @var GuesserChain
33 | */
34 | private $guesser;
35 |
36 | /**
37 | * @var VoterChain
38 | */
39 | private $voter;
40 |
41 | public function __construct(
42 | LoaderChain $loader,
43 | VoterChain $voter,
44 | GuesserChain $guesser
45 | ) {
46 | $this->loader = $loader;
47 | $this->voter = $voter;
48 | $this->guesser = $guesser;
49 | }
50 |
51 | /**
52 | * Get the UrlInformation for the specified sitemap.
53 | *
54 | * @param string $sitemap Name of the sitemap to create, "default" if not specified
55 | *
56 | * @return UrlInformation[]
57 | */
58 | public function getUrlInformation($sitemap = 'default')
59 | {
60 | $urlInformationList = [];
61 |
62 | $contentObjects = $this->loader->load($sitemap);
63 | foreach ($contentObjects as $content) {
64 | if (!$this->voter->exposeOnSitemap($content, $sitemap)) {
65 | continue;
66 | }
67 | $urlInformation = new UrlInformation();
68 | $this->guesser->guessValues($urlInformation, $content, $sitemap);
69 | $urlInformationList[] = $urlInformation;
70 | }
71 |
72 | return $urlInformationList;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Sitemap/VoterChain.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class VoterChain extends AbstractChain
20 | {
21 | /**
22 | * Decide whether the voters want to expose the content.
23 | *
24 | * @param object $content The content to expose
25 | * @param string $sitemap Name of the sitemap
26 | *
27 | * @return bool whether to expose this content
28 | */
29 | public function exposeOnSitemap($content, $sitemap)
30 | {
31 | /** @var $voter VoterInterface */
32 | foreach ($this->getSortedEntriesForSitemap($sitemap) as $voter) {
33 | if (!$voter->exposeOnSitemap($content, $sitemap)) {
34 | return false;
35 | }
36 | }
37 |
38 | return true;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Sitemap/VoterInterface.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | interface VoterInterface
20 | {
21 | /**
22 | * Decide whether this content is visible on the specified sitemap.
23 | *
24 | * @param object $content the content object
25 | * @param string $sitemap name of the sitemap
26 | *
27 | * @return bool true if the content should be visible on the sitemap, false otherwise
28 | */
29 | public function exposeOnSitemap($content, $sitemap);
30 | }
31 |
--------------------------------------------------------------------------------
/src/SitemapAwareInterface.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | interface SitemapAwareInterface
21 | {
22 | /**
23 | * Decision whether a document should be visible
24 | * in sitemap or not.
25 | *
26 | * @param $sitemap
27 | *
28 | * @return bool
29 | */
30 | public function isVisibleInSitemap($sitemap);
31 | }
32 |
--------------------------------------------------------------------------------
/src/SuggestionProviderInterface.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | interface SuggestionProviderInterface
26 | {
27 | /**
28 | * Based on the current request this method creates a list
29 | * of suggestions as an array of routes.
30 | *
31 | * @param Request $request
32 | *
33 | * @return array|Route[]
34 | */
35 | public function create(Request $request);
36 | }
37 |
--------------------------------------------------------------------------------
/src/Twig/Extension/CmfSeoExtension.php:
--------------------------------------------------------------------------------
1 | seoPresentation = $seoPresentation;
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public function getFunctions()
35 | {
36 | return [
37 | new \Twig_SimpleFunction('cmf_seo_update_metadata', [$this, 'updateMetadata']),
38 | ];
39 | }
40 |
41 | /**
42 | * @see SeoPresentationInterface
43 | */
44 | public function updateMetadata($content)
45 | {
46 | $this->seoPresentation->updateSeoPage($content);
47 | }
48 |
49 | public function getName()
50 | {
51 | return 'cmf_seo';
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Controller/TestController.php:
--------------------------------------------------------------------------------
1 | [
22 | 'title' => $contentDocument->getTitle(),
23 | 'body' => $contentDocument->getBody(),
24 | ],
25 | ];
26 |
27 | return $this->render('::tests/index.html.twig', $params);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Document/AllStrategiesDocument.php:
--------------------------------------------------------------------------------
1 | seoMetadata = new SeoMetadata();
28 | }
29 |
30 | /**
31 | * Provide a description of this page to be used in SEO context.
32 | *
33 | * @return string
34 | */
35 | public function getSeoDescription()
36 | {
37 | return 'Test Description.';
38 | }
39 |
40 | /**
41 | * Provide the original url of this page to be used in SEO context.
42 | *
43 | * @return Route the original route
44 | */
45 | public function getSeoOriginalUrl()
46 | {
47 | return '/test-route';
48 | }
49 |
50 | /**
51 | * Provide a title of this page to be used in SEO context.
52 | *
53 | * @return string
54 | */
55 | public function getSeoTitle()
56 | {
57 | return 'Test title';
58 | }
59 |
60 | /**
61 | * To let a content be seo aware means in the SeoBundle to serve the SeoMetadata.
62 | * This SeoMetadata contains the information to fill some meta tags or has
63 | * the information of the original url of the content.
64 | *
65 | * @return SeoMetadata
66 | */
67 | public function getSeoMetadata()
68 | {
69 | return $this->seoMetadata;
70 | }
71 |
72 | public function setSeoMetadata($metadata)
73 | {
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Document/AlternateLocaleContent.php:
--------------------------------------------------------------------------------
1 | routes = new ArrayCollection();
52 | }
53 |
54 | /**
55 | * Add a route to the collection.
56 | *
57 | * @param Route $route
58 | */
59 | public function addRoute($route)
60 | {
61 | $this->routes->add($route);
62 | }
63 |
64 | /**
65 | * Remove a route from the collection.
66 | *
67 | * @param Route $route
68 | */
69 | public function removeRoute($route)
70 | {
71 | $this->routes->removeElement($route);
72 | }
73 |
74 | /**
75 | * Get the routes that point to this content.
76 | *
77 | * @return Route[] Route instances that point to this content
78 | */
79 | public function getRoutes()
80 | {
81 | return $this->routes;
82 | }
83 |
84 | /**
85 | * @return string|bool the locale of this model or false if
86 | * translations are disabled in this project
87 | */
88 | public function getLocale()
89 | {
90 | return $this->locale;
91 | }
92 |
93 | /**
94 | * @param string|bool $locale the local for this model, or false if
95 | * translations are disabled in this project
96 | */
97 | public function setLocale($locale)
98 | {
99 | $this->locale = $locale;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Document/ContentBase.php:
--------------------------------------------------------------------------------
1 | id;
64 | }
65 |
66 | /**
67 | * Gets the underlying PHPCR node of this content.
68 | *
69 | * @return NodeInterface
70 | */
71 | public function getNode()
72 | {
73 | return $this->node;
74 | }
75 |
76 | /**
77 | * Sets the parent document.
78 | *
79 | * @param object $parent the parent document
80 | */
81 | public function setParentDocument($parent)
82 | {
83 | $this->parentDocument = $parent;
84 |
85 | return $this;
86 | }
87 |
88 | /**
89 | * Gets the parent document.
90 | *
91 | * @return object
92 | */
93 | public function getParentDocument()
94 | {
95 | return $this->parentDocument;
96 | }
97 |
98 | /**
99 | * Sets the document name.
100 | *
101 | * @param string $name
102 | */
103 | public function setName($name)
104 | {
105 | $this->name = $name;
106 |
107 | return $this;
108 | }
109 |
110 | /**
111 | * Gets the document name.
112 | *
113 | * @return string
114 | */
115 | public function getName()
116 | {
117 | return $this->name;
118 | }
119 |
120 | /**
121 | * @return string
122 | */
123 | public function getTitle()
124 | {
125 | return $this->title;
126 | }
127 |
128 | /**
129 | * @param string $title
130 | */
131 | public function setTitle($title)
132 | {
133 | $this->title = $title;
134 |
135 | return $this;
136 | }
137 |
138 | /**
139 | * @return string
140 | */
141 | public function getBody()
142 | {
143 | return $this->body;
144 | }
145 |
146 | /**
147 | * @param string $body
148 | */
149 | public function setBody($body)
150 | {
151 | $this->body = $body;
152 |
153 | return $this;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Document/ContentWithExtractors.php:
--------------------------------------------------------------------------------
1 | getTitle();
33 | }
34 |
35 | /**
36 | * Provide a description of this page to be used in SEO context.
37 | *
38 | * @return string
39 | */
40 | public function getSeoDescription()
41 | {
42 | return substr($this->getBody(), 0, 200).' ...';
43 | }
44 |
45 | /**
46 | * The method returns the absolute url as a string to redirect to
47 | * or set to the canonical link.
48 | *
49 | * @return string
50 | */
51 | public function getSeoOriginalUrl()
52 | {
53 | return '/home';
54 | }
55 |
56 | /**
57 | * Provide a list of the content's keywords for this page to be
58 | * used in SEO context.
59 | *
60 | * @return string
61 | */
62 | public function getSeoKeywords()
63 | {
64 | return 'test, key';
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Document/SeoAwareContent.php:
--------------------------------------------------------------------------------
1 | seoMetadata;
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | public function setSeoMetadata($seoMetadata)
43 | {
44 | $this->seoMetadata = $seoMetadata;
45 |
46 | return $this;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Document/SitemapAwareContent.php:
--------------------------------------------------------------------------------
1 |
25 | */
26 | class SitemapAwareContent extends ContentBase implements RouteReferrersReadInterface, TranslatableInterface, SitemapAwareInterface
27 | {
28 | /**
29 | * @var string
30 | *
31 | * @PHPCRODM\Locale
32 | */
33 | protected $locale;
34 |
35 | /**
36 | * @var ArrayCollection|Route[]
37 | *
38 | * @PHPCRODM\Referrers(
39 | * referringDocument="Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route",
40 | * referencedBy="content"
41 | * )
42 | */
43 | protected $routes;
44 |
45 | /**
46 | * @var bool
47 | *
48 | * @PHPCRODM\Field(type="boolean",property="visible_for_sitemap")
49 | */
50 | private $isVisibleForSitemap;
51 |
52 | /**
53 | * @var string
54 | *
55 | * @PHPCRODM\Field(type="string",translated=true)
56 | */
57 | protected $title;
58 |
59 | public function __construct()
60 | {
61 | $this->routes = new ArrayCollection();
62 | }
63 |
64 | /**
65 | * @param string $sitemap
66 | *
67 | * @return bool
68 | */
69 | public function isVisibleInSitemap($sitemap)
70 | {
71 | return $this->isVisibleForSitemap;
72 | }
73 |
74 | /**
75 | * @param bool $isVisibleForSitemap
76 | *
77 | * @return SitemapAwareContent
78 | */
79 | public function setIsVisibleForSitemap($isVisibleForSitemap)
80 | {
81 | $this->isVisibleForSitemap = $isVisibleForSitemap;
82 |
83 | return $this;
84 | }
85 |
86 | /**
87 | * Add a route to the collection.
88 | *
89 | * @param Route $route
90 | */
91 | public function addRoute($route)
92 | {
93 | $this->routes->add($route);
94 | }
95 |
96 | /**
97 | * Remove a route from the collection.
98 | *
99 | * @param Route $route
100 | */
101 | public function removeRoute($route)
102 | {
103 | $this->routes->removeElement($route);
104 | }
105 |
106 | /**
107 | * Get the routes that point to this content.
108 | *
109 | * @return Route[] Route instances that point to this content
110 | */
111 | public function getRoutes()
112 | {
113 | return $this->routes;
114 | }
115 |
116 | /**
117 | * @return string|bool the locale of this model or false if
118 | * translations are disabled in this project
119 | */
120 | public function getLocale()
121 | {
122 | return $this->locale;
123 | }
124 |
125 | /**
126 | * @param string|bool $locale the local for this model, or false if
127 | * translations are disabled in this project
128 | */
129 | public function setLocale($locale)
130 | {
131 | $this->locale = $locale;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Document/SitemapAwareWithLastModifiedDateContent.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class SitemapAwareWithLastModifiedDateContent extends ContentBase implements RouteReferrersReadInterface, SitemapAwareInterface
26 | {
27 | /**
28 | * @var ArrayCollection|Route[]
29 | *
30 | * @PHPCRODM\Referrers(
31 | * referringDocument="Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route",
32 | * referencedBy="content"
33 | * )
34 | */
35 | protected $routes;
36 |
37 | /**
38 | * @var bool
39 | *
40 | * @PHPCRODM\Field(type="boolean",property="visible_for_sitemap")
41 | */
42 | private $isVisibleForSitemap;
43 |
44 | /**
45 | * @var \DateTime
46 | * @PHPCRODM\Field(type="date", property="jcr:lastModified")
47 | */
48 | private $lastModified;
49 |
50 | public function __construct()
51 | {
52 | $this->routes = new ArrayCollection();
53 | }
54 |
55 | /**
56 | * @param string $sitemap
57 | *
58 | * @return bool
59 | */
60 | public function isVisibleInSitemap($sitemap)
61 | {
62 | return $this->isVisibleForSitemap;
63 | }
64 |
65 | /**
66 | * @param bool $isVisibleForSitemap
67 | *
68 | * @return SitemapAwareContent
69 | */
70 | public function setIsVisibleForSitemap($isVisibleForSitemap)
71 | {
72 | $this->isVisibleForSitemap = $isVisibleForSitemap;
73 |
74 | return $this;
75 | }
76 |
77 | /**
78 | * {@inheritdoc}
79 | */
80 | public function getRoutes()
81 | {
82 | return $this->routes;
83 | }
84 |
85 | /**
86 | * @return \DateTime
87 | */
88 | public function getLastModified()
89 | {
90 | return $this->lastModified;
91 | }
92 |
93 | /**
94 | * @param \DateTime $lastModified
95 | *
96 | * @return SitemapAwareWithLastModifiedDateContent
97 | */
98 | public function setLastModified($lastModified)
99 | {
100 | $this->lastModified = $lastModified;
101 |
102 | return $this;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Entity/SeoAwareOrmContent.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class SeoAwareOrmContent implements SeoAwareInterface
23 | {
24 | /**
25 | * @ORM\Id()
26 | * @ORM\Column(type="integer")
27 | * @ORM\GeneratedValue(strategy="AUTO")
28 | */
29 | private $id;
30 |
31 | /**
32 | * @ORM\Column(type="string")
33 | */
34 | private $title;
35 |
36 | /**
37 | * @ORM\Column(type="text")
38 | */
39 | private $body;
40 |
41 | /**
42 | * @ORM\Column(type="object")
43 | */
44 | private $seoMetadata;
45 |
46 | /**
47 | * @param mixed $body
48 | */
49 | public function setBody($body)
50 | {
51 | $this->body = $body;
52 |
53 | return $this;
54 | }
55 |
56 | /**
57 | * @return mixed
58 | */
59 | public function getBody()
60 | {
61 | return $this->body;
62 | }
63 |
64 | /**
65 | * @param mixed $id
66 | */
67 | public function setId($id)
68 | {
69 | $this->id = $id;
70 |
71 | return $this;
72 | }
73 |
74 | /**
75 | * @return mixed
76 | */
77 | public function getId()
78 | {
79 | return $this->id;
80 | }
81 |
82 | /**
83 | * @param mixed $title
84 | */
85 | public function setTitle($title)
86 | {
87 | $this->title = $title;
88 |
89 | return $this;
90 | }
91 |
92 | /**
93 | * @return mixed
94 | */
95 | public function getTitle()
96 | {
97 | return $this->title;
98 | }
99 |
100 | /**
101 | * {@inheritdoc}
102 | */
103 | public function getSeoMetadata()
104 | {
105 | return $this->seoMetadata;
106 | }
107 |
108 | /**
109 | * {@inheritdoc}
110 | */
111 | public function setSeoMetadata($metadata)
112 | {
113 | $this->seoMetadata = $metadata;
114 |
115 | return $this;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Kernel.php:
--------------------------------------------------------------------------------
1 | requireBundleSet('default');
22 |
23 | if ('phpcr' === $this->environment) {
24 | $this->requireBundleSets(['phpcr_odm']);
25 | } elseif ('orm' === $this->environment) {
26 | $this->requireBundleSet('doctrine_orm');
27 | }
28 |
29 | $this->registerConfiguredBundles();
30 | }
31 |
32 | public function registerContainerConfiguration(LoaderInterface $loader)
33 | {
34 | $loader->load(__DIR__.'/config/config.php');
35 | $loader->load(__DIR__.'/config/config_'.$this->environment.'.php');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Model/SeoAwareOrmContent.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class SeoAwareOrmContent implements SeoAwareInterface
23 | {
24 | /**
25 | * @ORM\Id()
26 | * @ORM\Column(type="integer")
27 | */
28 | private $id;
29 |
30 | /**
31 | * @ORM\String(type="string")
32 | */
33 | private $title;
34 |
35 | /**
36 | * @ORM\Column(type="text")
37 | */
38 | private $body;
39 |
40 | /**
41 | * @ORM\Column(type="object")
42 | */
43 | private $seoMetadata;
44 |
45 | /**
46 | * @param mixed $body
47 | */
48 | public function setBody($body)
49 | {
50 | $this->body = $body;
51 | }
52 |
53 | /**
54 | * @return mixed
55 | */
56 | public function getBody()
57 | {
58 | return $this->body;
59 | }
60 |
61 | /**
62 | * @param mixed $id
63 | */
64 | public function setId($id)
65 | {
66 | $this->id = $id;
67 | }
68 |
69 | /**
70 | * @return mixed
71 | */
72 | public function getId()
73 | {
74 | return $this->id;
75 | }
76 |
77 | /**
78 | * @param mixed $title
79 | */
80 | public function setTitle($title)
81 | {
82 | $this->title = $title;
83 | }
84 |
85 | /**
86 | * @return mixed
87 | */
88 | public function getTitle()
89 | {
90 | return $this->title;
91 | }
92 |
93 | /**
94 | * {@inheritdoc}
95 | */
96 | public function getSeoMetadata()
97 | {
98 | return $this->seoMetadata;
99 | }
100 |
101 | /**
102 | * {@inheritdoc}
103 | */
104 | public function setSeoMetadata($metadata)
105 | {
106 | $this->seoMetadata = $metadata;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Resources/views/base.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {% block seo_head %}
7 | {% block title %}{{ cmfMainContent.title|default('CMF Sandbox')|striptags }}{% endblock %}
8 | {% endblock %}
9 |
10 |
11 |
12 |
13 | {% block content %}
14 | {% endblock %}
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Resources/views/tests/exception.html.twig:
--------------------------------------------------------------------------------
1 | Exception-Test
2 |
3 |
4 | {{ status_code }} {{ status_text }} - {{ message }} - {{ exception.class|abbr_class }}
5 |
6 |
7 |
8 | {% for group, matchs in best_matches %}
9 | {% for match in matchs %}
10 | {{ group }} - {{ match.name }}
11 | {% endfor %}
12 | {% endfor %}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/Resources/views/tests/index.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "::base.html.twig" %}
2 |
3 | {% block title %}{{ cmfMainContent.title|striptags }}{% endblock %}
4 |
5 | {% block seo_head -%}
6 | {{ sonata_seo_title() }}
7 | {{ sonata_seo_metadatas() }}
8 | {{ sonata_seo_link_canonical() }}
9 | {{ sonata_seo_lang_alternates() }}
10 | {% endblock %}
11 |
12 | {% block content %}
13 |
14 |
This is a template used by the sandbox controller
15 |
{{ cmfMainContent.title }}
16 |
17 | {{ cmfMainContent.body|raw }}
18 |
19 |
20 | {{ parent() }}
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/config/bundles.php:
--------------------------------------------------------------------------------
1 | ['all' => true],
14 | Symfony\Cmf\Bundle\SeoBundle\CmfSeoBundle::class => ['all' => true],
15 | Symfony\Cmf\Bundle\RoutingBundle\CmfRoutingBundle::class => ['all' => true],
16 | Symfony\Cmf\Bundle\CoreBundle\CmfCoreBundle::class => ['all' => true],
17 | ];
18 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/config/cmf_seo.orm.yml:
--------------------------------------------------------------------------------
1 | cmf_seo:
2 | persistence: { orm: true }
3 |
4 | cmf_routing:
5 | dynamic:
6 | enabled: true
7 | persistence:
8 | orm:
9 | enabled: true
10 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/config/cmf_seo.phpcr.yml:
--------------------------------------------------------------------------------
1 | cmf_seo:
2 | persistence: { phpcr: true }
3 | alternate_locale: ~
4 |
5 | cmf_routing:
6 | dynamic:
7 | locales: [de, en]
8 | enabled: true
9 | persistence:
10 | phpcr:
11 | route_basepaths: [/test/routes]
12 | enabled: true
13 | cmf_core:
14 | persistence:
15 | phpcr:
16 | basepath: /test
17 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/config/cmf_seo.yml:
--------------------------------------------------------------------------------
1 | framework:
2 | translator: { enabled: true, fallback: en }
3 | serializer: true
4 |
5 | sonata_seo:
6 | page:
7 | metas:
8 | name:
9 | keywords: "testkey"
10 |
11 | cmf_seo:
12 | title: "Default | %%content_title%%"
13 | description: "Default description. %%content_description%%"
14 | original_route_pattern: canonical
15 | error:
16 | enable_parent_provider: true
17 | enable_sibling_provider: true
18 | templates:
19 | html: ":tests:exception.html.twig"
20 | exclusion_rules:
21 | - { path: 'excluded' }
22 | sitemap:
23 | defaults:
24 | default_change_frequency: never
25 | templates:
26 | xml: 'CmfSeoBundle:Sitemap:index.xml.twig'
27 | html: 'CmfSeoBundle:Sitemap:index.html.twig'
28 | configurations:
29 | sitemap: ~
30 | frequent:
31 | default_change_frequency: always
32 |
33 | cmf_routing:
34 | chain:
35 | routers_by_id:
36 | cmf_routing.dynamic_router: 20
37 | router.default: 100
38 |
39 | twig:
40 | exception_controller: cmf_seo.error.suggestion_provider.controller:listAction
41 |
42 | cmf_core:
43 | multilang:
44 | locales: [en, de]
45 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/config/config.php:
--------------------------------------------------------------------------------
1 | import(CMF_TEST_CONFIG_DIR.'/default.php');
13 | $loader->import(__DIR__.'/cmf_seo.yml');
14 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/config/config_orm.php:
--------------------------------------------------------------------------------
1 | setParameter('cmf_testing.bundle_fqn', 'Symfony\Cmf\Bundle\SeoBundle');
13 | $loader->import(CMF_TEST_CONFIG_DIR.'/doctrine_orm.php');
14 | $loader->import(__DIR__.'/cmf_seo.orm.yml');
15 |
16 | $container->loadFromExtension('doctrine', [
17 | 'orm' => [
18 | 'mappings' => [
19 | 'tests_fixtures' => [
20 | 'type' => 'annotation',
21 | 'prefix' => 'Symfony\Cmf\Bundle\SeoBundle\Tests\Fixtures\App\Entity',
22 | 'dir' => $container->getParameter('kernel.root_dir').'/Entity',
23 | 'is_bundle' => false,
24 | ],
25 | ],
26 | ],
27 | ]);
28 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/config/config_phpcr.php:
--------------------------------------------------------------------------------
1 | setParameter('cmf_testing.bundle_fqn', 'Symfony\Cmf\Bundle\SeoBundle');
13 | $loader->import(CMF_TEST_CONFIG_DIR.'/phpcr_odm.php');
14 | $loader->import(__DIR__.'/cmf_seo.phpcr.yml');
15 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/config/routing.php:
--------------------------------------------------------------------------------
1 | addCollection($loader->import(__DIR__.'/../../../../src/Resources/config/routing/sitemap.xml'));
17 |
18 | return $collection;
19 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/config/tree_browser_1.x.yml:
--------------------------------------------------------------------------------
1 | cmf_tree:
2 | resource: .
3 | type: 'cmf_tree'
4 |
5 | fos_js_routing:
6 | resource: '@FOSJsRoutingBundle/Resources/config/routing/routing.xml'
7 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/config/tree_browser_2.x.yml:
--------------------------------------------------------------------------------
1 | _cmf_resource:
2 | resource: '@CmfResourceRestBundle/Resources/config/routing.yml'
3 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/translations/mesages.de.xliff:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | default_title
7 | %content_title% | Default title
8 |
9 |
10 | default_description
11 | Default description. %content_description%
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/Fixtures/App/translations/mesages.en.xliff:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | default_title
7 | %title% | Default title
8 |
9 |
10 | default_description
11 | Default description. %description%
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/Fixtures/fixtures/config/config.php:
--------------------------------------------------------------------------------
1 | loadFromExtension('cmf_seo', [
13 | 'title' => 'default_title',
14 | 'description' => 'description_key',
15 | ]);
16 |
--------------------------------------------------------------------------------
/tests/Fixtures/fixtures/config/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/Fixtures/fixtures/config/config.yml:
--------------------------------------------------------------------------------
1 | cmf_seo:
2 | title: default_title
3 | description: default_description
4 |
--------------------------------------------------------------------------------
/tests/Fixtures/fixtures/config/config1.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/Fixtures/fixtures/config/config2.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | test.xml
15 | test.html
16 | _none
17 | _none
18 | _none
19 |
20 |
21 |
22 |
23 |
24 |
30 |
31 |
--------------------------------------------------------------------------------
/tests/Fixtures/fixtures/config/config3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/Fixtures/fixtures/config/config4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | foo.html.twig
5 | foo.xml.twig
6 |
7 |
8 | test.xml
9 | test.html
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/Fixtures/fixtures/config/config_error.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/Fixtures/fixtures/config/config_sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | foo.html.twig
6 | foo.xml.twig
7 |
8 |
9 | test.xml
10 | test.html
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/Functional/Doctrine/Orm/SeoMetadataTest.php:
--------------------------------------------------------------------------------
1 | getDbManager('ORM')->getOm()))->purge();
24 | }
25 |
26 | protected function getKernelConfiguration()
27 | {
28 | return [
29 | 'environment' => 'orm',
30 | ];
31 | }
32 |
33 | protected function getEm()
34 | {
35 | return $this->db('ORM')->getOm();
36 | }
37 |
38 | public function testSeoMetadata()
39 | {
40 | $content = new SeoAwareOrmContent();
41 | $content
42 | ->setTitle('Seo Aware test')
43 | ->setBody('Content for SeoAware Test')
44 | ;
45 |
46 | $seoMetadata = new SeoMetadata();
47 | $seoMetadata
48 | ->setTitle('Seo Title')
49 | ->setMetaDescription('Seo Description')
50 | ->setMetaKeywords('Seo, Keys')
51 | ->setOriginalUrl('/test')
52 | ->setExtraProperties(['og:title' => 'Extra title'])
53 | ->setExtraNames(['robots' => 'index, follow'])
54 | ->setExtraHttp(['content-type' => 'text/html'])
55 | ;
56 |
57 | $content->setSeoMetadata($seoMetadata);
58 |
59 | $this->getEm()->persist($content);
60 | $this->getEm()->flush();
61 | $this->getEm()->clear();
62 |
63 | $content = $this->getEm()
64 | ->getRepository('Symfony\Cmf\Bundle\SeoBundle\Tests\Fixtures\App\Entity\SeoAwareOrmContent')
65 | ->findOneByTitle('Seo Aware test');
66 |
67 | $this->assertNotNull($content);
68 |
69 | $persistedSeoMetadata = $content->getSeoMetadata();
70 | $this->assertEquals($seoMetadata, $persistedSeoMetadata);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tests/Functional/Doctrine/Phpcr/SeoMetadataTest.php:
--------------------------------------------------------------------------------
1 | getDbManager('PHPCR')->getOm()))->purge();
25 | $this->db('PHPCR')->createTestNode();
26 | $this->dm = $this->db('PHPCR')->getOm();
27 | $this->base = $this->dm->find(null, '/test');
28 | }
29 |
30 | public function testSeoMetadataMapping()
31 | {
32 | $content = new SeoAwareContent();
33 | $content
34 | ->setTitle('Seo Aware test')
35 | ->setName('seo-aware')
36 | ->setParentDocument($this->dm->find(null, '/test'))
37 | ->setBody('Content for SeoAware Test')
38 | ;
39 |
40 | $seoMetadata = new SeoMetadata();
41 | $seoMetadata
42 | ->setTitle('Seo Title')
43 | ->setMetaDescription('Seo Description')
44 | ->setMetaKeywords('Seo, Keys')
45 | ->setOriginalUrl('/test')
46 | ->setExtraProperties(['og:title' => 'Extra title'])
47 | ->setExtraNames(['robots' => 'index, follow'])
48 | ->setExtraHttp(['content-type' => 'text/html'])
49 | ;
50 |
51 | $content->setSeoMetadata($seoMetadata);
52 |
53 | $this->dm->persist($content);
54 | $this->dm->flush();
55 | $this->dm->clear();
56 |
57 | $content = $this->dm->find(null, '/test/seo-aware');
58 |
59 | $this->assertNotNull($content);
60 |
61 | $persistedSeoMetadata = $content->getSeoMetadata();
62 | $this->assertEquals($seoMetadata->getTitle(), $persistedSeoMetadata->getTitle());
63 | $this->assertEquals($seoMetadata->getMetaDescription(), $persistedSeoMetadata->getMetaDescription());
64 | $this->assertEquals($seoMetadata->getMetaKeywords(), $persistedSeoMetadata->getMetaKeywords());
65 | $this->assertEquals($seoMetadata->getOriginalUrl(), $persistedSeoMetadata->getOriginalUrl());
66 | $this->assertEquals($seoMetadata->getExtraProperties(), $persistedSeoMetadata->getExtraProperties());
67 | $this->assertEquals($seoMetadata->getExtraNames(), $persistedSeoMetadata->getExtraNames());
68 | $this->assertEquals($seoMetadata->getExtraHttp(), $persistedSeoMetadata->getExtraHttp());
69 | }
70 |
71 | /**
72 | * @expectedException \Doctrine\ODM\PHPCR\Exception\OutOfBoundsException
73 | * @expectedExceptionMessage It cannot have children
74 | */
75 | public function testAddSeoMetadataChild()
76 | {
77 | $seoMetadata = new SeoMetadata();
78 | $seoMetadata->setName('seo-metadata');
79 | $seoMetadata->setParentDocument($this->dm->find(null, '/test'));
80 | $this->dm->persist($seoMetadata);
81 |
82 | $document = new Generic();
83 | $document->setParentDocument($seoMetadata);
84 | $document->setNodename('invalid');
85 | $this->dm->persist($document);
86 |
87 | $this->dm->flush();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/Functional/Doctrine/Phpcr/SitemapDocumentProviderTest.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class SitemapDocumentProviderTest extends BaseTestCase
21 | {
22 | /**
23 | * @var SitemapDocumentProvider
24 | */
25 | private $documentProvider;
26 |
27 | public function setUp()
28 | {
29 | $this->db('PHPCR')->createTestNode();
30 | $this->dm = $this->db('PHPCR')->getOm();
31 | $this->base = $this->dm->find(null, '/test');
32 | $this->db('PHPCR')->loadFixtures(
33 | ['Symfony\Cmf\Bundle\SeoBundle\Tests\Fixtures\App\DataFixtures\Phpcr\LoadSitemapData']
34 | );
35 |
36 | $this->documentProvider = new SitemapDocumentProvider($this->dm);
37 | }
38 |
39 | public function testDocumentOrder()
40 | {
41 | $documents = $this->documentProvider->load('default');
42 |
43 | $paths = [];
44 | foreach ($documents as $document) {
45 | $paths[] = $document->getId();
46 | }
47 |
48 | $this->assertEquals(
49 | [
50 | '/test/content/sitemap-aware',
51 | '/test/content/sitemap-aware-last-mod-date',
52 | '/test/content/sitemap-aware-non-publish',
53 | '/test/content/sitemap-aware-publish',
54 | ],
55 | $paths
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Unit/Cache/FileCacheTest.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class FileCacheTest extends \PHPUnit_Framework_TestCase
18 | {
19 | public function testThrowingExceptionForUnknownBaseDir()
20 | {
21 | $thrown = false;
22 | $baseDir = __DIR__.'/unknown';
23 | $dir = 'test';
24 |
25 | try {
26 | new FileCache($baseDir, $dir);
27 | } catch (\InvalidArgumentException $e) {
28 | $message = $e->getMessage();
29 | $thrown = true;
30 | }
31 |
32 | $this->assertTrue($thrown);
33 | $this->assertEquals($message, sprintf('The directory "%s" does not exist.', $baseDir));
34 | }
35 |
36 | public function testDirectoryCreation()
37 | {
38 | $baseDir = __DIR__.'/base';
39 | $dir = 'test';
40 | mkdir($baseDir);
41 | new FileCache($baseDir, $dir);
42 |
43 | $expectedDirectory = $baseDir.'/'.$dir;
44 | $this->assertTrue(is_dir($expectedDirectory));
45 |
46 | rmdir($expectedDirectory);
47 | rmdir($baseDir);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Unit/DependencyInjection/Compiler/RegisterExtractorsPassTest.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new RegisterExtractorsPass());
25 | }
26 |
27 | public function testRegistersServicesWithExtractorTagAndDefaultPriority()
28 | {
29 | $nonExtractorService = new Definition();
30 | $this->setDefinition('some_service', $nonExtractorService);
31 |
32 | $extractorService = new Definition();
33 | $extractorService->addTag('cmf_seo.extractor');
34 | $this->setDefinition('extractor_service', $extractorService);
35 |
36 | $presentationService = new Definition();
37 | $this->setDefinition('cmf_seo.presentation', $presentationService);
38 |
39 | $this->compile();
40 |
41 | $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
42 | 'cmf_seo.presentation',
43 | 'addExtractor',
44 | [new Reference('extractor_service'), 0]
45 | );
46 | }
47 |
48 | public function testRegistersServicesWithExtractorTagAndPriority()
49 | {
50 | $nonExtractorService = new Definition();
51 | $this->setDefinition('some_service', $nonExtractorService);
52 |
53 | $extractorService = new Definition();
54 | $extractorService->addTag('cmf_seo.extractor', ['priority' => 1]);
55 | $this->setDefinition('extractor_service', $extractorService);
56 |
57 | $presentationService = new Definition();
58 | $this->setDefinition('cmf_seo.presentation', $presentationService);
59 |
60 | $this->compile();
61 |
62 | $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
63 | 'cmf_seo.presentation',
64 | 'addExtractor',
65 | [new Reference('extractor_service'), 1]
66 | );
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/Unit/DependencyInjection/Compiler/RegisterSuggestionProviderPassTest.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class RegisterSuggestionProviderPassTest extends AbstractCompilerPassTestCase
24 | {
25 | /**
26 | * Register the compiler pass under test, just like you would do inside a bundle's load()
27 | * method:.
28 | *
29 | * $container->addCompilerPass(new MyCompilerPass());
30 | */
31 | protected function registerCompilerPass(ContainerBuilder $container)
32 | {
33 | $container->addCompilerPass(new RegisterSuggestionProviderPass());
34 | }
35 |
36 | public function testRegistersServicesWithMatcherTag()
37 | {
38 | $nonMatcherService = new Definition();
39 | $this->setDefinition('some_service', $nonMatcherService);
40 |
41 | $matcherServiceWithGroup = new Definition();
42 | $matcherServiceWithGroup->addTag('cmf_seo.suggestion_provider', ['group' => 'some-group']);
43 | $this->setDefinition('matcher.with_group', $matcherServiceWithGroup);
44 |
45 | $matcherPresentationService = new Definition();
46 | $matcherPresentationService->setArguments([1, 1, 2, 3, []]);
47 | $this->setDefinition('cmf_seo.error.suggestion_provider.controller', $matcherPresentationService);
48 |
49 | $this->compile();
50 |
51 | $this->assertContainerBuilderHasServiceDefinitionWithArgument(
52 | 'cmf_seo.error.suggestion_provider.controller',
53 | 4,
54 | [['provider' => new Reference('matcher.with_group'), 'group' => 'some-group']]
55 | );
56 | }
57 |
58 | public function testRegistersServicesWithMatcherTagWithoutGroup()
59 | {
60 | $nonMatcherService = new Definition();
61 | $this->setDefinition('some_service', $nonMatcherService);
62 |
63 | $matcherServiceWithOutGroup = new Definition();
64 | $matcherServiceWithOutGroup->addTag('cmf_seo.suggestion_provider');
65 | $this->setDefinition('matcher.without_group', $matcherServiceWithOutGroup);
66 |
67 | $matcherPresentationService = new Definition();
68 | $matcherPresentationService->setArguments([1, 1, 2, 3, []]);
69 | $this->setDefinition('cmf_seo.error.suggestion_provider.controller', $matcherPresentationService);
70 |
71 | $this->compile();
72 |
73 | $this->assertContainerBuilderHasServiceDefinitionWithArgument(
74 | 'cmf_seo.error.suggestion_provider.controller',
75 | 4,
76 | [['provider' => new Reference('matcher.without_group'), 'group' => 'default']]
77 | );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/tests/Unit/DependencyInjection/ConfigValuesTest.php:
--------------------------------------------------------------------------------
1 | setOriginalUrlBehaviour('nonexistent');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Unit/DependencyInjection/XmlSchemaTest.php:
--------------------------------------------------------------------------------
1 | assertSchemaAcceptsXml([$xmlFile], __DIR__.'/../../../src/Resources/config/schema/seo-1.0.xsd');
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Unit/EventListener/ContentListenerTest.php:
--------------------------------------------------------------------------------
1 | seoPresentation = $this->createMock(SeoPresentationInterface::class);
35 | $this->request = $this->createMock(Request::class);
36 | $this->event = $this->createMock(GetResponseEvent::class);
37 | $this->listener = new ContentListener($this->seoPresentation, DynamicRouter::CONTENT_KEY);
38 | }
39 |
40 | /**
41 | * @dataProvider getRedirectRouteData
42 | */
43 | public function testRedirectRoute($targetUrl, $redirect = true, $currentPath = '/test')
44 | {
45 | $redirectResponse = $this->createMock(RedirectResponse::class);
46 | $redirectResponse->expects($this->any())
47 | ->method('getTargetUrl')
48 | ->will($this->returnValue($targetUrl));
49 |
50 | $content = new \stdClass();
51 | $this->seoPresentation
52 | ->expects($this->once())
53 | ->method('updateSeoPage')
54 | ->with($content)
55 | ;
56 | $this->seoPresentation
57 | ->expects($this->any())
58 | ->method('getRedirectResponse')
59 | ->will($this->returnValue($redirectResponse))
60 | ;
61 |
62 | $this->event
63 | ->expects($this->any())
64 | ->method('getRequest')
65 | ->will($this->returnValue($this->request))
66 | ;
67 | if ($redirect) {
68 | $this->event
69 | ->expects($this->once())
70 | ->method('setResponse')
71 | ->with($redirectResponse)
72 | ;
73 | } else {
74 | $this->event
75 | ->expects($this->never())
76 | ->method('setResponse')
77 | ;
78 | }
79 |
80 | $attributes = $this->createMock(ParameterBag::class);
81 | $attributes
82 | ->expects($this->any())
83 | ->method('has')
84 | ->with(DynamicRouter::CONTENT_KEY)
85 | ->will($this->returnValue(true))
86 | ;
87 | $attributes
88 | ->expects($this->any())
89 | ->method('get')
90 | ->with(DynamicRouter::CONTENT_KEY)
91 | ->will($this->returnValue($content))
92 | ;
93 | $this->request->attributes = $attributes;
94 | $this->request
95 | ->expects($this->any())
96 | ->method('getBaseUrl')
97 | ->will($this->returnValue(''))
98 | ;
99 | $this->request
100 | ->expects($this->any())
101 | ->method('getPathInfo')
102 | ->will($this->returnValue($currentPath))
103 | ;
104 |
105 | $this->listener->onKernelRequest($this->event);
106 | }
107 |
108 | public function getRedirectRouteData()
109 | {
110 | return [
111 | ['/test_redirect'],
112 | ['/a/test'],
113 | ['/test', false],
114 | ['/test?a', false],
115 | ['/test', false, '/test#b'],
116 | ['/test?a', false, '/test#b'],
117 | ];
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/tests/Unit/EventListener/LanguageListenerTest.php:
--------------------------------------------------------------------------------
1 | listener = new LanguageListener();
26 | }
27 |
28 | /**
29 | * @dataProvider provideRequestLocales
30 | */
31 | public function testSetsContentLanguageHeader($locale)
32 | {
33 | $request = Request::create('/');
34 | $request->setLocale($locale);
35 |
36 | $response = new Response();
37 |
38 | $event = $this->createMock(FilterResponseEvent::class);
39 | $event->expects($this->any())->method('getRequest')->will($this->returnValue($request));
40 | $event->expects($this->any())->method('getResponse')->will($this->returnValue($response));
41 |
42 | $this->listener->onKernelResponse($event);
43 |
44 | $this->assertEquals('en', $response->headers->get('Content-Language'));
45 | }
46 |
47 | public function provideRequestLocales()
48 | {
49 | return [
50 | ['en'],
51 | ['en_US'],
52 | ];
53 | }
54 |
55 | public function testDoesNotOverridePreSetContentLanguage()
56 | {
57 | $request = Request::create('/');
58 | $request->setLocale('en');
59 |
60 | $response = new Response();
61 | $response->headers->set('Content-Language', 'nl');
62 |
63 | $event = $this->createMock(FilterResponseEvent::class);
64 | $event->expects($this->any())->method('getRequest')->will($this->returnValue($request));
65 | $event->expects($this->any())->method('getResponse')->will($this->returnValue($response));
66 |
67 | $this->listener->onKernelResponse($event);
68 |
69 | $this->assertEquals('nl', $response->headers->get('Content-Language'));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Unit/Extractor/BaseTestCase.php:
--------------------------------------------------------------------------------
1 | seoMetadata = $this->createMock(SeoMetadataInterface::class);
30 | }
31 |
32 | /**
33 | * @dataProvider getSupportsData
34 | */
35 | public function testSupports($object, $supports = true)
36 | {
37 | $result = $this->extractor->supports($object);
38 |
39 | if ($supports) {
40 | $this->assertTrue($result);
41 | } else {
42 | $this->assertFalse($result);
43 | }
44 | }
45 |
46 | abstract public function getSupportsData();
47 |
48 | public function testExtracting()
49 | {
50 | $document = $this->getMockBuilder('ExtractedDocument')->setMethods([$this->extractMethod])->getMock();
51 | $document->expects($this->any())
52 | ->method($this->extractMethod)
53 | ->will($this->returnValue('extracted'));
54 |
55 | $this->seoMetadata->expects($this->once())
56 | ->method($this->metadataMethod)
57 | ->with($this->equalTo('extracted'))
58 | ;
59 |
60 | $this->extractor->updateMetadata($document, $this->seoMetadata);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Unit/Extractor/DescriptionExtractorTest.php:
--------------------------------------------------------------------------------
1 | extractor = new DescriptionExtractor();
25 | $this->extractMethod = 'getSeoDescription';
26 | $this->metadataMethod = 'setMetaDescription';
27 | }
28 |
29 | public function getSupportsData()
30 | {
31 | return [
32 | [$this->createMock(DescriptionReadInterface::class)],
33 | [$this->createMock(SeoAwareInterface::class), false],
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Unit/Extractor/ExtrasExtractorTest.php:
--------------------------------------------------------------------------------
1 | extractor = new ExtrasExtractor();
25 | }
26 |
27 | public function getSupportsData()
28 | {
29 | return [
30 | [$this->createMock(ExtrasReadInterface::class)],
31 | [$this->createMock(SeoAwareInterface::class), false],
32 | ];
33 | }
34 |
35 | public function testExtracting()
36 | {
37 | $document = $this->getMockBuilder('ExtractedDocument')->setMethods(['getSeoExtras'])->getMock();
38 | $document->expects($this->any())
39 | ->method('getSeoExtras')
40 | ->will($this->returnValue([
41 | 'property' => ['og:title' => 'Extra Title'],
42 | 'name' => ['robots' => 'index, follow'],
43 | 'http-equiv' => ['Content-Type' => 'text/html; charset=utf-8'],
44 | ]));
45 |
46 | $this->seoMetadata->expects($this->once())
47 | ->method('addExtraProperty')
48 | ->with($this->equalTo('og:title'), $this->equalTo('Extra Title'))
49 | ;
50 |
51 | $this->seoMetadata->expects($this->once())
52 | ->method('addExtraName')
53 | ->with($this->equalTo('robots'), $this->equalTo('index, follow'))
54 | ;
55 |
56 | $this->seoMetadata->expects($this->once())
57 | ->method('addExtraHttp')
58 | ->with($this->equalTo('Content-Type'), $this->equalTo('text/html; charset=utf-8'))
59 | ;
60 |
61 | $this->extractor->updateMetadata($document, $this->seoMetadata);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Unit/Extractor/KeywordsExtractorTest.php:
--------------------------------------------------------------------------------
1 | extractor = new KeywordsExtractor();
25 | $this->extractMethod = 'getSeoKeywords';
26 | $this->metadataMethod = 'setMetaKeywords';
27 | }
28 |
29 | public function getSupportsData()
30 | {
31 | return [
32 | [$this->createMock(KeywordsReadInterface::class)],
33 | [$this->createMock(SeoAwareInterface::class), false],
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Unit/Extractor/OriginalRouteExtractorTest.php:
--------------------------------------------------------------------------------
1 | urlGenerator = $this->createMock(UrlGeneratorInterface::class);
28 | $this->extractor = new OriginalRouteExtractor($this->urlGenerator);
29 | $this->extractMethod = 'getSeoOriginalRoute';
30 | $this->metadataMethod = 'setOriginalUrl';
31 | }
32 |
33 | public function getSupportsData()
34 | {
35 | return [
36 | [$this->createMock(OriginalRouteReadInterface::class)],
37 | [$this->createMock(SeoAwareInterface::class), false],
38 | ];
39 | }
40 |
41 | public function testExtracting()
42 | {
43 | $this->urlGenerator->expects($this->any())
44 | ->method('generate')
45 | ->with($this->equalTo('extracted'))
46 | ->will($this->returnValue('extracted'))
47 | ;
48 |
49 | parent::testExtracting();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Unit/Extractor/OriginalUrlExtractorTest.php:
--------------------------------------------------------------------------------
1 | extractor = new OriginalUrlExtractor();
25 | $this->extractMethod = 'getSeoOriginalUrl';
26 | $this->metadataMethod = 'setOriginalUrl';
27 | }
28 |
29 | public function getSupportsData()
30 | {
31 | return [
32 | [$this->createMock(OriginalUrlReadInterface::class)],
33 | [$this->createMock(SeoAwareInterface::class), false],
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Unit/Extractor/TitleExtractorTest.php:
--------------------------------------------------------------------------------
1 | extractor = new TitleExtractor();
25 | $this->extractMethod = 'getSeoTitle';
26 | $this->metadataMethod = 'setTitle';
27 | }
28 |
29 | public function getSupportsData()
30 | {
31 | return [
32 | [$this->createMock(TitleReadInterface::class)],
33 | [$this->createMock(SeoAwareInterface::class), false],
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Unit/Extractor/TitleReadExtractorTest.php:
--------------------------------------------------------------------------------
1 | extractor = new TitleReadExtractor();
24 | $this->extractMethod = 'getTitle';
25 | $this->metadataMethod = 'setTitle';
26 | }
27 |
28 | public function getSupportsData()
29 | {
30 | return [
31 | [$this->getMockBuilder('Foo')->setMethods(['getTitle'])->getMock()],
32 | [$this->createMock(SeoAwareInterface::class), false],
33 | ];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Unit/Matcher/ExclusionMatcherTest.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class ExclusionMatcherTest extends PHPUnit_Framework_TestCase
20 | {
21 | /**
22 | * @var RequestMatcherInterface
23 | */
24 | private $matcherB;
25 |
26 | /**
27 | * @var RequestMatcherInterface
28 | */
29 | private $matcherA;
30 |
31 | /**
32 | * @var ExclusionMatcher
33 | */
34 | private $exclusionMatcher;
35 |
36 | public function setUp()
37 | {
38 | $this->matcherA = $this->createMock(RequestMatcherInterface::class);
39 | $this->matcherB = $this->createMock(RequestMatcherInterface::class);
40 |
41 | $this->exclusionMatcher = new ExclusionMatcher();
42 | $this->exclusionMatcher->addRequestMatcher($this->matcherA);
43 | $this->exclusionMatcher->addRequestMatcher($this->matcherB);
44 | }
45 |
46 | public function testReturnTrueMatcherAReturnsTrue()
47 | {
48 | $this->matcherA->expects($this->once())->method('matches')->will($this->returnValue(true));
49 |
50 | $this->assertTrue($this->exclusionMatcher->matches(new Request()));
51 | }
52 |
53 | public function testReturnTrueMatcherBReturnsTrue()
54 | {
55 | $this->matcherA->expects($this->once())->method('matches')->will($this->returnValue(false));
56 | $this->matcherB->expects($this->once())->method('matches')->will($this->returnValue(true));
57 |
58 | $this->assertTrue($this->exclusionMatcher->matches(new Request()));
59 | }
60 |
61 | public function testReturnTrueBothReturningFalse()
62 | {
63 | $this->matcherA->expects($this->once())->method('matches')->will($this->returnValue(false));
64 | $this->matcherB->expects($this->once())->method('matches')->will($this->returnValue(false));
65 |
66 | $this->assertFalse($this->exclusionMatcher->matches(new Request()));
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/Unit/Model/UrlInformationTest.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class UrlInformationTest extends \PHPUnit_Framework_Testcase
20 | {
21 | /**
22 | * @var UrlInformation
23 | */
24 | private $model;
25 |
26 | public function setUp()
27 | {
28 | $this->model = new UrlInformation();
29 | }
30 |
31 | /**
32 | * @expectedException \Symfony\Cmf\Bundle\SeoBundle\Exception\InvalidArgumentException
33 | * @expectedExceptionMessage Invalid change frequency "some one", use one of always, hourly, daily, weekly, monthly, yearly, never.
34 | */
35 | public function testSetChangeFrequencyShouldThrowExceptionForInvalidArguments()
36 | {
37 | $this->model->setChangeFrequency('some one');
38 | }
39 |
40 | public function testValidChangeFrequency()
41 | {
42 | $this->model->setChangeFrequency('never');
43 |
44 | $this->assertEquals('never', $this->model->getChangeFrequency());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Unit/Sitemap/AbstractChainTest.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class AbstractChainTest extends \PHPUnit_Framework_Testcase
20 | {
21 | /** @var TestChain */
22 | private $chain;
23 |
24 | public function setUp()
25 | {
26 | $this->chain = new TestChain();
27 | }
28 |
29 | public function testAllChains()
30 | {
31 | $one = new TestEntry('info-one');
32 | $two = new TestEntry('info-two');
33 |
34 | $this->chain->addItem($one, 0);
35 | $this->chain->addItem($two, 0);
36 |
37 | $expectedList = ['info-one', 'info-two'];
38 |
39 | $this->assertEquals($expectedList, $this->chain->getValues('test'));
40 | }
41 |
42 | public function testSpecificChain()
43 | {
44 | $one = new TestEntry('info-one');
45 | $two = new TestEntry('info-two');
46 |
47 | $this->chain->addItem($one, 0, 'test');
48 | $this->chain->addItem($two, 0, 'test');
49 |
50 | $expectedList = ['info-one', 'info-two'];
51 |
52 | $this->assertEquals($expectedList, $this->chain->getValues('test'));
53 | }
54 |
55 | public function testPrioritisedInput()
56 | {
57 | $first = new TestEntry('info-first');
58 | $earlySpecific = new TestEntry('info-early-specific');
59 | $specific = new TestEntry('info-specific');
60 | $early = new TestEntry('info-early');
61 | $last = new TestEntry('info-last');
62 |
63 | $this->chain->addItem($early, 5);
64 | $this->chain->addItem($specific, 5, 'test');
65 | $this->chain->addItem($last, 0);
66 | $this->chain->addItem($first, 15);
67 | $this->chain->addItem($earlySpecific, 10, 'test');
68 |
69 | $this->assertEquals(
70 | ['info-first', 'info-early-specific', 'info-specific', 'info-early', 'info-last'],
71 | $this->chain->getValues('test')
72 | );
73 |
74 | $this->assertEquals(
75 | ['info-first', 'info-early', 'info-last'],
76 | $this->chain->getValues('foobar')
77 | );
78 | }
79 | }
80 |
81 | class TestChain extends AbstractChain
82 | {
83 | public function getValues($sitemap)
84 | {
85 | $values = [];
86 | /** @var $entry TestEntry */
87 | foreach ($this->getSortedEntriesForSitemap($sitemap) as $entry) {
88 | $values[] = $entry->name;
89 | }
90 |
91 | return $values;
92 | }
93 | }
94 |
95 | class TestEntry
96 | {
97 | public $name;
98 |
99 | public function __construct($name)
100 | {
101 | $this->name = $name;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/tests/Unit/Sitemap/AlternateLocalesGuesserTest.php:
--------------------------------------------------------------------------------
1 | add(new AlternateLocale('http://symfony.com/fr', 'fr'));
28 |
29 | $localeProvider = $this->createMock(AlternateLocaleProviderInterface::class);
30 | $localeProvider
31 | ->expects($this->any())
32 | ->method('createForContent')
33 | ->with($this)
34 | ->will($this->returnValue($collection))
35 | ;
36 |
37 | return new AlternateLocalesGuesser($localeProvider);
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | protected function createData()
44 | {
45 | return $this;
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | protected function getFields()
52 | {
53 | return ['AlternateLocales'];
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Unit/Sitemap/DefaultChangeFrequencyGuesserTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('weekly', $urlInformation->getChangeFrequency());
22 | }
23 |
24 | /**
25 | * {@inheritdoc}
26 | */
27 | protected function createGuesser()
28 | {
29 | return new DefaultChangeFrequencyGuesser('weekly');
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | protected function createData()
36 | {
37 | return $this;
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | protected function getFields()
44 | {
45 | return ['ChangeFrequency'];
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/Unit/Sitemap/GuesserTestCase.php:
--------------------------------------------------------------------------------
1 | guesser = $this->createGuesser();
35 | $this->data = $this->createData();
36 | }
37 |
38 | /**
39 | * Information is created.
40 | */
41 | public function testGuessCreate()
42 | {
43 | $urlInformation = new UrlInformation();
44 |
45 | $this->guesser->guessValues($urlInformation, $this->data, 'default');
46 | foreach ($this->getFields() as $field) {
47 | $this->assertNotNull($urlInformation->{'get'.$field}());
48 | }
49 |
50 | return $urlInformation;
51 | }
52 |
53 | /**
54 | * If the information is already set, it must not be overwritten.
55 | */
56 | public function testGuessNoOverwrite()
57 | {
58 | $urlInformation = new UrlInformation();
59 | foreach ($this->getFields() as $field) {
60 | $urlInformation->{'set'.$field}('always');
61 | }
62 |
63 | $this->guesser->guessValues($urlInformation, $this->data, 'default');
64 | foreach ($this->getFields() as $field) {
65 | $this->assertEquals('always', $urlInformation->{'get'.$field}());
66 | }
67 | }
68 |
69 | /**
70 | * Create the guesser for this test.
71 | *
72 | * @return GuesserInterface
73 | */
74 | abstract protected function createGuesser();
75 |
76 | /**
77 | * @return object
78 | */
79 | abstract protected function createData();
80 |
81 | /**
82 | * Provide list of fields in UrlInformation covered by this guesser.
83 | *
84 | * @return array
85 | */
86 | abstract protected function getFields();
87 | }
88 |
--------------------------------------------------------------------------------
/tests/Unit/Sitemap/LastModifiedGuesserTest.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | class LastModifiedGuesserTest extends GuesserTestCase
25 | {
26 | /**
27 | * @var ManagerRegistry
28 | */
29 | private $registry;
30 |
31 | /**
32 | * @var DocumentManager
33 | */
34 | private $manager;
35 |
36 | /**
37 | * @var ClassMetadata
38 | */
39 | private $metadata;
40 |
41 | public function testGuessCreate()
42 | {
43 | $urlInformation = parent::testGuessCreate();
44 | $this->assertEquals('2016-07-06T00:00:00+02:00', $urlInformation->getLastModification());
45 | }
46 |
47 | /**
48 | * Create the guesser for this test.
49 | *
50 | * @return GuesserInterface
51 | */
52 | protected function createGuesser()
53 | {
54 | $this->registry = $this->createMock(ManagerRegistry::class);
55 | $this->manager = $this->createMock(DocumentManager::class);
56 | $this->metadata = $this->createMock(ClassMetadata::class);
57 | $this->registry
58 | ->expects($this->any())
59 | ->method('getManagerForClass')
60 | ->with($this->equalTo('Symfony\Cmf\Bundle\SeoBundle\Tests\Unit\Sitemap\LastModifiedGuesserTest'))
61 | ->will($this->returnValue($this->manager));
62 | $this->manager
63 | ->expects($this->any())
64 | ->method('getClassMetadata')
65 | ->with($this->equalTo('Symfony\Cmf\Bundle\SeoBundle\Tests\Unit\Sitemap\LastModifiedGuesserTest'))
66 | ->will($this->returnValue($this->metadata));
67 | $this->metadata
68 | ->expects($this->any())
69 | ->method('getMixins')
70 | ->will($this->returnValue(['mix:lastModified']));
71 | $this->metadata
72 | ->expects($this->any())
73 | ->method('getFieldNames')
74 | ->will($this->returnValue(['lastModified']));
75 | $this->metadata
76 | ->expects($this->any())
77 | ->method('getFieldMapping')
78 | ->with($this->equalTo('lastModified'))
79 | ->will($this->returnValue(['property' => 'jcr:lastModified']));
80 | $this->metadata
81 | ->expects($this->any())
82 | ->method('getFieldValue')
83 | ->with($this->equalTo($this), $this->equalTo('lastModified'))
84 | ->will($this->returnValue(new \DateTime('2016-07-06', new \DateTimeZone('Europe/Berlin'))));
85 |
86 | return new LastModifiedGuesser($this->registry);
87 | }
88 |
89 | /**
90 | * @return object
91 | */
92 | protected function createData()
93 | {
94 | return $this;
95 | }
96 |
97 | /**
98 | * Provide list of fields in UrlInformation covered by this guesser.
99 | *
100 | * @return array
101 | */
102 | protected function getFields()
103 | {
104 | return ['LastModification'];
105 | }
106 |
107 | public function testGuessNoOverwrite()
108 | {
109 | $urlInformation = new UrlInformation();
110 | $urlInformation->setLastModification(new \DateTime('2016-06-06', new \DateTimeZone('Europe/Berlin')));
111 |
112 | $this->guesser->guessValues($urlInformation, $this->data, 'default');
113 | $this->assertEquals('2016-06-06T00:00:00+02:00', $urlInformation->getLastModification());
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/tests/Unit/Sitemap/LocationGuesserTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('http://symfony.com', $urlInformation->getLocation());
23 | }
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | protected function createGuesser()
29 | {
30 | $urlGenerator = $this->createMock(UrlGeneratorInterface::class);
31 | $urlGenerator
32 | ->expects($this->any())
33 | ->method('generate')
34 | ->with($this, [], UrlGeneratorInterface::ABSOLUTE_URL)
35 | ->will($this->returnValue('http://symfony.com'))
36 | ;
37 |
38 | return new LocationGuesser($urlGenerator);
39 | }
40 |
41 | /**
42 | * {@inheritdoc}
43 | */
44 | protected function createData()
45 | {
46 | return $this;
47 | }
48 |
49 | /**
50 | * {@inheritdoc}
51 | */
52 | protected function getFields()
53 | {
54 | return ['Location'];
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Unit/Sitemap/SeoMetadataTitleGuesserTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('Symfony CMF', $urlInformation->getLabel());
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | protected function createGuesser()
30 | {
31 | $seoMetadata = new SeoMetadata();
32 | $seoMetadata->setTitle('Symfony CMF');
33 | $seoPresentation = $this->createMock(SeoPresentation::class);
34 | $seoPresentation
35 | ->expects($this->any())
36 | ->method('getSeoMetadata')
37 | ->with($this)
38 | ->will($this->returnValue($seoMetadata))
39 | ;
40 |
41 | return new SeoMetadataTitleGuesser($seoPresentation);
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | protected function createData()
48 | {
49 | return $this;
50 | }
51 |
52 | /**
53 | * {@inheritdoc}
54 | */
55 | protected function getFields()
56 | {
57 | return ['Label'];
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Unit/Sitemap/SitemapAwareDocumentVoterTest.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class SitemapAwareDocumentVoterTest extends \PHPUnit_Framework_TestCase
22 | {
23 | /**
24 | * @var VoterInterface
25 | */
26 | protected $voter;
27 |
28 | protected $sitemapAwareDocument;
29 |
30 | public function setUp()
31 | {
32 | $this->voter = new SitemapAwareDocumentVoter();
33 | $this->sitemapAwareDocument = $this->createMock(SitemapAwareInterface::class);
34 | }
35 |
36 | public function testSitemapAwareDocumentShouldReturnTrueWhenDocumentIsExposed()
37 | {
38 | $this->sitemapAwareDocument
39 | ->expects($this->once())
40 | ->method('isVisibleInSitemap')
41 | ->will($this->returnValue(true));
42 | $this->assertTrue($this->voter->exposeOnSitemap($this->sitemapAwareDocument, 'some-sitemap'));
43 | }
44 |
45 | public function testSitemapAwareDocumentShouldReturnFalseWhenDocumentIsNotExposed()
46 | {
47 | $this->sitemapAwareDocument
48 | ->expects($this->once())
49 | ->method('isVisibleInSitemap')
50 | ->will($this->returnValue(false));
51 | $this->assertFalse($this->voter->exposeOnSitemap($this->sitemapAwareDocument, 'some-sitemap'));
52 | }
53 |
54 | public function testInvalidDocumentShouldReturnTrueToBeAwareForTheOtherVoters()
55 | {
56 | $this->assertTrue($this->voter->exposeOnSitemap(new \stdClass(), 'some-sitemap'));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Unit/Sitemap/UrlInformationProviderTest.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class UrlInformationProviderTest extends \PHPUnit_Framework_Testcase
24 | {
25 | /**
26 | * @var UrlInformationProvider
27 | */
28 | private $provider;
29 |
30 | public function setUp()
31 | {
32 | $accepted = new TestModel('accepted');
33 | $refused = new TestModel('refused');
34 |
35 | $loader = $this->createMock(LoaderChain::class);
36 | $loader
37 | ->expects($this->once())
38 | ->method('load')
39 | ->with('default')
40 | ->will(
41 | $this->returnValue([$accepted, $refused])
42 | )
43 | ;
44 |
45 | $voter = $this->createMock(VoterChain::class);
46 | $voter
47 | ->expects($this->at(0))
48 | ->method('exposeOnSitemap')
49 | ->with($accepted, 'default')
50 | ->will($this->returnValue(true))
51 | ;
52 | $voter
53 | ->expects($this->at(1))
54 | ->method('exposeOnSitemap')
55 | ->with($refused, 'default')
56 | ->will($this->returnValue(false))
57 | ;
58 |
59 | $guesser = $this->createMock(GuesserChain::class);
60 | $guesser
61 | ->expects($this->once())
62 | ->method('guessValues')
63 | ->with(
64 | $this->isInstanceOf(UrlInformation::class),
65 | $this->equalTo($accepted),
66 | $this->equalTo('default'))
67 | ->will($this->returnCallback(function (UrlInformation $info) {
68 | $info->setLocation('http://symfony.com');
69 | }))
70 | ;
71 |
72 | $this->provider = new UrlInformationProvider($loader, $voter, $guesser);
73 | }
74 |
75 | public function testProvider()
76 | {
77 | $urlInformations = $this->provider->getUrlInformation();
78 | $this->assertCount(1, $urlInformations);
79 | $urlInformation = reset($urlInformations);
80 | $this->assertInstanceof('Symfony\Cmf\Bundle\SeoBundle\Model\UrlInformation', $urlInformation);
81 | $this->assertEquals('http://symfony.com', $urlInformation->getLocation());
82 | }
83 | }
84 |
85 | class TestModel
86 | {
87 | public $name;
88 |
89 | public function __construct($name)
90 | {
91 | $this->name = $name;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |