コード例 #1
0
ファイル: OpenLayers.php プロジェクト: ffarago/pawtucket2
 /**
  * Returns HTML for editable Geocode attribute, suitable for inclusion in a bundleable editing form 
  *
  * @param array $pa_element_info Array of information about the element the bundle is being generate for
  * @param array $pa_options Options are:
  *		width = Width of map + controls in pixels; default is 690
  *		height = Height of map + controls in pixels; default is 300
  *		baseLayer = Tiles to user for base layer. Should be full class name with optional constructor string (Eg. OpenLayers.Layer.Stamen('toner')); default is OpenLayers.Layer.OSM()
  *		pointRadius = Radius, in pixels, of plotted points. Default is 5 pixels
  *		fillColor = Color (in hex format with leading "#") to fill regions and points with
  *		strokeWidth = Width of plotted paths, in pixels. Default is 2
  *		strokeColor = Color of plotted paths, in hex format with leading "#"
  *		fillColorSelected = Color to fill regions with when selected, in hex format with leading "#"
  *		strokeColorSelected = Color of plotted paths when selected, in hex format with leading "#"
  *
  * @return string HTML output
  */
 public function getAttributeBundleHTML($pa_element_info, $pa_options = null)
 {
     AssetLoadManager::register('openlayers');
     $o_config = Configuration::load();
     $va_element_width = caParseFormElementDimension($pa_element_info['settings']['fieldWidth']);
     $vn_element_width = $va_element_width['dimension'];
     $va_element_height = caParseFormElementDimension($pa_element_info['settings']['fieldHeight']);
     $vn_element_height = $va_element_height['dimension'];
     $va_options = caGetOptions($pa_options, array('width' => $vn_element_width, 'height' => $vn_element_height));
     if (($vn_width = $va_options['width']) < 100) {
         $vn_width = 690;
     }
     if (($vn_height = $va_options['height']) < 100) {
         $vn_height = 300;
     }
     if (!($vs_base_layer = $va_options['baseLayer'])) {
         if (!($vs_base_layer = $o_config->get('openlayers_base_layer'))) {
             $vs_base_layer = 'OpenLayers.Layer.OSM()';
         }
     }
     if (($vn_point_radius = $va_options['pointRadius']) < 1) {
         if (!($vn_point_radius = $o_config->get('openlayers_point_radius'))) {
             $vn_point_radius = 5;
         }
     }
     if (($vs_fill_color = $va_options['fillColor']) < 1) {
         if (!($vs_fill_color = $o_config->get('openlayers_fill_color'))) {
             $vs_fill_color = '#ffcc66';
         }
     }
     if (($vn_stroke_width = $va_options['strokeWidth']) < 1) {
         if (!($vn_stroke_width = $o_config->get('openlayers_stroke_width'))) {
             $vn_stroke_width = 2;
         }
     }
     if (($vs_stroke_color = $va_options['strokeColor']) < 1) {
         if (!($vs_stroke_color = $o_config->get('openlayers_stroke_color'))) {
             $vs_stroke_color = '#ff9933';
         }
     }
     if (($vs_fill_color_selected = $va_options['fillColorSelected']) < 1) {
         if (!($vs_fill_color_selected = $o_config->get('openlayers_fill_color_selected'))) {
             $vs_fill_color_selected = '#66ccff';
         }
     }
     if (($vs_stroke_color_selected = $va_options['strokeColorSelected']) < 1) {
         if (!($vs_stroke_color_selected = $o_config->get('openlayers_stroke_color_selected'))) {
             $vs_stroke_color_selected = '#3399ff';
         }
     }
     $po_request = isset($pa_options['request']) ? $pa_options['request'] : null;
     $vs_id = $pa_element_info['element_id'];
     $vs_element = '<div id="{fieldNamePrefix}mapholder_' . $vs_id . '_{n}" class="mapholder" style="width:' . $vn_width . 'spx; height:' . ($vn_height + 40) . 'px; float: left; margin:-18px 0 0 0;">';
     $vs_element .= '<div class="olMapSearchControls" id="{fieldNamePrefix}Controls_{n}">';
     if ($po_request) {
         $vs_element .= '<div class="olMapSearchBox">';
         $vs_element .= '<input type="text" class="olMapSearchText" name="{fieldNamePrefix}' . $pa_element_info['element_id'] . '_{n}_search"  id="{fieldNamePrefix}' . $pa_element_info['element_id'] . '_{n}_search" size="30" value="" autocomplete="off" onfocus="this.value = \'\';" onkeypress="return map_geocode_' . $vs_id . '(event);"/>';
         $vs_element .= "<a href='#' onclick='map_geocode_{$vs_id}();'>" . caGetThemeGraphic($po_request, 'buttons/glass.png', array('alt' => _t('Search'), 'class' => 'olMapSearchBoxIcon', 'id' => '{fieldNamePrefix}' . $pa_element_info['element_id'] . '_{n}_search_button')) . "</a>";
         $vs_element .= '</div>';
     }
     $vs_element .= '<div class="olMapKmlControl" id="{fieldNamePrefix}showKmlControl_{n}">';
     $vs_element .= '<div style="position: absolute; bottom: 0px; left: 0px;"><a href="#" class="button" id="{fieldNamePrefix}showKmlControl_{n}_button">' . _t('Upload KML file') . ' &rsaquo;</a></div>';
     $vs_element .= '</div>';
     $vs_element .= '</div>';
     $vs_element .= '<div class="olMapKMLInput" id="{fieldNamePrefix}KmlControl_{n}">';
     $vs_element .= _t("Select KML or KMZ file") . ': <input type="file" name="{fieldNamePrefix}' . $pa_element_info['element_id'] . '_{n}"/><a href="#" class="button"  id="{fieldNamePrefix}hideKmlControl_{n}_button">' . _t('Use map') . ' &rsaquo;</a>';
     $vs_element .= '</div>';
     $vs_element .= '<div class="olMap" id="{fieldNamePrefix}map_' . $vs_id . '_{n}" style="width:' . $vn_width . 'px; height:' . $vn_height . 'px;"> </div>';
     $vs_element .= '</div>';
     $vs_element .= "<script type='text/javascript'>\n\t\t\n\tvar map_{$vs_id};\n\tvar points_{$vs_id};\n\tjQuery(document).ready(function() {\n\t\t// Styles\n\t\tvar styles_{$vs_id} = new OpenLayers.StyleMap({\n\t\t\t'default': new OpenLayers.Style({\n\t\t\t\tpointRadius: '{$vn_point_radius}',\n\t\t\t\tfillColor: '{$vs_fill_color}',\n\t\t\t\tstrokeColor: '{$vs_stroke_color}',\n\t\t\t\tstrokeWidth: '{$vn_stroke_width}',\n\t\t\t\tgraphicZIndex: 1\n\t\t\t}),\n\t\t\t'select': new OpenLayers.Style({\n\t\t\t\tfillColor: '{$vs_fill_color_selected}',\n\t\t\t\tstrokeColor: '{$vs_stroke_color_selected}',\n\t\t\t\tgraphicZIndex: 2\n\t\t\t})\n\t\t});\n\n\t\tvar map_{$vs_id}_editing_toolbar;\n\t\t\n\t\tfunction map_serialize_features_{$vs_id}() {\n\t\t\t// get all points\n\t\t\tvar features = [];\n\t\t\tfor(var i=0; i < points_{$vs_id}.features.length; i++) {\n\t\t\t\tvar pl = [];\n\t\t\t\tvar geometry_type = points_{$vs_id}.features[i].geometry.CLASS_NAME;\n\t\t\t\tvar n = points_{$vs_id}.features[i].geometry.getVertices();\n\t\t\t\tfor (var j=0; j<n.length; j++) {\n\t\t\t\t\tvar np = n[j].clone();\n\t\t\t\t\tnp.transform(map_{$vs_id}.getProjectionObject(), new OpenLayers.Projection('EPSG:4326'));\n\t\t\t\t\tpl.push(np.y + ',' + np.x);\n\t\t\t\t}\n\t\t\t\tif ((pl.length > 1) && (geometry_type == 'OpenLayers.Geometry.Polygon')) { pl.push(pl[0]); } // close polygons\n\t\t\t\tfeatures.push(pl.join(';'));\n\t\t\t}\n\t\t\t\n\t\t\tjQuery('#{fieldNamePrefix}" . $pa_element_info['element_id'] . "_{n}').val('[' + features.join(':') + ']');\n\t\t}\n\t\t\n\t\t// Set up layer for added points/paths\n\t\tpoints_{$vs_id} = new OpenLayers.Layer.Vector('Points', {\n\t\t\tstyleMap: styles_{$vs_id},\n\t\t\trendererOptions: {zIndexing: true},\n\t\t\teventListeners: {\n\t\t\t\t'featureadded': map_serialize_features_{$vs_id}\n\t\t\t}\n\t\t});\n\t\t\n\t\tmap_{$vs_id}_editing_toolbar = new OpenLayers.Control.EditingToolbar(points_{$vs_id});\n\t\t\n\t\t// Set up map\n\t\tmap_{$vs_id} = new OpenLayers.Map({\n\t\t\tdiv: '{fieldNamePrefix}map_{$vs_id}_{n}',\n\t\t\tlayers: [new {$vs_base_layer}],\n\t\t\tcontrols: [\n\t\t\t\tnew OpenLayers.Control.Navigation({\n\t\t\t\t\tdragPanOptions: {\n\t\t\t\t\t\tenableKinetic: true\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t\tnew OpenLayers.Control.Zoom()\n\t\t\t],\n\t\t\tcenter: [0,0],\n\t\t\tzoom: 1\n\t\t});\n\t\t\n\t\tvar map_{$vs_id}_drag_ctrl = new OpenLayers.Control.DragFeature(points_{$vs_id}, {\n\t\t\tonComplete: function(f) { map_serialize_features_{$vs_id}(f); }\n\t\t});\n\t\tmap_{$vs_id}.addControl(map_{$vs_id}_drag_ctrl);\n\t\tmap_{$vs_id}_drag_ctrl.activate();\n\t\t\n\t\t// add delete control\n\t\tvar map_{$vs_id}_delete_button = new OpenLayers.Control.Button ({displayClass: 'olControlDelete', trigger: function() { \n\t\t\tif (points_{$vs_id}.selectedFeatures) { \n\t\t\t\tpoints_{$vs_id}.removeFeatures(points_{$vs_id}.selectedFeatures);\n\t\t\t\tmap_serialize_features_{$vs_id}();\n\t\t\t}\n\t\t}, title: '" . _t('Remove') . "'});\n\t\tvar map_{$vs_id}_delete_panel = new OpenLayers.Control.Panel({type: OpenLayers.Control.TYPE_BUTTON, displayClass: 'olControlDeletePanel'});\n\t\tmap_{$vs_id}_delete_panel.addControls([map_{$vs_id}_delete_button]);\n\t\tmap_{$vs_id}.addControl(map_{$vs_id}_delete_panel);\n\t\tmap_{$vs_id}_delete_button.activate();\n\t\t\n\t\t// Grab current map coordinates from input\n\t\tvar map_{$ps_id}_loc_str = '{" . $pa_element_info['element_id'] . "}';\n\t\tvar map_{$ps_id}_loc_features = map_{$ps_id}_loc_str.match(/\\[([\\d\\,\\-\\.\\:\\;]+)\\]/)\n\t\tif (map_{$ps_id}_loc_features && (map_{$ps_id}_loc_features.length > 1)) {\n\t\t\tmap_{$ps_id}_loc_features = map_{$ps_id}_loc_features[1].split(/:/);\n\t\t} else {\n\t\t\tmap_{$ps_id}_loc_features = [];\n\t\t}\n\t\tvar features_{$vs_id} = [];\n\t\t\n\t\tvar i, j, c=0;\n\t\tfor(i=0; i < map_{$ps_id}_loc_features.length; i++) {\n\t\t\tvar ptlist = map_{$ps_id}_loc_features[i].split(/;/);\n\t\t\t\n\t\t\tif (ptlist.length > 1) {\n\t\t\t\t// path\n\t\t\t\tvar ptolist = [];\n\t\t\t\tfor(j=0; j < ptlist.length; j++) {\n\t\t\t\t\tvar pt = ptlist[j].split(/,/);\n\t\t\t\t\tptolist.push(new OpenLayers.Geometry.Point(pt[1], pt[0]));\n\t\t\t\t\t\n\t\t\t\t}\n\t\t\t\tfeatures_{$vs_id}.push(new OpenLayers.Feature.Vector(\n\t\t\t\t\tnew OpenLayers.Geometry.LineString(ptolist).transform(new OpenLayers.Projection('EPSG:4326'),map_{$vs_id}.getProjectionObject()), {}\n\t\t\t\t));\n\t\t\t} else {\n\t\t\t\t// point\n\t\t\t\tvar pt = ptlist[0].split(/,/);\n\t\t\t\tfeatures_{$vs_id}.push(new OpenLayers.Feature.Vector(\n\t\t\t\t\tnew OpenLayers.Geometry.Point(pt[1], pt[0]).transform(new OpenLayers.Projection('EPSG:4326'),map_{$vs_id}.getProjectionObject()), {}\n\t\t\t\t));\n\t\t\t}\n\t\t\tc++;\n\t\t}\n\t\n\t\tpoints_{$vs_id}.addFeatures(features_{$vs_id});\n\t\t\n\t\tvar map_{$vs_id}_highlight_ctrl = new OpenLayers.Control.SelectFeature(points_{$vs_id}, {\n\t\t\thover: false,\n\t\t\trenderIntent: 'temporary',\n\t\t\tmultiple: true, clickout: true, toggle: true, box: true,\n\t\t\teventListeners: {}\n\t\t});\n\t\tmap_{$vs_id}.addControl(map_{$vs_id}_highlight_ctrl);\n\t\tmap_{$vs_id}_highlight_ctrl.activate();\n\t\t\n\t\tmap_{$vs_id}.addControl(map_{$vs_id}_editing_toolbar);\n\t\tmap_{$vs_id}_editing_toolbar.activate();\n \n\t\tmap_{$vs_id}.addLayer(points_{$vs_id});\n\t\t\n\t\tif (c > 0) {\n\t\t\tmap_{$vs_id}.zoomToExtent(points_{$vs_id}.getDataExtent());\n\t\t\tif (map_{$vs_id}.zoom > 14) { map_{$vs_id}.zoomTo(14); }\n\t\t}\n\t\t\n\t\tjQuery('#{fieldNamePrefix}showKmlControl_{n}_button').click(function(event) {\n\t\t\tevent.preventDefault();\n\t\t\tjQuery('#{fieldNamePrefix}Controls_{n}').hide(200, function() {\n\t\t\t\tjQuery('#{fieldNamePrefix}KmlControl_{n}').slideDown(200);\n\t\t\t});\n\t\t});\n\t\t\n\t\tjQuery('#{fieldNamePrefix}hideKmlControl_{n}_button').click(function(event) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tjQuery(this).parent().hide(200, function() {\n\t\t\t\t\tjQuery('#{fieldNamePrefix}Controls_{n}').slideDown(200);\n\t\t\t\t});\n\t\t\t});\n\t});\n\t\tfunction map_geocode_{$vs_id}(e) {\n\t\t\tif (e && ((e.keyCode || e.which || e.charCode || 0) !== 13)) { return true; }\n\t\t\tvar t = jQuery('#{fieldNamePrefix}" . $pa_element_info['element_id'] . "_{n}_search').val();\n\t\t\tjQuery('#{fieldNamePrefix}" . $pa_element_info['element_id'] . "_{n}_search_button').attr('src', '" . caGetThemeGraphicURL($po_request, '/icons/indicator.gif') . "');\n\t\t\tvar geocoder = new google.maps.Geocoder();\n\t\t\tgeocoder.geocode( { 'address': t}, function(results, status) {\n\t\t\t\tjQuery('#{fieldNamePrefix}" . $pa_element_info['element_id'] . "_{n}_search_button').attr('src', '" . caGetThemeGraphicURL($po_request, '/buttons/glass.png') . "');\n\t\t\t\tif (status == google.maps.GeocoderStatus.OK) {\n\t\t\t\t\tvar loc = results[0]['geometry']['location'];\n\t\t\t\t\tvar pt = new OpenLayers.LonLat(loc.lng(), loc.lat()).transform(new OpenLayers.Projection('EPSG:4326'),map_{$vs_id}.getProjectionObject());\n\t\t\t\t\tmap_{$vs_id}.panTo(pt);\n\t\t\t\t\tmap_{$vs_id}.zoomTo((results[0]['geometry']['location_type'] == 'APPROXIMATE') ? 10 : 14);\n\t\t\t\t\tpoints_{$vs_id}.addFeatures([new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(loc.lng(), loc.lat()).transform(new OpenLayers.Projection('EPSG:4326'),map_{$vs_id}.getProjectionObject()))]);\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t</script>";
     $vs_element .= '<input class="coordinates mapCoordinateDisplay" type="hidden" name="{fieldNamePrefix}' . $pa_element_info['element_id'] . '_{n}" id="{fieldNamePrefix}' . $pa_element_info['element_id'] . '_{n}"/>';
     return $vs_element;
 }
コード例 #2
0
 /**
  * Returns an associative array of relationship types for the relationship
  * organized by the sub_type_id specified by $ps_orientation. If $ps_orientation is the name of the "right" table
  * then sub_type_left_id is used for keys in the array, if $ps_orientation is the name of the "left" table
  * then sub_type_right_id is used for keys.
  *
  * For example, for ca_objects_x_entities, if $ps_orientation is ca_objects then then sub_type_right_id is
  * used as the key; if ca_entities is passed then sub_type_left_id is used; if a table name is passed that
  * is not either side of the relation then an empty array is returned
  *
  */
 public function getRelationshipTypesBySubtype($ps_orientation, $pn_type_id, $pa_options = null)
 {
     unset($pa_options['request']);
     if (!$this->hasField('type_id')) {
         return array();
     }
     $vs_left_table_name = $this->getLeftTableName();
     $vs_right_table_name = $this->getRightTableName();
     $vb_dont_include_subtypes_in_type_restriction = caGetOptions('dont_include_subtypes_in_type_restriction', $pa_options, false);
     $o_db = $this->getDb();
     $t_rel_type = new ca_relationship_types();
     $vs_restrict_to_relationship_type_sql = '';
     if (isset($pa_options['restrict_to_relationship_types']) && $pa_options['restrict_to_relationship_types']) {
         if (!is_array($pa_options['restrict_to_relationship_types'])) {
             $pa_options['restrict_to_relationship_types'] = array($pa_options['restrict_to_relationship_types']);
         }
         if (sizeof($pa_options['restrict_to_relationship_types'])) {
             $va_restrict_to_type_list = array();
             foreach ($pa_options['restrict_to_relationship_types'] as $vs_type_code) {
                 if (!strlen(trim($vs_type_code))) {
                     continue;
                 }
                 $va_criteria = array('table_num' => $this->tableNum());
                 if (is_numeric($vs_type_code)) {
                     $va_criteria['type_id'] = (int) $vs_type_code;
                 } else {
                     $va_criteria['type_code'] = $vs_type_code;
                 }
                 if ($t_rel_type->load($va_criteria)) {
                     $va_restrict_to_type_list[] = "(crt.hier_left >= " . $t_rel_type->get('hier_left') . " AND crt.hier_right <= " . $t_rel_type->get('hier_right') . ")";
                 }
             }
             if (sizeof($va_restrict_to_type_list)) {
                 $vs_restrict_to_relationship_type_sql = " AND (" . join(' OR ', $va_restrict_to_type_list) . ")";
             }
         }
     }
     $qr_res = $o_db->query("\n\t\t\t\tSELECT *\n\t\t\t\tFROM ca_relationship_types crt\n\t\t\t\tINNER JOIN ca_relationship_type_labels AS crtl ON crt.type_id = crtl.type_id\n\t\t\t\tWHERE\n\t\t\t\t\t(crt.table_num = ?)\n\t\t\t\t\t{$vs_restrict_to_relationship_type_sql}\n\t\t\t", $this->tableNum());
     // Support hierarchical subtypes - if the subtype restriction is a type with parents then include those as well
     // Allows subtypes to "inherit" bindings from parent types
     $t_list_item = new ca_list_items($pn_type_id);
     if (!$vb_dont_include_subtypes_in_type_restriction) {
         if (!is_array($va_ancestor_ids = $t_list_item->getHierarchyAncestors(null, array('idsOnly' => true, 'includeSelf' => true)))) {
             $va_ancestor_ids = array();
         }
         // remove hierarchy root from ancestor list, otherwise invalid bindings
         // from root nodes (which are not "real" rel types) may be inherited
         array_pop($va_ancestor_ids);
     } else {
         $va_ancestor_ids = array($pn_type_id);
     }
     $va_types = array();
     $va_parent_ids = array();
     $vn_l = 0;
     $vn_root_id = $t_rel_type->load(array('parent_id' => null, 'table_num' => $this->tableNum())) ? $t_rel_type->getPrimaryKey() : null;
     $va_hier = array();
     if ($vs_left_table_name === $vs_right_table_name) {
         // ----------------------------------------------------------------------------------------
         // self relationship
         while ($qr_res->nextRow()) {
             $va_row = $qr_res->getRow();
             $vn_parent_id = $va_row['parent_id'];
             $va_hier[$vn_parent_id][] = $va_row['type_id'];
             // skip type if it has a subtype set and it's not in our list
             $vs_subtype_orientation = null;
             $vs_subtype = null;
             if ($va_row['sub_type_left_id'] && !in_array($va_row['sub_type_left_id'], $va_ancestor_ids)) {
                 // not left
                 if ($va_row['sub_type_right_id'] && !in_array($va_row['sub_type_right_id'], $va_ancestor_ids)) {
                     // not left and not right
                     continue;
                 } else {
                     // not left and right
                     $vs_subtype = $va_row['sub_type_left_id'];
                     $vs_subtype_orientation = "left";
                 }
             } else {
                 if ($va_row['sub_type_left_id'] && in_array($va_row['sub_type_left_id'], $va_ancestor_ids)) {
                     // left
                     if ($va_row['sub_type_right_id'] && in_array($va_row['sub_type_right_id'], $va_ancestor_ids)) {
                         // left and right
                         $vs_subtype = $va_row['sub_type_right_id'];
                         $vs_subtype_orientation = "";
                     } else {
                         // left and not right
                         $vs_subtype_orientation = "right";
                         $vs_subtype = $va_row['sub_type_right_id'];
                     }
                 }
             }
             if (!$vs_subtype) {
                 $vs_subtype = 'NULL';
             }
             switch ($vs_subtype_orientation) {
                 case 'left':
                     $va_tmp = $va_row;
                     $vs_key = strlen($va_tmp['rank']) > 0 ? sprintf("%08d", (int) $va_tmp['rank']) . preg_replace('![^A-Za-z0-9_]+!', '_', $va_tmp['typename_reverse']) : preg_replace('![^A-Za-z0-9_]+!', '_', $va_tmp['typename_reverse']);
                     $va_tmp['typename'] = $va_tmp['typename_reverse'];
                     unset($va_tmp['typename_reverse']);
                     // we pass the typename adjusted for direction in 'typename', so there's no need to include typename_reverse in the returned values
                     $va_types[$vn_parent_id][$vs_subtype][$vs_key][$va_row['type_id']][$va_row['locale_id']] = $va_tmp;
                     break;
                 case 'right':
                     $va_tmp = $va_row;
                     $vs_key = strlen($va_tmp['rank']) > 0 ? sprintf("%08d", (int) $va_tmp['rank']) . preg_replace('![^A-Za-z0-9_]+!', '_', $va_tmp['typename']) : preg_replace('![^A-Za-z0-9_]+!', '_', $va_tmp['typename']);
                     unset($va_tmp['typename_reverse']);
                     // we pass the typename adjusted for direction in 'typename', so there's no need to include typename_reverse in the returned values
                     $va_types[$vn_parent_id][$vs_subtype][$vs_key][$va_row['type_id']][$va_row['locale_id']] = $va_tmp;
                     break;
                 default:
                     $va_tmp = $va_row;
                     if (trim($va_tmp['typename']) == trim($va_tmp['typename_reverse'])) {
                         //
                         // If the sides of the self-relationship are the same then treat it like a normal relationship type: one entry in the
                         // list and a plain type_id value
                         //
                         unset($va_tmp['typename_reverse']);
                         // we pass the typename adjusted for direction in 'typename', so there's no need to include typename_reverse in the returned values
                         $va_tmp['direction'] = null;
                         $vs_key = strlen($va_tmp['rank']) > 0 ? sprintf("%08d", (int) $va_tmp['rank']) . preg_replace('![^A-Za-z0-9_]+!', '_', $va_tmp['typename']) : preg_replace('![^A-Za-z0-9_]+!', '_', $va_tmp['typename']);
                         $va_types[$vn_parent_id][$vs_subtype][$vs_key][$va_row['type_id']][$va_row['locale_id']] = $va_tmp;
                     } else {
                         //
                         // If each side of the self-relationship type are different then add both to the list with special type_id values that
                         // indicate the directionality of the typename (ltor = left to right = "typename"; rtor = right to left = "typename_reverse")
                         //
                         $va_tmp = $va_row;
                         unset($va_tmp['typename_reverse']);
                         // we pass the typename adjusted for direction in 'typename', so there's no need to include typename_reverse in the returned values
                         $vs_key = strlen($va_tmp['rank']) > 0 ? sprintf("%08d", (int) $va_tmp['rank']) . preg_replace('![^A-Za-z0-9_]+!', '_', $va_tmp['typename']) : preg_replace('![^A-Za-z0-9_]+!', '_', $va_tmp['typename']);
                         $va_tmp['direction'] = 'ltor';
                         $va_types[$vn_parent_id][$vs_subtype][$vs_key]['ltor_' . $va_row['type_id']][$va_row['locale_id']] = $va_tmp;
                         $va_tmp = $va_row;
                         $va_tmp['typename'] = $va_tmp['typename_reverse'];
                         unset($va_tmp['typename_reverse']);
                         // we pass the typename adjusted for direction in 'typename', so there's no need to include typename_reverse in the returned values
                         $vs_key = strlen($va_tmp['rank']) > 0 ? sprintf("%08d", (int) $va_tmp['rank']) . preg_replace('![^A-Za-z0-9_]+!', '_', $va_tmp['typename_reverse']) : preg_replace('![^A-Za-z0-9_]+!', '_', $va_tmp['typename_reverse']);
                         $va_tmp['direction'] = 'rtol';
                         $va_types[$vn_parent_id][$vs_subtype][$vs_key]['rtol_' . $va_row['type_id']][$va_row['locale_id']] = $va_tmp;
                     }
                     break;
             }
         }
         $va_types = $this->_processRelationshipHierarchy($vn_root_id, $va_hier, $va_types, 1);
         $va_processed_types = array('_type_map' => array());
         $va_subtype_lookups = array();
         foreach ($va_types as $vs_subtype => $va_types_by_subtype) {
             $va_types_by_locale = array();
             foreach ($va_types_by_subtype as $vs_key => $va_types_by_key) {
                 foreach ($va_types_by_key as $vs_k => $va_v) {
                     foreach ($va_v as $vs_k2 => $vs_v2) {
                         $va_types_by_locale[$vs_k][$vs_k2] = $vs_v2;
                     }
                 }
             }
             if (!$vb_dont_include_subtypes_in_type_restriction) {
                 // include mapping from parent type used in restriction to child types that inherit the binding
                 if ($vs_subtype != 'NULL' && (!isset($va_subtype_lookups[$vs_subtype]) || !$va_subtype_lookups[$vs_subtype])) {
                     $va_children = $t_list_item->getHierarchyChildren($vs_subtype, array('idsOnly' => true));
                     foreach ($va_children as $vn_child) {
                         $va_processed_types['_type_map'][$vn_child] = $vs_subtype;
                     }
                     $va_subtype_lookups[$vs_subtype] = true;
                 }
             }
             $va_processed_types[$vs_subtype] = caExtractValuesByUserLocale($va_types_by_locale, null, null, array('returnList' => true));
         }
     } else {
         // ----------------------------------------------------------------------------------------
         // regular relationship
         if (!in_array($ps_orientation, array($vs_left_table_name, $vs_right_table_name))) {
             return array();
         }
         while ($qr_res->nextRow()) {
             $va_row = $qr_res->getRow();
             $vn_parent_id = $va_row['parent_id'];
             $va_hier[$vn_parent_id][] = $va_row['type_id'];
             if ($ps_orientation == $vs_left_table_name) {
                 // right-to-left
                 // expand subtype
                 $va_subtypes_to_check = $va_row['sub_type_left_id'] > 0 ? caMakeTypeIDList($vs_left_table_name, array($va_row['sub_type_left_id'])) : null;
                 // skip type if it has a subtype set and it's not in our list
                 if (!(!$va_subtypes_to_check || sizeof(array_intersect($va_subtypes_to_check, $va_ancestor_ids)))) {
                     continue;
                 }
                 $vs_subtype = $va_row['sub_type_right_id'];
                 $vs_key = strlen($va_row['rank']) > 0 ? sprintf("%08d", (int) $va_row['rank']) . preg_replace('![^A-Za-z0-9_]+!', '_', $va_row['typename']) : preg_replace('![^A-Za-z0-9_]+!', '_', $va_row['typename']);
             } else {
                 // left-to-right
                 // expand subtype
                 $va_subtypes_to_check = $va_row['sub_type_right_id'] > 0 ? caMakeTypeIDList($vs_right_table_name, array($va_row['sub_type_right_id'])) : null;
                 // skip type if it has a subtype set and it's not in our list
                 if (!(!$va_subtypes_to_check || sizeof(array_intersect($va_subtypes_to_check, $va_ancestor_ids)))) {
                     continue;
                 }
                 $vs_subtype = $va_row['sub_type_left_id'];
                 $va_row['typename'] = $va_row['typename_reverse'];
                 $vs_key = strlen($va_row['rank']) > 0 ? sprintf("%08d", (int) $va_row['rank']) . preg_replace('![^A-Za-z0-9_]+!', '_', $va_row['typename_reverse']) : preg_replace('![^A-Za-z0-9_]+!', '_', $va_row['typename_reverse']);
             }
             unset($va_row['typename_reverse']);
             // we pass the typename adjusted for direction in '_display', so there's no need to include typename_reverse in the returned values
             if (!$vs_subtype) {
                 $vs_subtype = 'NULL';
             }
             $vn_type_id = $va_row['type_id'];
             $va_types[$vn_parent_id][$vs_subtype][$vs_key][$vn_type_id][$va_row['locale_id']] = $va_row;
         }
         $va_types = $this->_processRelationshipHierarchy($vn_root_id, $va_hier, $va_types, 1);
         $va_processed_types = array('_type_map' => array());
         $va_subtype_lookups = array();
         foreach ($va_types as $vs_subtype => $va_types_by_subtype) {
             $va_types_by_locale = array();
             foreach ($va_types_by_subtype as $vs_key => $va_types_by_key) {
                 foreach ($va_types_by_key as $vn_locale_id => $va_t) {
                     if (!is_array($va_types_by_locale[$vn_locale_id])) {
                         $va_types_by_locale[$vn_locale_id] = array();
                     }
                     $va_types_by_locale[$vn_locale_id] += $va_t;
                 }
             }
             if (!$vb_dont_include_subtypes_in_type_restriction) {
                 // include mapping from parent type used in restriction to child types that inherit the binding
                 if ($vs_subtype != 'NULL' && (!isset($va_subtype_lookups[$vs_subtype]) || !$va_subtype_lookups[$vs_subtype])) {
                     $va_children = $t_list_item->getHierarchyChildren($vs_subtype, array('idsOnly' => true));
                     foreach ($va_children as $vn_child) {
                         $va_processed_types['_type_map'][$vn_child] = $vs_subtype;
                     }
                     $va_subtype_lookups[$vs_subtype] = true;
                 }
             }
             $va_processed_types[$vs_subtype] = caExtractValuesByUserLocale($va_types_by_locale, null, null, array('returnList' => true));
         }
     }
     return $va_processed_types;
 }