Completed
Pull Request — master (#105)
by Nic
26:42
created

Locator_Controller::LocationSearch()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 1 Features 0
Metric Value
c 10
b 1
f 0
dl 0
loc 15
rs 9.4285
cc 2
eloc 10
nc 2
nop 0
1
<?php
2
3
/**
4
 * Class Locator
5
 *
6
 * @property bool $AutoGeocode
7
 * @property bool $ModalWindow
8
 * @property string $Unit
9
 * @method Categories|ManyManyList $Categories
10
 */
11
class Locator extends Page
12
{
13
14
    /**
15
     * @var array
16
     */
17
    private static $db = array(
18
        'AutoGeocode' => 'Boolean',
19
        'ModalWindow' => 'Boolean',
20
        'Unit' => 'Enum("m,km","m")',
21
    );
22
23
    /**
24
     * @var array
25
     */
26
    private static $many_many = array(
27
        'Categories' => 'LocationCategory',
28
    );
29
30
    /**
31
     * @var array
32
     */
33
    private static $defaults = array(
34
        'AutoGeocode' => true,
35
    );
36
37
    /**
38
     * @var string
39
     */
40
    private static $singular_name = 'Locator';
41
    /**
42
     * @var string
43
     */
44
    private static $plural_name = 'Locators';
45
    /**
46
     * @var string
47
     */
48
    private static $description = 'Find locations on a map';
1 ignored issue
show
Unused Code introduced by
The property $description is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
49
50
    /**
51
     * @return FieldList
52
     */
53
    public function getCMSFields()
54
    {
55
        $fields = parent::getCMSFields();
56
57
        // Settings
58
        $fields->addFieldsToTab('Root.Settings', array(
59
            HeaderField::create('DisplayOptions', 'Display Options', 3),
60
            OptionsetField::create('Unit', 'Unit of measure', array('m' => 'Miles', 'km' => 'Kilometers')),
61
            CheckboxField::create('AutoGeocode', 'Auto Geocode - Automatically filter map results based on user location')
62
                ->setDescription('Note: if any locations are set as featured, the auto geocode is automatically disabled.'),
63
            CheckboxField::create('ModalWindow', 'Modal Window - Show Map results in a modal window'),
64
        ));
65
66
        // Filter categories
67
        $config = GridFieldConfig_RelationEditor::create();
68
        if (class_exists('GridFieldAddExistingSearchButton')) {
69
            $config->removeComponentsByType('GridFieldAddExistingAutocompleter');
70
            $config->addComponent(new GridFieldAddExistingSearchButton());
71
        }
72
        $categories = $this->Categories();
73
        $categoriesField = GridField::create('Categories', 'Categories', $categories, $config)
74
            ->setDescription('only show locations from the selected category');
75
76
        // Filter
77
        $fields->addFieldsToTab('Root.Filter', array(
78
            HeaderField::create('CategoryOptionsHeader', 'Location Filtering', 3),
79
            $categoriesField,
80
        ));
81
82
        $this->extend('updateCMSFields', $fields);
83
84
        return $fields;
85
    }
86
87
    /**
88
     * @param SS_HTTPRequest|null $request
89
     * @param int $locatorID
90
     * @param array $filter
91
     * @param array $filterAny
92
     * @param array $exclude
93
     * @param string $locationClass
94
     * @param null $filterByCallback
95
     * @return ArrayList
96
     */
97
    public static function get_locations(
98
        SS_HTTPRequest $request = null,
99
        $locatorID,
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
100
        $filter = [],
101
        $filterAny = [],
102
        $exclude = [],
103
        $locationClass = 'Location',
104
        $filterByCallback = null
105
    )
106
    {
107
108
        $request = ($request != null) ? $request : Controller::curr()->getRequest();
109
110
        // search across all address related fields
111
        $address = ($request->getVar('Address')) ? $request->getVar('Address') : false;
112
        if ($address && !Locator::get()->byID($locatorID)->AutoGeocode) {
113
            $filterAny['Address:PartialMatch'] = $address;
114
            $filterAny['Suburb:PartialMatch'] = $address;
115
            $filterAny['State:PartialMatch'] = $address;
116
            $filterAny['Postcode:PartialMatch'] = $address;
117
            $filterAny['Country:PartialMatch'] = $address;
118
        }
119
120
        // search for category from form, else categories from Locator
121
        $category = ($request->getVar('CategoryID')) ? $request->getVar('CategoryID') : false;
122
        if ($category) {
123
            $filter['CategoryID:ExactMatch'] = $category;
124
        } elseif (Locator::get()->byID($locatorID)->Categories()->exists()) {
125
            $categories = Locator::get()->byID($locatorID)->Categories();
126
            $categoryArray = array();
127
            foreach ($categories as $category) {
128
                array_push($categoryArray, $category->ID);
129
            }
130
            $filter['CategoryID'] = $categoryArray;
131
        }
132
133
        $locationsList = ArrayList::create();
134
135
        $locations = $locationClass::get()->filter($filter)->exclude($exclude);
136
137
        if (!empty($filterAny)) {
138
            $locations = $locations->filterAny($filterAny);
139
        }
140
141
        if ($filterByCallback !== null && is_callable($filterByCallback)) {
142
            $locations = $locations->filterByCallback($filterByCallback);
143
        }
144
145
        if ($locations->exists()) {
146
            $locationsList->merge($locations);
147
        }
148
149
        return $locationsList;
150
    }
151
152
    /**
153
     * @return DataList
154
     */
155
    public static function getAllCategories()
156
    {
157
        return LocationCategory::get();
158
    }
159
160
    /**
161
     * @param null $id
162
     * @return bool
163
     */
164
    public static function getPageCategories($id = null)
165
    {
166
        if ($id) {
167
            if ($locator = self::get()->byID($id)) {
168
                return $locator->Categories();
169
            }
170
171
            return false;
172
        }
173
174
        return false;
175
    }
176
177
178
}
179
180
/**
181
 * Class Locator_Controller
182
 */
183
class Locator_Controller extends Page_Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
184
{
185
    /**
186
     * @var array
187
     */
188
    private static $allowed_actions = [
189
        'xml',
190
    ];
191
192
    /**
193
     * @var array
194
     */
195
    private static $base_filter = [
0 ignored issues
show
Unused Code introduced by
The property $base_filter is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
196
        'ShowInLocator' => true,
197
    ];
198
199
    /**
200
     * @var array
201
     */
202
    private static $base_filter_any = [];
0 ignored issues
show
Unused Code introduced by
The property $base_filter_any is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
203
204
    /**
205
     * @var array
206
     */
207
    private static $base_exclude = [
0 ignored issues
show
Unused Code introduced by
The property $base_exclude is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
208
        'Lat' => 0.00000,
209
        'Lng' => 0.00000,
210
    ];
211
212
    /**
213
     * @var
214
     */
215
    private $locations;
216
217
    /**
218
     * Set Requirements based on input from CMS
219
     */
220
    public function init()
221
    {
222
        parent::init();
223
224
        $themeDir = SSViewer::get_theme_folder();
225
226
        // google maps api key
227
        $key = Config::inst()->get('GoogleGeocoding', 'google_api_key');
228
229
        $locations = $this->getLocations();
230
231
        Requirements::javascript('framework/thirdparty/jquery/jquery.js');
232
        if ($locations) {
233
            Requirements::javascript('http://maps.google.com/maps/api/js?key=' . $key);
234
            Requirements::javascript('locator/thirdparty/handlebars/handlebars-v1.3.0.js');
235
            Requirements::javascript('locator/thirdparty/jquery-store-locator/js/jquery.storelocator.js');
236
        }
237
238
        Requirements::css('locator/css/map.css');
239
240
        $featuredInList = ($locations->filter('Featured', true)->count() > 0);
241
242
        $featured = $featuredInList
243
            ? 'featuredLocations: true'
244
            : 'featuredLocations: false';
245
246
        // map config based on user input in Settings tab
247
        // AutoGeocode or Full Map
248
        if ($this->data()->AutoGeocode) {
249
            $load = $featuredInList
250
                ? 'autoGeocode: false, fullMapStart: true, storeLimit: 1000, maxDistance: true,'
251
                : 'autoGeocode: true, fullMapStart: false,';
252
        } else {
253
            $load = 'autoGeocode: false, fullMapStart: true, storeLimit: 1000, maxDistance: true,';
254
        }
255
256
        $base = Director::baseFolder();
257
        $themePath = $base . '/' . $themeDir;
258
259
        $listTemplatePath = (file_exists($themePath . '/templates/location-list-description.html')) ?
260
            $themeDir . '/templates/location-list-description.html' :
261
            'locator/templates/location-list-description.html';
262
        $infowindowTemplatePath = (file_exists($themePath . '/templates/infowindow-description.html')) ?
263
            $themeDir . '/templates/infowindow-description.html' :
264
            'locator/templates/infowindow-description.html';
265
266
        // in page or modal
267
        $modal = ($this->data()->ModalWindow) ? 'modalWindow: true' : 'modalWindow: false';
268
269
        $kilometer = ($this->data()->Unit == 'km') ? 'lengthUnit: "km"' : 'lengthUnit: "m"';
270
271
        // pass GET variables to xml action
272
        $vars = $this->request->getVars();
273
        unset($vars['url']);
274
        unset($vars['action_index']);
275
        $url = '';
276
        if (count($vars)) {
277
            $url .= '?' . http_build_query($vars);
278
        }
279
        $link = $this->Link() . 'xml.xml' . $url;
280
281
        // init map
282
        if ($locations) {
283
            Requirements::customScript("
284
                $(function($) {
285
                    $('#map-container').storeLocator({
286
                        " . $load . "
287
                        dataLocation: '" . $link . "',
288
                        listTemplatePath: '" . $listTemplatePath . "',
289
                        infowindowTemplatePath: '" . $infowindowTemplatePath . "',
290
                        originMarker: true,
291
                        " . $modal . ',
292
                        ' . $featured . ",
293
                        slideMap: false,
294
                        zoomLevel: 0,
295
                        noForm: true,
296
                        formID: 'Form_LocationSearch',
297
                        inputID: 'Form_LocationSearch_Address',
298
                        categoryID: 'Form_LocationSearch_category',
299
                        distanceAlert: -1,
300
                        " . $kilometer . '
301
                    });
302
                });
303
            ');
304
        }
305
    }
306
307
    /**
308
     * @param SS_HTTPRequest $request
309
     *
310
     * @return ViewableData_Customised
311
     */
312
    public function index(SS_HTTPRequest $request)
313
    {
314
        $locations = $this->getLocations();
315
316
        return $this->customise(array(
317
            'Locations' => $locations,
318
        ));
319
    }
320
321
    /**
322
     * Return a XML feed of all locations marked "show in locator"
323
     *
324
     * @param SS_HTTPRequest $request
325
     * @return HTMLText
326
     */
327
    public function xml(SS_HTTPRequest $request)
328
    {
329
        $this->setLocations($request);
330
        $locations = $this->getLocations();
331
332
        return $this->customise(array(
333
            'Locations' => $locations,
334
        ))->renderWith('LocationXML');
335
    }
336
337
    /**
338
     * @return mixed
339
     */
340
    public function getLocations()
341
    {
342
        if (!$this->locations) {
343
            $this->setLocations($this->request);
344
        }
345
        return $this->locations;
346
    }
347
348
    /**
349
     * @param SS_HTTPRequest|null $request
350
     * @return $this
351
     */
352
    public function setLocations(SS_HTTPRequest $request = null)
353
    {
354
        if ($request === null) {
355
            $request = $this->request;
356
        }
357
        $filter = $this->config()->get('base_filter');
358
        $this->extend('updateLocatorFilter', $filter, $request);
359
360
        $filterAny = $this->config()->get('base_filter_any');
361
        $this->extend('updateLocatorFilterAny', $filterAny, $request);
362
363
        $exclude = $this->config()->get('base_exclude');
364
        $this->extend('updateLocatorExclude', $exclude, $request);
365
366
        $this->locations = Locator::get_locations($request, $this->data()->ID, $filter, $filterAny, $exclude);
367
        return $this;
368
    }
369
370
    /**
371
     * LocationSearch form.
372
     *
373
     * Search form for locations, updates map and results list via AJAX
374
     *
375
     * @return Form
376
     */
377
    public function LocationSearch()
378
    {
379
380
        if (class_exists('BootstrapForm')) {
381
            $form = LocatorBootstrapForm::create($this, 'LocationSearch');
382
        } else {
383
            $form = LocatorForm::create($this, 'LocationSearch');
384
        }
385
386
        return $form
387
            ->setFormMethod('GET')
388
            ->setFormAction($this->Link())
389
            ->disableSecurityToken()
390
            ->loadDataFrom($this->request->getVars());
391
    }
392
393
}
394