array( 'variables' => array('jsmap' => NULL, 'options' => NULL, 'regions' => NULL), ), 'jsmap_region' => array( 'variables' => array('jsmap_name' => NULL, 'region_title' => NULL, 'records' => NULL, 'region_data' => NULL), ), ); } /** * Implements hook_module_preprocess_hook(). * * Add map data as Drupal JS settings */ function template_preprocess_jsmap(&$vars) { global $language; static $counter = 0; $vars['jsmap_id'] = $settings['jsmap_id'] = "jsmap_$counter"; if ($path = libraries_get_path('raphael')) { drupal_add_js($path . '/raphael-min.js'); } drupal_add_css(drupal_get_path('module', 'jsmap') .'/css/jsmap.css'); drupal_add_js(drupal_get_path('module', 'jsmap') .'/js/jsmap.js'); //set defaults $settings['defaults'] = $vars['options'] ? $vars['options'] : array( 'general' => array( 'fontSize' => "10", 'scale' => '1', 'showNames' => MAP_VALUE_DEFAULT, 'data_position' => 'static', ), 'pathDefaults' => array( 'fill' => '#fff', 'fill-hover' => '#c80000', 'stroke' => '#3899E6', 'stroke-width' => '1', 'interactive' => MAP_VALUE_DEFAULT, ) ); $settings['regions'] = $vars['regions']; drupal_add_js(array('jsmaps' => array($counter => $settings)), 'setting'); $counter++; } /** * Theme a map! */ function theme_jsmap($vars) { return '
'; } /** * Implements hook_views_api(). */ function jsmap_views_api() { return array( 'api' => '3.0-alpha1', ); } /*field api ==========================================*/ /** * Implements hook_field_info() * Enter description here ... */ function jsmap_field_info(){ return array( 'jsmap_region_reference' => array( 'label' => t('Map region'), 'description' => t('Reference to a map region'), 'default_formatter' => 'jsmap_region_reference_format', 'default_widget' => 'options_select', 'settings' => array('map' => ''), ) ); } /** * Implements hook_field_widget_info_alter(). * Add our field type to the list of types that are allowed to use options_select */ function jsmap_field_widget_info_alter(&$info) { $info['options_select']['field types'][] = 'jsmap_region_reference'; } /** * Implements hook_field_settings_form * Enter description here ... * @param unknown_type $field * @param unknown_type $instance */ function jsmap_field_settings_form($field, $instance, $has_data){ $settings = $field['settings']; $options = array(); if($maps = entity_load('jsmap_map')){ foreach($maps as $map){ $options[$map->map] = $map->label; } } $form['map'] = array( '#type' => 'select', '#title' => t('Map'), '#options'=> $options, '#default_value' => $settings['map'], '#required' => TRUE, '#disabled' => $has_data, ); return $form; } /** * Implements hook_options_list(). */ function jsmap_options_list($field) { $function = !empty($field['settings']['options_list_callback']) ? $field['settings']['options_list_callback'] : 'jsmap_allowed_values'; return $function($field); } function jsmap_allowed_values($field){ $options = array(); if($regions = entity_load('jsmap_region', FALSE, array('map' => $field['settings']['map']))){ foreach($regions as $region){ $options[$region->id] = $region->name; } } return $options; } /** * Implements hook_field_is_empty(). */ function jsmap_field_is_empty($item, $field) { switch ($field['type']) { case 'jsmap_region_reference': return empty($item['id']); break; } } /** * Implements hook_field_formatter_info * Enter description here ... */ function jsmap_field_formatter_info(){ return array( 'jsmap_region_reference_format' => array( 'label' => 'Rendered region', 'description' => 'Draws the region based on the provided shape data', 'field types' => array('jsmap_region_reference'), 'settings' => array( 'scale' => '1', 'max_width' => '', 'max_height' => '', 'display_labels' => MAP_VALUE_DEFAULT, 'label_size' => '10', 'region_color' => '#FFFFFF', 'interactive' => MAP_VALUE_DEFAULT, 'region_hover_color' => '#1669AD', 'stroke_color' => '#3899E6', 'stroke_width' => '1' ), ) ); } /** * Implements hook_field_formatter_settings_form * Enter description here ... * @param unknown_type $field * @param unknown_type $instance * @param unknown_type $view_mode * @param unknown_type $form * @param unknown_type $form_state */ function jsmap_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state){ $yesno_options = array( MAP_VALUE_DEFAULT => 'Determined by the region', MAP_VALUE_YES => 'Always', MAP_VALUE_NO => 'Never', ); $display = $instance['display'][$view_mode]; $settings = $display['settings']; $form = array(); $form['scale'] = array( '#title' => t('Scale'), '#type' => 'textfield', '#size' => 3, '#default_value' => $settings['scale'], ); $form['max_width'] = array( '#title' => t('Maximum Width (in px)'), '#type' => 'textfield', '#size' => 5, '#default_value' => $settings['max_width'], ); $form['max_height'] = array( '#title' => t('Maximum Height (in px)'), '#type' => 'textfield', '#size' => 5, '#default_value' => $settings['max_height'], ); $form['display_labels'] = array( '#title' => t('Display Labels'), '#type' => 'select', '#options' => $yesno_options, '#default_value' => $settings['display_labels'], ); $form['label_size'] = array( '#type' => 'textfield', '#title' => t('Region label font size'), '#default_value' => $settings['label_size'], ); $form['region_color'] = array( '#type' => 'textfield', '#title' => t('Region background color'), '#default_value' => $settings['region_color'], ); $form['interactive'] = array( '#type' => 'select', '#options' => $yesno_options, '#title' => t('Make region interactive'), '#default_value' => $settings['interactive'], ); $form['region_hover_color'] = array( '#type' => 'textfield', '#title' => t('Region background color on hover'), '#default_value' => $settings['region_hover_color'], ); $form['stroke_color'] = array( '#type' => 'textfield', '#title' => t('Stroke (line) color'), '#default_value' => $settings['stroke_color'], ); $form['stroke_width'] = array( '#type' => 'textfield', '#title' => t('Stroke (line) width'), '#default_value' => $settings['stroke_width'], ); return $form; } /** * Implements hook_field_formatter_settings_summary * Enter description here ... * @param unknown_type $field * @param unknown_type $instance * @param unknown_type $view_mode */ function jsmap_field_formatter_settings_summary($field, $instance, $view_mode){ $display = $instance['display'][$view_mode]; $settings = $display['settings']; if($settings['scale'] > 0) return t('Map region displayed at a scale of %scale', array('%scale' => $settings['scale'])); return t('Map region scaled to fit region %widthpx wide and %heightpx high', array('%width' => $settings['max_width'], '%height' => $settings['max_height'])); } /** * Implements hook_field_formatter_view * Enter description here ... * @param unknown_type $entity_type * @param unknown_type $entity * @param unknown_type $field * @param unknown_type $instance * @param unknown_type $langcode * @param unknown_type $items * @param unknown_type $display */ function jsmap_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display){ $settings = $display['settings']; $output = array(); switch($display['type']){ case 'jsmap_region_reference_format': if(is_array($items)){ foreach($items as $delta => $item){ $regions = entity_load('jsmap_region', array($item['id'])); $region = reset($regions); $region->path = $region->svg_path; $uri = entity_uri($entity_type,$entity); $region->url = $uri['path']; $region->body = $region->name; //@todo put this in a proper render array using #theme $output[$delta] = array('#markup' => theme('jsmap', array( 'jsmap' => NULL, 'options' => array( 'general' => array( 'fontSize' => $settings['label_size'], 'scale' => $settings['scale'], 'max_width' => $settings['max_width'], 'max_height' => $settings['max_height'], 'showNames' => $settings['display_labels'], ), 'pathDefaults' => array( 'interactive' => $settings['interactive'], 'fill' => $settings['region_color'], 'fill-hover' => $settings['region_hover_color'], 'stroke' => $settings['stroke_color'], 'stroke-width' => $settings['stroke_width'] ) ), 'regions' => array($region), ) )); } } break; } return $output; } /* Entity stuff ======================================================================*/ /** * Implement hook_entity_info(). * * We define two entities here - the actual entity that will hold our domain * specific information and an entity that holds information about the different * types of entities. See here: http://drupal.org/node/977380 for a discussion on this * choice. */ function jsmap_entity_info() { $return['jsmap_region'] = array( 'label' => t('Region'), 'entity class' => 'JsmapRegion', 'controller class' => 'JsmapRegionController', 'base table' => 'jsmap_region', 'fieldable' => TRUE, 'entity keys' => array( 'id' => 'id', 'bundle' => 'map', ), // Bundles are defined in hook_entity_info_alter 'bundles' => array(), // Bundle keys tell the FieldAPI how to extract information from the bundle objects 'bundle keys' => array( 'bundle' => 'map', ), 'label callback' => 'entity_class_label', 'uri callback' => 'entity_class_uri', 'creation callback' => 'jsmap_region_create', 'access callback' => 'jsmap_region_access', 'module' => 'jsmap', // Enable the entity API's admin UI. 'admin ui' => array( 'path' => 'admin/structure/jsmap_maps/regions', 'file' => 'jsmap_region.admin.inc', 'controller class' => 'JsmapRegionUIController', 'menu wildcard' => '%jsmap_region', ), ); $return['jsmap_map'] = array( 'label' => t('Map'), 'entity class' => 'JsmapMap', 'controller class' => 'JsmapMapController', 'base table' => 'jsmap_map', 'fieldable' => FALSE, 'bundle of' => 'jsmap_region', 'exportable' => TRUE, 'entity keys' => array( 'id' => 'id', 'name' => 'map', 'label' => 'label', ), 'access callback' => 'jsmap_map_access', 'module' => 'jsmap', // Enable the entity API's admin UI. 'admin ui' => array( 'path' => 'admin/structure/jsmap_maps', 'file' => 'jsmap_map.admin.inc', 'controller class' => 'JsmapMapUIController', ), ); return $return; } /** * Implements hook_entity_info_alter(). * * We are adding the info about the maps via a hook to avoid a recursion * issue as loading the maps requires the entity info as well. * * @todo This needs to be improved */ function jsmap_entity_info_alter(&$entity_info) { foreach (jsmap_get_maps() as $map => $info) { $entity_info['jsmap_region']['bundles'][$map] = array( 'label' => $info->label, 'admin' => array( 'path' => 'admin/structure/jsmap_maps/manage/%jsmap_map', 'real path' => 'admin/structure/jsmap_maps/manage/' . $map, 'bundle argument' => 4, 'access arguments' => array('administer maps'), ), ); } } class JsmapMap extends Entity { public $label; public function __construct($values = array()) { parent::__construct($values, 'jsmap_map'); } } /** * The Controller for maps */ class JsmapMapController extends EntityAPIControllerExportable { public function __construct($entityType) { parent::__construct($entityType); } /** * Create a map - we first set up the values that are specific * to our map schema but then also go through the EntityAPIController * function. * * @param $values * Initial values for our Map * * @return * A map object with all default fields initialized. */ public function create(array $values = array()) { // Add values that are specific to our map $values += array( 'id' => '', ); $map = parent::create($values); //add all regions that are passed into this function if(!empty($values['regions']) && is_array($values['regions'])){ foreach($values['regions'] as $region){ $region = entity_create('jsmap_region', $region); entity_save('jsmap_region', $region); } } return $map; } /** * Implements EntityAPIControllerInterface. * * @return * A serialized string in JSON format suitable for the import() method. */ public function export($entity, $prefix = '') { $vars = $this->get_exportable_data($entity); return entity_var_json_export($vars, $prefix); } public function svgExport($entity){ $vars = $this->get_exportable_data($entity); $dom = new DOMDocument('1.0','utf-8'); $svg = $dom->createElement('svg'); $dom->appendChild($svg); $svg->setAttribute('version', '1.1'); $svg->setAttribute('id', 'svg2'); $svg->setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg'); $svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg'); $svg->setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); $svg->setAttribute('xml:space', 'preserve'); if(is_array($vars['regions'])){ foreach($vars['regions'] as $region){ $path = $dom->createElement('path'); $path->setAttribute('id',$region['external_id_value']); $path->setAttribute('d',$region['svg_path']); $svg->appendChild($path); } } $dom->formatOutput = true; return $dom->saveXML(); } private function get_exportable_data($entity){ $vars = get_object_vars($entity); unset($vars['id']);//unset the auto-increment field //add all the regions to the entity $vars['regions'] = array(); $jsmap_region_controller = entity_get_controller('jsmap_region'); if($jsmap_regions = $jsmap_region_controller->load(FALSE, array('map' => $entity->map))){ foreach($jsmap_regions as $jsmap_region){ $region_vars = get_object_vars($jsmap_region); unset($region_vars['id']); $vars['regions'][] = $region_vars; } } return $vars; } } class JsmapRegion extends Entity { public function __construct($values = array()) { parent::__construct($values, 'jsmap_region'); } protected function defaultLabel() { return $this->name; } protected function defaultUri() { return array('path' => 'jsmap_region/'. $this->id); } } /** * The Controller for region entities */ class JsmapRegionController extends EntityAPIController { public function __construct($entityType) { parent::__construct($entityType); } /** * Create a region - we first set up the values that are specific * to our region schema but then also go through the EntityAPIController * function. * * @param $values * initial values for our region * * @return * A region object with all default fields initialized. */ public function create(array $values = array()) { // Add values that are specific to our region $values += array( 'id' => '', 'name' => '', 'created' => '', 'changed' => '', ); $region = parent::create($values); return $region; } } /** * Implements hook_permission(). */ function jsmap_permission() { // We set up permisssions to manage entity types, manage all entities and the // permissions for each individual entity $permissions = array( 'administer maps' => array( 'title' => t('Administer maps'), 'description' => t('Create and delete fields for regions'), ), 'administer regions' => array( 'title' => t('Administer regions'), 'description' => t('Edit and delete all regions'), ), ); //Generate permissions per map foreach (jsmap_get_maps() as $map) { $map_name = check_plain($map->map); $permissions += array( "edit any $map_name region" => array( 'title' => t('%map_name: Edit any region', array('%map_name' => $map->label)), ), "view any $map_name region" => array( 'title' => t('%map_name: View any region', array('%map_name' => $map->label)), ), ); } return $permissions; } /** * Determines whether the given user has access to a region. * * @param $op * The operation being performed. One of 'view', 'update', 'create', 'delete' * or just 'edit' (being the same as 'create' or 'update'). * @param $region * Optionally a region or a map to check access for. If nothing is * given, access for all regions is determined. * @param $account * The user to check for. Leave it to NULL to check for the global user. * @return boolean * Whether access is allowed or not. */ function jsmap_region_access($op, $region = NULL, $account = NULL) { if (user_access('administer regions', $account)) { return TRUE; } if (isset($region) && $map_name = $region->map) { $op = ($op == 'view') ? 'view' : 'edit'; if (user_access("$op any $map_name region", $account)) { return TRUE; } } return FALSE; } /** * Access callback for the entity API. */ function jsmap_map_access($op, $map = NULL, $account = NULL) { return user_access('administer maps', $account); } /** * Gets an array of all maps, keyed by the map name. * * @param $map_name * If set, the map with the given name is returned. * @return Map[] * Depending whether $map_name isset, an array of maps or a single one. */ function jsmap_get_maps($map_name = NULL) { $maps = entity_load_multiple_by_name('jsmap_map', isset($map_name) ? array($map_name) : FALSE); return isset($map_name) ? reset($maps) : $maps; } /** * Menu argument loader; Load a map by string. * * @param $map * The machine-readable name of a map to load. * @return * A map array or FALSE if $map does not exist. */ function jsmap_map_load($map) { return jsmap_get_maps($map); } /** * Fetch a jsmap_region object. * * @param $jsmap_region_id * Integer specifying the region id. * @param $reset * A boolean indicating that the internal cache should be reset. * @return * A fully-loaded $jsmap_region object or FALSE if it cannot be loaded. * */ function jsmap_region_load($jsmap_region_id, $reset = FALSE) { $regions = jsmap_region_load_multiple(array($jsmap_region_id), array(), $reset); return reset($regions); } /** * Load multiple jsmap_regions based on certain conditions. * * @param $jsmap_region_ids * An array of jsmap_region IDs. * @param $conditions * An array of conditions to match against the {jsmap_region} table. * @param $reset * A boolean indicating that the internal cache should be reset. * @return * An array of jsmap_region objects, indexed by jsmap_region_id. * * @see entity_load() * @see jsmap_region_load() */ function jsmap_region_load_multiple($jsmap_region_ids = array(), $conditions = array(), $reset = FALSE) { return entity_load('jsmap_region', $jsmap_region_ids, $conditions, $reset); } /** * Deletes a jsmap_region. */ function jsmap_region_delete(JsmapRegion $jsmap_region) { $jsmap_region->delete(); } /** * Delete multiple jsmap_regions. * * @param $jsmap_region_ids * An array of jsmap_region IDs. */ function jsmap_region_delete_multiple(array $jsmap_region_ids) { entity_get_controller('jsmap_region')->delete($jsmap_region_ids); } /** * Create a jsmap_region object. */ function jsmap_region_create($values = array()) { return entity_get_controller('jsmap_region')->create($values); } /** * Saves a map to the db. */ function jsmap_map_save(JsmapMap $map) { $map->save(); } /** * Deletes a map from the db. */ function jsmap_map_delete(JsmapMap $map) { $map->delete(); } /** * Responds to deletion of a jsmap_map entity */ function jsmap_jsmap_map_delete(JsmapMap $map) { if($regions = entity_load('jsmap_region', FALSE, array('map' => $map->map))){ foreach($regions as $region){ $region->delete(); } } } /** * URI callback for jsmap_regions */ function jsmap_region_uri(JsmapRegion $jsmap_region){ return array( 'path' => 'jsmap_region/' . $jsmap_region->id, ); } /** * Menu title callback for showing individual entities */ function jsmap_region_page_title(JsmapRegion $jsmap_region){ return $jsmap_region->name; }