/[drupal]/contributions/modules/exhibit/exhibit.module
ViewVC logotype

Contents of /contributions/modules/exhibit/exhibit.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.10 - (show annotations) (download) (as text)
Fri May 1 05:09:27 2009 UTC (6 months, 3 weeks ago) by arto
Branch: MAIN
CVS Tags: DRUPAL-6--1-0-BETA1, HEAD
Changes since 1.9: +7 -7 lines
File MIME type: text/x-php
Merged latest changes from http://github.com/jhuckabee/drupal-exhibit (50455e2) by jhuckabee.

Changelog:
- Minor fixes for the Exhibit views and core modules.
- Implemented an Exhibit localization module.
1 <?php
2 // $Id$
3
4 //////////////////////////////////////////////////////////////////////////////
5 // Module settings
6
7 define('EXHIBIT_DATE_FORMAT', '%Y-%m-%dT%H:%M:%SZ');
8 define('EXHIBIT_XMLNS_URL', 'http://simile.mit.edu/2006/11/exhibit#'); // TODO: What's this for? This needs to be updated or removed
9 define('EXHIBIT_JS_URL', variable_get('exhibit_js_url', 'http://api.simile-widgets.org/exhibit/2.2.0/exhibit-api.js'));
10 define('EXHIBIT_MAP_JS_URL', variable_get('exhibit_map_js_url', 'http://api.simile-widgets.org/exhibit/2.2.0/extensions/map/map-extension.js'));
11 define('EXHIBIT_BARCHART_JS_URL', variable_get('exhibit_barchart_js_url', 'http://api.simile-widgets.org/exhibit/2.2.0/extensions/chart/chart-extension.js'));
12 define('EXHIBIT_SCATTERPLOT_JS_URL', variable_get('exhibit_scatterplot_js_url', 'http://api.simile-widgets.org/exhibit/2.2.0/extensions/chart/chart-extension.js'));
13 define('EXHIBIT_TIMELINE_JS_URL', variable_get('exhibit_timeline_js_url', 'http://api.simile-widgets.org/exhibit/2.2.0/extensions/time/time-extension.js'));
14 define('EXHIBIT_CALENDAR_JS_URL', variable_get('exhibit_calendar_js_url', 'http://api.simile-widgets.org/exhibit/2.2.0/extensions/calendar/calendar-extension.js'));
15 define('EXHIBIT_FEED_LIFETIME', (int)variable_get('timeline_feed_lifetime', 0));
16 define('EXHIBIT_FEED_ETAG', (bool)variable_get('timeline_feed_etag', TRUE));
17
18
19 //////////////////////////////////////////////////////////////////////////////
20 // Core API hooks
21
22 /**
23 * Implementation of hook_help().
24 */
25 function exhibit_help($path) {
26 switch ($path) {
27 case 'admin/help#exhibit':
28 return '<p>' . t('') . '</p>'; // TODO
29 case 'admin/settings/exhibit':
30 //return '<p>' . t('') . '</p>'; // TODO
31 }
32 }
33
34 /**
35 * Implementation of hook_perm().
36 */
37 function exhibit_perm() {
38 return array(
39 'administer exhibit feeds',
40 //'use external exhibit feeds', // TODO
41 'access exhibits',
42 'create exhibits',
43 'edit exhibits',
44 'edit own exhibits',
45 'delete exhibits',
46 'delete own exhibits',
47 );
48 }
49
50 /**
51 * Implementation of hook_menu().
52 */
53 function exhibit_menu() {
54 return array(
55 'node/%node/exhibit-json' => array(
56 'type' => MENU_CALLBACK,
57 'access callback' => 'user_access',
58 'access arguments' => array('access exhibits'),
59 'page callback' => 'exhibit_output_node',
60 'page arguments' => array(1),
61 'file' => 'exhibit.pages.inc',
62 ),
63 'node/__history__.html' => array(
64 'type' => MENU_CALLBACK,
65 'access arguments' => array('access exhibits'),
66 'page callback' => 'exhibit_output_history',
67 'file' => 'exhibit.pages.inc',
68 ),
69 'exhibit/tsv/%' => array(
70 'type' => MENU_CALLBACK,
71 'access arguments' => array('access exhibits'),
72 'page callback' => 'exhibit_output_convert',
73 'page arguments' => array(2, 'exhibit_parse_tsv', 'text/tab-separated-values'),
74 'file' => 'exhibit.pages.inc',
75 ),
76 'exhibit/feeds/%exhibit_feed' => array(
77 'type' => MENU_CALLBACK,
78 'access arguments' => array('access exhibits'),
79 'page callback' => 'exhibit_output_feed',
80 'page arguments' => array(2),
81 'file' => 'exhibit.pages.inc',
82 ),
83 'admin/settings/exhibit' => array(
84 'title' => 'Exhibits',
85 'description' => 'Settings for the Exhibit module.',
86 'access arguments' => array('administer site configuration'),
87 'page callback' => 'drupal_get_form',
88 'page arguments' => array('exhibit_admin_settings'),
89 'file' => 'exhibit.admin.inc',
90 ),
91 'admin/content/exhibit' => array(
92 'title' => 'Exhibit feeds',
93 'access arguments' => array('administer exhibit feeds'),
94 'page callback' => 'exhibit_admin_feeds',
95 'file' => 'exhibit.admin.inc',
96 ),
97 'admin/content/exhibit/list' => array(
98 'title' => 'List',
99 'type' => MENU_DEFAULT_LOCAL_TASK,
100 'access arguments' => array('administer exhibit feeds'),
101 'weight' => -10,
102 ),
103 'admin/content/exhibit/add' => array(
104 'title' => 'Add data feed',
105 'type' => MENU_LOCAL_TASK,
106 'access arguments' => array('administer exhibit feeds'),
107 'page callback' => 'drupal_get_form',
108 'page arguments' => array('exhibit_admin_feed_form'),
109 'file' => 'exhibit.admin.inc',
110 'weight' => 10,
111 ),
112 'admin/content/exhibit/edit/%exhibit_feed' => array(
113 'title' => 'Edit data feed',
114 'type' => MENU_CALLBACK,
115 'access arguments' => array('administer exhibit feeds'),
116 'page callback' => 'drupal_get_form',
117 'page arguments' => array('exhibit_admin_feed_form', 4),
118 'file' => 'exhibit.admin.inc',
119 ),
120 'admin/content/exhibit/delete/%exhibit_feed' => array(
121 'title' => 'Delete data feed',
122 'type' => MENU_CALLBACK,
123 'access arguments' => array('administer exhibit feeds'),
124 'page callback' => 'drupal_get_form',
125 'page arguments' => array('exhibit_admin_feed_delete', 4),
126 'file' => 'exhibit.admin.inc',
127 ),
128 );
129 }
130
131 /**
132 * Implementation of hook_block().
133 */
134 function exhibit_block($op = 'list', $delta = 0, $edit = array()) {
135 switch ($op) {
136 case 'list':
137 return array('facets' => array('info' => t('Exhibit facets')));
138 case 'view':
139 switch ($delta) {
140 case 'facets':
141 if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) != 'edit') {
142 $node = node_load(arg(1));
143 $block['content'] = $node->exhibit['facet_definition'];
144 }
145 break;
146 }
147 return $block;
148 }
149 }
150
151 /**
152 * Implementation of hook_theme().
153 */
154 function exhibit_theme() {
155 return array(
156 'exhibit_definition' => array(
157 'arguments' => array('node' => NULL),
158 ),
159 );
160 }
161
162 //////////////////////////////////////////////////////////////////////////////
163 // Node API hooks
164
165 /**
166 * Implementation of hook_node_info().
167 */
168 function exhibit_node_info() {
169 return array(
170 'exhibit' => array(
171 'name' => t('Exhibit'),
172 'module' => 'exhibit', //_node',
173 'description' => t('An <em>exhibit</em> displays structured data in the form of rich visualizations that can be searched, filtered and sorted using faceted browsing.'),
174 'has_title' => TRUE,
175 'title_label' => t('Title'),
176 'has_body' => TRUE,
177 'body_label' => t('Description'),
178 ),
179 );
180 }
181
182 /**
183 * Implementation of hook_access().
184 */
185 function exhibit_access($op, $node) {
186 switch ($op) {
187 case 'view':
188 return user_access('access exhibits');
189 case 'create':
190 return user_access('create exhibits');
191 case 'update':
192 global $user;
193 return user_access('edit exhibits') || (user_access('edit own exhibits') && $user->uid == $node->uid);
194 case 'delete':
195 global $user;
196 return user_access('delete exhibits') || (user_access('delete own exhibits') && $user->uid == $node->uid);
197 }
198 }
199
200 /**
201 * Implementation of hook_load().
202 */
203 function exhibit_load($node) {
204 $exhibit = db_fetch_array(db_query('SELECT * FROM {exhibit_nodes} WHERE vid = %d', $node->vid));
205 $exhibit['feeds'] = explode(',', $exhibit['feeds']);
206 return (object)array('exhibit' => $exhibit);
207 }
208
209 /**
210 * Implementation of hook_validate().
211 */
212 function exhibit_validate(&$node) {}
213
214 /**
215 * Implementation of hook_insert().
216 */
217 function exhibit_insert($node) {
218 $node->exhibit['feeds'] = implode(',', array_filter(array_values($node->exhibit['feeds']), 'trim'));
219 db_query("INSERT INTO {exhibit_nodes} (nid, vid, feeds, definition, facet_definition) VALUES (%d, %d, '%s', '%s', '%s')", $node->nid, $node->vid, $node->exhibit['feeds'], $node->exhibit['definition'], $node->exhibit['facet_definition']);
220 }
221
222 /**
223 * Implementation of hook_update().
224 */
225 function exhibit_update($node) {
226 if ($node->revision) {
227 return exhibit_insert($node);
228 }
229 $node->exhibit['feeds'] = implode(',', array_filter(array_values($node->exhibit['feeds']), 'trim'));
230 db_query("UPDATE {exhibit_nodes} SET feeds = '%s', definition = '%s', facet_definition = '%s' WHERE vid = %d", $node->exhibit['feeds'], $node->exhibit['definition'], $node->exhibit['facet_definition'], $node->vid);
231 }
232
233 /**
234 * Implementation of hook_delete().
235 */
236 function exhibit_delete($node) {
237 db_query('DELETE FROM {exhibit_nodes} WHERE nid = %d', $node->nid);
238 }
239
240 /**
241 * Implementation of hook_nodeapi().
242 */
243 function exhibit_nodeapi(&$node, $op, $teaser, $page) {
244 switch ($op) {
245 case 'delete revision':
246 db_query('DELETE FROM {exhibit_nodes} WHERE vid = %d', $node->vid);
247 break;
248 }
249 }
250
251 /**
252 * Implementation of hook_form().
253 */
254 function exhibit_form(&$node, &$param) {
255 drupal_add_js(drupal_get_path('module', 'exhibit') .'/exhibit.js');
256 drupal_add_js(array('exhibit' => exhibit_get_feeds('url')), 'setting');
257
258 $type = node_get_types('type', $node);
259
260 if ($type->has_title) {
261 $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#required' => TRUE, '#default_value' => $node->title, '#weight' => -5);
262 }
263
264 if ($type->has_body) {
265 // FIXME: this works around a bug in node_submit(), lines 782-793, which
266 // causes the body field to be emptied when not using node_body_field()
267 // to generate the body and format fieldset. Need to submit a core patch.
268 $form['teaser_include'] = array('#type' => 'hidden', '#value' => '1');
269
270 $form['body_filter'] = array('#type' => 'fieldset', '#title' => check_plain($type->body_label), '#collapsible' => TRUE, '#collapsed' => TRUE);
271 $form['body_filter']['body'] = array('#type' => 'textarea', '#title' => t('Description'), '#default_value' => $node->body, '#required' => ($type->min_word_count > 0), '#rows' => 2);
272 $form['body_filter']['format'] = filter_form($node->format);
273 }
274
275 $form['exhibit'] = array('#type' => 'fieldset', '#title' => t('Exhibit'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#tree' => TRUE);
276 $form['exhibit']['feeds'] = array('#type' => 'checkboxes', '#title' => t('Data feeds'), '#default_value' => !empty($node->exhibit['feeds']) ? $node->exhibit['feeds'] : array(), '#options' => exhibit_get_feeds('title'), '#attributes' => array('class' => 'exhibit-feed-checkbox'), '#prefix' => '<div id="feed-field-selection">', '#suffix' => '</div>');
277 $form['exhibit']['definition'] = array('#type' => 'textarea', '#title' => t('Definition'), '#default_value' => !empty($node->exhibit['definition']) ? $node->exhibit['definition'] : theme('exhibit_definition'), '#rows' => 10);
278 $form['exhibit']['facet_definition'] = array('#type' => 'textarea', '#title' => t('Facet definition'), '#default_value' => @$node->exhibit['facet_definition'], '#rows' => 5);
279 $form['exhibit']['facet-generator-form-wrapper'] = array('#type' => 'fieldset', '#title' => t('Facet Generator'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#tree' => TRUE, '#attributes' => array('id' => 'exhibit-facet-builder', 'style' => 'display:none;'), '#value' => '<div id="facet-generator-form"></div>');
280
281 return $form;
282 }
283
284 /**
285 * Implementation of hook_view().
286 */
287 function exhibit_view($node, $teaser = FALSE, $page = FALSE) {
288 // Load the SIMILE Exhibit API and any enabled extensions
289 drupal_set_html_head('<script type="text/javascript" src="'. EXHIBIT_JS_URL .'"></script>'."\n");
290
291 // Load any enabled Exhibit extensions
292 foreach (exhibit_exhibit_views() as $id => $view) {
293 if (variable_get('exhibit_extensions_'. $id, FALSE)) {
294 drupal_set_html_head('<script type="text/javascript" src="'. $view['href'] .'"></script>'."\n");
295 }
296 }
297
298 // Insert <LINK> references to all enabled data feeds
299 foreach ($node->exhibit['feeds'] as $fid) {
300 if ($feed = exhibit_feed_load($fid)) {
301 exhibit_add_feed(exhibit_get_feed_url($feed), check_plain($feed['type']));
302 }
303 }
304
305 $node = node_prepare($node, $teaser);
306 $node->content['exhibit'] = array('#value' => $node->exhibit['definition'], '#weight' => 1);
307 return $node;
308 }
309
310 //////////////////////////////////////////////////////////////////////////////
311 // Views API hooks
312
313 /**
314 * Implementation of hook_views_api().
315 */
316 function exhibit_views_api() {
317 return array(
318 'api' => 2,
319 'path' => drupal_get_path('module', 'exhibit'),
320 );
321 }
322
323 //////////////////////////////////////////////////////////////////////////////
324 // Exhibit API hooks
325
326 /**
327 * Implementation of hook_exhibit_views().
328 */
329 function exhibit_exhibit_views() {
330 return array(
331 'tile' => array(
332 'title' => t('Tile (default)'),
333 'class' => '',
334 ),
335 'thumbnail' => array(
336 'title' => t('Thumbnail'),
337 'class' => 'Thumbnail',
338 ),
339 'map' => array(
340 'title' => t('Map'),
341 'class' => 'Map',
342 'href' => variable_get('exhibit_extensions_map_api', EXHIBIT_MAP_JS_URL) . '?gmapkey=' . (module_exists('gmap') ? gmap_get_key() : variable_get('exhibit_gmap_key', '')),
343 ),
344 'barchart' => array(
345 'title' => t('Barchart'),
346 'class' => 'Barchart',
347 'href' => variable_get('exhibit_extensions_barchart_api', EXHIBIT_BARCHART_JS_URL),
348 ),
349 'scatterplot' => array(
350 'title' => t('Scatterplot'),
351 'class' => 'Scatterplot',
352 'href' => variable_get('exhibit_extensions_scatterplot_api', EXHIBIT_SCATTERPLOT_JS_URL),
353 ),
354 'timeline' => array(
355 'title' => t('Timeline'),
356 'class' => 'Timeline',
357 'href' => variable_get('exhibit_extensions_timeline_api', EXHIBIT_TIMELINE_JS_URL),
358 ),
359 'calendar' => array(
360 'title' => t('Calendar'),
361 'class' => 'Calendar',
362 'href' => variable_get('exhibit_extensions_calendar_api', EXHIBIT_CALENDAR_JS_URL),
363 ),
364 'tabular' => array(
365 'title' => t('Tabular'),
366 'class' => 'Tabular',
367 ),
368 );
369 }
370
371 //////////////////////////////////////////////////////////////////////////////
372 // Exhibit API functions
373
374 function exhibit_add_link($url) {
375 drupal_add_link(array('rel' => 'meta', 'type' => 'application/json', 'title' => 'Exhibit JSON', 'href' => exhibit_get_url($url)));
376 }
377
378 function exhibit_get_url($url) {
379 if (exhibit_is_link_internal($url)) {
380 $url_parts = parse_url($url);
381 // Handle views args
382 if (isset($_GET['args'])) {
383 $url_parts['path'] = preg_replace('/\%/', $_GET['args'], $url_parts['path']);
384 }
385 if (empty($url_parts['query'])) {
386 return url($url_parts['path']);
387 }
388 else {
389 return url($url_parts['path'], array('query' => $url_parts['query']));
390 }
391 }
392 else {
393 return url($url, array('external' => TRUE));
394 }
395 }
396
397 function exhibit_is_link_internal($url) {
398 return (bool)!preg_match('!^[\w\d\+\-]+://!', $url);
399 }
400
401 function exhibit_add_feed($url, $type = 'application/json') {
402 switch ($type) {
403 case 'application/vnd.google-spreadsheet': // special case
404 drupal_add_link(array('rel' => 'exhibit/data', 'href' => $url, 'type' => 'application/jsonp', 'ex:converter' => 'googleSpreadsheets'));
405 break;
406 default:
407 drupal_add_link(array('rel' => 'exhibit/data', 'href' => $url, 'type' => $type));
408 break;
409 }
410 }
411
412 function exhibit_feed_load($fid) {
413 if (is_numeric($fid)) {
414 return db_fetch_array(db_query('SELECT f.* FROM {exhibit_feeds} f WHERE f.fid = %d', $fid));
415 }
416 else {
417 if (module_exists('views')) {
418 $views = views_get_all_views();
419 foreach ($views as $view) {
420 // Skip disabled views or broken views
421 if (!empty($view->disabled) || empty($view->display)) {
422 continue;
423 }
424
425 // Check each display, add those using the Exhibit JSON style plugin
426 foreach (array_keys($view->display) as $id) {
427 $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
428 $display_options = $view->display[$id]->display_options;
429 if ($plugin['handler'] == 'views_plugin_display_feed' && $display_options['style_plugin'] == 'exhibit_json') {
430 $view_feed_id = $view->vid . '_' . $id;
431 if ($view_feed_id == $fid) {
432 return array('fid' => $view_feed_id,
433 'module' => 'views',
434 'title' => $view->name . ': ' . $view->display[$id]->display_title,
435 'enabled' => TRUE,
436 'cache' => FALSE,
437 'type' => 'application/json',
438 'url' => $display_options['path']);
439 }
440 }
441 }
442 }
443 }
444 }
445 }
446
447 function exhibit_get_feeds($key = NULL) {
448 $feeds = array();
449 $result = db_query('SELECT f.* FROM {exhibit_feeds} f ORDER BY f.title ASC');
450 while ($feed = db_fetch_object($result)) {
451 $feeds[$feed->fid] = $key ? $feed->$key : $feed;
452 }
453
454 // Check for Exhibit JSON feeds in views and add automaticall add them to the list
455 if (module_exists('views')) {
456 $views = views_get_all_views();
457 foreach ($views as $view) {
458 // Skip disabled views or broken views
459 if (!empty($view->disabled) || empty($view->display)) {
460 continue;
461 }
462
463 // Check each display, add those using the Exhibit JSON style plugin
464 foreach (array_keys($view->display) as $id) {
465 $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
466 $display_options = $view->display[$id]->display_options;
467 if ($plugin['handler'] == 'views_plugin_display_feed' && $display_options['style_plugin'] == 'exhibit_json') {
468 $view_feed_id = $view->vid . '_' . $id;
469 if ($key) {
470 switch ($key) {
471 case 'fid':
472 $feeds[$view_feed_id] = $view_feed_id;
473 break;
474 case 'module':
475 $feeds[$view_feed_id] = 'views';
476 break;
477 case 'title':
478 $feeds[$view_feed_id] = l($view->name . ': ' . $view->display[$id]->display_title, 'admin/build/views/edit/' . $view->name);
479 break;
480 case 'enabled':
481 $feeds[$view_feed_id] = TRUE;
482 break;
483 case 'cache':
484 $feeds[$view_feed_id] = FALSE;
485 break;
486 case 'type':
487 $feeds[$view_feed_id] = 'application/json';
488 break;
489 case 'url':
490 $feeds[$view_feed_id] = $display_options['path'];
491 break;
492 }
493 }
494 else {
495 $feeds[$view_feed_id] = (object)array('fid' => $view_feed_id,
496 'module' => 'views',
497 'title' => l($view->name . ': ' . $view->display[$id]->display_title, 'admin/build/views/edit/' . $view->name),
498 'enabled' => TRUE,
499 'cache' => FALSE,
500 'type' => 'application/json',
501 'url' => $display_options['path']);
502 }
503 }
504 }
505 }
506 }
507
508 return $feeds;
509 }
510
511 function exhibit_get_feed_url($feed) {
512 $feed = (object)$feed;
513 return exhibit_get_url(!empty($feed->cache) ? 'exhibit/feeds/' . $feed->fid : $feed->url);
514 }
515
516 function exhibit_get_feed_contents($url) {
517 if (exhibit_is_link_internal($url)) {
518 // Internal Drupal path
519 ob_start();
520 $result = $url ? menu_execute_active_handler($url) : MENU_NOT_FOUND;
521 $output = ob_get_clean();
522 if (is_int($result)) {
523 switch ($result) {
524 case MENU_NOT_FOUND:
525 return die(drupal_not_found());
526 case MENU_ACCESS_DENIED:
527 return die(drupal_access_denied());
528 }
529 }
530 }
531 else {
532 // External URL
533 $output = file_get_contents($url);
534 }
535 return $output;
536 }
537
538 function exhibit_json($items, $types = NULL, $properties = NULL) {
539 return exhibit_compact_item(array(
540 'types' => $types,
541 'properties' => $properties,
542 'items' => $items,
543 ));
544 }
545
546 function exhibit_compact_item($item) {
547 foreach ($item as $k => $v) {
548 if (is_null($v)) {
549 unset($item[$k]);
550 }
551 }
552 return $item;
553 }
554
555 //////////////////////////////////////////////////////////////////////////////
556 // Theme callbacks
557
558 function theme_exhibit_definition() { // TODO
559 return '<div ex:role="viewPanel"><div ex:role="view"></div></div>';
560 }
561
562 function theme_exhibit_lens() { // TODO
563 return '<div ex:content=".label" class="label"></div>';
564 }

  ViewVC Help
Powered by ViewVC 1.1.2