Completed
Push — master ( 83571a...9e9b74 )
by Nic
30s
created

Locator_Controller::LocationSearch()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 9
Bugs 0 Features 0
Metric Value
c 9
b 0
f 0
dl 0
loc 14
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 Jason Irish
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 array $filter
89
     * @param array $filterAny
90
     * @param array $exclude
91
     * @param null|callable $callback
92
     * @return DataList|ArrayList
93
     */
94
    public static function get_locations(
95
        $filter = [],
96
        $filterAny = [],
97
        $exclude = [],
98
        $callback = null
99
    )
100
    {
101
        $locations = Location::get()->filter($filter)->exclude($exclude);
102
103
        if (!empty($filterAny)) {
104
            $locations = $locations->filterAny($filterAny);
105
        }
106
        if (!empty($exclude)) {
107
            $locations = $locations->exclude($exclude);
108
        }
109
110
        if ($callback !== null && is_callable($callback)) {
111
            $locations->filterByCallback($callback);
112
        }
113
114
        return $locations;
115
    }
116
117
    /**
118
     * @return DataList
119
     */
120
    public static function get_all_categories()
121
    {
122
        return LocationCategory::get();
123
    }
124
125
    /**
126
     * @return bool
127
     */
128
    public function getPageCategories()
129
    {
130
        return self::locator_categories_by_locator($this->ID);
131
    }
132
133
    /**
134
     * @param int $id
135
     * @return bool|
136
     */
137
    public static function locator_categories_by_locator($id = 0)
138
    {
139
        if ($id == 0) {
140
            return false;
141
        }
142
143
        return Locator::get()->byID($id)->Categories();
144
    }
145
146
147
}
148
149
/**
150
 * Class Locator_Controller
151
 */
152
class Locator_Controller extends Page_Controller
0 ignored issues
show
Coding Style Compatibility introduced by Jason Irish
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...
153
{
154
    /**
155
     * @var array
156
     */
157
    private static $allowed_actions = array(
158
        'xml',
159
    );
160
161
    /**
162
     * @var array
163
     */
164
    private static $base_filter = [
0 ignored issues
show
Unused Code introduced by muskie9
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...
165
        'ShowInLocator' => true,
166
    ];
167
168
    /**
169
     * @var array
170
     */
171
    private static $base_exclude = [
0 ignored issues
show
Unused Code introduced by muskie9
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...
172
        'Lat' => 0,
173
        'Lng' => 0,
174
    ];
175
176
    /**
177
     * @var array
178
     */
179
    private static $base_filter_any = [];
0 ignored issues
show
Unused Code introduced by muskie9
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...
180
181
    /**
182
     * @var string
183
     */
184
    private static $list_template_path = 'locator/templates/location-list-description.html';
0 ignored issues
show
Unused Code introduced by muskie9
The property $list_template_path 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...
185
186
    /**
187
     * @var string
188
     */
189
    private static $info_window_template_path = 'locator/templates/infowindow-description.html';
0 ignored issues
show
Unused Code introduced by muskie9
The property $info_window_template_path 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...
190
191
    /**
192
     * @var DataList|ArrayList
193
     */
194
    private $locations;
195
196
    /**
197
     * Set Requirements based on input from CMS
198
     */
199
    public function init()
0 ignored issues
show
Coding Style introduced by muskie9
init uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
200
    {
201
        parent::init();
202
203
        // google maps api key
204
        $key = Config::inst()->get('GoogleGeocoding', 'google_api_key');
205
206
        $locations = $this->getLocations();
207
208
        if ($locations) {
209
210
            Requirements::javascript('framework/thirdparty/jquery/jquery.js');
211
            Requirements::javascript('https://maps.google.com/maps/api/js?key=' . $key);
212
            Requirements::javascript('locator/thirdparty/handlebars/handlebars-v1.3.0.js');
213
            Requirements::javascript('locator/thirdparty/jquery-store-locator/js/jquery.storelocator.js');
214
            Requirements::css('locator/css/map.css');
215
216
            $featuredInList = ($locations->filter('Featured', true)->count() > 0);
217
            $defaultCoords = $this->getAddressSearchCoords() ? $this->getAddressSearchCoords() : '';
218
            $isChrome = (strpos($_SERVER['HTTP_USER_AGENT'], 'Chrome') !== FALSE);
219
220
            $featured = $featuredInList
221
                ? 'featuredLocations: true'
222
                : 'featuredLocations: false';
223
224
            // map config based on user input in Settings tab
225
            // AutoGeocode or Full Map
226
            if ($this->data()->AutoGeocode) {
227
                $load = $featuredInList || $defaultCoords || $isChrome != ''
228
                    ? 'autoGeocode: false, fullMapStart: true, storeLimit: 1000, maxDistance: true,'
229
                    : 'autoGeocode: true, fullMapStart: false,';
230
            } else {
231
                $load = 'autoGeocode: false, fullMapStart: true, storeLimit: 1000, maxDistance: true,';
232
            }
233
234
            $listTemplatePath = $this->config()->get('list_template_path');
235
            $infowindowTemplatePath = $this->config()->get('info_window_template_path');
236
237
            // in page or modal
238
            $modal = ($this->data()->ModalWindow) ? 'modalWindow: true' : 'modalWindow: false';
239
240
            $kilometer = ($this->data()->Unit == 'km') ? 'lengthUnit: "km"' : 'lengthUnit: "m"';
241
242
            // pass GET variables to xml action
243
            $vars = $this->request->getVars();
244
            unset($vars['url']);
245
            unset($vars['action_doFilterLocations']);
246
            $url = '';
247
            if (count($vars)) {
248
                $url .= '?' . http_build_query($vars);
249
            }
250
            $link = $this->Link() . 'xml.xml' . $url;
251
252
            // init map
253
            Requirements::customScript("
254
                $(function(){
255
                    $('#map-container').storeLocator({
256
                        " . $load . "
257
                        dataLocation: '" . $link . "',
258
                        listTemplatePath: '" . $listTemplatePath . "',
259
                        infowindowTemplatePath: '" . $infowindowTemplatePath . "',
260
                        originMarker: true,
261
                        " . $modal . ',
262
                        ' . $featured . ",
263
                        slideMap: false,
264
                        zoomLevel: 0,
265
                        noForm: true,
266
                        distanceAlert: -1,
267
                        " . $kilometer . ',
268
                        ' . $defaultCoords . '
269
                    });
270
                });
271
            ');
272
        }
273
    }
274
275
    /**
276
     * @param SS_HTTPRequest $request
277
     *
278
     * @return ViewableData_Customised
279
     */
280 View Code Duplication
    public function index(SS_HTTPRequest $request)
0 ignored issues
show
Duplication introduced by muskie9
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
281
    {
282
        $locations = $this->getLocations();
283
284
        if ($locations->canSortBy('distance')) {
285
            $locations = $locations->sort('distance');
286
        }
287
288
        return $this->customise(array(
289
            'Locations' => $locations,
290
        ));
291
    }
292
293
    /**
294
     * Return a XML feed of all locations marked "show in locator"
295
     *
296
     * @param SS_HTTPRequest $request
297
     * @return HTMLText
298
     */
299 View Code Duplication
    public function xml(SS_HTTPRequest $request)
0 ignored issues
show
Duplication introduced by muskie9
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
300
    {
301
        $locations = $this->getLocations();
302
303
        if ($locations->canSortBy('distance')) {
304
            $locations = $locations->sort('distance');
305
        }
306
307
        return $this->customise(array(
308
            'Locations' => $locations,
309
        ))->renderWith('LocationXML');
310
    }
311
312
    /**
313
     * @return ArrayList|DataList
314
     */
315
    public function getLocations()
316
    {
317
        if (!$this->locations) {
318
            $this->setLocations($this->request);
319
        }
320
        return $this->locations;
321
    }
322
323
    /**
324
     * @param SS_HTTPRequest|null $request
325
     * @return $this
326
     */
327
    public function setLocations(SS_HTTPRequest $request = null)
328
    {
329
330
        if ($request === null) {
331
            $request = $this->request;
332
        }
333
        $filter = $this->config()->get('base_filter');
334
335
        if ($request->getVar('CategoryID')) {
336
            $filter['CategoryID'] = $request->getVar('CategoryID');
337
        }
338
339
        $this->extend('updateLocatorFilter', $filter, $request);
340
341
        $filterAny = $this->config()->get('base_filter_any');
342
        $this->extend('updateLocatorFilterAny', $filterAny, $request);
343
344
        $exclude = $this->config()->get('base_exclude');
345
        $this->extend('updateLocatorExclude', $exclude, $request);
346
347
        $callback = null;
348
        $this->extend('updateLocatorCallback', $callback, $request);
349
350
        $locations = Locator::get_locations($filter, $filterAny, $exclude, $callback);
351
        $locations = DataToArrayListHelper::to_array_list($locations);
0 ignored issues
show
Bug introduced by muskie9
It seems like $locations defined by \DataToArrayListHelper::to_array_list($locations) on line 351 can also be of type object<ArrayList>; however, DataToArrayListHelper::to_array_list() does only seem to accept object<DataList>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
352
353
        $this->locations = $locations;
354
        return $this;
355
356
    }
357
358
    /**
359
     * @return bool|string
360
     */
361
    public function getAddressSearchCoords()
362
    {
363
        if (!$this->request->getVar('Address')) {
364
            return false;
365
        }
366
        $coords = GoogleGeocoding::address_to_point(Controller::curr()->getRequest()->getVar('Address'));
367
368
        $lat = $coords['lat'];
369
        $lng = $coords['lng'];
370
371
        return "defaultLat: {$lat}, defaultLng: {$lng},";
372
    }
373
374
    /**
375
     * LocationSearch form.
376
     *
377
     * Search form for locations, updates map and results list via AJAX
378
     *
379
     * @return Form
380
     */
381
    public function LocationSearch()
382
    {
383
        if (class_exists('BootstrapForm')) {
384
            $form = LocatorBootstrapForm::create($this, 'LocationSearch');
385
        } else {
386
            $form = LocatorForm::create($this, 'LocationSearch');
387
        }
388
389
        return $form
390
            ->setFormMethod('GET')
391
            ->setFormAction($this->Link())
392
            ->disableSecurityToken()
393
            ->loadDataFrom($this->request->getVars());
394
    }
395
396
}
397