Commit fa3cc218 authored by Tino Goratsch's avatar Tino Goratsch

Merge branch 'feature/share-ratings' into develop

parents 72da6e5c 3dbb36e7
......@@ -8,6 +8,7 @@
namespace ACP3\Core\Assets;
use ACP3\Core\Assets\Event\AddLibraryEvent;
use ACP3\Core\Http\RequestInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class Libraries
......@@ -22,12 +23,20 @@ class Libraries
],
'jquery' => [
'enabled' => true,
'enabled_for_ajax' => false,
'js' => 'jquery.min.js',
],
'js-cookie' => [
'enabled' => true,
'enabled' => false,
'enabled_for_ajax' => false,
'js' => 'js.cookie.js',
],
'ajax-form' => [
'enabled' => true,
'enabled_for_ajax' => false,
'dependencies' => ['jquery'],
'js' => 'ajax-form.js',
],
'fancybox' => [
'enabled' => false,
'dependencies' => ['jquery'],
......@@ -66,15 +75,23 @@ class Libraries
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* @var \ACP3\Core\Http\RequestInterface
*/
private $request;
/**
* Libraries constructor.
*
* @param EventDispatcherInterface $eventDispatcher
* @param \ACP3\Core\Http\RequestInterface $request
* @param EventDispatcherInterface $eventDispatcher
*/
public function __construct(EventDispatcherInterface $eventDispatcher)
public function __construct(
RequestInterface $request,
EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
$this->request = $request;
}
public function dispatchAddLibraryEvent()
......@@ -140,11 +157,28 @@ class Libraries
{
$enabledLibraries = [];
foreach ($this->libraries as $library => $values) {
if ($values['enabled'] === true) {
$enabledLibraries[] = $library;
if ($this->includeInXmlHttpRequest($values)) {
continue;
}
if ($values['enabled'] === false) {
continue;
}
$enabledLibraries[] = $library;
}
return $enabledLibraries;
}
/**
* @param array $values
*
* @return bool
*/
private function includeInXmlHttpRequest(array $values): bool
{
return $this->request->isXmlHttpRequest()
&& isset($values['enabled_for_ajax'])
&& $values['enabled_for_ajax'] === false;
}
}
......@@ -48,6 +48,7 @@ services:
core.assets.libraries:
class: ACP3\Core\Assets\Libraries
arguments:
- '@core.http.request'
- '@core.event_dispatcher'
core.assets.page_css_classes:
......
......@@ -27,5 +27,5 @@
{/if}
</ul>
{/if}
{event name="share.layout.add_shariff"}
{event name="share.layout.add_social_sharing"}
{/block}
......@@ -18,7 +18,4 @@
{event name="captcha.event.display_captcha"}
{include file="asset:System/Partials/form_group.submit.tpl" form_token=$form_token}
</form>
{javascripts}
{include_js module="system" file="ajax-form"}
{/javascripts}
{/if}
......@@ -25,7 +25,7 @@
</a>
</footer>
</div>
{event name="share.layout.add_shariff"}
{event name="share.layout.add_social_sharing"}
</section>
{if $comments_allowed === true}
<section>
......
......@@ -21,7 +21,7 @@
<div class="description">
{$picture.description}
</div>
{event name="share.layout.add_shariff"}
{event name="share.layout.add_social_sharing"}
<footer>
<ul class="pagination">
{if !empty($picture_previous)}
......
......@@ -27,7 +27,7 @@
</a>
{/foreach}
{/if}
{event name="share.layout.add_shariff"}
{event name="share.layout.add_social_sharing"}
{else}
{include file="asset:System/Partials/no_results.tpl" no_results_text={lang t="gallery|no_pictures"}}
{/if}
......
......@@ -104,9 +104,6 @@
</table>
</div>
{if isset($pages_list)}
{javascripts}
{include_js module="system" file="ajax-form"}
{/javascripts}
{if $can_delete === true}
{include file="asset:System/Partials/mark.tpl"}
{/if}
......
......@@ -21,7 +21,7 @@
{/if}
</div>
{event name="news.event.news_details_after" id=$news.id title=$news.title}
{event name="share.layout.add_shariff"}
{event name="share.layout.add_social_sharing"}
</section>
{if $comments_allowed === true}
<section>
......
......@@ -12,7 +12,4 @@
{/block}
{block ADMIN_GRID_CONTENT}
{include file="asset:System/Partials/datagrid.tpl" dataTable=$grid}
{javascripts}
{include_js module="system" file="ajax-form"}
{/javascripts}
{/block}
......@@ -13,6 +13,6 @@
<div class="content">
{$newsletter.text|nl2p}
</div>
{event name="share.layout.add_shariff"}
{event name="share.layout.add_social_sharing"}
</article>
{/block}
......@@ -74,8 +74,10 @@ class PollsModel extends AbstractModel implements UpdatedAtAwareModelInterface
* @param int $pollId
*
* @return bool|int
*
* @throws \Doctrine\DBAL\ConnectionException
*/
public function saveAnswers(array $answers, $pollId)
public function saveAnswers(array $answers, int $pollId)
{
$bool = false;
foreach ($answers as $row) {
......@@ -104,8 +106,10 @@ class PollsModel extends AbstractModel implements UpdatedAtAwareModelInterface
* @param int $pollId
*
* @return bool|int
*
* @throws \Doctrine\DBAL\ConnectionException
*/
public function resetVotesByPollId($pollId)
public function resetVotesByPollId(int $pollId)
{
return $this->voteRepository->delete($pollId, 'poll_id');
}
......
......@@ -55,7 +55,6 @@
{include file="asset:System/Partials/form_group.submit_split.tpl" form_token=$form_token back_url={uri args="acp/polls"}}
</form>
{javascripts}
{include_js module="system" file="ajax-form"}
{include_js module="system" file="hash-change"}
{/javascripts}
{/if}
......
......@@ -8,6 +8,7 @@
namespace ACP3\Modules\ACP3\Seo\Event\Listener;
use ACP3\Core\Model\Event\ModelSaveEvent;
use ACP3\Core\Modules;
use ACP3\Core\Settings\SettingsInterface;
use ACP3\Modules\ACP3\Seo\Exception\SitemapGenerationException;
use ACP3\Modules\ACP3\Seo\Installer\Schema;
......@@ -33,17 +34,23 @@ class GenerateSitemapOnModelAfterSaveListener
* @var SitemapGenerationModel
*/
private $sitemapGenerationModel;
/**
* @var \ACP3\Core\Modules
*/
private $modules;
/**
* GenerateSitemapOnModelAfterSaveListener constructor.
*
* @param LoggerInterface $logger
* @param \ACP3\Core\Modules $modules
* @param SettingsInterface $settings
* @param SitemapAvailabilityRegistrar $sitemapRegistrar
* @param SitemapGenerationModel $sitemapGenerationModel
*/
public function __construct(
LoggerInterface $logger,
Modules $modules,
SettingsInterface $settings,
SitemapAvailabilityRegistrar $sitemapRegistrar,
SitemapGenerationModel $sitemapGenerationModel
......@@ -52,6 +59,7 @@ class GenerateSitemapOnModelAfterSaveListener
$this->settings = $settings;
$this->sitemapRegistrar = $sitemapRegistrar;
$this->sitemapGenerationModel = $sitemapGenerationModel;
$this->modules = $modules;
}
/**
......@@ -59,6 +67,10 @@ class GenerateSitemapOnModelAfterSaveListener
*/
public function generateSeoSitemap(ModelSaveEvent $event)
{
if (!$this->modules->isActive(Schema::MODULE_NAME)) {
return;
}
if ($this->canGenerateSitemapAutomatically() && $this->isAllowedModule($event->getModuleName())) {
try {
$this->sitemapGenerationModel->save();
......
......@@ -61,6 +61,8 @@ class SitemapGenerationModel
/**
* @return bool
*
* @throws \ACP3\Modules\ACP3\Seo\Exception\SitemapGenerationException
*/
public function save()
{
......@@ -82,7 +84,7 @@ class SitemapGenerationModel
*
* @throws SitemapGenerationException
*/
protected function checkSitemapFilePermissions($filename)
protected function checkSitemapFilePermissions(string $filename)
{
$filePath = $this->getSitemapFilePath($filename);
......@@ -103,7 +105,7 @@ class SitemapGenerationModel
*
* @return string
*/
protected function getSitemapFilePath($filename)
protected function getSitemapFilePath(string $filename)
{
return ACP3_ROOT_DIR . $filename;
}
......@@ -113,7 +115,7 @@ class SitemapGenerationModel
*
* @return Urlset
*/
protected function collectSitemapItems($isSecure)
protected function collectSitemapItems(?bool $isSecure)
{
$urlSet = new Urlset();
foreach ($this->sitemapRegistrar->getAvailableModules() as $module) {
......@@ -131,7 +133,7 @@ class SitemapGenerationModel
*
* @return bool
*/
protected function saveSitemap(Urlset $urlSet, $filename)
protected function saveSitemap(Urlset $urlSet, string $filename)
{
$output = (new Output())->getOutput($urlSet);
......
......@@ -39,6 +39,7 @@ services:
class: ACP3\Modules\ACP3\Seo\Event\Listener\GenerateSitemapOnModelAfterSaveListener
arguments:
- '@seo.sitemap_logger'
- '@core.modules'
- '@core.config'
- '@seo.utility.sitemap_availability_registrar'
- '@seo.model.sitemap_generation_model'
......
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENSE file at the top-level module directory for licensing details.
*/
namespace ACP3\Modules\ACP3\Share\Controller\Frontend\Index;
use ACP3\Core\Controller\Context\FrontendContext;
use ACP3\Core\Controller\Exception\ResultNotExistsException;
use ACP3\Modules\ACP3\Comments\Controller\Frontend\Index\AbstractFrontendAction;
use ACP3\Modules\ACP3\Share\Model\Repository\ShareRatingsRepository;
use ACP3\Modules\ACP3\Share\Model\Repository\ShareRepository;
use ACP3\Modules\ACP3\Share\Model\ShareRatingModel;
class Rate extends AbstractFrontendAction
{
/**
* @var \ACP3\Modules\ACP3\Share\Model\Repository\ShareRepository
*/
private $shareRepository;
/**
* @var \ACP3\Modules\ACP3\Share\Model\Repository\ShareRatingsRepository
*/
private $shareRatingsRepository;
/**
* @var \ACP3\Modules\ACP3\Share\Model\ShareRatingModel
*/
private $shareRatingModel;
/**
* Rate constructor.
*
* @param \ACP3\Core\Controller\Context\FrontendContext $context
* @param \ACP3\Modules\ACP3\Share\Model\Repository\ShareRepository $shareRepository
* @param \ACP3\Modules\ACP3\Share\Model\Repository\ShareRatingsRepository $shareRatingsRepository
* @param \ACP3\Modules\ACP3\Share\Model\ShareRatingModel $shareRatingModel
*/
public function __construct(
FrontendContext $context,
ShareRepository $shareRepository,
ShareRatingsRepository $shareRatingsRepository,
ShareRatingModel $shareRatingModel)
{
parent::__construct($context);
$this->shareRepository = $shareRepository;
$this->shareRatingsRepository = $shareRatingsRepository;
$this->shareRatingModel = $shareRatingModel;
}
/**
* @param int $id
* @param int $stars
*
* @return array
*
* @throws \ACP3\Core\Controller\Exception\ResultNotExistsException
* @throws \Doctrine\DBAL\DBALException
*/
public function execute(int $id, int $stars): array
{
if (!($stars >= 1 && $stars <= 5)) {
throw new ResultNotExistsException();
}
if ($this->shareRepository->resultExistsById($id) === false) {
throw new ResultNotExistsException();
}
$this->shareRatingModel->save([
'share_id' => $id,
'stars' => $stars,
]);
return [
'rating' => $this->shareRatingsRepository->getRatingStatistics($id),
];
}
}
......@@ -63,7 +63,7 @@ class Index extends AbstractWidgetAction
'shariff' => [
'lang' => $this->translator->getShortIsoCode(),
'path' => $path,
'services' => \json_encode($this->getServices($sharingInfo)),
'services' => $this->getServices($sharingInfo),
],
];
}
......@@ -83,6 +83,6 @@ class Index extends AbstractWidgetAction
$services = $this->socialServices->getActiveServices();
}
return $services;
return \array_values($services);
}
}
......@@ -11,9 +11,10 @@ use ACP3\Core\Controller\AreaEnum;
use ACP3\Core\Http\RequestInterface;
use ACP3\Core\View;
use ACP3\Modules\ACP3\Share\Helpers\SocialServices;
use ACP3\Modules\ACP3\Share\Model\Repository\ShareRatingsRepository;
use ACP3\Modules\ACP3\Share\Model\Repository\ShareRepository;
class AddShariffOnLayoutContentAfterListener
class AddSocialSharingListener
{
/**
* @var \ACP3\Core\Http\RequestInterface
......@@ -31,25 +32,32 @@ class AddShariffOnLayoutContentAfterListener
* @var \ACP3\Modules\ACP3\Share\Model\Repository\ShareRepository
*/
private $shareRepository;
/**
* @var \ACP3\Modules\ACP3\Share\Model\Repository\ShareRatingsRepository
*/
private $shareRatingsRepository;
/**
* AddShariffOnLayoutContentAfterListener constructor.
* AddSocialSharingListener constructor.
*
* @param \ACP3\Core\Http\RequestInterface $request
* @param \ACP3\Core\View $view
* @param \ACP3\Modules\ACP3\Share\Helpers\SocialServices $socialServices
* @param \ACP3\Modules\ACP3\Share\Model\Repository\ShareRepository $shareRepository
* @param \ACP3\Core\Http\RequestInterface $request
* @param \ACP3\Core\View $view
* @param \ACP3\Modules\ACP3\Share\Helpers\SocialServices $socialServices
* @param \ACP3\Modules\ACP3\Share\Model\Repository\ShareRepository $shareRepository
* @param \ACP3\Modules\ACP3\Share\Model\Repository\ShareRatingsRepository $shareRatingsRepository
*/
public function __construct(
RequestInterface $request,
View $view,
SocialServices $socialServices,
ShareRepository $shareRepository)
ShareRepository $shareRepository,
ShareRatingsRepository $shareRatingsRepository)
{
$this->request = $request;
$this->view = $view;
$this->socialServices = $socialServices;
$this->shareRepository = $shareRepository;
$this->shareRatingsRepository = $shareRatingsRepository;
}
/**
......@@ -58,15 +66,17 @@ class AddShariffOnLayoutContentAfterListener
public function execute(): void
{
if ($this->request->getArea() === AreaEnum::AREA_FRONTEND) {
$item = $this->shareRepository->getOneByUri($this->request->getUriWithoutPages());
$sharingInfo = $this->shareRepository->getOneByUri($this->request->getUriWithoutPages());
if (!empty($item) && (int) $item['active'] == 1) {
$this->view->assign('shariff', [
if (!empty($sharingInfo) && (int) $sharingInfo['active'] == 1) {
$this->view->assign('sharing', [
'path' => $this->request->getUriWithoutPages(),
'services' => \json_encode($this->socialServices->getActiveServices()),
'services' => $this->socialServices->getActiveServices(),
'ratings_active' => ((int) $sharingInfo['ratings_active']) === 1,
'rating' => $this->shareRatingsRepository->getRatingStatistics($sharingInfo['id']),
]);
$this->view->displayTemplate('Share/Partials/add_shariff.tpl');
$this->view->displayTemplate('Share/Partials/add_social_sharing.tpl');
}
}
}
......
......@@ -39,6 +39,8 @@ class SaveSharingInfoOnModelAfterSaveListener
/**
* @param ModelSaveEvent $event
*
* @throws \Doctrine\DBAL\DBALException
*/
public function execute(ModelSaveEvent $event)
{
......@@ -49,7 +51,8 @@ class SaveSharingInfoOnModelAfterSaveListener
$this->socialSharingManager->saveSharingInfo(
\sprintf($formData['share_uri_pattern'], $event->getEntryId()),
$formData['share_active'],
$formData['share_customize_services'] == 1 ? $formData['share_services'] : []
$formData['share_customize_services'] == 1 ? $formData['share_services'] : [],
$formData['share_ratings_active']
);
}
}
......
......@@ -59,7 +59,9 @@ class UpdateServicesOnSettingsSaveBeforeListener
if (!empty($services) && \is_array($services)) {
$data = [
'share_services' => \array_diff($services, $diff),
'share_services' => \array_values(
\array_diff($services, $diff)
),
];
$this->shareModel->save($data, $result['id']);
}
......
......@@ -85,6 +85,17 @@ class ValidateSharingInfoOnValidationInfo
'haystack' => $this->socialServices->getActiveServices(),
],
]
)
->addConstraint(
InArrayValidationRule::class,
[
'data' => $event->getFormData(),
'field' => 'share_ratings_active',
'message' => $this->translator->t('share', 'select_ratings_active'),
'extra' => [
'haystack' => [0, 1],
],
]
);
}
}
......
......@@ -86,6 +86,10 @@ class ShareFormFields
$this->getAvailableServices(),
$this->getCurrentServices(\unserialize($sharingInfo['services']))
),
'ratings_active' => $this->formsHelper->yesNoCheckboxGenerator(
'share_ratings_active',
$sharingInfo['ratings_active']
),
];
}
......
......@@ -68,18 +68,24 @@ class SocialSharingManager
* @param string $path
* @param bool $active
* @param array $services
* @param bool $ratingsActive
*
* @return bool
*
* @throws \Doctrine\DBAL\DBALException
*/
public function saveSharingInfo(string $path, bool $active = false, array $services = []): bool
public function saveSharingInfo(
string $path,
bool $active = false,
array $services = [],
bool $ratingsActive = false): bool
{
$path .= $this->preparePath($path);
$data = [
'uri' => $path,
'share_active' => $active,
'share_services' => $services,
'share_ratings_active' => $ratingsActive,
];
$sharingInfo = $this->shareRepository->getOneByUri($path);
......
......@@ -13,18 +13,28 @@ class Migration implements Modules\Installer\MigrationInterface
{
/**
* {@inheritdoc}
*
* @return array
*/
public function schemaUpdates()
{
return [];
return [
2 => [
'ALTER TABLE `{pre}share` ADD COLUMN `ratings_active` TINYINT(1) UNSIGNED NOT NULL AFTER `services`;',
'CREATE TABLE IF NOT EXISTS `{pre}share_ratings` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`stars` TINYINT(1) UNSIGNED NOT NULL,
`ip` VARCHAR(40) NOT NULL,
`share_id` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
INDEX(`share_id`),
FOREIGN KEY (`share_id`) REFERENCES `{pre}share` (`id`) ON DELETE CASCADE
) {ENGINE} {CHARSET};',
"INSERT INTO `{pre}acl_resources` (`id`, `module_id`, `area`, `controller`, `page`, `params`, `privilege_id`) VALUES('', '{moduleId}', 'frontend', 'index', 'rate', '', 1);",
],
];
}
/**
* {@inheritdoc}
*
* @return array
*/
public function renameModule()
{
......
......@@ -32,6 +32,7 @@ class Schema implements Modules\Installer\SchemaInterface
'frontend' => [
'index' => [
'index' => PrivilegeEnum::FRONTEND_VIEW,
'rate' => PrivilegeEnum::FRONTEND_VIEW,
],
],
'widget' => [
......@@ -55,7 +56,7 @@ class Schema implements Modules\Installer\SchemaInterface
*/
public function getSchemaVersion()
{
return 1;
return 2;
}
/**
......@@ -69,7 +70,18 @@ class Schema implements Modules\Installer\SchemaInterface
`uri` VARCHAR(255) NOT NULL,
`active` TINYINT(1) UNSIGNED NOT NULL,
`services` TEXT NOT NULL,
PRIMARY KEY (`id`), UNIQUE(`uri`)
`ratings_active` TINYINT(1) UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
UNIQUE(`uri`)
) {ENGINE} {CHARSET};',
'CREATE TABLE IF NOT EXISTS `{pre}share_ratings` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`stars` TINYINT(1) UNSIGNED NOT NULL,
`ip` VARCHAR(40) NOT NULL,
`share_id` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
INDEX(`share_id`),
FOREIGN KEY (`share_id`) REFERENCES `{pre}share` (`id`) ON DELETE CASCADE
) {ENGINE} {CHARSET};',
];
}
......@@ -80,6 +92,7 @@ class Schema implements Modules\Installer\SchemaInterface
public function removeTables()
{
return [
'DROP TABLE IF EXISTS `{pre}share_ratings`;',
'DROP TABLE IF EXISTS `{pre}share`;',
];
}
......
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENSE file at the top-level module directory for licensing details.
*/
namespace ACP3\Modules\ACP3\Share\Model\Repository;
use ACP3\Core\Model\Repository\AbstractRepository;
class ShareRatingsRepository extends AbstractRepository
{
const TABLE_NAME = 'share_ratings';
/**
* @param int $shareId
*
* @return array
*
* @throws \Doctrine\DBAL\DBALException
*/
public function getRatingStatistics(int $shareId): array
{
return $this->db->fetchAssoc(
"SELECT `share_id`, COUNT(*) AS total_ratings, AVG(`stars`) AS average_rating FROM {$this->getTableName()} WHERE `share_id` = :shareId GROUP BY `share_id`;",
['shareId' => $shareId]
) ?: [];
}
}
......@@ -13,6 +13,21 @@ class ShareRepository extends Core\Model\Repository\AbstractRepository
{
const TABLE_NAME = 'share';
/**
* @param int $id
*
* @return bool
*
* @throws \Doctrine\DBAL\DBALException
*/
public function resultExistsById(int $id): bool