Disclaimer: not compat. with openlayers.
[project/geotaxonomy.git] / geotaxonomy.module
1 <?php
2 // $Id$
3 /**
4 * @file
5 * Function definitions for geotaxonomy module.
6 */
7
8 /**
9 * Implementation of specific hook_form_alter().
10 * Adds option of geo storage to vocab edit page.
11 */
12 function geotaxonomy_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) {
13 $vid = $form['vid']['#value'];
14 $form['settings']['geotaxonomy_status'] = array(
15 '#type' => 'checkbox',
16 '#title' => t('Store geo data for this vocabulary'),
17 '#default_value' => variable_get("geotaxonomy_$vid", 0),
18 '#description' => t('Allows latitude and longitude data to be stored with terms of this taxonomy.'),
19 );
20 $form['#submit'][] = 'geotaxonomy_form_vocabulary_submit';
21 }
22
23 /**
24 * Additional submit handler for vocabulary form to accomodate geo setting.
25 */
26 function geotaxonomy_form_vocabulary_submit($form, &$form_state) {
27 $vid = $form_state['values']['vid'];
28 variable_set("geotaxonomy_$vid", $form_state['values']['geotaxonomy_status']);
29 }
30
31 /**
32 * Implementation of specific hook_form_alter().
33 * Allows entry of geo data on term edit page.
34 */
35 function geotaxonomy_form_taxonomy_form_term_alter(&$form, &$form_state) {
36 $vid = $form['vid']['#value'];
37 if (!variable_get("geotaxonomy_".$vid, 0)) {
38 return;
39 }
40 $form['submit']['#weight'] = $form['submit']['#weight'] + 1;
41 $form['delete']['#weight'] = $form['delete']['#weight'] + 1;
42
43 $latlon = geotaxonomy_get_term($form['tid']['#value']);
44 $form['geotaxonomy'] = array(
45 '#type' => 'fieldset',
46 '#title' => t("Geotaxonomy data"),
47 '#collapsible' => 1,
48 );
49 $form['geotaxonomy']['lat'] = array (
50 '#type' => 'textfield',
51 '#title' => t('Latitude'),
52 '#default_value' => $latlon['lat'],
53 );
54 $form['geotaxonomy']['lon'] = array (
55 '#type' => 'textfield',
56 '#title' => t('Longitude'),
57 '#default_value' => $latlon['lon'],
58 );
59
60 $form['geotaxonomy']['bound_top'] = array (
61 '#type' => 'textfield',
62 '#title' => t('Bound Top'),
63 '#default_value' => $latlon['bound_top'],
64 );
65 $form['geotaxonomy']['bound_right'] = array (
66 '#type' => 'textfield',
67 '#title' => t('Bound Right'),
68 '#default_value' => $latlon['bound_right'],
69 );
70
71 $form['geotaxonomy']['bound_bottom'] = array (
72 '#type' => 'textfield',
73 '#title' => t('Bound Bottom'),
74 '#default_value' => $latlon['bound_bottom'],
75 );
76 $form['geotaxonomy']['bound_left'] = array (
77 '#type' => 'textfield',
78 '#title' => t('Bound Left'),
79 '#default_value' => $latlon['bound_left'],
80 );
81
82 // Helper map for lat/lon if OpenLayers UI is available.
83 // Not compatiable with current openlayers module.
84 if (module_exists('openlayers_ui')) {
85 drupal_add_js(drupal_get_path('module', 'geotaxonomy') .'/js/geotaxonomy.admin.js', 'module');
86
87 $default_preset = openlayers_preset_load(variable_get('openlayers_default_preset', 'default'));
88 $defaults = $default_preset->data;
89
90 $form['geotaxonomy']['helpmap'] = array(
91 '#value' => '<div class="form-item geotaxonomy-latlon-helpmap" style="display:block">'. geotaxonomy_form_latlon_map($defaults) .'</div>'
92 );
93 }
94 }
95
96 /**
97 * Create LatLon Helper Map.
98 */
99 function geotaxonomy_form_latlon_map($defaults = array()) {
100 // Pass variables etc. to javascript
101 $pass_values = array(
102 'geotaxonomy' => array(),
103 );
104 drupal_add_js($pass_values, 'setting');
105
106 // Set up our map to help set lat and lon
107 // This map will always be projected as 4326 and use just the default map preset
108 $centermap_def = array(
109 'id' => 'geotaxonomy-latlon-helpmap',
110 'projection' => '4326',
111 'default_layer' => 'openlayers_default_wms',
112 'width' => '400px',
113 'height' => '200px',
114 'center' => array(
115 'lat' => ($defaults['center']['lat']) ? $defaults['center']['lat'] : 0,
116 'lon' => ($defaults['center']['lon']) ? $defaults['center']['lon'] : 0,
117 'zoom' => ($defaults['center']['zoom']) ? $defaults['center']['zoom'] : 2,
118 ),
119 'layers' => array(
120 'openlayers_default_wms',
121 ),
122 'behaviors' => array(
123 'openlayers_behavior_navigation' => array(),
124 ),
125 'options' => array(
126 'displayProjection' => '4326',
127 'maxResolution' => '1.40625',
128 'maxExtent' => array(
129 'top' => 90,
130 'bottom' => -90,
131 'left' => -180,
132 'right' => 180
133 ),
134 ),
135 );
136 return openlayers_render_map($centermap_def);
137 }
138
139
140 /**
141 * Implementation of hook_node_views().
142 */
143 function geotaxonomy_views_api() {
144 return array(
145 'api' => 2,
146 'path' => drupal_get_path('module', 'geotaxonomy') .'/views',
147 );
148 }
149
150 /**
151 * Little API helper function to retrieve geo data for a given term.
152 */
153 function geotaxonomy_get_term($tid) {
154 return db_fetch_array(db_query("SELECT lat, lon, bound_top, bound_right, bound_bottom, bound_left FROM term_geo WHERE tid = %d", $tid));
155 }
156
157 /**
158 * Implementation of hook_taxonomy().
159 */
160 function geotaxonomy_taxonomy($op = NULL, $type = NULL, $term = NULL){
161 if($type =='term' && $term && variable_get("geotaxonomy_".$term['vid'], 0)) {
162 switch ($op) {
163 case 'delete':
164 db_query("DELETE FROM {term_geo} WHERE tid = %d", $term['tid']);
165 break;
166 case 'update':
167 db_query("DELETE FROM {term_geo} WHERE tid = %d", $term['tid']);
168 case 'insert':
169 $term_geo = array(
170 'tid' => $term['tid'],
171 'lat' => is_numeric($term['lat']) ? $term['lat'] : NULL,
172 'lon' => is_numeric($term['lon']) ? $term['lon'] : NULL,
173 'bound_top' => is_numeric($term['bound_top']) ? $term['bound_top'] : NULL,
174 'bound_right' => is_numeric($term['bound_right']) ? $term['bound_right'] : NULL,
175 'bound_bottom' => is_numeric($term['bound_bottom']) ? $term['bound_bottom'] : NULL,
176 'bound_left' => is_numeric($term['bound_left']) ? $term['bound_left'] : NULL,
177 );
178 drupal_write_record('term_geo', $term_geo);
179 break;
180 }
181 }
182 }
183
184 /**
185 * FEEDAPI INTEGRATION
186 * Allows flexible geo and other term importation.
187 */
188
189 /**
190 * Implementation of hook_feedapi_settings_form().
191 * If a module provides parsers and processors it MUST evaluate the $type variable
192 * to return different forms for parsers and processors.
193 * There might be a better term for parsers and processors than $type.
194 */
195 function geotaxonomy_feedapi_settings_form($type) {
196 $form = array();
197 switch ($type) {
198 case 'processors':
199 $vocabularies = taxonomy_get_vocabularies();
200 $v_types = array();
201 foreach ($vocabularies as $vocabulary) {
202 $v_types[$vocabulary->vid] = $vocabulary->name;
203 }
204 $default_v = current(array_keys($v_types));
205
206 $form['vocabulary'] = array(
207 '#type' => 'select',
208 '#title' => t('Vocabulary of stored terms'),
209 '#default_value' => $default_v,
210 '#options' => $v_types,
211 '#description' => t('Choose the vocabulary for terms created by this feed. Feedapi term assumes complete freedom in this vocab, meaning it will delete all the vocabs terms when purging and will also update any terms with matching names.'),
212 );
213 break;
214 }
215 return $form;
216 }
217
218 /**
219 * Implementation of hook_feedapi_item().
220 */
221 function geotaxonomy_feedapi_item($op) {
222 switch ($op) {
223 case 'type':
224 return array("XML feed");
225 case 'save':
226 case 'expire':
227 case 'update':
228 case 'delete':
229 case 'purge':
230 case 'unique':
231 default:
232 if (function_exists('_geotaxonomy_'. $op)) {
233 $args = array_slice(func_get_args(), 1);
234 return call_user_func_array('_geotaxonomy_'. $op, $args);
235 }
236 }
237 }
238
239 /**
240 * Check for expired items.
241 * Not currently supported, taxonomy doesn't store dates.
242 */
243 function _geotaxonomy_expire($feed, $settings) {
244 return 0;
245 }
246
247 /**
248 * Create a term from the feed item.
249 * Stores geo data as well so long as some of it is set.
250 */
251 function _geotaxonomy_save($feed_item, $feed_nid, $settings = array()) {
252 $feed_node = node_load($feed_nid);
253 if ($feed_item = feedapi_mapper_map($feed_node, 'geotaxonomy', $feed_item)) {
254 if (isset($feed_item['term_name'])) {
255 $term = array(
256 'name' => $feed_item['term_name'],
257 'vid' => $settings['vocabulary'],
258 'description' => $feed_item['term_description'],
259 'weight' => $feed_item['term_weight'],
260 );
261 if (drupal_write_record('term_data', $term)) {
262 _geotaxonomy_item_tid((!empty($feed_item['term_unique']) ? $feed_item['term_unique'] : $feed_item['term_name'] . crc32(serialize($feed_item))), $term['tid']);
263 if (is_numeric($feed_item['term_lat']) || is_numeric($feed_item['term_lon'])) {
264 $term_geo = array(
265 'tid' => $term['tid'],
266 'lat' => is_numeric($feed_item['term_lat']) ? $feed_item['term_lat'] : NULL,
267 'lon' => is_numeric($feed_item['term_lon']) ? $feed_item['term_lon'] : NULL,
268 'bound_top' => is_numeric($feed_item['term_bound_top']) ? $feed_item['term_bound_top'] : NULL,
269 'bound_right' => is_numeric($feed_item['term_bound_right']) ? $feed_item['term_bound_right'] : NULL,
270 'bound_bottom' => is_numeric($feed_item['term_bound_bottom']) ? $feed_item['term_bound_bottom'] : NULL,
271 'bound_left' => is_numeric($feed_item['term_bound_left']) ? $feed_item['term_bound_left'] : NULL,
272 );
273 drupal_write_record('term_geo', $term_geo);
274 }
275 }
276 }
277 }
278 return $term;
279 }
280
281 /**
282 * Static cache function to temporarily resolve hierarchical imported information until everything is safely in the database.
283 */
284 function _geotaxonomy_item_tid($key, $tid = NULL) {
285 static $allkeys = array();
286 if ($tid) {
287 $allkeys[$key] = $tid;
288 }
289 return $allkeys[$key];
290 }
291
292 /**
293 * Update a node which already assigned to a feed item
294 */
295 function _geotaxonomy_update($feed_item, $feed_nid, $settings, $id) {
296 // not yet supported
297 return array();
298 }
299
300 /**
301 * Delete a term which already assigned to a feed item
302 */
303 function _geotaxonomy_delete($feed_item, $feed_nid) {
304 $feed_node = node_load($feed_nid);
305 if ($feed_item = feedapi_mapper_map($feed_node, 'geotaxonomy', $feed_item)) {
306 if (isset($feed_item['term_name'])) {
307 $tid = db_result(db_query("SELECT tid FROM {term_data} WHERE vid = %d AND name = '%s'", $feed_node->feed->settings['processors']['geotaxonomy']['vocabulary'], $feed_item['term_name']));
308 if($tid) {
309 taxonomy_del_term($tid);
310 }
311 }
312 }
313 }
314
315 /**
316 * Delete all terms associated with a feed's vocabulary.
317 */
318 function _geotaxonomy_purge($feed) {
319 $feed_node = node_load($feed->nid);
320 $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $feed_node->feed->settings['processors']['geotaxonomy']['vocabulary']);
321 while ($term = db_fetch_object($result)) {
322 taxonomy_del_term($term->tid);
323 }
324
325 cache_clear_all();
326 drupal_set_message("Deleted all terms in the vocabulary.");
327 }
328
329 /**
330 * Tell if the feed item was seen before or not at the feed
331 * Assumes every term in the vocabulary it uses is its own property.
332 *
333 * @param $feed_item
334 * Feed item object
335 * @param $feed_nid
336 * Feed ID
337 * @return
338 * TRUE if the item is new, FALSE if the item is a duplicated one
339 */
340 function _geotaxonomy_unique($feed_item, $feed_nid, $settings) {
341 $feed_node = node_load($feed_nid);
342 if ($feed_item = feedapi_mapper_map($feed_node, 'geotaxonomy', $feed_item)) {
343 if (isset($feed_item['term_name'])) {
344 if (!empty($feed_item['term_parent_name'])) {
345 $tid = db_result(db_query("SELECT td.tid FROM {term_data} td LEFT JOIN {term_hierarchy} th ON td.tid = th.tid LEFT JOIN {term_data} td2 ON td2.tid = th.parent WHERE td.vid = %d AND td.name = '%s' AND td2.name = '%s'", $feed_node->feed->settings['processors']['geotaxonomy']['vocabulary'], $feed_item['term_name'], $feed_item['term_parent_name']));
346 } else {
347 $tid = db_result(db_query("SELECT td.tid FROM {term_data} td LEFT JOIN {term_hierarchy} th ON td.tid = th.tid WHERE td.vid = %d AND td.name = '%s' AND th.parent = 0", $feed_node->feed->settings['processors']['geotaxonomy']['vocabulary'], $feed_item['term_name']));
348 }
349 if ($tid) {
350 return $tid;
351 }
352 } else {
353 return FALSE;
354 }
355 }
356 return TRUE;
357 }
358
359 /**
360 * Implementation of hook_feedapi_after_refresh().
361 */
362 function geotaxonomy_feedapi_after_refresh($feed) {
363 if (in_array('geotaxonomy', $feed->processors)) {
364 $feed_node = node_load($feed->nid);
365
366 // Refuse to continue if there are no items on the feed.
367 if (empty($feed->items)) {
368 // feedapi will already alert the user, we just return.
369 return;
370 }
371 $allitems = array();
372 foreach($feed->items as $feed_item){
373 if ($feed_item = feedapi_mapper_map($feed_node, 'geotaxonomy', $feed_item)) {
374 if (isset($feed_item['term_name'])) {
375 $tid = _geotaxonomy_item_tid((!empty($feed_item['term_unique']) ? $feed_item['term_unique'] : $feed_item['term_name'] . crc32(serialize($feed_item))));
376 if ($tid) {
377 $hierarchy = array(
378 'tid' => $tid,
379 'parent' => 0,
380 );
381 $ptid = 0;
382 if (isset($feed_item['term_parent_unique'])) {
383 $ptid = _geotaxonomy_item_tid($feed_item['term_parent_unique']);
384 }
385 if (!$ptid && isset($feed_item['term_parent_name'])) {
386 // Just find parent by name.
387 $ptid = db_result(db_query("SELECT tid FROM {term_data} WHERE vid = %d AND name = '%s'", $feed_node->feed->settings['processors']['geotaxonomy']['vocabulary'], $feed_item['term_parent_name']));
388 }
389 $hierarchy['parent'] = $ptid ? $ptid : 0;
390 drupal_write_record('term_hierarchy', $hierarchy);
391 }
392 }
393 }
394 }
395 }
396 }
397
398 /**
399 * Implementation of hook_feedapi_mapper().
400 */
401 function geotaxonomy_feedapi_mapper($op, $feed_node, $processor, $target = NULL, $feed_element = array(), $field_name = '', $sub_field = '') {
402 if ($processor != 'geotaxonomy') {
403 return;
404 }
405 switch ($op) {
406 case 'describe':
407 return t('Maps feed elements to taxonomy term, parent, and description fields as well as geotaxonomy information fields.');
408 break;
409 case 'list':
410 return array(
411 'term_name' => 'Term name',
412 'term_description' => 'Term description',
413 'term_weight' => 'Term weight',
414 'term_parent_name' => 'Term parent name. Unreliable if names can be duplicated.',
415 'term_unique' => 'Unique field to identify this term if names can be repeated, for creating hierarchy.',
416 'term_parent_unique' => 'Unique field of the parent of this term. Requires the unique field to be mapped as well to be relevant.',
417 'term_lat' => 'Latitude',
418 'term_lon' => 'Longitude',
419 'term_bound_top' => 'Bound Top',
420 'term_bound_right' => 'Bound Right',
421 'term_bound_bottom' => 'Bound Bottom',
422 'term_bound_left' => 'Bound Left',
423 );
424 break;
425 case 'map':
426 // Map $feed_element to the key specified by $field_name.
427 $target[$field_name] = $feed_element;
428 return $target;
429 }
430 }