vendor/numero2/contao-storelocator/src/Resources/contao/modules/ModuleStoreLocatorList.php line 69

Open in your IDE?
  1. <?php
  2. /**
  3.  * StoreLocator Bundle for Contao Open Source CMS
  4.  *
  5.  * @author    Benny Born <benny.born@numero2.de>
  6.  * @author    Michael Bösherz <michael.boesherz@numero2.de>
  7.  * @license   LGPL-3.0-or-later
  8.  * @copyright Copyright (c) 2024, numero2 - Agentur für digitales Marketing GbR
  9.  */
  10. namespace numero2\StoreLocator;
  11. use Contao\BackendTemplate;
  12. use Contao\Config;
  13. use Contao\CoreBundle\Exception\ResponseException;
  14. use Contao\Environment;
  15. use Contao\FilesModel;
  16. use Contao\FrontendTemplate;
  17. use Contao\Input;
  18. use Contao\Module;
  19. use Contao\ModuleModel;
  20. use Contao\PageModel;
  21. use Contao\StringUtil;
  22. use Contao\System;
  23. use numero2\StoreLocator\DCAHelper\Stores;
  24. use numero2\TagsBundle\TagsBundle;
  25. use stdClass;
  26. use Symfony\Component\HttpFoundation\JsonResponse;
  27. class ModuleStoreLocatorList extends Module {
  28.     /**
  29.      * Template
  30.      * @var string
  31.      */
  32.     protected $strTemplate 'mod_storelocator_list';
  33.     /**
  34.      * Display a wildcard in the back end
  35.      *
  36.      * @return string
  37.      */
  38.     public function generate(): string {
  39.         $scopeMatcher System::getContainer()->get('contao.routing.scope_matcher');
  40.         $requestStack System::getContainer()->get('request_stack');
  41.         if( $scopeMatcher->isBackendRequest($requestStack->getCurrentRequest()) ) {
  42.             $objTemplate = new BackendTemplate('be_wildcard');
  43.             $objTemplate->wildcard '### '.$GLOBALS['TL_LANG']['FMD']['storelocator_list'][0].' ###';
  44.             $objTemplate->title $this->headline;
  45.             $objTemplate->id $this->id;
  46.             $objTemplate->link $this->name;
  47.             $objTemplate->href System::getContainer()->get('router')->generate(
  48.                 'contao_backend',
  49.                 ['do' => 'themes''table' => 'tl_module''act' => 'edit''id' => $this->id],
  50.             );
  51.             return $objTemplate->parse();
  52.         }
  53.         return parent::generate();
  54.     }
  55.     /**
  56.      * Generate module
  57.      */
  58.     protected function compile(): void {
  59.         global $objPage;
  60.         if( !isset($_GET['search']) && Config::get('useAutoItem') && isset($_GET['auto_item']) ) {
  61.             Input::setGet('search'Input::get('auto_item'));
  62.         }
  63.         $sSearchVal null;
  64.         $sSearchVal Input::get('search') ? Input::get('search') : null;
  65.         $aStores = [];
  66.         $filterFields = [];
  67.         if( $this->storelocator_use_filter ) {
  68.             $modFilter ModuleModel::findById($this->storelocator_mod_filter);
  69.             if( $modFilter ) {
  70.                 $filterFields StringUtil::deserialize($modFilter->storelocator_search_intrue);
  71.             }
  72.         }
  73.         // do not render list module if no empty search is allowed
  74.         if( !$this->storelocator_always_show_results && !$sSearchVal ) {
  75.             $this->Template->preventRendering true;
  76.         // normal search
  77.         } else {
  78.             $aCategories = [];
  79.             $aCategories StringUtil::deserialize($this->storelocator_list_categories);
  80.             $aSearchValues = [];
  81.             $aSearchValues StoreLocator::parseSearchValue($sSearchVal);
  82.             $category null;
  83.             if( !empty($aSearchValues['category']) ) {
  84.                 $objCategory null;
  85.                 $objCategory CategoriesModel::findByAlias($aSearchValues['category']);
  86.                 if( $objCategory && $objCategory->count() > && in_array($objCategory->id$aCategories) ) {
  87.                     $category = [$objCategory->id];
  88.                 } else {
  89.                     $category null;
  90.                 }
  91.             }
  92.             $filteredTag null;
  93.             if( class_exists(TagsBundle::class) && !empty($aSearchValues['tags']) ) {
  94.                 $categories StringUtil::deserialize($this->storelocator_list_categoriestrue);
  95.                 $tags TagsModel::findByStorelocatorCategories($categories);
  96.                 if( $tags ) {
  97.                     foreach( $tags as $tag ) {
  98.                         if( $aSearchValues['tags'] === StringUtil::standardize($tag->tag) ) {
  99.                             $filteredTag $tag->id;
  100.                             break;
  101.                         }
  102.                     }
  103.                 }
  104.             }
  105.             // handle ajax request for searching markers in map
  106.             if( Environment::get('isAjaxRequest') ) {
  107.                 if( Input::get('action') == "getMarkers" ) {
  108.                     $filterStr '';
  109.                     if( $this->storelocator_use_filter ) {
  110.                         $filterStr StoreLocator::createFilterWhereClause($aSearchValues['filter']??''$filterFields$filteredTag);
  111.                     }
  112.                     $oStores StoresModel::searchBetweenCoords(
  113.                         Input::get('fromlng'), Input::get('tolng'),
  114.                         Input::get('fromlat'), Input::get('tolat'),
  115.                         $this->storelocator_limit_marker,
  116.                         ($category?$category:$aCategories),
  117.                         (strlen($filterStr)?$filterStr:null)
  118.                     );
  119.                     $aJson = [];
  120.                     if( $oStores && $oStores->count() > ) {
  121.                         $aStores = [];
  122.                         foreach( $oStores as $entry ) {
  123.                             StoreLocator::parseStoreData($entry$this);
  124.                             $aStores[] = $entry;
  125.                         }
  126.                         // HOOK: add custom logic to modify the entries of the list
  127.                         if( is_array($GLOBALS['N2SL_HOOKS']['modifyListEntries']) ) {
  128.                             foreach( $GLOBALS['N2SL_HOOKS']['modifyListEntries'] as $callback ) {
  129.                                 if( is_array($callback) ) {
  130.                                     $this->import($callback[0]);
  131.                                     $aStores $this->{$callback[0]}->{$callback[1]}($aStores$this);
  132.                                 }
  133.                             }
  134.                         }
  135.                         $oTemplateInfoWindow = new FrontendTemplate('mod_storelocator_infowindow');
  136.                         $oTemplateInfoWindow->labelPhone $GLOBALS['TL_LANG']['tl_storelocator']['field']['phone'];
  137.                         $oTemplateInfoWindow->labelFax $GLOBALS['TL_LANG']['tl_storelocator']['field']['fax'];
  138.                         $oTemplateInfoWindow->labelEMail $GLOBALS['TL_LANG']['tl_storelocator']['field']['email'];
  139.                         $oTemplateInfoWindow->labelWWW $GLOBALS['TL_LANG']['tl_storelocator']['field']['www'];
  140.                         $oTemplateInfoWindow->labelDistance $GLOBALS['TL_LANG']['tl_storelocator']['field']['distance'];
  141.                         $oTemplateInfoWindow->labelMore $GLOBALS['TL_LANG']['tl_storelocator']['field']['more'];
  142.                         foreach( $aStores as $key => $value ) {
  143.                             $oTemplateInfoWindow->entry $value;
  144.                             $aJson[] = [
  145.                                 'id'    => $value->id
  146.                             ,   'pid'   => $value->pid
  147.                             ,   'lat'   => $value->latitude
  148.                             ,   'lng'   => $value->longitude
  149.                             ,   'info'  => System::getContainer()->has('contao.insert_tag.parser') ? System::getContainer()->get('contao.insert_tag.parser')->replace$oTemplateInfoWindow->parse() ) : $this->replaceInsertTags($oTemplateInfoWindow->parse())
  150.                             ];
  151.                         }
  152.                     }
  153.                     $response = new JsonResponse($aJson);
  154.                     throw new ResponseException($response);
  155.                 }
  156.             }
  157.             $aCountryNames = [];
  158.             $aCountryNames Stores::getCountries();
  159.             if( !empty($aSearchValues['term']) || $this->storelocator_always_show_results ) {
  160.                 // search for longitude and latitude
  161.                 if( !empty($aSearchValues['term']) && (empty($aSearchValues['longitude']) || empty($aSearchValues['latitude'])) ) {
  162.                     $oSL null;
  163.                     $oSL = new StoreLocator();
  164.                     $aCoordinates = [];
  165.                     $aCoordinates $oSL->getCoordinatesByString($aSearchValues['term']);
  166.                     if( !empty($aCoordinates) ) {
  167.                         $aSearchValues['latitude'] = $aCoordinates['latitude'];
  168.                         $aSearchValues['longitude'] = $aCoordinates['longitude'];
  169.                     }
  170.                 }
  171.                 $objStores null;
  172.                 $filterStr '';
  173.                 if( $this->storelocator_use_filter ) {
  174.                     $filterStr StoreLocator::createFilterWhereClause($aSearchValues['filter']??''$filterFields$filteredTag);
  175.                 }
  176.                 // search all countries
  177.                 if( !empty($aSearchValues['term']) ) {
  178.                     $objStores StoresModel::searchNearby(
  179.                         $aSearchValues['latitude'], $aSearchValues['longitude'],
  180.                         ($this->storelocator_limit_distance?$this->storelocator_max_distance:0),
  181.                         $this->storelocator_list_limit,
  182.                         ($category?$category:$aCategories),
  183.                         (strlen($filterStr)?$filterStr:null),
  184.                         (!empty($aSearchValues['order'])&&!empty($aSearchValues['sort']))?$aSearchValues['order'].' '.strtoupper($aSearchValues['sort']):null,
  185.                         $filteredTag
  186.                     );
  187.                 // search selected country only
  188.                 } else {
  189.                     // default sorting
  190.                     if( !empty($this->storelocator_list_sort_field) && empty($aSearchValues['order']) ) {
  191.                         $aSearchValues['order'] = $this->storelocator_list_sort_field;
  192.                         if( $this->storelocator_list_sort_direction ) {
  193.                             $aSearchValues['sort'] = $this->storelocator_list_sort_direction=='ascending'?'ASC':'DESC';
  194.                         }
  195.                     }
  196.                     $objStores StoresModel::searchCountry(
  197.                         $this->storelocator_default_country,
  198.                         $this->storelocator_list_limit,
  199.                         ($category?$category:$aCategories),
  200.                         (strlen($filterStr)?$filterStr:null),
  201.                         (!empty($aSearchValues['order'])&&!empty($aSearchValues['sort']))?$aSearchValues['order'].' '.strtoupper($aSearchValues['sort']):null,
  202.                         $filteredTag
  203.                     );
  204.                 }
  205.                 if( count($objStores) ) {
  206.                     foreach( $objStores as $entry ) {
  207.                         if( empty($sSearchVal) ) {
  208.                             $entry->distance null;
  209.                         }
  210.                         StoreLocator::parseStoreData($entry$this);
  211.                         $entry->class $entry->highlight 'starred' '';
  212.                         // get image
  213.                         if( $entry->singleSRC ) {
  214.                             $objFile null;
  215.                             $objFile FilesModel::findByUuid($entry->singleSRC);
  216.                             if( $objFile ) {
  217.                                 $entry->image $objFile;
  218.                                 $temp = new stdClass();
  219.                                 // Contao >= 4.9
  220.                                 if( method_exists($this'addImageToTemplate') ) {
  221.                                     $this->addImageToTemplate($temp, [
  222.                                         'singleSRC' => $objFile->path
  223.                                     ,   'size' => $this->imgSize
  224.                                     ], nullnull$objFile);
  225.                                 // Contao 5
  226.                                 } else {
  227.                                     $figureBuilder System::getContainer()
  228.                                         ->get('contao.image.studio')
  229.                                         ->createFigureBuilder()
  230.                                         ->from($objFile->path)
  231.                                         ->setSize($this->imgSize);
  232.                                     if( null !== ($figure $figureBuilder->buildIfResourceExists()) ) {
  233.                                         $figure->applyLegacyTemplateData($temp);
  234.                                     }
  235.                                 }
  236.                                 foreach( $temp as $k => $v ) {
  237.                                     $entry->$k $v;
  238.                                 }
  239.                                 unset($temp);
  240.                             }
  241.                         }
  242.                         $aStores[] = $entry;
  243.                     }
  244.                     // use translated country names when sorting by country
  245.                     if( !empty($this->storelocator_list_sort_field) && $this->storelocator_list_sort_field == 'country' ) {
  246.                         usort($aStores, fn($a$b) => strcmp($a->country_name$b->country_name));
  247.                         if( $this->storelocator_list_sort_direction == 'descending' ) {
  248.                             $aStores array_reverse($aStores);
  249.                         }
  250.                     }
  251.                     // HOOK: add custom logic to modify the entries of the list
  252.                     if( is_array($GLOBALS['N2SL_HOOKS']['modifyListEntries']) ) {
  253.                         foreach( $GLOBALS['N2SL_HOOKS']['modifyListEntries'] as $callback ) {
  254.                             if( is_array($callback) ) {
  255.                                 $this->import($callback[0]);
  256.                                 $aStores $this->{$callback[0]}->{$callback[1]}($aStores$this);
  257.                             }
  258.                         }
  259.                     }
  260.                     if( $this->storelocator_show_map ) {
  261.                         if( $aStores && count($aStores) > ) {
  262.                             $oTemplateInfoWindow = new FrontendTemplate('mod_storelocator_infowindow');
  263.                             $oTemplateInfoWindow->labelPhone $GLOBALS['TL_LANG']['tl_storelocator']['field']['phone'];
  264.                             $oTemplateInfoWindow->labelFax $GLOBALS['TL_LANG']['tl_storelocator']['field']['fax'];
  265.                             $oTemplateInfoWindow->labelEMail $GLOBALS['TL_LANG']['tl_storelocator']['field']['email'];
  266.                             $oTemplateInfoWindow->labelWWW $GLOBALS['TL_LANG']['tl_storelocator']['field']['www'];
  267.                             $oTemplateInfoWindow->labelDistance $GLOBALS['TL_LANG']['tl_storelocator']['field']['distance'];
  268.                             $oTemplateInfoWindow->labelMore $GLOBALS['TL_LANG']['tl_storelocator']['field']['more'];
  269.                             foreach( $aStores as $key => $value ) {
  270.                                 $oTemplateInfoWindow->entry $value;
  271.                                 if( System::getContainer()->has('contao.insert_tag.parser') ) {
  272.                                     $aStores[$key]->info json_encode(
  273.                                         System::getContainer()->get('contao.insert_tag.parser')->replace$oTemplateInfoWindow->parse() )
  274.                                     );
  275.                                 } else {
  276.                                     $aStores[$key]->info json_encode(
  277.                                         $this->replaceInsertTags$oTemplateInfoWindow->parse() )
  278.                                     );
  279.                                 }
  280.                             }
  281.                         }
  282.                         if( $this->storelocator_provider === 'google-maps' ) {
  283.                             $this->addGoogleMap($aStores);
  284.                         } else {
  285.                             // HOOK for adding custom javascript provider
  286.                         }
  287.                     }
  288.                 }
  289.                 if( !count($aStores) ) {
  290.                     $this->Template->noResults true;
  291.                 }
  292.             }
  293.         }
  294.         $this->Template->labelPhone $GLOBALS['TL_LANG']['tl_storelocator']['field']['phone'];
  295.         $this->Template->labelFax $GLOBALS['TL_LANG']['tl_storelocator']['field']['fax'];
  296.         $this->Template->labelEMail $GLOBALS['TL_LANG']['tl_storelocator']['field']['email'];
  297.         $this->Template->labelWWW $GLOBALS['TL_LANG']['tl_storelocator']['field']['www'];
  298.         $this->Template->labelDistance $GLOBALS['TL_LANG']['tl_storelocator']['field']['distance'];
  299.         $this->Template->labelMore $GLOBALS['TL_LANG']['tl_storelocator']['field']['more'];
  300.         $this->Template->msgNoResults $GLOBALS['TL_LANG']['tl_storelocator']['noresults'];
  301.         $this->Template->stores $aStores;
  302.     }
  303.     /**
  304.      * Add necessary template for google map
  305.      *
  306.      * @param array $aStores
  307.      */
  308.     private function addGoogleMap$aStores=null ): void {
  309.         global $objPage;
  310.         $this->Template->showMap true;
  311.         $oTemplateGoogleMap = new FrontendTemplate('script_storelocator_googlemap');
  312.         $oTemplateGoogleMap->country $this->storelocator_default_country;
  313.         $oTemplateGoogleMap->mapsKey Config::get('google_maps_browser_key');
  314.         $oTemplateGoogleMap->requestToken = (defined('VERSION') ? '{{request_token}}' System::getContainer()->get('contao.csrf.token_manager')->getDefaultTokenValue());
  315.         $mapPins = [];
  316.         if( !$oTemplateGoogleMap->mapsKey ) {
  317.             return;
  318.         }
  319.         if( $this->storelocator_map_pin ) {
  320.             $mapPins['default'] = $this->storelocator_map_pin;
  321.         }
  322.         // gather pins graphics
  323.         $oMapPins null;
  324.         $oMapPins CategoriesModel::getMapPins();
  325.         $oMapPins $oMapPins->fetchAll();
  326.         foreach( $oMapPins as $key => $value ) {
  327.             if( !empty($value['map_pin']) ) {
  328.                 $mapPins[$value['id']] = $value['map_pin'];
  329.             }
  330.         }
  331.         foreach( $mapPins as $key => $value ) {
  332.             $oFile null;
  333.             $oFile FilesModel::findByUuid($value);
  334.             if( !empty($oFile->path) ) {
  335.                 $mapPins[$key] = $oFile->path;
  336.             } else {
  337.                 unset($mapPins[$key]);
  338.             }
  339.         }
  340.         $oTemplateGoogleMap->mapPins $mapPins;
  341.         $oTemplateGoogleMap->loadMoreResults $this->storelocator_load_results_on_pan;
  342.         $oTemplateGoogleMap->mapInteraction $this->storelocator_map_interaction;
  343.         $oTemplateGoogleMap->listInteraction $this->storelocator_list_interaction;
  344.         $oTemplateGoogleMap->markerclusterer $this->storelocator_markerclusterer;
  345.         $oTemplateGoogleMap->loadedMapsApi $objPage->loadedMapsApi;
  346.         $oTemplateGoogleMap->entries array_slice($aStores,0,500,true);
  347.         $this->Template->scriptMap $oTemplateGoogleMap->parse();
  348.     }
  349. }