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 @@
/cache/*
/node_modules
/tests/cache/
/uploads/.htaccess
/uploads/assets/*
/uploads/categories/*
/uploads/files/*
/uploads/gallery/*
/uploads/kcfinder/*
/vendor
/.htaccess
/sitemap*.xml
!.gitignore
......@@ -26,14 +26,14 @@ abstract class AbstractFrontendAction extends Core\Controller\AbstractWidgetActi
* @var \ACP3\Core\Breadcrumb\Title
*/
protected $title;
/**
* @var Core\Helpers\RedirectMessages
*/
protected $redirectMessages;
/**
* @var \ACP3\Core\Modules\Helper\Action
*/
protected $actionHelper;
/**
* @var Core\Helpers\RedirectMessages
*/
private $redirectMessages;
/**
* @var string
*/
......
......@@ -226,9 +226,9 @@ class Mailer
$this->phpMailer->Subject = $this->generateSubject();
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 {
$this->phpMailer->SetFrom($this->from);
$this->phpMailer->setFrom($this->from);
}
$this->generateBody();
......@@ -288,7 +288,8 @@ class Mailer
if (!empty($this->body)) {
$this->phpMailer->AltBody = $this->decodeHtmlEntities($this->body . $this->getTextSignature());
} else {
$this->phpMailer->AltBody = $this->phpMailer->html2text($this->htmlBody . $this->getHtmlSignature(), true);
$this->phpMailer->AltBody = $this->phpMailer->html2text($this->htmlBody . $this->getHtmlSignature(),
true);
}
} else {
$this->phpMailer->Body = $this->decodeHtmlEntities($this->body . $this->getTextSignature());
......@@ -465,7 +466,9 @@ class Mailer
$this->phpMailer->set('Mailer', 'smtp');
$this->phpMailer->Host = $settings['mailer_smtp_host'];
$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) {
$this->phpMailer->SMTPAuth = true;
$this->phpMailer->Username = $settings['mailer_smtp_user'];
......
......@@ -97,7 +97,7 @@ abstract class AbstractModel
{
$this->eventDispatcher->dispatch(
$eventName,
new ModelSaveEvent($data, $entryId)
new ModelSaveEvent(static::EVENT_PREFIX, $data, $entryId)
);
}
......
......@@ -11,6 +11,10 @@ use Symfony\Component\EventDispatcher\Event;
class ModelSaveEvent extends Event
{
/**
* @var string
*/
private $moduleName;
/**
* @var array
*/
......@@ -22,15 +26,25 @@ class ModelSaveEvent extends Event
/**
* ModelSaveEvent constructor.
* @param string $moduleName
* @param array $data
* @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->entryId = $entryId;
}
/**
* @return string
*/
public function getModuleName()
{
return $this->moduleName;
}
/**
* @return array
*/
......
......@@ -4,16 +4,16 @@
* 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\Router\RouterInterface;
use ACP3\Modules\ACP3\Articles\Installer\Schema;
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
......
<?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:
- { resource: components/cache.yml }
- { resource: components/controllers.yml }
- { resource: components/events.yml }
- { resource: components/extensions.yml }
- { resource: components/installer.yml }
- { resource: components/models.yml }
- { resource: components/search.yml }
- { resource: components/validation.yml }
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:
- { resource: components/controllers.yml }
- { resource: components/extensions.yml }
- { resource: components/installer.yml }
- { resource: components/models.yml }
- { resource: components/validation.yml }
......@@ -23,19 +23,19 @@ class Index extends Core\Controller\AbstractFrontendAction
*/
protected $feedGenerator;
/**
* @var Feeds\Utility\AvailableFeedsRegistrar
* @var Feeds\Utility\FeedAvailabilityRegistrar
*/
protected $availableFeedsRegistrar;
/**
* @param \ACP3\Core\Controller\Context\FrontendContext $context
* @param \ACP3\Modules\ACP3\Feeds\View\Renderer\FeedGenerator $feedGenerator
* @param Feeds\Utility\AvailableFeedsRegistrar $availableFeedsRegistrar
* @param Feeds\Utility\FeedAvailabilityRegistrar $availableFeedsRegistrar
*/
public function __construct(
Core\Controller\Context\FrontendContext $context,
Feeds\View\Renderer\FeedGenerator $feedGenerator,
Feeds\Utility\AvailableFeedsRegistrar $availableFeedsRegistrar
Feeds\Utility\FeedAvailabilityRegistrar $availableFeedsRegistrar
) {
parent::__construct($context);
......
......@@ -20,8 +20,8 @@ class FeedAvailabilityCompilerPass implements CompilerPassInterface
*/
public function process(ContainerBuilder $container)
{
$definition = $container->findDefinition('feeds.utility.available_feeds_registrar');
$plugins = $container->findTaggedServiceIds('feeds.available_module');
$definition = $container->findDefinition('feeds.utility.feed_availability_registrar');
$plugins = $container->findTaggedServiceIds('feeds.extension.feed_availability');
foreach ($plugins as $serviceId => $tags) {
$definition->addMethodCall(
......
......@@ -8,7 +8,7 @@ namespace ACP3\Modules\ACP3\Feeds\Event\Listener;
use ACP3\Core\View;
use ACP3\Modules\ACP3\Feeds\Utility\AvailableFeedsRegistrar;
use ACP3\Modules\ACP3\Feeds\Utility\FeedAvailabilityRegistrar;
class OnLayoutHeadListener
{
......@@ -17,24 +17,24 @@ class OnLayoutHeadListener
*/
private $view;
/**
* @var AvailableFeedsRegistrar
* @var FeedAvailabilityRegistrar
*/
private $availableFeedsRegistrar;
/**
* OnLayoutHeadListener constructor.
* @param View $view
* @param AvailableFeedsRegistrar $availableFeedsRegistrar
* @param FeedAvailabilityRegistrar $availableFeedsRegistrar
*/
public function __construct(
View $view,
AvailableFeedsRegistrar $availableFeedsRegistrar
FeedAvailabilityRegistrar $availableFeedsRegistrar
) {
$this->view = $view;
$this->availableFeedsRegistrar = $availableFeedsRegistrar;
}
public function renderFeedLinkTags()
public function renderFeedLinks()
{
$this->view->assign('available_feeds', $this->availableFeedsRegistrar->getAvailableModuleNames());
......
......@@ -4,10 +4,10 @@
* 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
......
......@@ -12,4 +12,4 @@ services:
arguments:
- '@core.context.frontend'
- '@feeds.view.renderer.feedgenerator'
- '@feeds.utility.available_feeds_registrar'
- '@feeds.utility.feed_availability_registrar'
......@@ -3,6 +3,6 @@ services:
class: ACP3\Modules\ACP3\Feeds\Event\Listener\OnLayoutHeadListener
arguments:
- '@core.view'
- '@feeds.utility.available_feeds_registrar'
- '@feeds.utility.feed_availability_registrar'
tags:
- { name: core.eventListener, event: core.layout.head, method: renderFeedLinkTags }
- { name: core.eventListener, event: core.layout.head, method: renderFeedLinks }
......@@ -11,5 +11,5 @@ services:
- '@core.config'
- '@core.router'
feeds.utility.available_feeds_registrar:
class: ACP3\Modules\ACP3\Feeds\Utility\AvailableFeedsRegistrar
feeds.utility.feed_availability_registrar:
class: ACP3\Modules\ACP3\Feeds\Utility\FeedAvailabilityRegistrar
......@@ -7,18 +7,20 @@
namespace ACP3\Modules\ACP3\Feeds\Utility;
class AvailableFeedsRegistrar
use ACP3\Modules\ACP3\Feeds\Extension\FeedAvailabilityExtensionInterface;
class FeedAvailabilityRegistrar
{
/**
* @var FeedAvailabilityInterface[]
* @var FeedAvailabilityExtensionInterface[]
*/
protected $availableModules = [];
/**
* @param FeedAvailabilityInterface $searchAvailability
* @param FeedAvailabilityExtensionInterface $searchAvailability
* @return $this
*/
public function registerModule(FeedAvailabilityInterface $searchAvailability)
public function registerModule(FeedAvailabilityExtensionInterface $searchAvailability)
{
$this->availableModules[$searchAvailability->getModuleName()] = $searchAvailability;
......@@ -35,7 +37,7 @@ class AvailableFeedsRegistrar
/**
* @param string $moduleName
* @return FeedAvailabilityInterface
* @return FeedAvailabilityExtensionInterface
*/
public function getFeedItemsByModuleName($moduleName)
{
......
......@@ -4,17 +4,17 @@
* 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\Helpers\StringFormatter;
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\Model\Repository\FilesRepository;
class FeedAvailability implements FeedAvailabilityInterface
class FeedAvailabilityExtension implements FeedAvailabilityExtensionInterface
{
/**
* @var \ACP3\Core\Date
......
......@@ -4,16 +4,16 @@
* 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\Router\RouterInterface;
use ACP3\Modules\ACP3\Files\Installer\Schema;
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
......
<?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;
use ACP3\Modules\ACP3\Files\Model\Repository\FilesRepository;
use ACP3\Modules\ACP3\Seo\Extension\AbstractSitemapAvailabilityExtension;
use ACP3\Modules\ACP3\Seo\Helper\MetaStatements;
class SitemapAvailabilityExtension extends AbstractSitemapAvailabilityExtension
{
/**
* @var Date
*/
protected $date;
/**
* @var FilesRepository
*/
protected $filesRepository;
/**
* @var CategoryRepository
*/
protected $categoryRepository;
/**
* SitemapAvailabilityExtension constructor.
* @param Date $date
* @param Router $router
* @param FilesRepository $filesRepository
* @param CategoryRepository $categoryRepository
* @param MetaStatements $metaStatements
*/
public function __construct(
Date $date,
Router $router,
FilesRepository $filesRepository,
CategoryRepository $categoryRepository,
MetaStatements $metaStatements
) {
parent::__construct($router, $metaStatements);
$this->date = $date;
$this->filesRepository = $filesRepository;
$this->categoryRepository = $categoryRepository;
}
/**
* @return string
*/
public function getModuleName()
{
return Schema::MODULE_NAME;
}
public function fetchSitemapUrls()
{
$this->addUrl('files/index/index');
foreach ($this->categoryRepository->getAllByModuleName(Schema::MODULE_NAME) as $category) {
$this->addUrl('files/index/files/cat_' . $category['id']);
}
foreach ($this->filesRepository->getAll($this->date->getCurrentDateTime()) as $result) {
$this->addUrl('files/index/details/id_' . $result['id'], $result['start']);
}
}
}
services:
files.extenion.feed_availability_extension:
class: ACP3\Modules\ACP3\Files\Extension\FeedAvailabilityExtension
arguments:
- '@core.date'
- '@core.router'
- '@core.helpers.string_formatter'
- '@files.model.filesrepository'
tags:
- { name: feeds.extension.feed_availability }
files.extension.search_availability_extension:
class: ACP3\Modules\ACP3\Files\Extension\SearchAvailabilityExtension
arguments:
- '@core.date'
- '@core.router'
- '@files.model.filesrepository'
tags:
- { name: search.extension.search_availability }
files.extension.sitemap_availability_extension:
class: ACP3\Modules\ACP3\Files\Extension\SitemapAvailabilityExtension
arguments:
- '@core.date'
- '@core.router'
- '@files.model.filesrepository'
- '@categories.model.categories_repository'
- '@?seo.helper.meta_statements'
tags:
- { name: seo.extension.sitemap_availability }
services:
files.feeds.feed_availability:
class: ACP3\Modules\ACP3\Files\Feeds\FeedAvailability
arguments:
- '@core.date'
- '@core.router'
- '@core.helpers.string_formatter'
- '@files.model.filesrepository'
tags:
- { name: feeds.available_module }
services:
files.search.search_availability:
class: ACP3\Modules\ACP3\Files\Search\SearchAvailability
arguments:
- '@core.date'
- '@core.router'
- '@files.model.filesrepository'
tags:
- { name: search.available_module }
......@@ -2,8 +2,7 @@ imports:
- { resource: components/cache.yml }
- { resource: components/controllers.yml }
- { resource: components/events.yml }
- { resource: components/feeds.yml }
- { resource: components/extensions.yml }
- { resource: components/installer.yml }
- { resource: components/models.yml }
- { resource: components/search.yml }
- { resource: components/validation.yml }
<?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\Gallery\Extension;
use ACP3\Core\Date;
use ACP3\Core\Router\Router;
use ACP3\Modules\ACP3\Gallery\Installer\Schema;
use ACP3\Modules\ACP3\Gallery\Model\Repository\GalleryRepository;
use ACP3\Modules\ACP3\Gallery\Model\Repository\PictureRepository;
use ACP3\Modules\ACP3\Seo\Extension\AbstractSitemapAvailabilityExtension;
use ACP3\Modules\ACP3\Seo\Helper\MetaStatements;
class SitemapAvailabilityExtension extends AbstractSitemapAvailabilityExtension
{
/**
* @var Date
*/
protected $date;
/**
* @var GalleryRepository
*/
protected $galleryRepository;
/**
* @var PictureRepository
*/
protected $pictureRepository;
/**
* SitemapAvailabilityExtension constructor.
* @param Date $date
* @param Router $router
* @param GalleryRepository $galleryRepository
* @param PictureRepository $pictureRepository
* @param MetaStatements $metaStatements
*/
public function __construct(
Date $date,
Router $router,
GalleryRepository $galleryRepository,
PictureRepository $pictureRepository,
MetaStatements $metaStatements
) {
parent::__construct($router, $metaStatements);