diff --git a/contao/dca/tl_module.php b/contao/dca/tl_module.php index d8714db..fee6cba 100644 --- a/contao/dca/tl_module.php +++ b/contao/dca/tl_module.php @@ -15,16 +15,18 @@ use Contao\Controller; use Contao\System; -use Oveleon\ContaoMemberExtensionBundle\EventListener\DataContainer\MemberFieldsOptionsListener; System::loadLanguageFile('tl_member_settings'); // Add palettes to tl_module $GLOBALS['TL_DCA']['tl_module']['palettes']['avatar'] = '{title_legend},name,headline,type;{source_legend},imgSize;{template_legend:hide},customTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; $GLOBALS['TL_DCA']['tl_module']['palettes']['deleteAvatar'] = '{title_legend},name,headline,type;{template_legend:hide},customTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; -$GLOBALS['TL_DCA']['tl_module']['palettes']['memberList'] = '{title_legend},name,headline,type;{config_legend},ext_order,ext_orderField,numberOfItems,perPage,ext_groups,memberFields,imgSize,ext_activateFilter,ext_parseDetails,ext_memberAlias;{redirect_legend},jumpTo;{template_legend:hide},customTpl,memberListTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; +$GLOBALS['TL_DCA']['tl_module']['palettes']['memberList'] = '{title_legend},name,headline,type;{config_legend},ext_order,ext_orderField,numberOfItems,perPage,ext_groups,memberFields,imgSize,ext_parseDetails,ext_memberAlias;{filter_search_legend:hide},ext_where,ext_activateFilter;{redirect_legend},jumpTo;{template_legend:hide},customTpl,memberListTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; $GLOBALS['TL_DCA']['tl_module']['palettes']['memberReader'] = '{title_legend},name,headline,type;{config_legend},ext_groups,memberFields,imgSize,ext_parseDetails,overviewPage,customLabel;{template_legend:hide},customTpl,memberReaderTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; +$GLOBALS['TL_DCA']['tl_module']['palettes']['__selector__'][] = 'ext_activateFilter'; +$GLOBALS['TL_DCA']['tl_module']['subpalettes']['ext_activateFilter'] = 'ext_selectFilter'; + $GLOBALS['TL_DCA']['tl_module']['fields']['memberListTpl'] = [ 'exclude' => true, 'inputType' => 'select', @@ -87,9 +89,23 @@ 'relation' => ['type' => 'hasMany', 'load' => 'lazy'] ]; +$GLOBALS['TL_DCA']['tl_module']['fields']['ext_where'] = [ + 'exclude' => true, + 'inputType' => 'select', + 'eval' => ['tl_class' => 'w50', 'includeBlankOption' => true, 'chosen' => true], + 'sql' => "varchar(32) NOT NULL default ''" +]; + $GLOBALS['TL_DCA']['tl_module']['fields']['ext_activateFilter'] = [ 'exclude' => true, 'inputType' => 'checkbox', - 'eval' => ['tl_class' => 'w50 m12'], + 'eval' => ['tl_class' => 'w50 m12', 'submitOnChange' => true], 'sql' => "char(1) NOT NULL default ''" ]; + +$GLOBALS['TL_DCA']['tl_module']['fields']['ext_selectFilter'] = [ + 'exclude' => true, + 'inputType' => 'select', + 'eval' => ['tl_class' => 'w50', 'includeBlankOption' => true, 'chosen' => true], + 'sql' => "varchar(32) NOT NULL default ''" +]; diff --git a/contao/languages/de/tl_module.xlf b/contao/languages/de/tl_module.xlf index e5dacf9..d9f9f86 100644 --- a/contao/languages/de/tl_module.xlf +++ b/contao/languages/de/tl_module.xlf @@ -1,6 +1,10 @@ + + Filter / Search + Filterung / Suche + Sort order Sortierreihenfolge @@ -33,14 +37,6 @@ Here you can select the member fields to be displayed. Hier können Sie die auszugebenden Mitgliederfelder auswählen. - - Activate filters - Filter aktivieren - - - Adds filtering. Only works with checkbox fields with the eval flag 'feFilterable'. - Fügt eine Filterung hinzu. Funktioniert nur mit Checkbox-Feldern mit dem eval-flag 'feFilterable'. - Parse details Details umwandeln @@ -58,6 +54,31 @@ Verwendet den Mitgliedsalias für die Detailseite. + + Search field + Suchfeld + + + Activates search. Here you can select the field that you want to be searchable. + Aktiviert die Suche. Hier können Sie das Feld auswählen, welches durchsuchbar sein soll. + + + Activate filters + Filter aktivieren + + + Adds filtering. Only works with checkbox fields with the eval flag 'feFilterable'. + Fügt eine Filterung hinzu. Funktioniert nur mit Checkbox-Feldern mit dem eval-flag 'feFilterable'. + + + Select filter + Select-Filter + + + Here you can select a field that provides an options filter in the frontend. + Hier können Sie ein Feld auswählen, welches einen Optionen-Filter im Frontend zur Verfügung stellt. + + List template Listen-Template diff --git a/contao/languages/en/tl_module.xlf b/contao/languages/en/tl_module.xlf index 19e637a..3028495 100644 --- a/contao/languages/en/tl_module.xlf +++ b/contao/languages/en/tl_module.xlf @@ -1,6 +1,9 @@ + + Filter / Search + Sort order @@ -25,12 +28,6 @@ Here you can select the member fields to be displayed. - - Activate filters - - - Adds filtering. Only works with checkbox fields with the eval flag 'feFilterable'. - Parse details @@ -44,6 +41,25 @@ Uses the member alias for the detail page. + + Search field + + + Activates search. Here you can select the field that you want to be searchable. + + + Activate filters + + + Adds filtering. Only works with checkbox fields with the eval flag 'feFilterable'. + + + Select filter + + + Here you can select a field that provides an options filter in the frontend. + + List template diff --git a/contao/templates/block/memberExtension_searchFilter.html5 b/contao/templates/block/memberExtension_searchFilter.html5 new file mode 100644 index 0000000..3dd56c4 --- /dev/null +++ b/contao/templates/block/memberExtension_searchFilter.html5 @@ -0,0 +1,25 @@ +
+
+ selectField): ?> +
+ + +
+ + searchField): ?> +
+ + +
+ +
+ +
+
+
diff --git a/contao/templates/modules/mod_memberList.html5 b/contao/templates/modules/mod_memberList.html5 index 20d8034..dac6c33 100644 --- a/contao/templates/modules/mod_memberList.html5 +++ b/contao/templates/modules/mod_memberList.html5 @@ -2,6 +2,19 @@ block('content'); ?> +ext_where || $this->selectFilterable): ?> +
+ insert('memberExtension_searchFilter', [ + 'selectField' => $this->ext_selectFilter ?? '', + 'selectOptions' => $this->selectOptions ?? [], + 'selected' => $this->selectedOption ?? '', + 'searchField' => $this->ext_where ?? '', + 'searchString' => $this->searchString ?? '', + 'requestToken' => $this->requestToken + ]) ?> +
+ + filters)): ?>
insert('memberExtension_filter', [ diff --git a/src/Controller/FrontendModule/MemberExtensionController.php b/src/Controller/FrontendModule/MemberExtensionController.php index 6f82aee..69155fb 100644 --- a/src/Controller/FrontendModule/MemberExtensionController.php +++ b/src/Controller/FrontendModule/MemberExtensionController.php @@ -44,11 +44,6 @@ abstract class MemberExtensionController extends AbstractFrontendModuleControlle protected function parseMemberTemplate(MemberModel|Model $objMember, FrontendTemplate $objTemplate, ModuleModel $model): string { - System::loadLanguageFile('default'); - System::loadLanguageFile('tl_member'); - System::loadLanguageFile('countries'); - System::loadLanguageFile('languages'); - $this->model = $model; $arrFields = []; @@ -96,9 +91,18 @@ protected function parseMemberTemplate(MemberModel|Model $objMember, FrontendTem $returnFields = []; + $skipEmptyValues = System::getContainer()->getParameter('contao_member_extension.skip_empty_values'); + foreach ($this->memberFields as $value) { - $returnFields[$value] = $arrFields[$value] ?? ''; + $val = $arrFields[$value] ?? ''; + + if ($skipEmptyValues && !$val) + { + continue; + } + + $returnFields[$value] = $val; } $labels = array_keys($returnFields); diff --git a/src/Controller/FrontendModule/MemberListController.php b/src/Controller/FrontendModule/MemberListController.php index ba27e66..ad3d9cb 100644 --- a/src/Controller/FrontendModule/MemberListController.php +++ b/src/Controller/FrontendModule/MemberListController.php @@ -39,32 +39,48 @@ class MemberListController extends MemberExtensionController { const TYPE = 'memberList'; private ModuleModel $model; - private Template $template; + public Template $template; private array $memberFilter = []; + /** + * @var array|mixed|string|string[]|null + */ + private array $groups; protected function getResponse(Template $template, ModuleModel $model, Request $request): Response { $this->model = $model; $this->template = $template; + $this->request = $request; + $this->groups = StringUtil::deserialize($model->ext_groups, true); - $limit = null; - $offset = 0; - - $arrGroups = StringUtil::deserialize($model->ext_groups); - - if (empty($arrGroups) || !\is_array($arrGroups)) + if (empty($this->groups)) { $template->empty = $GLOBALS['TL_LANG']['MSC']['emptyMemberList']; $template->getResponse(); } + return $this->parseMemberList(); + } + + protected function parseMemberList(): Response + { + System::loadLanguageFile('default'); + System::loadLanguageFile('tl_member'); + System::loadLanguageFile('countries'); + System::loadLanguageFile('languages'); + + $limit = null; + $offset = 0; + + $this->template->selectFilterable = $this->model->ext_activateFilter && $this->model->ext_selectFilter; + if ($this->model->ext_activateFilter) { $this->parseFilters(); } - $memberTemplate = new FrontendTemplate($model->memberListTpl ?: 'memberExtension_list_default'); + $memberTemplate = new FrontendTemplate($this->model->memberListTpl ?: 'memberExtension_list_default'); if ( str_starts_with($this->template->getName(), 'mod_' . self::TYPE . '_table') && @@ -81,7 +97,7 @@ protected function getResponse(Template $template, ModuleModel $model, Request $ foreach ($objMembers as $objMember) { if ( - !$this->checkMemberGroups($arrGroups, $objMember) || + !$this->checkMemberGroups($objMember) || ($this->model->ext_activateFilter && $this->excludeMember($objMember)) ) { continue; @@ -89,37 +105,39 @@ protected function getResponse(Template $template, ModuleModel $model, Request $ $intTotal += 1; - $this->memberFields = StringUtil::deserialize($model->memberFields, true); + $this->memberFields = StringUtil::deserialize($this->model->memberFields, true); $memberTemplate->setData($objMember->row()); - $arrMembers[] = $this->parseMemberTemplate($objMember, $memberTemplate, $model); + $arrMembers[] = $this->parseMemberTemplate($objMember, $memberTemplate, $this->model); } } + $this->template->total = $intTotal; + $total = $intTotal - $offset; - if ($model->numberOfItems > 0) + if ($this->model->numberOfItems > 0) { - $limit = $model->numberOfItems; + $limit = $this->model->numberOfItems; } - if ($model->perPage > 0 && (!isset($limit) || $model->numberOfItems > $model->perPage) && !$this->isTable) + if ($this->model->perPage > 0 && (!isset($limit) || $this->model->numberOfItems > $this->model->perPage) && !$this->isTable) { if (isset($limit)) { $total = min($limit, $total); } - $id = 'page_n' . $model->id; + $id = 'page_n' . $this->model->id; $page = Input::get($id) ?? 1; - if ($page < 1 || $page > max(ceil($total/$model->perPage), 1)) + if ($page < 1 || $page > max(ceil($total/$this->model->perPage), 1)) { throw new PageNotFoundException('Page not found: ' . Environment::get('uri')); } - $limit = $model->perPage; - $offset += (max($page, 1) - 1) * $model->perPage; + $limit = $this->model->perPage; + $offset += (max($page, 1) - 1) * $this->model->perPage; $skip = 0; if ($offset + $limit > $total + $skip) @@ -129,34 +147,34 @@ protected function getResponse(Template $template, ModuleModel $model, Request $ $arrMembers = \array_slice($arrMembers, $offset, ((int) $limit ?: $intTotal), true); - $objPagination = new Pagination($total, $model->perPage, Config::get('maxPaginationLinks'), $id); - $template->pagination = $objPagination->generate("\n "); + $objPagination = new Pagination($total, $this->model->perPage, Config::get('maxPaginationLinks'), $id); + $this->template->pagination = $objPagination->generate("\n "); } if (empty($arrMembers)) { - $template->empty = $GLOBALS['TL_LANG']['MSC']['emptyMemberList']; + $this->template->empty = $GLOBALS['TL_LANG']['MSC']['emptyMemberList']; } - $template->hasDetailPage = !!$model->jumpTo; + $this->template->hasDetailPage = !!$this->model->jumpTo; - $template->total = $total; - $template->labels = $this->labels; - $template->members = $arrMembers; + $this->template->total = $total; + $this->template->labels = $this->labels; + $this->template->members = $arrMembers; - return $template->getResponse(); + return $this->template->getResponse(); } - private function checkMemberGroups(array $arrGroups, MemberModel $objMember): bool + protected function checkMemberGroups(MemberModel $objMember): bool { - if (empty($arrGroups)) + if (empty($this->groups)) { return false; } $arrMemberGroups = StringUtil::deserialize($objMember->groups); - if (!\is_array($arrMemberGroups) || !\count(array_intersect($arrGroups, $arrMemberGroups))) + if (!\is_array($arrMemberGroups) || !\count(array_intersect($this->groups, $arrMemberGroups))) { return false; } @@ -164,7 +182,7 @@ private function checkMemberGroups(array $arrGroups, MemberModel $objMember): bo return true; } - private function getMembers(): Collection|MemberModel|null + protected function getMembers(): Collection|MemberModel|null { $t = MemberModel::getTable(); $time = Date::floorToMinute(); @@ -172,6 +190,24 @@ private function getMembers(): Collection|MemberModel|null $arrColumns = ["$t.disable='' AND ($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'$time') "]; $arrOptions = []; + if (!!($field = $this->model->ext_where) && !!($string = Input::get('search_string'))) + { + $this->template->searchString = $string; + $arrColumns[] = "$t.$field LIKE '$string%'"; + } + + if ($this->model->ext_activateFilter && !!($select = $this->model->ext_selectFilter)) + { + $uniqueOptions = System::getContainer()->get('database_connection')?->fetchAllAssociative('SELECT DISTINCT '.$t.'.'.$select.' FROM ' . $t . ' ORDER BY '.$t.'.'.$select); + $this->template->selectOptions = array_column($uniqueOptions, $select); + + if (!!($option = Input::get('select_filter'))) + { + $this->template->selectedOption = $option; + $arrColumns[] = "$t.$select='$option'"; + } + } + if (!!$orderField = $this->model->ext_orderField) { $arrOptions['order'] = "$t.$orderField "; @@ -205,6 +241,11 @@ private function getMembers(): Collection|MemberModel|null } } + if (null === $arrColumns) + { + return null; + } + return MemberModel::findBy($arrColumns, null, $arrOptions); } @@ -221,7 +262,7 @@ private function excludeMember(MemberModel $member): bool return false; } - private function parseFilters(): void + protected function parseFilters(): void { Controller::loadDataContainer('tl_member'); System::loadLanguageFile('tl_member'); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..77e39a1 --- /dev/null +++ b/src/DependencyInjection/Configuration.php @@ -0,0 +1,34 @@ + + * @author Daniele Sciannimanica + * @author Fabian Ekert + * @copyright Oveleon + */ + +namespace Oveleon\ContaoMemberExtensionBundle\DependencyInjection; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +class Configuration implements ConfigurationInterface +{ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder('contao_member_extension'); + $treeBuilder->getRootNode() + ->children() + ->booleanNode('skip_empty_values') + ->defaultFalse() + ->end() + ->end() + ; + + return $treeBuilder; + } +} diff --git a/src/DependencyInjection/ContaoMemberExtensionExtension.php b/src/DependencyInjection/ContaoMemberExtensionExtension.php index ab50d8a..72d1c09 100644 --- a/src/DependencyInjection/ContaoMemberExtensionExtension.php +++ b/src/DependencyInjection/ContaoMemberExtensionExtension.php @@ -22,11 +22,15 @@ class ContaoMemberExtensionExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { + $config = $this->processConfiguration(new Configuration(), $configs); + $loader = new YamlFileLoader( $container, new FileLocator(__DIR__ . '/../../config') ); $loader->load('services.yaml'); + + $container->setParameter('contao_member_extension.skip_empty_values', $config['skip_empty_values']); } } diff --git a/src/EventListener/DataContainer/MemberFieldsOptionsListener.php b/src/EventListener/DataContainer/MemberFieldsOptionsListener.php index 39d8c70..47ea918 100644 --- a/src/EventListener/DataContainer/MemberFieldsOptionsListener.php +++ b/src/EventListener/DataContainer/MemberFieldsOptionsListener.php @@ -14,6 +14,8 @@ public function __construct() { } #[AsCallback(table: 'tl_module', target: 'fields.ext_orderField.options')] + #[AsCallback(table: 'tl_module', target: 'fields.ext_where.options')] + #[AsCallback(table: 'tl_module', target: 'fields.ext_selectFilter.options')] public function getEditableMemberFields(): array { $fields = [];