diff --git a/app/Resources/views/error/index.html.twig b/app/Resources/views/error/index.html.twig
new file mode 100644
index 00000000..0b217b9f
--- /dev/null
+++ b/app/Resources/views/error/index.html.twig
@@ -0,0 +1,42 @@
+{% extends 'base.html.twig' %}
+
+{% block content %}
+
Oops! An Error Occurred
+ The server returned a "{{ status_code }} {{ status_text }}".
+ If you see this page, it means your sandbox is not correctly set up.
+ Please see the README file in the sandbox root folder and if you can't figure out
+ what is wrong, ask us on freenode irc #symfonycmf or the mailinglist cmfusers@groups.google.com.
+
+
+ If you are seeing this page as the result of an edit in the admin tool, please report what you were doing
+ to our ticket system,
+ so that we can add means to prevent this issue in the future. But to get things working again
+ for now, please just click here
+ to reload the data fixtures.
+
+ Detected the following problem:
+ {{ exception.getMessage() }}
+
+
Suggested pages
+
+ {% for group, list in best_matches if list is not empty %}
+ {{ group|capitalize }}
+
+ {% else %}
+ No suggestions found
+ {% endfor %}
+{% endblock %}
diff --git a/app/Resources/views/sitemap/index.html.twig b/app/Resources/views/sitemap/index.html.twig
new file mode 100644
index 00000000..538038de
--- /dev/null
+++ b/app/Resources/views/sitemap/index.html.twig
@@ -0,0 +1,20 @@
+{% extends 'base.html.twig' %}
+
+{% block content %}
+ Sitemap
+
+ The sitemap feature allows to give an overview of the content.
+ The content document decides whether it should be displayed on a sitemap or not.
+ The sitemap of the symfony-cmf sandbox is relatively flat because the content URL structure is flat.
+ If you have deeper nested content, the sitemap is organized along the nested structure.
+ This information is arranged for human users. For search engines, the sitemap also exists in
+ an xml format.
+
+
+{% endblock %}
diff --git a/app/Resources/views/sitemap/index.xml.twig b/app/Resources/views/sitemap/index.xml.twig
new file mode 100644
index 00000000..751ed7df
--- /dev/null
+++ b/app/Resources/views/sitemap/index.xml.twig
@@ -0,0 +1,17 @@
+
+
+ {% for url in urls %}
+
+ {{ url.location }}
+ {% if url.lastModification %}
+ {{ url.lastModification }}
+ {% endif %}
+ {{ url.changeFrequency }}
+ {% if url.alternateLocales is defined and url.alternateLocales|length > 0 %}
+ {% for locale in url.alternateLocales %}
+
+ {% endfor %}
+ {% endif %}
+
+ {% endfor %}
+
diff --git a/app/config/config.yml b/app/config/config.yml
index 90bf130e..1a0a9820 100644
--- a/app/config/config.yml
+++ b/app/config/config.yml
@@ -22,7 +22,7 @@ framework:
twig:
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
- exception_controller: 'FOS\RestBundle\Controller\ExceptionController::showAction'
+ exception_controller: cmf_seo.error.suggestion_provider.controller:listAction
# Assetic Configuration
assetic:
@@ -113,6 +113,23 @@ cmf_seo:
title: 'CMF Sandbox - %%content_title%%'
description: 'The Content Management Framework. %%content_description%%'
alternate_locale: ~
+ error:
+ enable_parent_provider: true
+ enable_sibling_provider: true
+ templates:
+ html: ":error/index.html.twig"
+ exclusion_rules:
+ - { path: 'excluded' }
+ sitemap:
+ defaults:
+ default_change_frequency: never
+ templates:
+ xml: ':sitemap/index.xml.twig'
+ html: ':sitemap/index.html.twig'
+ configurations:
+ sitemap: ~
+ frequent:
+ default_change_frequency: always
cmf_menu:
voters:
diff --git a/app/config/routing.yml b/app/config/routing.yml
index 1d7620fc..811b826a 100644
--- a/app/config/routing.yml
+++ b/app/config/routing.yml
@@ -52,3 +52,7 @@ block_cache:
cmf_resource:
resource: '@CmfResourceRestBundle/Resources/config/routing.yml'
prefix: /admin
+
+sitemaps:
+ prefix: /sitemaps
+ resource: "@CmfSeoBundle/Resources/config/routing/sitemap.xml"
diff --git a/app/config/services.yml b/app/config/services.yml
index 188fedd1..690a5593 100644
--- a/app/config/services.yml
+++ b/app/config/services.yml
@@ -3,13 +3,6 @@ services:
class: AppBundle\Controller\ContentController
parent: cmf_content.controller
- app.exception_listener:
- class: AppBundle\EventListener\SandboxExceptionListener
- calls:
- - [setContainer, ['@service_container']]
- tags:
- - { name: kernel.event_subscriber }
-
app.twig.menu_extension:
class: AppBundle\Twig\MenuExtension
arguments: ['@knp_menu.helper', '@knp_menu.matcher']
diff --git a/src/AppBundle/DataFixtures/PHPCR/LoadMenuData.php b/src/AppBundle/DataFixtures/PHPCR/LoadMenuData.php
index d9a7b435..1972f97c 100644
--- a/src/AppBundle/DataFixtures/PHPCR/LoadMenuData.php
+++ b/src/AppBundle/DataFixtures/PHPCR/LoadMenuData.php
@@ -85,6 +85,8 @@ public function load(ObjectManager $manager)
$this->createMenuNode($manager, $seo, 'simple-seo-example', array('en' => 'Seo-Simple-Content'), $manager->find(null, "$content_path/simple-seo-example"));
$this->createMenuNode($manager, $seo, 'demo-seo-extractor', array('en' => 'Seo-Extractor'), $manager->find(null, "$content_path/demo-seo-extractor"));
$this->createMenuNode($manager, $seo, 'simple-seo-property', array('en' => 'Seo-Extra-Properties'), $manager->find(null, "$content_path/simple-seo-property"));
+ $this->createMenuNode($manager, $seo, 'seo-error-pages', array('en' => 'Seo-Error-Pages'), $manager->find(null, "$content_path/seo-error-pages"));
+ $this->createMenuNode($manager, $seo, 'seo-sitemap', 'Sitemap', null, '/sitemaps/sitemap.html');
$this->createMenuNode($manager, $main, 'routing-auto-item', array('en' => 'Auto routing example', 'de' => 'Auto routing beispiel', 'fr' => 'Auto routing exemple'), $manager->find(null, "$content_path/news/RoutingAutoBundle generates routes!"));
diff --git a/src/AppBundle/DataFixtures/PHPCR/LoadNewsData.php b/src/AppBundle/DataFixtures/PHPCR/LoadNewsData.php
index a263ab93..e646856e 100644
--- a/src/AppBundle/DataFixtures/PHPCR/LoadNewsData.php
+++ b/src/AppBundle/DataFixtures/PHPCR/LoadNewsData.php
@@ -51,6 +51,7 @@ public function load(ObjectManager $manager)
See the routing auto configuration file to see how this works.
EOT
);
+ $news->setIsVisibleForSitemap(true);
$manager->persist($news);
$manager->flush();
diff --git a/src/AppBundle/DataFixtures/PHPCR/LoadRoutingData.php b/src/AppBundle/DataFixtures/PHPCR/LoadRoutingData.php
index a664aaac..ebd35860 100644
--- a/src/AppBundle/DataFixtures/PHPCR/LoadRoutingData.php
+++ b/src/AppBundle/DataFixtures/PHPCR/LoadRoutingData.php
@@ -115,6 +115,11 @@ public function load(ObjectManager $manager)
$seo->setPosition($home, 'demo-seo-extractor');
$seo->setContent($manager->find(null, "$content_path/demo-seo-extractor"));
$manager->persist($seo);
+
+ $seo = new Route();
+ $seo->setPosition($home, 'seo-error-pages');
+ $seo->setContent($manager->find(null, "$content_path/seo-error-pages"));
+ $manager->persist($seo);
}
// demo features of routing
diff --git a/src/AppBundle/DataFixtures/PHPCR/LoadStaticPageData.php b/src/AppBundle/DataFixtures/PHPCR/LoadStaticPageData.php
index 8bbf35d3..d8d84e58 100644
--- a/src/AppBundle/DataFixtures/PHPCR/LoadStaticPageData.php
+++ b/src/AppBundle/DataFixtures/PHPCR/LoadStaticPageData.php
@@ -18,6 +18,7 @@
use PHPCR\Util\NodeHelper;
use AppBundle\Document\DemoSeoContent;
use Symfony\Cmf\Bundle\SeoBundle\Doctrine\Phpcr\SeoMetadata;
+use Symfony\Cmf\Bundle\SeoBundle\SitemapAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\Yaml\Parser;
@@ -95,6 +96,10 @@ public function load(ObjectManager $manager)
$this->loadBlock($manager, $page, $name, $block);
}
}
+
+ if ($page instanceof SitemapAwareInterface) {
+ $page->setIsVisibleForSitemap(true);
+ }
}
//add a loading for a simple seo aware page
@@ -114,6 +119,7 @@ public function load(ObjectManager $manager)
EOH
);
$seoDemo->setParentDocument($parent);
+ $seoDemo->setIsVisibleForSitemap(true);
$seoMetadata = new SeoMetadata();
$seoMetadata->setTitle('Simple seo example');
@@ -146,6 +152,7 @@ public function load(ObjectManager $manager)
$seoMetadata->addExtraName('robots', 'index, follow');
$seoMetadata->addExtraProperty('og:title', 'extra title');
$seoDemo->setSeoMetadata($seoMetadata);
+ $seoDemo->setIsVisibleForSitemap(true);
$manager->persist($seoDemo);
$manager->bindTranslation($seoDemo, 'en');
diff --git a/src/AppBundle/Document/DemoClassContent.php b/src/AppBundle/Document/DemoClassContent.php
index cc9897ee..5defb947 100644
--- a/src/AppBundle/Document/DemoClassContent.php
+++ b/src/AppBundle/Document/DemoClassContent.php
@@ -12,6 +12,7 @@
namespace AppBundle\Document;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
+use Symfony\Cmf\Bundle\SeoBundle\SitemapAwareInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Cmf\Component\Routing\RouteReferrersReadInterface;
@@ -20,7 +21,7 @@
*
* @PHPCRODM\Document(referenceable=true)
*/
-class DemoClassContent implements RouteReferrersReadInterface
+class DemoClassContent implements RouteReferrersReadInterface, SitemapAwareInterface
{
/**
* to create the document at the specified location. read only for existing documents.
@@ -62,6 +63,13 @@ class DemoClassContent implements RouteReferrersReadInterface
*/
public $routes;
+ /**
+ * @var bool
+ *
+ * @PHPCRODM\Field(type="boolean", property="visible_for_sitemap")
+ */
+ private $isVisibleForSitemap;
+
public function getName()
{
return $this->name;
@@ -117,4 +125,25 @@ public function getRoutes()
{
return $this->routes->toArray();
}
+
+ /**
+ * Decision whether a document should be visible
+ * in sitemap or not.
+ *
+ * @param $sitemap
+ *
+ * @return bool
+ */
+ public function isVisibleInSitemap($sitemap)
+ {
+ return $this->isVisibleForSitemap;
+ }
+
+ /**
+ * @param bool $isVisibleForSitemap
+ */
+ public function setIsVisibleForSitemap($isVisibleForSitemap)
+ {
+ $this->isVisibleForSitemap = $isVisibleForSitemap;
+ }
}
diff --git a/src/AppBundle/Document/DemoSeoContent.php b/src/AppBundle/Document/DemoSeoContent.php
index facfa447..0fb257fd 100644
--- a/src/AppBundle/Document/DemoSeoContent.php
+++ b/src/AppBundle/Document/DemoSeoContent.php
@@ -15,6 +15,7 @@
use Symfony\Cmf\Bundle\ContentBundle\Doctrine\Phpcr\StaticContent;
use Symfony\Cmf\Bundle\SeoBundle\SeoAwareInterface;
use Symfony\Cmf\Bundle\SeoBundle\Doctrine\Phpcr\SeoMetadata;
+use Symfony\Cmf\Bundle\SeoBundle\SitemapAwareInterface;
/**
* That example class uses the extractors for the creation of the SeoMetadata.
@@ -23,7 +24,7 @@
*
* @author Maximilian Berghoff
*/
-class DemoSeoContent extends StaticContent implements SeoAwareInterface
+class DemoSeoContent extends StaticContent implements SeoAwareInterface, SitemapAwareInterface
{
/**
* @var SeoMetadata
@@ -32,6 +33,13 @@ class DemoSeoContent extends StaticContent implements SeoAwareInterface
*/
protected $seoMetadata;
+ /**
+ * @var bool
+ *
+ * @PHPCRODM\Field(type="boolean", property="visible_for_sitemap")
+ */
+ private $isVisibleForSitemap;
+
public function __construct()
{
$this->seoMetadata = new SeoMetadata();
@@ -53,4 +61,25 @@ public function setSeoMetadata($seoMetadata)
{
$this->seoMetadata = $seoMetadata;
}
+
+ /**
+ * Decision whether a document should be visible
+ * in sitemap or not.
+ *
+ * @param $sitemap
+ *
+ * @return bool
+ */
+ public function isVisibleInSitemap($sitemap)
+ {
+ return $this->isVisibleForSitemap;
+ }
+
+ /**
+ * @param bool $isVisibleForSitemap
+ */
+ public function setIsVisibleForSitemap($isVisibleForSitemap)
+ {
+ $this->isVisibleForSitemap = $isVisibleForSitemap;
+ }
}
diff --git a/src/AppBundle/Document/DemoSeoExtractor.php b/src/AppBundle/Document/DemoSeoExtractor.php
index e596562f..6b2c5233 100644
--- a/src/AppBundle/Document/DemoSeoExtractor.php
+++ b/src/AppBundle/Document/DemoSeoExtractor.php
@@ -24,11 +24,7 @@
*
* @author Maximilian Berghoff
*/
-class DemoSeoExtractor extends DemoSeoContent implements
- TitleReadInterface,
- DescriptionReadInterface,
- OriginalUrlReadInterface,
- KeywordsReadInterface
+class DemoSeoExtractor extends DemoSeoContent implements TitleReadInterface, DescriptionReadInterface, OriginalUrlReadInterface, KeywordsReadInterface
{
/**
* {@inheritdoc}
diff --git a/src/AppBundle/EventListener/SandboxExceptionListener.php b/src/AppBundle/EventListener/SandboxExceptionListener.php
deleted file mode 100644
index 8770dcf9..00000000
--- a/src/AppBundle/EventListener/SandboxExceptionListener.php
+++ /dev/null
@@ -1,82 +0,0 @@
-getException() instanceof NotFoundHttpException) {
- return;
- }
-
- if (!$this->container->has('doctrine_phpcr.odm.default_document_manager')) {
- $error = 'Missing the service doctrine_phpcr.odm.default_document_manager.';
- } else {
- try {
- $om = $this->container->get('doctrine_phpcr.odm.default_document_manager');
- $doc = $om->find(null, $this->container->getParameter('cmf_menu.persistence.phpcr.menu_basepath'));
- if ($doc) {
- $error = 'Hm. No clue what goes wrong. Maybe this is a real 404?'.$event->getException()->__toString().'
';
- } else {
- $error = 'Did you load the fixtures? See README for how to load them. I found no node at menu_basepath: '.$this->container->getParameter('cmf_menu.persistence.phpcr.menu_basepath');
- }
- } catch (RepositoryException $e) {
- $error = 'There was an exception loading the document manager: '.$e->getMessage().
- "
\nMake sure you have a phpcr backend properly set up and running.
".
- $e->__toString().'
';
- }
- }
- // do not even trust the templating system to work
- $response = new Response("
- Sandbox
- If you see this page, it means your sandbox is not correctly set up.
- Please see the README file in the sandbox root folder and if you can't figure out
- what is wrong, ask us on freenode irc #symfony-cmf or the mailinglist cmf-users@groups.google.com.
-
-
- If you are seeing this page as the result of an edit in the admin tool, please report what you were doing
- to our ticket system,
- so that we can add means to prevent this issue in the future. But to get things working again
- for now, please just getRequest()->getSchemeAndHttpHost()."/reload-fixtures.php\">click here
- to reload the data fixtures.
-
- Detected the following problem: $error
-
-
- ");
-
- $event->setResponse($response);
- }
-
- public static function getSubscribedEvents()
- {
- return array(
- KernelEvents::EXCEPTION => array('onKernelException', 0),
- );
- }
-}
diff --git a/src/AppBundle/Resources/data/page.yml b/src/AppBundle/Resources/data/page.yml
index 4b8c0f4c..ff048ef7 100644
--- a/src/AppBundle/Resources/data/page.yml
+++ b/src/AppBundle/Resources/data/page.yml
@@ -236,3 +236,38 @@ static:
können einfach ausgelesen werden.
Read about this feature in the CMF documentation (translation needed).
+ -
+ name: seo-error-pages
+ title:
+ en: SEO error pages
+ de: SEO optimierte Fehlerseiten
+ body:
+ en: |
+ Error pages with stack traces are good for development, but very bad end user experience and
+ a security risk. The default Symfony error handling produces a decent but very unhelpful
+ error page.
+
+ With the CMF, we have the possibility to do better, particulary on not found pages.
+ The CmfSeoBundle defines the SuggestionProvider. Implementations provide possible alternatives
+ when content is not found. This could be pages with a parent path of the requested path
+ or other custom logic.
+
+ To see this in action, try to open the following pages:
+
+ de: |
+ Fehlerseiten mit Stack Traces von Exceptions sind gut zum entwickeln, aber schlecht für
+ die Benutzer der Seite und ein Sicherheitsproblem. Das Symfony Fehlerhandling produziert
+ vernünftige Fehlerseiten, die aber dem Benutzer nicht wirklich weiter helfen.
+ Mit dem CMF haben wir bessere Möglichkeiten, insbesondere wenn etwas nicht gefunden wird.
+ Das CmfSeoBundle definiert SuggestionProvider. Implementationen dieses providers liefern
+ mögliche Alternativen wenn ein Inhalt nicht gefunden wird. Zum Beispiel Nachbarn oder Eltern
+ der gewünschten URL.
+ Probiere es einfach aus, nimm Buchstaben aus der aktuellen URL oder klicke einen dieser Links:
+
+
diff --git a/tests/Functional/HomepageTest.php b/tests/Functional/HomepageTest.php
index 5823c8ac..65d806e4 100644
--- a/tests/Functional/HomepageTest.php
+++ b/tests/Functional/HomepageTest.php
@@ -38,7 +38,7 @@ public function testContents()
$this->assertCount(1, $crawler->filter('h1:contains(Homepage)'));
$this->assertCount(1, $crawler->filter('h2:contains("Welcome to the Symfony CMF Demo")'));
- $this->assertCount(23, $crawler->filter('.panel-nav li'));
+ $this->assertCount(25, $crawler->filter('.panel-nav li'));
}
public function testJsonContents()
diff --git a/tests/Functional/StaticPageTest.php b/tests/Functional/StaticPageTest.php
index efb5c8a7..667f49a5 100644
--- a/tests/Functional/StaticPageTest.php
+++ b/tests/Functional/StaticPageTest.php
@@ -47,6 +47,7 @@ public function contentDataProvider()
array('/demo/class', 'Controller by class'),
array('/hello', 'Hello World!'),
array('/en/about', 'Some information about us'),
+ 'sitemap' => array('/sitemaps/sitemap.html', 'Sitemap'),
);
}
@@ -65,4 +66,14 @@ public function testJson()
);
$this->assertContains('"title":"The Team",', $response->getContent());
}
+
+ public function testErrorPage()
+ {
+ $client = $this->createClient();
+ $client->request('GET', '/en/company/tea');
+ $this->assertStatusCode(404, $client);
+
+ $response = $client->getResponse();
+ $this->assertContains('Oops! An Error Occurred', $response->getContent());
+ }
}
diff --git a/web/app.php b/web/app.php
index bb47d3c9..638ad3a2 100644
--- a/web/app.php
+++ b/web/app.php
@@ -21,7 +21,6 @@
$kernel->loadClassCache();
//$kernel = new AppCache($kernel);
-
// When using the HttpCache, you need to call the method in your front
// controller instead of relying on the configuration parameter
//Request::enableHttpMethodParameterOverride();
diff --git a/web/assets/css/style.css b/web/assets/css/style.css
index 9596d790..19586af6 100644
--- a/web/assets/css/style.css
+++ b/web/assets/css/style.css
@@ -26,6 +26,21 @@ body { padding-top:50px; }
}
.navbar-dropdown .dropdown-menu { margin-top:-5px; }
+.cmf-sitemap .indent-1 {
+ margin-left: 20px;
+}
+
+.cmf-sitemap .indent-2 {
+ margin-left: 40px;
+}
+
+.cmf-sitemap .indent-3 {
+ margin-left: 60px;
+}
+
+.cmf-sitemap .indent-4 {
+ margin-left: 80px;
+}