Commit ecc3fc5f authored by Tino Goratsch's avatar Tino Goratsch Committed by GitHub

feature/seo sitemap (#44)

* fix the update_version_check.sh Travis CI script

* added 2 more paths to the .gitignore file

* laid the groundwork for the SEO sitemap.xml generation

* unified the naming of various *AvailabilityRegistrar classes, etc.

* save current work in progress at implementing the sitemap generator

* - updated the .gitignore file to ignore sitemap.xml files too

* extended the ModelSaveEvent with the called module name

* added a first rough version of the XML sitemap generation

* updated the changelog

* minor fix

* moved some files around...

* - implemented the sitemap generation capability for the news module
- refactored the SitemapAvailabilityExtension class a little bit to reduce the code duplication

* implemented the sitemap generation capability for the contact module

* implemented the sitemap generation capability for the files module

* minor code refactoring

* implemented the sitemap generation capability for the gallery module

* extended the sitemap generation of the files module

* - added a new switch to the SEO settings to enable or disable the sitemap generation
- added a new switch to the SEO settings to select whether the sitemap should be generation on model save or manually

* added the possibility to manually refresh/generate the SEO sitemap

* do not trigger the page cache invalidation when in developer mode, as the page cache is disabled when the developer mode is engaged

* - minor bug fix for refresh ACP3 installation
- extended the validation of the mailer system settings

* updated the changelog

* improved the changelog

* added some references to related Github issues to the changelog
parent 0926f458
...@@ -4,11 +4,14 @@ ...@@ -4,11 +4,14 @@
/cache/* /cache/*
/node_modules /node_modules
/tests/cache/ /tests/cache/
/uploads/.htaccess
/uploads/assets/* /uploads/assets/*
/uploads/categories/* /uploads/categories/*
/uploads/files/* /uploads/files/*
/uploads/gallery/* /uploads/gallery/*
/uploads/kcfinder/*
/vendor /vendor
/.htaccess /.htaccess
/sitemap*.xml
!.gitignore !.gitignore
...@@ -26,14 +26,14 @@ abstract class AbstractFrontendAction extends Core\Controller\AbstractWidgetActi ...@@ -26,14 +26,14 @@ abstract class AbstractFrontendAction extends Core\Controller\AbstractWidgetActi
* @var \ACP3\Core\Breadcrumb\Title * @var \ACP3\Core\Breadcrumb\Title
*/ */
protected $title; protected $title;
/**
* @var Core\Helpers\RedirectMessages
*/
protected $redirectMessages;
/** /**
* @var \ACP3\Core\Modules\Helper\Action * @var \ACP3\Core\Modules\Helper\Action
*/ */
protected $actionHelper; protected $actionHelper;
/**
* @var Core\Helpers\RedirectMessages
*/
private $redirectMessages;
/** /**
* @var string * @var string
*/ */
......
...@@ -226,9 +226,9 @@ class Mailer ...@@ -226,9 +226,9 @@ class Mailer
$this->phpMailer->Subject = $this->generateSubject(); $this->phpMailer->Subject = $this->generateSubject();
if (is_array($this->from) === true) { if (is_array($this->from) === true) {
$this->phpMailer->SetFrom($this->from['email'], $this->from['name']); $this->phpMailer->setFrom($this->from['email'], $this->from['name']);
} else { } else {
$this->phpMailer->SetFrom($this->from); $this->phpMailer->setFrom($this->from);
} }
$this->generateBody(); $this->generateBody();
...@@ -288,7 +288,8 @@ class Mailer ...@@ -288,7 +288,8 @@ class Mailer
if (!empty($this->body)) { if (!empty($this->body)) {
$this->phpMailer->AltBody = $this->decodeHtmlEntities($this->body . $this->getTextSignature()); $this->phpMailer->AltBody = $this->decodeHtmlEntities($this->body . $this->getTextSignature());
} else { } else {
$this->phpMailer->AltBody = $this->phpMailer->html2text($this->htmlBody . $this->getHtmlSignature(), true); $this->phpMailer->AltBody = $this->phpMailer->html2text($this->htmlBody . $this->getHtmlSignature(),
true);
} }
} else { } else {
$this->phpMailer->Body = $this->decodeHtmlEntities($this->body . $this->getTextSignature()); $this->phpMailer->Body = $this->decodeHtmlEntities($this->body . $this->getTextSignature());
...@@ -357,7 +358,7 @@ class Mailer ...@@ -357,7 +358,7 @@ class Mailer
* Adds multiple recipients to the to be send email * Adds multiple recipients to the to be send email
* *
* @param string|array $recipients * @param string|array $recipients
* @param bool $bcc * @param bool $bcc
* *
* @return $this * @return $this
*/ */
...@@ -387,7 +388,7 @@ class Mailer ...@@ -387,7 +388,7 @@ class Mailer
* *
* @param string $email * @param string $email
* @param string $name * @param string $name
* @param bool $bcc * @param bool $bcc
* *
* @return $this * @return $this
*/ */
...@@ -465,7 +466,9 @@ class Mailer ...@@ -465,7 +466,9 @@ class Mailer
$this->phpMailer->set('Mailer', 'smtp'); $this->phpMailer->set('Mailer', 'smtp');
$this->phpMailer->Host = $settings['mailer_smtp_host']; $this->phpMailer->Host = $settings['mailer_smtp_host'];
$this->phpMailer->Port = $settings['mailer_smtp_port']; $this->phpMailer->Port = $settings['mailer_smtp_port'];
$this->phpMailer->SMTPSecure = in_array($settings['mailer_smtp_security'], ['ssl', 'tls']) ? $settings['mailer_smtp_security'] : ''; $this->phpMailer->SMTPSecure = in_array($settings['mailer_smtp_security'], ['ssl', 'tls'])
? $settings['mailer_smtp_security']
: '';
if ((bool)$settings['mailer_smtp_auth'] === true) { if ((bool)$settings['mailer_smtp_auth'] === true) {
$this->phpMailer->SMTPAuth = true; $this->phpMailer->SMTPAuth = true;
$this->phpMailer->Username = $settings['mailer_smtp_user']; $this->phpMailer->Username = $settings['mailer_smtp_user'];
......
...@@ -97,7 +97,7 @@ abstract class AbstractModel ...@@ -97,7 +97,7 @@ abstract class AbstractModel
{ {
$this->eventDispatcher->dispatch( $this->eventDispatcher->dispatch(
$eventName, $eventName,
new ModelSaveEvent($data, $entryId) new ModelSaveEvent(static::EVENT_PREFIX, $data, $entryId)
); );
} }
......
...@@ -11,6 +11,10 @@ use Symfony\Component\EventDispatcher\Event; ...@@ -11,6 +11,10 @@ use Symfony\Component\EventDispatcher\Event;
class ModelSaveEvent extends Event class ModelSaveEvent extends Event
{ {
/**
* @var string
*/
private $moduleName;
/** /**
* @var array * @var array
*/ */
...@@ -22,15 +26,25 @@ class ModelSaveEvent extends Event ...@@ -22,15 +26,25 @@ class ModelSaveEvent extends Event
/** /**
* ModelSaveEvent constructor. * ModelSaveEvent constructor.
* @param string $moduleName
* @param array $data * @param array $data
* @param int|null|array $entryId * @param int|null|array $entryId
*/ */
public function __construct(array $data, $entryId) public function __construct($moduleName, array $data, $entryId)
{ {
$this->moduleName = $moduleName;
$this->data = $data; $this->data = $data;
$this->entryId = $entryId; $this->entryId = $entryId;
} }
/**
* @return string
*/
public function getModuleName()
{
return $this->moduleName;
}
/** /**
* @return array * @return array
*/ */
......
...@@ -4,16 +4,16 @@ ...@@ -4,16 +4,16 @@
* See the LICENCE file at the top-level module directory for licencing details. * See the LICENCE file at the top-level module directory for licencing details.
*/ */
namespace ACP3\Modules\ACP3\Articles\Search; namespace ACP3\Modules\ACP3\Articles\Extension;
use ACP3\Core\Date; use ACP3\Core\Date;
use ACP3\Core\Router\RouterInterface; use ACP3\Core\Router\RouterInterface;
use ACP3\Modules\ACP3\Articles\Installer\Schema; use ACP3\Modules\ACP3\Articles\Installer\Schema;
use ACP3\Modules\ACP3\Articles\Model\Repository\ArticleRepository; use ACP3\Modules\ACP3\Articles\Model\Repository\ArticleRepository;
use ACP3\Modules\ACP3\Search\Utility\SearchAvailabilityInterface; use ACP3\Modules\ACP3\Search\Extension\SearchAvailabilityExtensionInterface;
class SearchAvailability implements SearchAvailabilityInterface class SearchAvailabilityExtension implements SearchAvailabilityExtensionInterface
{ {
/** /**
* @var \ACP3\Core\Date * @var \ACP3\Core\Date
......
<?php
/**
* Copyright (c) 2016 by the ACP3 Developers.
* See the LICENCE file at the top-level module directory for licencing details.
*/
namespace ACP3\Modules\ACP3\Articles\Extension;
use ACP3\Core\Date;
use ACP3\Core\Router\Router;
use ACP3\Modules\ACP3\Articles\Installer\Schema;
use ACP3\Modules\ACP3\Articles\Model\Repository\ArticleRepository;
use ACP3\Modules\ACP3\Seo\Extension\AbstractSitemapAvailabilityExtension;
use ACP3\Modules\ACP3\Seo\Helper\MetaStatements;
class SitemapAvailabilityExtension extends AbstractSitemapAvailabilityExtension
{
/**
* @var Date
*/
protected $date;
/**
* @var ArticleRepository
*/
protected $articleRepository;
/**
* SitemapAvailability constructor.
* @param Date $date
* @param Router $router
* @param ArticleRepository $articleRepository
* @param MetaStatements $metaStatements
*/
public function __construct(
Date $date,
Router $router,
ArticleRepository $articleRepository,
MetaStatements $metaStatements
) {
parent::__construct($router, $metaStatements);
$this->date = $date;
$this->articleRepository = $articleRepository;
}
/**
* @return string
*/
public function getModuleName()
{
return Schema::MODULE_NAME;
}
public function fetchSitemapUrls()
{
$this->addUrl('articles/index/index');
foreach ($this->articleRepository->getAll($this->date->getCurrentDateTime()) as $result) {
$this->addUrl('articles/index/details/id_' . $result['id'], $result['start']);
}
}
}
services:
articles.extension.search_availability_extension:
class: ACP3\Modules\ACP3\Articles\Extension\SearchAvailabilityExtension
arguments:
- '@core.date'
- '@core.router'
- '@articles.model.articlerepository'
tags:
- { name: search.extension.search_availability }
articles.extension.sitemap_availability_extension:
class: ACP3\Modules\ACP3\Articles\Extension\SitemapAvailabilityExtension
arguments:
- '@core.date'
- '@core.router'
- '@articles.model.articlerepository'
- '@?seo.helper.meta_statements'
tags:
- { name: seo.extension.sitemap_availability }
services:
articles.search.search_availability:
class: ACP3\Modules\ACP3\Articles\Search\SearchAvailability
arguments:
- '@core.date'
- '@core.router'
- '@articles.model.articlerepository'
tags:
- { name: search.available_module }
...@@ -2,9 +2,9 @@ imports: ...@@ -2,9 +2,9 @@ imports:
- { resource: components/cache.yml } - { resource: components/cache.yml }
- { resource: components/controllers.yml } - { resource: components/controllers.yml }
- { resource: components/events.yml } - { resource: components/events.yml }
- { resource: components/extensions.yml }
- { resource: components/installer.yml } - { resource: components/installer.yml }
- { resource: components/models.yml } - { resource: components/models.yml }
- { resource: components/search.yml }
- { resource: components/validation.yml } - { resource: components/validation.yml }
services: services:
......
<?php
/**
* Copyright (c) 2016 by the ACP3 Developers.
* See the LICENCE file at the top-level module directory for licencing details.
*/
namespace ACP3\Modules\ACP3\Contact\Extension;
use ACP3\Modules\ACP3\Contact\Installer\Schema;
use ACP3\Modules\ACP3\Seo\Extension\AbstractSitemapAvailabilityExtension;
class SitemapAvailabilityExtension extends AbstractSitemapAvailabilityExtension
{
/**
* @return string
*/
public function getModuleName()
{
return Schema::MODULE_NAME;
}
public function fetchSitemapUrls()
{
$routeNames = [
'contact/index/index',
'contact/index/imprint'
];
foreach ($routeNames as $routeName) {
$this->addUrl($routeName);
}
}
}
services:
contact.extension.sitemap_availability_extension:
class: ACP3\Modules\ACP3\Contact\Extension\SitemapAvailabilityExtension
arguments:
- '@core.router'
- '@?seo.helper.meta_statements'
tags:
- { name: seo.extension.sitemap_availability }
imports: imports:
- { resource: components/controllers.yml } - { resource: components/controllers.yml }
- { resource: components/extensions.yml }
- { resource: components/installer.yml } - { resource: components/installer.yml }
- { resource: components/models.yml } - { resource: components/models.yml }
- { resource: components/validation.yml } - { resource: components/validation.yml }
...@@ -23,19 +23,19 @@ class Index extends Core\Controller\AbstractFrontendAction ...@@ -23,19 +23,19 @@ class Index extends Core\Controller\AbstractFrontendAction
*/ */
protected $feedGenerator; protected $feedGenerator;
/** /**
* @var Feeds\Utility\AvailableFeedsRegistrar * @var Feeds\Utility\FeedAvailabilityRegistrar
*/ */
protected $availableFeedsRegistrar; protected $availableFeedsRegistrar;
/** /**
* @param \ACP3\Core\Controller\Context\FrontendContext $context * @param \ACP3\Core\Controller\Context\FrontendContext $context
* @param \ACP3\Modules\ACP3\Feeds\View\Renderer\FeedGenerator $feedGenerator * @param \ACP3\Modules\ACP3\Feeds\View\Renderer\FeedGenerator $feedGenerator
* @param Feeds\Utility\AvailableFeedsRegistrar $availableFeedsRegistrar * @param Feeds\Utility\FeedAvailabilityRegistrar $availableFeedsRegistrar
*/ */
public function __construct( public function __construct(
Core\Controller\Context\FrontendContext $context, Core\Controller\Context\FrontendContext $context,
Feeds\View\Renderer\FeedGenerator $feedGenerator, Feeds\View\Renderer\FeedGenerator $feedGenerator,
Feeds\Utility\AvailableFeedsRegistrar $availableFeedsRegistrar Feeds\Utility\FeedAvailabilityRegistrar $availableFeedsRegistrar
) { ) {
parent::__construct($context); parent::__construct($context);
......
...@@ -20,8 +20,8 @@ class FeedAvailabilityCompilerPass implements CompilerPassInterface ...@@ -20,8 +20,8 @@ class FeedAvailabilityCompilerPass implements CompilerPassInterface
*/ */
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
$definition = $container->findDefinition('feeds.utility.available_feeds_registrar'); $definition = $container->findDefinition('feeds.utility.feed_availability_registrar');
$plugins = $container->findTaggedServiceIds('feeds.available_module'); $plugins = $container->findTaggedServiceIds('feeds.extension.feed_availability');
foreach ($plugins as $serviceId => $tags) { foreach ($plugins as $serviceId => $tags) {
$definition->addMethodCall( $definition->addMethodCall(
......
...@@ -8,7 +8,7 @@ namespace ACP3\Modules\ACP3\Feeds\Event\Listener; ...@@ -8,7 +8,7 @@ namespace ACP3\Modules\ACP3\Feeds\Event\Listener;
use ACP3\Core\View; use ACP3\Core\View;
use ACP3\Modules\ACP3\Feeds\Utility\AvailableFeedsRegistrar; use ACP3\Modules\ACP3\Feeds\Utility\FeedAvailabilityRegistrar;
class OnLayoutHeadListener class OnLayoutHeadListener
{ {
...@@ -17,24 +17,24 @@ class OnLayoutHeadListener ...@@ -17,24 +17,24 @@ class OnLayoutHeadListener
*/ */
private $view; private $view;
/** /**
* @var AvailableFeedsRegistrar * @var FeedAvailabilityRegistrar
*/ */
private $availableFeedsRegistrar; private $availableFeedsRegistrar;
/** /**
* OnLayoutHeadListener constructor. * OnLayoutHeadListener constructor.
* @param View $view * @param View $view
* @param AvailableFeedsRegistrar $availableFeedsRegistrar * @param FeedAvailabilityRegistrar $availableFeedsRegistrar
*/ */
public function __construct( public function __construct(
View $view, View $view,
AvailableFeedsRegistrar $availableFeedsRegistrar FeedAvailabilityRegistrar $availableFeedsRegistrar
) { ) {
$this->view = $view; $this->view = $view;
$this->availableFeedsRegistrar = $availableFeedsRegistrar; $this->availableFeedsRegistrar = $availableFeedsRegistrar;
} }
public function renderFeedLinkTags() public function renderFeedLinks()
{ {
$this->view->assign('available_feeds', $this->availableFeedsRegistrar->getAvailableModuleNames()); $this->view->assign('available_feeds', $this->availableFeedsRegistrar->getAvailableModuleNames());
......
...@@ -4,10 +4,10 @@ ...@@ -4,10 +4,10 @@
* See the LICENCE file at the top-level module directory for licencing details. * See the LICENCE file at the top-level module directory for licencing details.
*/ */
namespace ACP3\Modules\ACP3\Feeds\Utility; namespace ACP3\Modules\ACP3\Feeds\Extension;
interface FeedAvailabilityInterface interface FeedAvailabilityExtensionInterface
{ {
/** /**
* @return string * @return string
......
...@@ -12,4 +12,4 @@ services: ...@@ -12,4 +12,4 @@ services:
arguments: arguments:
- '@core.context.frontend' - '@core.context.frontend'
- '@feeds.view.renderer.feedgenerator' - '@feeds.view.renderer.feedgenerator'
- '@feeds.utility.available_feeds_registrar' - '@feeds.utility.feed_availability_registrar'
...@@ -3,6 +3,6 @@ services: ...@@ -3,6 +3,6 @@ services:
class: ACP3\Modules\ACP3\Feeds\Event\Listener\OnLayoutHeadListener class: ACP3\Modules\ACP3\Feeds\Event\Listener\OnLayoutHeadListener
arguments: arguments:
- '@core.view' - '@core.view'
- '@feeds.utility.available_feeds_registrar' - '@feeds.utility.feed_availability_registrar'
tags: tags:
- { name: core.eventListener, event: core.layout.head, method: renderFeedLinkTags } - { name: core.eventListener, event: core.layout.head, method: renderFeedLinks }
...@@ -11,5 +11,5 @@ services: ...@@ -11,5 +11,5 @@ services:
- '@core.config' - '@core.config'
- '@core.router' - '@core.router'
feeds.utility.available_feeds_registrar: feeds.utility.feed_availability_registrar:
class: ACP3\Modules\ACP3\Feeds\Utility\AvailableFeedsRegistrar class: ACP3\Modules\ACP3\Feeds\Utility\FeedAvailabilityRegistrar
...@@ -7,18 +7,20 @@ ...@@ -7,18 +7,20 @@
namespace ACP3\Modules\ACP3\Feeds\Utility; namespace ACP3\Modules\ACP3\Feeds\Utility;
class AvailableFeedsRegistrar use ACP3\Modules\ACP3\Feeds\Extension\FeedAvailabilityExtensionInterface;
class FeedAvailabilityRegistrar
{ {
/** /**
* @var FeedAvailabilityInterface[] * @var FeedAvailabilityExtensionInterface[]
*/ */
protected $availableModules = []; protected $availableModules = [];
/** /**
* @param FeedAvailabilityInterface $searchAvailability * @param FeedAvailabilityExtensionInterface $searchAvailability
* @return $this * @return $this
*/ */
public function registerModule(FeedAvailabilityInterface $searchAvailability) public function registerModule(FeedAvailabilityExtensionInterface $searchAvailability)
{ {
$this->availableModules[$searchAvailability->getModuleName()] = $searchAvailability; $this->availableModules[$searchAvailability->getModuleName()] = $searchAvailability;
...@@ -35,7 +37,7 @@ class AvailableFeedsRegistrar ...@@ -35,7 +37,7 @@ class AvailableFeedsRegistrar
/** /**
* @param string $moduleName * @param string $moduleName
* @return FeedAvailabilityInterface * @return FeedAvailabilityExtensionInterface
*/ */
public function getFeedItemsByModuleName($moduleName) public function getFeedItemsByModuleName($moduleName)
{ {
......
...@@ -4,17 +4,17 @@ ...@@ -4,17 +4,17 @@
* See the LICENCE file at the top-level module directory for licencing details. * See the LICENCE file at the top-level module directory for licencing details.
*/ */
namespace ACP3\Modules\ACP3\Files\Feeds; namespace ACP3\Modules\ACP3\Files\Extension;
use ACP3\Core\Date; use ACP3\Core\Date;
use ACP3\Core\Helpers\StringFormatter; use ACP3\Core\Helpers\StringFormatter;
use ACP3\Core\Router\RouterInterface; use ACP3\Core\Router\RouterInterface;
use ACP3\Modules\ACP3\Feeds\Utility\FeedAvailabilityInterface; use ACP3\Modules\ACP3\Feeds\Extension\FeedAvailabilityExtensionInterface;
use ACP3\Modules\ACP3\Files\Installer\Schema; use ACP3\Modules\ACP3\Files\Installer\Schema;
use ACP3\Modules\ACP3\Files\Model\Repository\FilesRepository; use ACP3\Modules\ACP3\Files\Model\Repository\FilesRepository;
class FeedAvailability implements FeedAvailabilityInterface class FeedAvailabilityExtension implements FeedAvailabilityExtensionInterface
{ {
/** /**
* @var \ACP3\Core\Date * @var \ACP3\Core\Date
......
...@@ -4,16 +4,16 @@ ...@@ -4,16 +4,16 @@
* See the LICENCE file at the top-level module directory for licencing details. * See the LICENCE file at the top-level module directory for licencing details.
*/ */
namespace ACP3\Modules\ACP3\Files\Search; namespace ACP3\Modules\ACP3\Files\Extension;
use ACP3\Core\Date; use ACP3\Core\Date;
use ACP3\Core\Router\RouterInterface; use ACP3\Core\Router\RouterInterface;
use ACP3\Modules\ACP3\Files\Installer\Schema; use ACP3\Modules\ACP3\Files\Installer\Schema;
use ACP3\Modules\ACP3\Files\Model\Repository\FilesRepository; use ACP3\Modules\ACP3\Files\Model\Repository\FilesRepository;
use ACP3\Modules\ACP3\Search\Utility\SearchAvailabilityInterface; use ACP3\Modules\ACP3\Search\Extension\SearchAvailabilityExtensionInterface;
class SearchAvailability implements SearchAvailabilityInterface class SearchAvailabilityExtension implements SearchAvailabilityExtensionInterface
{ {
/** /**
* @var \ACP3\Core\Date * @var \ACP3\Core\Date
......
<?php
/**
* Copyright (c) 2016 by the ACP3 Developers.
* See the LICENCE file at the top-level module directory for licencing details.
*/
namespace ACP3\Modules\ACP3\Files\Extension;
use ACP3\Core\Date;
use ACP3\Core\Router\Router;
use ACP3\Modules\ACP3\Categories\Model\Repository\CategoryRepository;
use ACP3\Modules\ACP3\Files\Installer\Schema;