├── entitiesdiagram.info ├── entitiesdiagram.js ├── entitiesdiagram.drush.inc ├── README.md └── entitiesdiagram.module /entitiesdiagram.info: -------------------------------------------------------------------------------- 1 | name = Entity relationships 2 | core = 7.x -------------------------------------------------------------------------------- /entitiesdiagram.js: -------------------------------------------------------------------------------- 1 | Drupal.behaviors.renderDotSVG = { 2 | attach: function (context, settings) { 3 | var result = Viz(dataSVG); 4 | document.body.innerHTML += result; 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /entitiesdiagram.drush.inc: -------------------------------------------------------------------------------- 1 | 'Generate a graph of the entities and fields.', 9 | 'aliases' => array('ed'), 10 | 'options' => array( 11 | 'include_fields' => 'Include fields', 12 | 'entity_type' => 'Select a single entity type', 13 | ), 14 | 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, 15 | 'core' => array('7+'), 16 | 'drupal dependencies' => array('field'), 17 | 'examples' => array( 18 | 'drush entitiesdiagram | dot -Gratio=0.7 -Eminlen=2 -T png -o ./test.png' => 'Generate the Entity-Relationship graph for the current site and export it has a PNG image.', 19 | 'drush entitiesdiagram --include_fields --entity_type=node | dot -Gratio=0.7 -Eminlen=2 -T png -o ./test.png' => 'Generate the Entity-Relationship graph for a entity type site including fields and export it has a PNG image.', 20 | ), 21 | ); 22 | return $items; 23 | } 24 | 25 | function drush_entitiesdiagram() { 26 | $include_fields = drush_get_option('include_fields', FALSE); 27 | $entity_type = drush_get_option('entity_type', FALSE); 28 | $graph = entitiesdiagram_entitygraph($include_fields, $entity_type); 29 | echo entitiesdiagram_entitygraph_generate($graph); 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This module generates a graph of the entities, fields and their 2 | relationship of a particular installation of Drupal 7. This enables a quick 3 | overview of your Drupal 7 entities and bundles. 4 | 5 | # Usage Drush 6 | 7 | $ drush entitiesdiagram | dot -Gratio=0.7 -Eminlen=2 -T svg -o ./test.svg 8 | 9 | $ drush entitiesdiagram --include_fields --entity_type=node | dot -Gratio=0.7 -Eminlen=2 -T svg -o ./test.svg 10 | 11 | # Usage UI 12 | 13 | go to URL `admin/entities-diagram//` to see one entity type or, 14 | 15 | to see all entities `admin/entity-relations` 16 | 17 | ### example: 18 | Display only Nodes with no fields: 19 | * ```http://mywebsite.com/admin/entities-diagram/node/false``` 20 | 21 | Display all entities: 22 | * ```http://mywebsite.com/admin/entities-diagram``` 23 | 24 | Generates a graph in the PNG format. 25 | 26 | ![entity_relations___productivity](https://cloud.githubusercontent.com/assets/165644/12092755/ad4bb60e-b307-11e5-904f-a75ee8db7b5c.png) 27 | ![entity_relations___productivity](https://cloud.githubusercontent.com/assets/165644/12093435/8a52dd54-b30b-11e5-9b43-2f63e5befd66.png) 28 | 29 | ## Make file 30 | ``` 31 | projects[entitiesdiagram][type] = "module" 32 | projects[entitiesdiagram][subdir] = "contrib" 33 | projects[entitiesdiagram][download][type] = "git" 34 | projects[entitiesdiagram][download][branch] = "master" 35 | projects[entitiesdiagram][download][url] = "https://github.com/Gizra/entitiesdiagram.git" 36 | ``` 37 | -------------------------------------------------------------------------------- /entitiesdiagram.module: -------------------------------------------------------------------------------- 1 | 'Entity relations Diagram', 12 | 'page callback' => 'entitiesdiagram_menu_callback', 13 | 'page arguments' => array(3, 4), 14 | 'access arguments' => array('administer content'), 15 | 'type' => MENU_NORMAL_ITEM, 16 | ); 17 | return $items; 18 | } 19 | 20 | /** 21 | * Main function to create full graph of entities. 22 | * 23 | * @param 24 | * $entity_type - Pass on and anetity type to render only. 25 | */ 26 | function entitiesdiagram_menu_callback($entity_type = FALSE, $include_fields = '') { 27 | drupal_add_css(drupal_get_path('module', 'entitiesdiagram') . '/entitiesdiagram.css'); 28 | drupal_add_js('http://mdaines.github.io/viz.js/bower_components/viz.js/viz.js', 'file'); 29 | 30 | $data = 'var dataSVG = `' . entitiesdiagram_entitygraph_generate(entitiesdiagram_entitygraph(empty($include_fields), $entity_type)) . '`;'; 31 | drupal_add_js($data, 'inline'); 32 | 33 | drupal_add_js(drupal_get_path('module', 'entitiesdiagram') . '/entitiesdiagram.js', 'file'); 34 | return ''; 35 | } 36 | 37 | /** 38 | * Main function to create full graph of entities. 39 | */ 40 | function entitiesdiagram_entitygraph($include_fields = FALSE, $single_entity_type = FALSE) { 41 | $graph = array(); 42 | $entity_refs = array(); 43 | entitiesdiagram_entitygraph_entityreference_connections($entity_refs); 44 | entitiesdiagram_entitygraph_core_reference_connections($entity_refs); 45 | if ($single_entity_type) { 46 | $entities = array($single_entity_type => entity_get_info($single_entity_type)); 47 | } 48 | else { 49 | $entities = entity_get_info(); 50 | } 51 | 52 | foreach ($entities as $entity_type => $entity_info) { 53 | // Exclude non important entities. 54 | if (in_array($entity_type, array('message_type', 'restful_token_auth',))) { 55 | continue; 56 | } 57 | $node_info = array(); 58 | $node_info['title'] = $entity_info['label']; 59 | 60 | if (!empty($entity_info['base table'])) { 61 | $table_schema = drupal_get_schema($entity_info['base table']); 62 | $table_schema += array('fields' => array(), 'foreign keys' => array()); 63 | 64 | if ($include_fields) { 65 | foreach ($table_schema['fields'] as $field_name => $field_info) { 66 | $node_info['properties'][$field_name] = array( 67 | 'type' => $field_info['type'], 68 | ); 69 | } 70 | } 71 | 72 | 73 | // Generate the bundles. 74 | $has_bundles = !empty($entity_info['entity keys']['bundle']) && count($entity_info['bundles']) > 0; 75 | if ($has_bundles) { 76 | foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) { 77 | $graph['nodes']['cluster_entity_group_' . $entity_type]['entity_' . $entity_type . '__bundle_' . str_replace('-', '_', $bundle_name)] = array( 78 | 'title' => $bundle_info['label'], 79 | ); 80 | } 81 | } 82 | 83 | // Traverse the instances. 84 | if ($include_fields) { 85 | 86 | $fields = array(); 87 | foreach (field_info_instances($entity_type) as $bundle => $instances) { 88 | foreach ($instances as $field_name => $instance_info) { 89 | $fields[$field_name][] = $bundle; 90 | } 91 | } 92 | 93 | foreach ($fields as $field_name => $bundles) { 94 | $field_info = field_info_field($field_name); 95 | $field_property_info = array( 96 | 'label' => $field_name, 97 | 'type' => $field_info['type'], 98 | ); 99 | if ($field_info['cardinality'] <> 1) { 100 | $field_property_info['type'] = 'list<' . $field_property_info['type'] . '>'; 101 | } 102 | 103 | // Build the relationships. 104 | $relationships = array(); 105 | foreach ($field_info['foreign keys'] as $foreign_key_name => $foreign_key_info) { 106 | if (count($foreign_key_info['columns']) != 1) { 107 | // We cannot process multiple key foreign keys. 108 | continue; 109 | } 110 | // Remote column. 111 | reset($foreign_key_info['columns']); 112 | // Local column. 113 | key($foreign_key_info['columns']); 114 | 115 | $foreign_entity_type = entitiesdiagram_entitygraph_get_entity_by_table($foreign_key_info['table']); 116 | if ($foreign_entity_type) { 117 | $relationships[] = $foreign_entity_type; 118 | } 119 | } 120 | 121 | foreach ($bundles as $bundle_name) { 122 | $graph['nodes']['cluster_entity_group_' . $entity_type]['entity_' . $entity_type . '__bundle_' . $bundle_name]['fields'][$field_name] = $field_property_info; 123 | } 124 | } 125 | } 126 | } 127 | 128 | if ($has_bundles) { 129 | $group = &$graph['nodes']['cluster_entity_group_' . $entity_type]; 130 | $group['label'] = $entity_info['label']; 131 | $group['group'] = TRUE; 132 | } 133 | 134 | // Entity reference edges. 135 | if (isset($entity_refs[$entity_type])) { 136 | foreach ($entity_refs[$entity_type] as $bundle_name => $field_ref_info) { 137 | foreach ($field_ref_info as $field_name => $target) { 138 | foreach ($target as $target_type => $target_info) { 139 | $relationship = 'entity_' . $target_type . '__bundle_' . $target_info['bundle']; // 140 | entitiesdiagram_entitygraph_relationship($graph, 'entity_' . $entity_type . '__bundle_' . $bundle_name, $relationship, $target_info['required'], $target_info['cardinality'], $target_info['fieldname']); 141 | } 142 | } 143 | } 144 | } 145 | } 146 | 147 | return $graph; 148 | } 149 | 150 | /** 151 | * Get entity reference connections. 152 | */ 153 | function entitiesdiagram_entitygraph_entityreference_connections(&$field_relation) { 154 | // Get all entityreference field names 155 | $query = db_select('field_config', 'f'); 156 | $query->fields('f', array('field_name')); 157 | $query->condition('f.type', 'entityreference'); 158 | $query->distinct(); 159 | $rows = $query->execute(); 160 | 161 | // Create an associative array: 162 | // $ar[source_type][source_bundle][field_name][target_type][target_bundle] 163 | foreach ($rows as $row) { 164 | $field_name = $row->field_name; 165 | $field_info = field_info_field($row->field_name); 166 | foreach($field_info['bundles'] as $type => $bundles) { 167 | foreach($bundles as $bundle) { 168 | $target_bundles = !empty($field_info['settings']['handler_settings']['target_bundles']) ? $field_info['settings']['handler_settings']['target_bundles'] : array($field_info['settings']['target_type'] => $field_info['settings']['target_type']); 169 | foreach ($target_bundles as $target) { 170 | $instance_info = field_info_instance($type, $field_name, $bundle); 171 | $field_relation[$type][$bundle][$field_name][$field_info['settings']['target_type']] = array ( 172 | 'bundle' => $target, 173 | 'cardinality' => $field_info['cardinality'], 174 | 'required' => $instance_info['required'], 175 | 'fieldname' => $field_name, 176 | ); 177 | } 178 | } 179 | } 180 | } 181 | } 182 | 183 | 184 | 185 | /** 186 | * Get term and node reference connections. 187 | */ 188 | function entitiesdiagram_entitygraph_core_reference_connections(&$entity_refs) { 189 | // Get node and term refs. 190 | $field_types = array( 191 | 'taxonomy_term' => 'taxonomy_term_reference', 192 | 'node' => 'node_reference', 193 | 'user' => 'user_reference', 194 | ); 195 | foreach ($field_types as $source_type => $field_type) { 196 | $fields = field_read_fields(array('type' => $field_type)); 197 | // Create an associative array: 198 | // $ar[source_type][source_bundle][field_name][target_type][target_bundle] 199 | foreach ($fields as $field_name => $taxonomy_field) { 200 | $field_info = field_info_field($field_name); 201 | foreach ($field_info['bundles'] as $type => $bundles) { 202 | foreach ($bundles as $bundle) { 203 | $targets = array(); 204 | // Build list of target bundle. 205 | if ($source_type == 'taxonomy_term') { 206 | foreach ($field_info['settings']['allowed_values'] as $target) { 207 | $targets[] = $target['vocabulary']; 208 | } 209 | } 210 | elseif ($source_type == 'node') { 211 | foreach ($field_info['settings']['referenceable_types'] as $target) { 212 | if ($target) { 213 | $targets[] = $target; 214 | } 215 | } 216 | } 217 | elseif ($source_type == 'user') { 218 | $targets[] = 'user'; 219 | } 220 | 221 | foreach ($targets as $target) { 222 | $instance_info = field_info_instance($type, $field_name, $bundle); 223 | $entity_refs[$type][$bundle][$field_name][$source_type] = array( 224 | 'bundle' => $target, 225 | 'cardinality' => $field_info['cardinality'], 226 | 'required' => $instance_info['required'], 227 | 'fieldname' => $field_name, 228 | ); 229 | } 230 | } 231 | } 232 | } 233 | } 234 | } 235 | 236 | /** 237 | * Get entity name by table. 238 | */ 239 | function entitiesdiagram_entitygraph_get_entity_by_table($table) { 240 | foreach (entity_get_info() as $entity_type => $entity_info) { 241 | if (!empty($entity_info['base table']) && $entity_info['base table'] == $table) { 242 | return $entity_type; 243 | } 244 | } 245 | } 246 | 247 | /** 248 | * Create an Edge connection. 249 | * 250 | * @param $graph 251 | * Graph array to update. 252 | */ 253 | function entitiesdiagram_entitygraph_relationship(&$graph, $source, $target, $required, $cardinality, $fieldname) { 254 | $edge_info = array( 255 | 'arrowhead' => 'normal', 256 | ); 257 | if ($cardinality >= 1) { 258 | $min_cardinality = $required ? $cardinality : 0; 259 | $max_cardinality = $cardinality; 260 | } 261 | else { 262 | $min_cardinality = $required ? 1 : 0; 263 | $max_cardinality = '*'; 264 | } 265 | $edge_info['taillabel'] = $min_cardinality . '..' . $max_cardinality; 266 | 267 | $edge_info['headlabel'] = '1..*'; 268 | $edge_info['fieldname'] = $fieldname; 269 | 270 | $graph['edges'][$source][$target] = $edge_info; 271 | } 272 | 273 | /** 274 | * Render graph into digraph format. 275 | * using https://en.wikipedia.org/wiki/DOT_(graph_description_language) 276 | */ 277 | function entitiesdiagram_entitygraph_generate($graph) { 278 | // Merge in defaults. 279 | $graph += array( 280 | 'nodes' => array(), 281 | 'edges' => array(), 282 | ); 283 | $output = "digraph G {\n"; 284 | 285 | $output .= "node [\n"; 286 | $output .= "shape = \"record\"\n"; 287 | $output .= "]\n"; 288 | 289 | foreach ($graph['nodes'] as $name => $node_info) { 290 | if (!empty($node_info['group'])) { 291 | $output .= entitiesdiagram_entitygraph_generate_subgraph($name, $node_info); 292 | } 293 | else { 294 | $output .= entitiesdiagram_entitygraph_generate_node($name, $node_info); 295 | } 296 | } 297 | 298 | foreach ($graph['edges'] as $source_node => $edges) { 299 | foreach ($edges as $target_node => $edge_info) { 300 | $output .= "edge [\n"; 301 | foreach ($edge_info as $k => $v) { 302 | $output .= ' "' . check_plain($k) . '" = "'. check_plain($v) . '"' . "\n"; 303 | } 304 | $output .= "]\n"; 305 | $color = entitiesdiagram_random_color(); 306 | $output .= format_string('@source_node -> @target_node [color="@color"][label="(@fieldname)" fontcolor="@color"]', 307 | array( 308 | '@source_node' => $source_node, 309 | '@target_node' => $target_node, 310 | '@color' => $color, 311 | '@fieldname' => $edge_info['fieldname'], 312 | ) 313 | ); 314 | } 315 | } 316 | 317 | $output .= "\n}\n"; 318 | return $output; 319 | } 320 | 321 | /** 322 | * Create random RGB element. 323 | */ 324 | function entitiesdiagram_random_color_part() { 325 | return str_pad( dechex( mt_rand( 0, 255 ) ), 2, '0', STR_PAD_LEFT); 326 | } 327 | 328 | /** 329 | * Create random RGB color. 330 | */ 331 | function entitiesdiagram_random_color() { 332 | return '#' . entitiesdiagram_random_color_part() . entitiesdiagram_random_color_part() . entitiesdiagram_random_color_part(); 333 | } 334 | 335 | /** 336 | * Create a subgraph 337 | */ 338 | function entitiesdiagram_entitygraph_generate_subgraph($name, $subgraph_info) { 339 | $label = $subgraph_info['label']; 340 | unset($subgraph_info['label']); 341 | unset($subgraph_info['group']); 342 | 343 | $output = "subgraph $name {\n"; 344 | $output .= 'label = "' . check_plain($label) . '"' . "\n"; 345 | 346 | foreach ($subgraph_info as $node_name => $node_info) { 347 | $output .= entitiesdiagram_entitygraph_generate_node($node_name, $node_info); 348 | } 349 | 350 | $output .= "}\n"; 351 | return $output; 352 | } 353 | 354 | /** 355 | * Create a single node box. 356 | */ 357 | function entitiesdiagram_entitygraph_generate_node($name, $node_info) { 358 | // Merge in defaults. 359 | $node_info += array( 360 | 'title' => $name, 361 | 'properties' => array(), 362 | 'fields' => array(), 363 | 'methods' => array(), 364 | ); 365 | 366 | $label = $node_info['title'] . '|'; 367 | 368 | foreach ($node_info['properties'] as $property_name => $property_info) { 369 | $property_info += array( 370 | 'type' => '', 371 | ); 372 | $label .= $property_name . ' : ' . $property_info['type'] . '\l'; 373 | } 374 | 375 | $label .= '|'; 376 | 377 | foreach ($node_info['fields'] as $field_name => $field_info) { 378 | $field_info += array( 379 | 'type' => '', 380 | ); 381 | $label .= $field_name . ' : ' . $field_info['type'] . '|'; 382 | } 383 | 384 | return $name . ' [ label = "{' . check_plain($label) . '}" ]'; 385 | } 386 | --------------------------------------------------------------------------------