adding example apps, fixing powerup issues
[feisty_meow.git] / production / example_apps / zippy_maps / src / Controller / CategoriesController.php
1 <?php
2 namespace App\Controller;
3
4 use App\Controller\AppController;
5 use Avmaps\Controller\Component\SimpleMapsComponent;
6 use Cake\Log\Log;
7
8 /**
9  * Categories Controller
10  *
11  * @property \App\Model\Table\CategoriesTable $Categories
12  */
13 class CategoriesController extends AppController
14 {       
15         // keeps track of the API key to be used for our google queries, if one is known.
16         private $api_key = null;
17         
18         public function initialize()
19         {
20                 parent::initialize();
21                 
22                 // load up the request handler to make processing easier on ajax requests.
23                 $this->loadComponent('RequestHandler');         
24                 
25                 // configure the request handler to deal with json encoding.
26                 $this->RequestHandler->config('inputTypeMap.json', ['json_decode', true]);              
27                                 
28                 $this->api_key = SimpleMapsComponent::getGoogleAPIKey();
29                 
30                 // allow simple access to location db.
31                 $this->loadModel ( 'Locations' );
32                 
33         }
34         
35
36
37     /**
38      * Index method
39      *
40      * @return \Cake\Network\Response|null
41      */
42     public function index()
43     {
44         $categories = $this->paginate($this->Categories);
45
46         $this->set(compact('categories'));
47         $this->set('_serialize', ['categories']);
48     }
49
50     /**
51      * View method
52      *
53      * @param string|null $id Category id.
54      * @return \Cake\Network\Response|null
55      * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
56      */
57     public function view($id = null)
58     {
59         $category = $this->Categories->get($id, [
60                         'contain' => ['Locations']
61         ]);
62
63         $this->set('category', $category);
64         $this->set('_serialize', ['category']);
65         
66         // query all the locations belonging to this category but only retrieve part of the info.
67         $locationBits = $this->Categories->CategoriesLocations->find('list', [
68                         'conditions' => [ 'category_id' => $id ],
69                         'contain' => ['Locations'],
70                         'valueField' => function ($row) {
71                                 return $row->location->get('lat')
72                                  . ',' . $row->location->get('lng')
73                                 . ' ' . $row->location->get('location');
74                         }
75         ]);
76         $this->set('locationBits', $locationBits);
77     }
78     
79     /**
80      * queries the locations to display on a map given an "id" to lookup in categories table.
81      */
82     public function map($id = null)
83     {
84         $category = $this->Categories->get($id, [
85                         'contain' => ['Locations']
86         ]);
87         
88         $locationsFound = $this->Categories->getLocationsInCategory($id);
89         $this->set('locationsInCategory', $locationsFound);     
90
91         // grab the flags in requests.
92         $parms = $this->request->getQueryParams();
93         
94         // we will cluster by default, but they can also pass the clustering flag with 0 or 1.
95         $clustering = (array_key_exists('clustering', $parms) && ($parms['clustering'] == '1'))
96                 || !array_key_exists('clustering', $parms);
97         //Log::Debug('clustering is: ' . var_export($clustering, true));
98         
99         // set up the selected items in our config chooser.
100         $selectedList = [];     
101         if ($clustering) {
102                 array_push($selectedList, 'clustering');
103         }
104         $this->set('selectedList', $selectedList);
105                 
106         // pass the pre-loading function in for the map element.
107         $this->set('dbProcessor', $this);
108         $this->set('preloader', '$this->MapDisplay->addMarkers($dbProcessor, $locationsInCategory, $category );');      
109         
110         if ($this->request->is(['post', 'put'])) {
111                         //, 'patch'])) {
112                 $postData = $this->request->getData('view_options')['_ids'];
113                 
114                 $new_clustering = is_array($postData) && in_array('clustering', $postData);
115                 
116                 if ($clustering != $new_clustering) {
117                         $this->redirect(['action' => 'map', 
118                                         '0' => $id,
119                                         '?' => [
120                                                         'clustering' => $new_clustering,
121                                         ]
122                         ]);
123                 }
124         }
125         
126         $this->set('category', $category);
127                 
128 //      $this->set('_serialize', ['category']);
129     }
130     
131     /**
132      * processes a location row by extracting the important information into an array.
133      * separates the db structure from the pieces needed for google maps.
134      */
135     public function processRow(& $location_row)
136     {
137         return [
138                 'lat' => $location_row ['location'] ['lat'],
139                 'lng' => $location_row ['location'] ['lng'],
140                 'title' => $location_row ['location'] ['name'],
141                 // kludge below adds extra space on content, since someone is not left justifying these.
142                 'content' => h ( $location_row ['location'] ['location'] ) . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
143                         ];
144     }
145     
146     public function extractCategoryImage(& $category)
147     {
148         return $category->image;
149     }
150     
151     
152     /**
153      * attempts to jump to the center of the category, given a particular interpretation of the
154      * category.  if possible we will decide that the category is a state name, and then attempt
155      * to jump to the center of that state.
156      * will also show markers in the category.
157      */
158     public function center($id = null)
159     {
160         $category = $this->Categories->get($id, [
161                         'contain' => ['Locations']
162         ]);
163         
164         // grab the flags in requests.
165         $parms = $this->request->getQueryParams();
166         
167         // we will cluster by default, but they can also pass the clustering flag with 0 or 1.
168         $clustering = (array_key_exists('clustering', $parms) && ($parms['clustering'] == '1'))
169                 || !array_key_exists('clustering', $parms);
170         //Log::Debug('clustering is: ' . var_export($clustering, true));
171         
172         // set up the selected items in our config chooser.
173         $selectedList = [];
174         if ($clustering) {
175                 array_push($selectedList, 'clustering');
176         }
177         $this->set('selectedList', $selectedList);
178         
179         $state = null;
180         // interpret the category name as a state if possible.
181         if (substr($category->name, 0, 3) == 'US-') {
182                 // bingo, we have a US prefix on the string, so decide what state they mean.
183                 $state = substr($category->name, 3, 2);
184                 
185         }
186         // perform the lookup to get the readable name.  otherwise a state like indiana (IN) will
187         // not get geocoded properly.  probably others as well.
188         $state = SimpleMapsComponent::lookupStateFromAbbreviation($state);
189         
190         $this->set('state', $state);
191         
192         Log::debug('state was found as: ' . $state);
193
194         if ($state !== null) {
195                         $centeredPoint = SimpleMapsComponent::geocode($state . " USA", [
196                                 'key' => $this->api_key
197                         ]);
198                         $mapCenter = [ 'lat' => $centeredPoint[0], 'lng' => $centeredPoint[1] ] ;
199                         $this->set('mapCenter', $mapCenter);
200                         Log::Debug('computed a map center of: ' . var_export($mapCenter, true));
201         }   
202         
203         
204         // thoughts on algorithm: is the center of the state according to google what we want to rely on?
205         // we could at least start there, record the lat/longs and then edit them later as we decide.           
206
207         $radius = null;
208         $lat = null;
209         $long = null;
210         if ($mapCenter) {
211                 $radius = 40; // miles from center to show markers.
212                 $lat = $mapCenter['lat'];
213                 $long = $mapCenter['lng'];
214         }       
215         
216         Log::Debug('given lat=' . $lat . ' long='.  $long . ' radius=' . $radius);
217         
218         Log::Debug('loading locations in category within radius');
219         
220         // compute the lat/long bounding box for our search.
221         $bounds = SimpleMapsComponent::calculateLatLongBoundingBox ( $lat, $long, $radius );
222         
223         if (! $bounds) {
224                 Log::Debug("failed to calculate the bounding box!");
225         } else {
226                 Log::Debug("bounding box: " . var_export($bounds, true));
227         }
228         
229         // query all the locations belonging to this category within the box.
230         $locationsInCategory = $this->Categories->getLocationsInCategoryInBox($id,
231                         $bounds [0], $bounds [1], $bounds [2], $bounds [3] );
232         
233         $this->set('locationsInCategory', $locationsInCategory);
234         
235         $this->set('dbProcessor', $this);       
236         $this->set('preloader', '$this->MapDisplay->addMarkers($dbProcessor, $locationsInCategory, $category );');
237         
238         if ($this->request->is(['post', 'put'])) {
239                 //, 'patch'])) {
240                 $postData = $this->request->getData('view_options')['_ids'];
241                 
242                 $new_clustering = is_array($postData) && in_array('clustering', $postData);
243                 
244                 if ($clustering != $new_clustering) {
245                         $this->redirect(['action' => 'map',
246                                         '0' => $id,
247                                         '?' => [
248                                                         'clustering' => $new_clustering,
249                                         ]
250                         ]);
251                 }
252         }
253         
254         $this->set('category', $category);
255 //      $this->set('_serialize', ['category','selectedList']);
256     }
257     
258     /**
259      * Add method
260      *
261      * @return \Cake\Network\Response|null Redirects on successful add, renders view otherwise.
262      */
263     public function add()
264     {
265         $category = $this->Categories->newEntity();
266         if ($this->request->is('post')) {
267             $category = $this->Categories->patchEntity($category, $this->request->getData());
268             if ($this->Categories->save($category)) {
269                 $this->Flash->success(__('The category has been saved.'));
270
271                 return $this->redirect(['action' => 'index']);
272             }
273             $this->Flash->error(__('The category could not be saved. Please, try again.'));
274         }
275         $this->set(compact('category'));
276         $this->set('_serialize', ['category']);
277     }
278
279     /**
280      * Edit method
281      *
282      * @param string|null $id Category id.
283      * @return \Cake\Network\Response|null Redirects on successful edit, renders view otherwise.
284      * @throws \Cake\Network\Exception\NotFoundException When record not found.
285      */
286     public function edit($id = null)
287     {
288         $category = $this->Categories->get($id, [
289             'contain' => []
290         ]);
291         if ($this->request->is(['patch', 'post', 'put'])) {
292             $category = $this->Categories->patchEntity($category, $this->request->getData());
293             if ($this->Categories->save($category)) {
294                 $this->Flash->success(__('The category has been saved.'));
295
296                 return $this->redirect(['action' => 'index']);
297             }
298             $this->Flash->error(__('The category could not be saved. Please, try again.'));
299         }
300         $this->set(compact('category'));
301         $this->set('_serialize', ['category']);
302     }
303
304     /**
305      * Delete method
306      *
307      * @param string|null $id Category id.
308      * @return \Cake\Network\Response|null Redirects to index.
309      * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
310      */
311     public function delete($id = null)
312     {
313         $this->request->allowMethod(['post', 'delete']);
314         $category = $this->Categories->get($id);
315         if ($this->Categories->delete($category)) {
316             $this->Flash->success(__('The category has been deleted.'));
317         } else {
318             $this->Flash->error(__('The category could not be deleted. Please, try again.'));
319         }
320
321         return $this->redirect(['action' => 'index']);
322     }
323     
324     /**
325      * provides ajax friendly responses.  expects to be passed 'category' in post data.
326      * requires bounding box for lookup be passed as 'sw_lat', 'sw_lng', 'ne_lat', 'ne_lng'. 
327      */
328     public function lookupajax()
329     {
330         $reqData = $this->request->getData();
331         
332         Log::Debug('got to lookup with data: ' . var_export($reqData, true));
333
334         // check that this is a post method, since we don't support anything else.
335         if (! $this->request->is(['post'])) {
336                 die('this is a post method');
337         }
338
339         if (array_key_exists('action', $reqData)) {
340                 $action = $reqData['action'];
341                 
342                 if (strcasecmp($action, 'lookupBox') == 0) {
343                         $this->findLocationsInCategoryWithinBounds($reqData);
344                 } else if (strcasecmp($action, 'getInfo') == 0) {
345                         $this->getInfoOnLocation($reqData);
346                 } else {
347                         die('lookupajax call was given unknown action: ' . $action);                            
348                 }
349                 
350         } else {
351                 die('lookupajax call has no action specified');
352         }
353     }
354     
355     public function findLocationsInCategoryWithinBounds($reqData)
356     {   
357         if (array_key_exists('category', $reqData)) {
358                 $id = $reqData['category'];
359         } else {
360                 // throw an exception here?
361                 $message = 'failed to find category id in request data';
362                 Log::Debug($message);
363                 die($message);
364         }
365         
366         $category = $this->Categories->get($id, [
367                         'contain' => ['Locations']
368         ]);
369         $this->set('category', $category);
370         
371         if (array_key_exists('sw_lat', $reqData)) {
372                 $sw_lat = $reqData['sw_lat'];
373         }
374         if (array_key_exists('sw_lng', $reqData)) {
375                 $sw_lng = $reqData['sw_lng'];
376         }
377         if (array_key_exists('ne_lat', $reqData)) {
378                 $ne_lat = $reqData['ne_lat'];
379         }
380         if (array_key_exists('ne_lng', $reqData)) {
381                 $ne_lng = $reqData['ne_lng'];
382         }
383         
384         //temp!  fails over to using whole range.
385         if ($sw_lat === null) { $sw_lat = -90; }
386         if ($sw_lng === null) { $sw_lng = -180; }
387         if ($ne_lat === null) { $ne_lat = 90; }
388         if ($ne_lng === null) { $ne_lng = 180; }
389
390         $start = null;
391         if (array_key_exists('start', $reqData)) {
392                 $start = $reqData['start'];
393         }
394         $end = null;
395         if (array_key_exists('end', $reqData)) {
396                 $end = $reqData['end'];
397         }
398         
399         // lookup the locations inside that box and store for view.
400         $locationsToSerialize = $this->Categories->getChewedLocationsInCategoryInBox($id, $sw_lat, $sw_lng, $ne_lat, $ne_lng, $start, $end);            
401         //Log::debug('before encoding, php array looks like: ' . var_export($locationsToSerialize, true));      
402         Log::Debug("returning json now...");    
403         $encoded = json_encode($locationsToSerialize);
404         //      Log::debug('encoded json is: ' . var_export($encoded, true));
405         die($encoded);          
406     }
407     
408     public function getInfoOnLocation($reqData)
409     {
410         if (array_key_exists('id', $reqData)) {
411                 $id = $reqData['id'];
412         } else {
413                 // throw an exception here?
414                 $message = 'failed to find location id in request data';
415                 Log::Debug($message);
416                 die($message);
417         }
418         
419         $location = $this->Locations->get($id, [
420         ]);
421         return die(json_encode($location));
422     }
423 }