diff --git a/src/DataContainer/ProductArchiveContainer.php b/src/DataContainer/ProductArchiveContainer.php index 3af1bbc..7e734b7 100644 --- a/src/DataContainer/ProductArchiveContainer.php +++ b/src/DataContainer/ProductArchiveContainer.php @@ -10,28 +10,37 @@ use Contao\BackendUser; use Contao\Controller; +use Contao\CoreBundle\Exception\AccessDeniedException; +use Contao\CoreBundle\Security\ContaoCorePermissions; +use Contao\Database; +use Contao\DataContainer; +use Contao\Image; +use Contao\Input; +use Contao\RequestToken; +use Contao\StringUtil; use Contao\System; use HeimrichHannot\UtilsBundle\File\FileUtil; use HeimrichHannot\UtilsBundle\Model\ModelUtil; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Security\Core\Security; class ProductArchiveContainer { - /** - * @var FileUtil - */ - protected $fileUtil; + protected FileUtil $fileUtil; - /** - * @var ModelUtil - */ - protected $modelUtil; + protected ModelUtil $modelUtil; + + protected Security $security; public function __construct( FileUtil $fileUtil, - ModelUtil $modelUtil + ModelUtil $modelUtil, + Security $security ) { $this->fileUtil = $fileUtil; $this->modelUtil = $modelUtil; + $this->security = $security; } /** @@ -61,8 +70,8 @@ public function getImageSizes() public function checkPermission() { - $user = \Contao\BackendUser::getInstance(); - $database = \Contao\Database::getInstance(); + $user = BackendUser::getInstance(); + $database = Database::getInstance(); if ($user->isAdmin) { return; @@ -82,11 +91,11 @@ public function checkPermission() $GLOBALS['TL_DCA']['tl_ml_product_archive']['config']['closed'] = true; } - /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $objSession */ - $objSession = \Contao\System::getContainer()->get('session'); + /** @var SessionInterface $objSession */ + $objSession = System::getContainer()->get('session'); // Check current action - switch (\Contao\Input::get('act')) { + switch (Input::get('act')) { case 'create': case 'select': // Allow @@ -94,33 +103,29 @@ public function checkPermission() case 'edit': // Dynamically add the record to the user profile - if (!\in_array(\Contao\Input::get('id'), $root, true)) { - /** @var \Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface $sessionBag */ + if (!\in_array(Input::get('id'), $root, true)) { + /** @var AttributeBagInterface $sessionBag */ $sessionBag = $objSession->getBag('contao_backend'); $arrNew = $sessionBag->get('new_records'); - if (\is_array($arrNew['tl_ml_product_archive']) && \in_array(\Contao\Input::get('id'), + if (\is_array($arrNew['tl_ml_product_archive']) && \in_array(Input::get('id'), $arrNew['tl_ml_product_archive'], true)) { // Add the permissions on group level - if ('custom' != $user->inherit) { - $objGroup = $database->execute( - 'SELECT id, contao_media_library_bundles, contao_media_library_bundlep FROM tl_user_group WHERE id IN('.implode( - ',', - array_map( - 'intval', - $user->groups - ) - ).')' - ); + if ('custom' != $user->inherit) + { + $sql = "SELECT id, contao_media_library_bundles, contao_media_library_bundlep FROM tl_user_group WHERE id IN(%s);"; + $sql = sprintf($sql, implode(',', array_map('intval', $user->groups))); + + $objGroup = $database->execute($sql); while ($objGroup->next()) { - $arrModulep = \StringUtil::deserialize($objGroup->contao_media_library_bundlep); + $arrModulep = StringUtil::deserialize($objGroup->contao_media_library_bundlep); if (\is_array($arrModulep) && \in_array('create', $arrModulep, true)) { - $arrModules = \StringUtil::deserialize($objGroup->contao_media_library_bundles, + $arrModules = StringUtil::deserialize($objGroup->contao_media_library_bundles, true); - $arrModules[] = \Contao\Input::get('id'); + $arrModules[] = Input::get('id'); $database->prepare('UPDATE tl_user_group SET contao_media_library_bundles=? WHERE id=?')->execute( serialize($arrModules), @@ -136,21 +141,19 @@ public function checkPermission() ->limit(1) ->execute($user->id); - $arrModulep = \StringUtil::deserialize($user->contao_media_library_bundlep); + $arrModulep = StringUtil::deserialize($user->contao_media_library_bundlep); if (\is_array($arrModulep) && \in_array('create', $arrModulep, true)) { - $arrModules = \StringUtil::deserialize($user->contao_media_library_bundles, true); - $arrModules[] = \Contao\Input::get('id'); + $arrModules = StringUtil::deserialize($user->contao_media_library_bundles, true); + $arrModules[] = Input::get('id'); - $database->prepare('UPDATE tl_user SET contao_media_library_bundles=? WHERE id=?')->execute( - serialize($arrModules), - $user->id - ); + $database->prepare('UPDATE tl_user SET contao_media_library_bundles=? WHERE id=?') + ->execute(serialize($arrModules), $user->id); } } // Add the new element to the user object - $root[] = \Contao\Input::get('id'); + $root[] = Input::get('id'); $user->contao_media_library_bundles = $root; } } @@ -159,14 +162,14 @@ public function checkPermission() case 'copy': case 'delete': case 'show': - if (!\in_array(\Contao\Input::get('id'), $root, true) - || ('delete' == \Contao\Input::get('act') + if (!\in_array(Input::get('id'), $root, true) + || ('delete' == Input::get('act') && !$user->hasAccess( 'delete', 'contao_media_library_bundlep' )) ) { - throw new \Contao\CoreBundle\Exception\AccessDeniedException('Not enough permissions to '.\Contao\Input::get('act').' ml_product_archive ID '.\Contao\Input::get('id').'.'); + throw new AccessDeniedException('Not enough permissions to '. Input::get('act').' ml_product_archive ID '. Input::get('id').'.'); } break; @@ -176,7 +179,7 @@ public function checkPermission() case 'overrideAll': $session = $objSession->all(); - if ('deleteAll' == \Contao\Input::get('act') && !$user->hasAccess('delete', + if ('deleteAll' == Input::get('act') && !$user->hasAccess('delete', 'contao_media_library_bundlep')) { $session['CURRENT']['IDS'] = []; } else { @@ -187,43 +190,71 @@ public function checkPermission() break; default: - if (\strlen(\Contao\Input::get('act'))) { - throw new \Contao\CoreBundle\Exception\AccessDeniedException('Not enough permissions to '.\Contao\Input::get('act').' ml_product_archives.'); + if (\strlen(Input::get('act'))) { + throw new AccessDeniedException('Not enough permissions to '. Input::get('act').' ml_product_archives.'); } break; } } + public function checkIncludeDelete(DataContainer $dc) + { + $record = Database::getInstance() + ->prepare('SELECT * FROM tl_ml_product_archive WHERE id=?') + ->limit(1) + ->execute($dc->id) + ; + + if (!$record->numRows) { + return; + } + + if ($record->includeDelete ?? false) { + $GLOBALS['TL_DCA']['tl_ml_product_archive']['fields']['redirectAfterDelete']['eval']['mandatory'] = true; + } else { + unset($GLOBALS['TL_DCA']['tl_ml_product_archive']['fields']['redirectAfterDelete']); + } + } + public function editHeader($row, $href, $label, $title, $icon, $attributes) { - return \Contao\BackendUser::getInstance()->canEditFieldsOf('tl_ml_product_archive') ? ''.\Image::getHtml( - $icon, - $label - ).' ' : \Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)).' '; + if ($this->security->isGranted(ContaoCorePermissions::USER_CAN_EDIT_FIELDS_OF_TABLE, 'tl_ml_product_archive')) + { + $anchor = sprintf( + '%s ', + Controller::addToUrl("$href&id={$row['id']}"), + RequestToken::get(), + StringUtil::specialchars($title), + $attributes, + Image::getHtml($icon, $label) + ); + + return $anchor; + } + + return Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)) . ' '; } public function copyArchive($row, $href, $label, $title, $icon, $attributes) { - return \Contao\BackendUser::getInstance()->hasAccess('create', + return BackendUser::getInstance()->hasAccess('create', 'contao_media_library_bundlep') ? ''.\Image::getHtml( + ).'&rt='.RequestToken::get().'" title="'. StringUtil::specialchars($title).'"'.$attributes.'>'.Image::getHtml( $icon, $label - ).' ' : \Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)).' '; + ).' ' : Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)).' '; } public function deleteArchive($row, $href, $label, $title, $icon, $attributes) { - return \Contao\BackendUser::getInstance()->hasAccess('delete', + return BackendUser::getInstance()->hasAccess('delete', 'contao_media_library_bundlep') ? ''.\Image::getHtml( + ).'&rt='.RequestToken::get().'" title="'.StringUtil::specialchars($title).'"'.$attributes.'>'.Image::getHtml( $icon, $label - ).' ' : \Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)).' '; + ).' ' : Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)).' '; } } diff --git a/src/EventListener/DeleteProductListener.php b/src/EventListener/DeleteProductListener.php new file mode 100644 index 0000000..734eb0e --- /dev/null +++ b/src/EventListener/DeleteProductListener.php @@ -0,0 +1,92 @@ +requestStack = $requestStack; + $this->translator = $translator; + $this->session = $session; + } + + #[AsEventListener('huh.reader.event.reader_before_render')] + public function deleteProduct(ReaderBeforeRenderEvent $event): void + { + $item = $event->getItem(); + + if ($item->getDataContainer() !== 'tl_ml_product') { + return; + } + + $request = $this->requestStack->getCurrentRequest(); + + if (!$request->isMethod(Request::METHOD_DELETE) + && Input::post('_method') !== 'DELETE') + { + return; + } + + /** @var class-string $modelClass */ + $modelClass = Model::getClassFromTable('tl_ml_product'); + $product = $modelClass::findByPk($item->getRawValue('id')); + if ($product === null) { + throw new BadRequestHttpException('Invalid product id'); + } + + /** @var ProductArchiveModel|null $productArchive */ + $productArchive = $product->getRelated('pid'); + + if ($productArchive === null) { + throw new InternalServerErrorHttpException('Product archive not found'); + } + + if (!$productArchive->includeDelete) { + throw new MethodNotAllowedHttpException([Request::METHOD_DELETE], 'Delete not allowed'); + } + + if (!$product->delete()) { + throw new InternalServerErrorHttpException('Could not delete product'); + } + + $pageId = $productArchive->redirectAfterDelete; + $page = PageModel::findByPk($pageId); + + if ($page === null) { + throw new InternalServerErrorHttpException('No redirect page found'); + } + + $this->session + ->getFlashBag() + ->add('success', $this->translator->trans('huh.mediaLibrary.product.delete.success', ['title' => $product->title])) + ; + + throw new RedirectResponseException($page->getAbsoluteUrl()); + } +} \ No newline at end of file diff --git a/src/FormType/MediaLibraryType.php b/src/FormType/MediaLibraryType.php index a5c7ea7..71325c8 100644 --- a/src/FormType/MediaLibraryType.php +++ b/src/FormType/MediaLibraryType.php @@ -106,11 +106,15 @@ public function onPrepareFormData(PrepareFormDataEvent $event): void $event->getForm()->storeValues = '1'; $event->getForm()->targetTable = ProductModel::getTable(); } + + parent::onPrepareFormData($event); } public function onStoreFormData(StoreFormDataEvent $event): void { - if ($event->getForm()->ml_archive && ($archiveModel = ProductArchiveModel::findByPk($event->getForm()->ml_archive))) { + if ($event->getForm()->ml_archive + && ($archiveModel = ProductArchiveModel::findByPk($event->getForm()->ml_archive))) + { $data = $event->getData(); $data = array_intersect_key($data, array_flip(Database::getInstance()->getFieldNames(ProductModel::getTable()))); $data['pid'] = $event->getForm()->ml_archive; @@ -140,10 +144,14 @@ public function onStoreFormData(StoreFormDataEvent $event): void $event->setData($data); } + + parent::onStoreFormData($event); } public function onProcessFormData(ProcessFormDataEvent $event): void { + parent::onProcessFormData($event); + if (!class_exists(HeimrichHannotFileCreditsBundle::class)) { return; } diff --git a/src/Model/ProductArchiveModel.php b/src/Model/ProductArchiveModel.php index 7ef6ee4..36ef975 100644 --- a/src/Model/ProductArchiveModel.php +++ b/src/Model/ProductArchiveModel.php @@ -18,6 +18,8 @@ * @property string $type * @property string $additionalFields * @property bool $keepProductTitleForDownloadItems + * @property bool $includeDelete + * @property bool $redirectAfterDelete * @property bool $protected */ class ProductArchiveModel extends Model diff --git a/src/Resources/contao/dca/tl_ml_product_archive.php b/src/Resources/contao/dca/tl_ml_product_archive.php index fdc101d..23d23fe 100644 --- a/src/Resources/contao/dca/tl_ml_product_archive.php +++ b/src/Resources/contao/dca/tl_ml_product_archive.php @@ -1,12 +1,16 @@ [ @@ -14,7 +18,8 @@ 'ctable' => ['tl_ml_product'], 'enableVersioning' => true, 'onload_callback' => [ - [\HeimrichHannot\MediaLibraryBundle\DataContainer\ProductArchiveContainer::class, 'checkPermission'], + [ProductArchiveContainer::class, 'checkPermission'], + [ProductArchiveContainer::class, 'checkIncludeDelete'], ], 'onsubmit_callback' => [ ['huh.utils.dca', 'setDateAdded'], @@ -57,13 +62,13 @@ 'label' => &$GLOBALS['TL_LANG']['tl_ml_product_archive']['editheader'], 'href' => 'act=edit', 'icon' => 'header.svg', - 'button_callback' => [\HeimrichHannot\MediaLibraryBundle\DataContainer\ProductArchiveContainer::class, 'editHeader'], + 'button_callback' => [ProductArchiveContainer::class, 'editHeader'], ], 'copy' => [ 'label' => &$GLOBALS['TL_LANG']['tl_ml_product_archive']['copy'], 'href' => 'act=copy', 'icon' => 'copy.svg', - 'button_callback' => [\HeimrichHannot\MediaLibraryBundle\DataContainer\ProductArchiveContainer::class, 'copyArchive'], + 'button_callback' => [ProductArchiveContainer::class, 'copyArchive'], ], 'delete' => [ 'label' => &$GLOBALS['TL_LANG']['tl_ml_product_archive']['delete'], @@ -71,7 +76,7 @@ 'icon' => 'delete.svg', 'attributes' => 'onclick="if(!confirm(\''.($GLOBALS['TL_LANG']['MSC']['deleteConfirm'] ?? '') .'\'))return false;Backend.getScrollOffset()"', - 'button_callback' => [\HeimrichHannot\MediaLibraryBundle\DataContainer\ProductArchiveContainer::class, 'deleteArchive'], + 'button_callback' => [ProductArchiveContainer::class, 'deleteArchive'], ], 'show' => [ 'label' => &$GLOBALS['TL_LANG']['tl_ml_product_archive']['show'], @@ -82,10 +87,10 @@ ], 'palettes' => [ '__selector__' => ['type', 'protected', 'useExifDataForTags'], - 'default' => '{general_legend},title;{config_legend},type,additionalFields,keepProductTitleForDownloadItems;{protected_legend},protected;', + 'default' => '{general_legend},title;{config_legend},type,additionalFields,keepProductTitleForDownloadItems,includeDelete,redirectAfterDelete;{protected_legend},protected;', ], 'subpalettes' => [ - 'type_'.\HeimrichHannot\MediaLibraryBundle\DataContainer\ProductContainer::TYPE_IMAGE => 'imageSizes', + 'type_'. ProductContainer::TYPE_IMAGE => 'imageSizes', 'protected' => 'groups', ], 'fields' => [ @@ -119,7 +124,7 @@ 'exclude' => true, 'filter' => true, 'inputType' => 'select', - 'options' => \HeimrichHannot\MediaLibraryBundle\DataContainer\ProductContainer::TYPES, + 'options' => ProductContainer::TYPES, 'reference' => &$GLOBALS['TL_LANG']['tl_ml_product']['reference'], 'eval' => ['tl_class' => 'w50', 'mandatory' => true, 'includeBlankOption' => true, 'submitOnChange' => true], 'sql' => "varchar(64) NOT NULL default ''", @@ -130,7 +135,7 @@ 'filter' => true, 'inputType' => 'checkboxWizard', 'options_callback' => function (Contao\DataContainer $dc) { - return \Contao\System::getContainer()->get('huh.utils.choice.field')->getCachedChoices( + return System::getContainer()->get('huh.utils.choice.field')->getCachedChoices( [ 'dataContainer' => 'tl_ml_product', 'evalConditions' => [ @@ -148,7 +153,7 @@ 'exclude' => true, 'flag' => 1, 'inputType' => 'checkboxWizard', - 'options_callback' => [\HeimrichHannot\MediaLibraryBundle\DataContainer\ProductArchiveContainer::class, 'getImageSizes'], + 'options_callback' => [ProductArchiveContainer::class, 'getImageSizes'], 'eval' => ['includeBlankOption' => true, 'multiple' => true, 'tl_class' => 'clr w50 autoheight'], 'sql' => 'blob NULL', ], @@ -176,5 +181,29 @@ 'eval' => ['tl_class' => 'clr'], 'sql' => "char(1) NOT NULL default ''", ], + 'includeDelete' => [ + 'exclude' => true, + 'filter' => true, + 'inputType' => 'checkbox', + 'default' => true, + 'eval' => [ + 'tl_class' => 'clr', + 'submitOnChange' => true, + ], + 'sql' => "char(1) NOT NULL default ''", + ], + 'redirectAfterDelete' => [ + 'inputType' => 'pageTree', + 'foreignKey' => 'tl_page.id', + 'eval' => [ + 'fieldType' => 'radio', + 'tl_class' => 'clr' + ], + 'sql' => "int(10) unsigned NOT NULL default 0", + 'relation' => [ + 'type' => 'hasOne', + 'load' => 'lazy' + ] + ] ], ]; diff --git a/src/Resources/contao/languages/de/tl_ml_product_archive.php b/src/Resources/contao/languages/de/tl_ml_product_archive.php index 85a90a0..68d4ce8 100644 --- a/src/Resources/contao/languages/de/tl_ml_product_archive.php +++ b/src/Resources/contao/languages/de/tl_ml_product_archive.php @@ -21,7 +21,10 @@ $lang['groups'][1] = 'Wählen Sie hier die gewünschten Mitgliedergruppen für den geschützten Zugriff aus.'; $lang['keepProductTitleForDownloadItems'][0] = 'Produktnamen im Downloadtitel behalten'; $lang['keepProductTitleForDownloadItems'][1] = 'Wählen Sie diese Option, wenn der Titel des Produktes in den Titeln der Downloadelementen bestehen bleiben soll.'; - +$lang['includeDelete'][0] = 'Produkte können gelöscht werden'; +$lang['includeDelete'][1] = 'Wählen Sie diese Option, wenn dem Nutzer die Möglichkeit gegeben werden soll, das Produkt zu löschen.'; +$lang['redirectAfterDelete'][0] = 'Weiterleitungsseite nach dem Löschen'; +$lang['redirectAfterDelete'][1] = 'Wählen Sie hier die Seite aus, zu der der Nutzer nach dem Löschen des Produktes weitergeleitet werden soll.'; /** * Legends diff --git a/src/Resources/translations/messages.de.yml b/src/Resources/translations/messages.de.yml index e057c09..c7d7cde 100644 --- a/src/Resources/translations/messages.de.yml +++ b/src/Resources/translations/messages.de.yml @@ -6,4 +6,6 @@ huh.mediaLibrary.btn.abort.label: 'abbrechen' huh.mediaLibrary.alert.download.title: 'Option wählen' huh.mediaLibrary.downloadTitle.original: 'Originalgröße' -huh.mediaLibrary.downloadTitle.sizeWithProductTitle: '{title} ({size})' \ No newline at end of file +huh.mediaLibrary.downloadTitle.sizeWithProductTitle: '{title} ({size})' + +huh.mediaLibrary.product.delete.success: 'Bild "{title}" wurde erfolgreich gelöscht'