/**
  *
  *
  * @param $ps_field -
  * @param $pa_options -
  *		returnAsArray - 
  * 		delimiter -
  *		template -
  *		locale -
  *		returnAllLocales - Returns requested value in all locales for which it is defined. Default is false. Note that this is not supported for hierarchy specifications (eg. ca_objects.hierarchy).
  *		direction - For hierarchy specifications (eg. ca_objects.hierarchy) this determines the order in which the hierarchy is returned. ASC will return the hierarchy root first while DESC will return it with the lowest node first. Default is ASC.
  *		top - For hierarchy specifications (eg. ca_objects.hierarchy) this option, if set, will limit the returned hierarchy to the first X nodes from the root down. Default is to not limit.
  *		bottom - For hierarchy specifications (eg. ca_objects.hierarchy) this option, if set, will limit the returned hierarchy to the first X nodes from the lowest node up. Default is to not limit.
  * 		hierarchicalDelimiter - Text to place between items in a hierarchy for a hierarchical specification (eg. ca_objects.hierarchy) when returning as a string
  *		removeFirstItems - If set to a non-zero value, the specified number of items at the top of the hierarchy will be omitted. For example, if set to 2, the root and first child of the hierarchy will be omitted. Default is zero (don't delete anything).
  *		checkAccess = array of access values to filter results by; if defined only items with the specified access code(s) are returned. Only supported for <table_name>.hierarchy.preferred_labels and <table_name>.children.preferred_labels because these returns sets of items. For <table_name>.parent.preferred_labels, which returns a single row at most, you should do access checking yourself. (Everything here applies equally to nonpreferred_labels)
  *		sort = optional bundles to sort returned values on. Only supported for <table_name>.children.preferred_labels. The bundle specifiers are fields with or without tablename.
  *		sort_direction = direction to sort results by, either 'asc' for ascending order or 'desc' for descending order; default is 'asc'
  *		convertCodesToDisplayText = if true then non-preferred label type_ids are automatically converted to display text in the current locale; default is false (return non-preferred label type_id raw)
  */
 public function get($ps_field, $pa_options = null)
 {
     $vs_template = isset($pa_options['template']) ? $pa_options['template'] : null;
     $vb_return_as_array = isset($pa_options['returnAsArray']) ? (bool) $pa_options['returnAsArray'] : false;
     $vb_return_all_locales = isset($pa_options['returnAllLocales']) ? (bool) $pa_options['returnAllLocales'] : false;
     $vs_delimiter = isset($pa_options['delimiter']) ? $pa_options['delimiter'] : ' ';
     $vb_convert_codes_to_display_text = isset($pa_options['convertCodesToDisplayText']) ? (bool) $pa_options['convertCodesToDisplayText'] : false;
     if ($vb_return_all_locales && !$vb_return_as_array) {
         $vb_return_as_array = true;
     }
     // if desired try to return values in a preferred language/locale
     $va_preferred_locales = null;
     if (isset($pa_options['locale']) && $pa_options['locale']) {
         $va_preferred_locales = array($pa_options['locale']);
     }
     // does get refer to an attribute?
     $va_tmp = explode('.', $ps_field);
     if ($va_tmp[1] == 'hierarchy' && sizeof($va_tmp) == 2) {
         $va_tmp[2] = 'preferred_labels';
         $ps_field = join('.', $va_tmp);
     }
     $t_label = $this->getLabelTableInstance();
     $t_instance = $this;
     if (sizeof($va_tmp) >= 3 && ($va_tmp[2] == 'preferred_labels' && (!$va_tmp[3] || $t_label->hasField($va_tmp[3]))) || $va_tmp[1] == 'hierarchy') {
         switch ($va_tmp[1]) {
             case 'parent':
                 if ($this->isHierarchical() && ($vn_parent_id = $this->get($this->getProperty('HIERARCHY_PARENT_ID_FLD')))) {
                     $t_instance = $this->getAppDatamodel()->getInstanceByTableNum($this->tableNum());
                     if (!$t_instance->load($vn_parent_id)) {
                         $t_instance = $this;
                     } else {
                         unset($va_tmp[1]);
                         $va_tmp = array_values($va_tmp);
                     }
                 }
                 break;
             case 'children':
                 if ($this->isHierarchical()) {
                     unset($va_tmp[1]);
                     // remove 'children' from field path
                     $va_tmp = array_values($va_tmp);
                     $vs_childless_path = join('.', $va_tmp);
                     $va_data = array();
                     $va_children_ids = $this->getHierarchyChildren(null, array('idsOnly' => true));
                     if (is_array($va_children_ids) && sizeof($va_children_ids)) {
                         $t_instance = $this->getAppDatamodel()->getInstanceByTableNum($this->tableNum());
                         $vb_check_access = is_array($pa_options['checkAccess']) && $t_instance->hasField('access');
                         $va_sort = isset($pa_options['sort']) ? $pa_options['sort'] : null;
                         if (!is_array($va_sort) && $va_sort) {
                             $va_sort = array($va_sort);
                         }
                         if (!is_array($va_sort)) {
                             $va_sort = array();
                         }
                         $vs_sort_direction = isset($pa_options['sort_direction']) && in_array(strtolower($pa_options['sort_direction']), array('asc', 'desc')) ? strtolower($pa_options['sort_direction']) : 'asc';
                         $qr_children = $this->makeSearchResult($this->tableName(), $va_children_ids);
                         $vs_table = $this->tableName();
                         while ($qr_children->nextHit()) {
                             if ($vb_check_access && !in_array($qr_children->get("{$vs_table}.access"), $pa_options['checkAccess'])) {
                                 continue;
                             }
                             $vs_sort_key = '';
                             foreach ($va_sort as $vs_sort) {
                                 $vs_sort_key .= $vs_sort ? $qr_children->get($vs_sort) : 0;
                             }
                             if (!is_array($va_data[$vs_sort_key])) {
                                 $va_data[$vs_sort_key] = array();
                             }
                             $va_data[$vs_sort_key] = array_merge($va_data[$vs_sort_key], $qr_children->get($vs_childless_path, array_merge($pa_options, array('returnAsArray' => true))));
                         }
                         ksort($va_data);
                         if ($vs_sort_direction && $vs_sort_direction == 'desc') {
                             $va_data = array_reverse($va_data);
                         }
                         $va_sorted_data = array();
                         foreach ($va_data as $vs_sort_key => $va_items) {
                             foreach ($va_items as $vs_k => $vs_v) {
                                 $va_sorted_data[] = $vs_v;
                             }
                         }
                         $va_data = $va_sorted_data;
                     }
                     if ($vb_return_as_array) {
                         return $va_data;
                     } else {
                         return join($vs_delimiter, $va_data);
                     }
                 }
                 break;
             case 'hierarchy':
                 $vs_direction = isset($pa_options['direction']) ? strtoupper($pa_options['direction']) : null;
                 if (!in_array($vs_direction, array('ASC', 'DESC'))) {
                     $vs_direction = 'ASC';
                 }
                 $vn_top = (int) isset($pa_options['top']) ? strtoupper($pa_options['top']) : 0;
                 if ($vn_top < 0) {
                     $vn_top = 0;
                 }
                 $vn_bottom = (int) isset($pa_options['bottom']) ? strtoupper($pa_options['bottom']) : 0;
                 if ($vn_bottom < 0) {
                     $vn_bottom = 0;
                 }
                 $vs_pk = $this->primaryKey();
                 $vs_label_table_name = $this->getLabelTableName();
                 $t_label_instance = $this->getLabelTableInstance();
                 if (!$vs_template && ($vs_display_field = $t_label_instance->hasField($va_tmp[2]) ? $t_label_instance->tableName() . "." . $va_tmp[2] : ($this->hasField($va_tmp[2]) ? $this->tableName() . "." . $va_tmp[2] : null))) {
                     $vs_template = "^{$vs_display_field}";
                 }
                 $vn_top_id = null;
                 if (!($va_ancestor_list = $this->getHierarchyAncestors(null, array('idsOnly' => true, 'includeSelf' => true)))) {
                     $va_ancestor_list = array();
                 }
                 // TODO: this should really be in a model subclass
                 if ($this->tableName() == 'ca_objects' && $this->getAppConfig()->get('ca_objects_x_collections_hierarchy_enabled') && ($vs_coll_rel_type = $this->getAppConfig()->get('ca_objects_x_collections_hierarchy_relationship_type'))) {
                     require_once __CA_MODELS_DIR__ . '/ca_objects.php';
                     if ($this->getPrimaryKey() == $vn_top_id) {
                         $t_object = $this;
                     } else {
                         $t_object = new ca_objects($vn_top_id);
                     }
                     if (is_array($va_collections = $t_object->getRelatedItems('ca_collections', array('restrictToRelationshipTypes' => $vs_coll_rel_type)))) {
                         require_once __CA_MODELS_DIR__ . '/ca_collections.php';
                         $t_collection = new ca_collections();
                         foreach ($va_collections as $vn_i => $va_collection) {
                             if ($va_collections_ancestor_list = $t_collection->getHierarchyAncestors($va_collection['collection_id'], array('idsOnly' => true, 'includeSelf' => true))) {
                                 $va_ancestor_list = array_merge($va_ancestor_list, $va_collections_ancestor_list);
                             }
                             break;
                             // for now only process first collection (no polyhierarchies)
                         }
                     }
                 }
                 // remove root and children if so desired
                 if (isset($pa_options['removeFirstItems']) && (int) $pa_options['removeFirstItems'] > 0) {
                     for ($vn_i = 0; $vn_i < (int) $pa_options['removeFirstItems']; $vn_i++) {
                         array_pop($va_ancestor_list);
                     }
                 }
                 if ($vs_display_field != $va_tmp[2]) {
                     if ($this->hasField($va_tmp[2])) {
                         $vs_display_field = $va_tmp[2];
                     }
                 }
                 $vb_check_access = is_array($pa_options['checkAccess']) && $this->hasField('access');
                 if ($vb_check_access) {
                     $va_access_values = $this->getFieldValuesForIDs($va_ancestor_list, array('access'));
                     $va_ancestor_list = array();
                     foreach ($va_access_values as $vn_ancestor_id => $vn_access_value) {
                         if (in_array($vn_access_value, $pa_options['checkAccess'])) {
                             $va_ancestor_list[] = $vn_ancestor_id;
                         }
                     }
                 }
                 if ($vs_template) {
                     $va_tmp = caProcessTemplateForIDs($vs_template, $this->tableName(), $va_ancestor_list, array('returnAsArray' => true));
                 } else {
                     $va_tmp = $this->getPreferredDisplayLabelsForIDs($va_ancestor_list, array('returnAsArray' => true, 'returnAllLocales' => $vb_return_all_locales));
                 }
                 if ($vn_top > 0) {
                     $va_tmp = array_slice($va_tmp, sizeof($va_tmp) - $vn_top, $vn_top, true);
                 } else {
                     if ($vn_bottom > 0) {
                         $va_tmp = array_slice($va_tmp, 0, $vn_bottom, true);
                     }
                 }
                 if ($vs_direction == 'ASC') {
                     $va_tmp = array_reverse($va_tmp, true);
                 }
                 if (caGetOption('returnAsLink', $pa_options, false)) {
                     $va_tmp = caCreateLinksFromText(array_values($va_tmp), $this->tableName(), array_keys($va_tmp));
                 }
                 if ($vb_return_as_array) {
                     return $va_tmp;
                 } else {
                     $vs_hier_delimiter = isset($pa_options['hierarchicalDelimiter']) ? $pa_options['hierarchicalDelimiter'] : $pa_options['delimiter'];
                     return join($vs_hier_delimiter, $va_tmp);
                 }
                 break;
         }
     }
     switch (sizeof($va_tmp)) {
         case 1:
             switch ($va_tmp[0]) {
                 # ---------------------------------------------
                 case 'preferred_labels':
                     if (!$vb_return_as_array) {
                         $va_labels = caExtractValuesByUserLocale($t_instance->getPreferredLabels(), null, $va_preferred_locales);
                         $vs_disp_field = $this->getLabelDisplayField();
                         $va_values = array();
                         foreach ($va_labels as $vn_row_id => $va_label_list) {
                             foreach ($va_label_list as $vn_i => $va_label) {
                                 if ($vs_template) {
                                     $va_values[] = caProcessTemplate($vs_template, $va_label, array('removePrefix' => 'preferred_labels.'));
                                 } else {
                                     $va_values[] = $va_label[$vs_disp_field];
                                 }
                             }
                         }
                         return join($vs_delimiter, $va_values);
                     } else {
                         $va_labels = $t_instance->getPreferredLabels(null, false);
                         if ($vb_return_all_locales) {
                             return $va_labels;
                         } else {
                             // Simplify array by getting rid of third level array which is unnecessary since
                             // there is only ever one preferred label for a locale
                             $va_labels = caExtractValuesByUserLocale($va_labels, null, $va_preferred_locales);
                             $va_processed_labels = array();
                             foreach ($va_labels as $vn_label_id => $va_label_list) {
                                 $va_processed_labels[$vn_label_id] = $va_label_list[0];
                             }
                             return $va_processed_labels;
                         }
                     }
                     break;
                     # ---------------------------------------------
                 # ---------------------------------------------
                 case 'nonpreferred_labels':
                     if (!$vb_return_as_array) {
                         $vs_disp_field = $this->getLabelDisplayField();
                         $va_labels = caExtractValuesByUserLocale($t_instance->getNonPreferredLabels(), null, $va_preferred_locales);
                         $t_list = new ca_lists();
                         if ($vb_convert_codes_to_display_text) {
                             $va_types = $t_list->getItemsForList($this->getLabelTableInstance()->getFieldInfo('type_id', 'LIST_CODE'), array('extractValuesByUserLocale' => true));
                         }
                         $va_values = array();
                         foreach ($va_labels as $vn_row_id => $va_label_list) {
                             foreach ($va_label_list as $vn_i => $va_label) {
                                 if ($vs_template) {
                                     $va_label_values = $va_label;
                                     $va_label_values['typename_singular'] = $va_types[$va_label['type_id']]['name_singular'];
                                     $va_label_values['typename_plural'] = $va_types[$va_label['type_id']]['name_plural'];
                                     if ($vb_convert_codes_to_display_text) {
                                         $va_label_values['type_id'] = $va_types[$va_label['type_id']]['name_singular'];
                                     }
                                     $va_values[] = caProcessTemplate($vs_template, $va_label_values, array('removePrefix' => 'nonpreferred_labels.'));
                                 } else {
                                     if ($vb_convert_codes_to_display_text && $vs_disp_field == 'type_id') {
                                         $va_values[] = $va_types[$va_label[$vs_disp_field]]['name_singular'];
                                     } else {
                                         $va_values[] = $va_label[$vs_disp_field];
                                     }
                                 }
                             }
                         }
                         return join($vs_delimiter, $va_values);
                         $va_labels = caExtractValuesByUserLocale($t_instance->getNonPreferredLabels(null, false));
                         $vs_disp_field = $this->getLabelDisplayField();
                         $va_processed_labels = array();
                         foreach ($va_labels as $vn_label_id => $va_label_list) {
                             foreach ($va_label_list as $vn_i => $va_label) {
                                 $va_processed_labels[] = $va_label[$vs_disp_field];
                             }
                         }
                         return join($vs_delimiter, $va_processed_labels);
                     } else {
                         $va_labels = $t_instance->getNonPreferredLabels(null, false);
                         if ($vb_return_all_locales) {
                             return $va_labels;
                         } else {
                             return caExtractValuesByUserLocale($va_labels, null, $va_preferred_locales);
                         }
                     }
                     break;
                     # ---------------------------------------------
             }
             break;
         case 2:
         case 3:
             if ($va_tmp[0] === $t_instance->tableName()) {
                 switch ($va_tmp[1]) {
                     # ---------------------------------------------
                     case 'preferred_labels':
                         if (!$vb_return_as_array) {
                             if (isset($va_tmp[2]) && $va_tmp[2]) {
                                 $vs_disp_field = $va_tmp[2];
                             } else {
                                 $vs_disp_field = $this->getLabelDisplayField();
                             }
                             $va_labels = caExtractValuesByUserLocale($t_instance->getPreferredLabels(), null, $va_preferred_locales);
                             $va_values = array();
                             foreach ($va_labels as $vn_row_id => $va_label_list) {
                                 foreach ($va_label_list as $vn_i => $va_label) {
                                     if ($vs_template) {
                                         $va_values[] = caProcessTemplate($vs_template, $va_label, array('removePrefix' => 'preferred_labels.'));
                                     } else {
                                         $va_values[] = $va_label[$vs_disp_field];
                                     }
                                 }
                             }
                             return join($vs_delimiter, $va_values);
                         } else {
                             $va_labels = $t_instance->getPreferredLabels(null, false);
                             if (!$vb_return_all_locales) {
                                 // Simplify array by getting rid of third level array which is unnecessary since
                                 // there is only ever one preferred label for a locale
                                 $va_labels = caExtractValuesByUserLocale($va_labels, null, $va_preferred_locales);
                                 $va_processed_labels = array();
                                 foreach ($va_labels as $vn_label_id => $va_label_list) {
                                     $va_processed_labels[$vn_label_id] = $va_label_list[0];
                                 }
                                 $va_labels = $va_processed_labels;
                             }
                             if (isset($va_tmp[2]) && $va_tmp[2]) {
                                 // specific field
                                 if ($vb_return_all_locales) {
                                     foreach ($va_labels as $vn_label_id => $va_labels_by_locale) {
                                         foreach ($va_labels_by_locale as $vn_locale_id => $va_label_list) {
                                             foreach ($va_label_list as $vn_i => $va_label) {
                                                 $va_labels[$vn_label_id][$vn_locale_id][$vn_i] = $va_label[$va_tmp[2]];
                                             }
                                         }
                                     }
                                 } else {
                                     // get specified field value
                                     foreach ($va_labels as $vn_label_id => $va_label_info) {
                                         $va_labels[$vn_label_id] = $va_label_info[$va_tmp[2]];
                                     }
                                 }
                             }
                             return $va_labels;
                         }
                         break;
                         # ---------------------------------------------
                     # ---------------------------------------------
                     case 'nonpreferred_labels':
                         if (!$vb_return_as_array) {
                             if (isset($va_tmp[2]) && $va_tmp[2]) {
                                 $vs_disp_field = $va_tmp[2];
                             } else {
                                 $vs_disp_field = $this->getLabelDisplayField();
                             }
                             $va_labels = caExtractValuesByUserLocale($t_instance->getNonPreferredLabels(), null, $va_preferred_locales);
                             $t_list = new ca_lists();
                             if ($vb_convert_codes_to_display_text) {
                                 $va_types = $t_list->getItemsForList($this->getLabelTableInstance()->getFieldInfo('type_id', 'LIST_CODE'), array('extractValuesByUserLocale' => true));
                             }
                             $va_values = array();
                             foreach ($va_labels as $vn_row_id => $va_label_list) {
                                 foreach ($va_label_list as $vn_i => $va_label) {
                                     if ($vs_template) {
                                         $va_label_values = $va_label;
                                         $va_label_values['typename_singular'] = $va_types[$va_label['type_id']]['name_singular'];
                                         $va_label_values['typename_plural'] = $va_types[$va_label['type_id']]['name_plural'];
                                         if ($vb_convert_codes_to_display_text) {
                                             $va_label_values['type_id'] = $va_types[$va_label['type_id']]['name_singular'];
                                         }
                                         $va_values[] = caProcessTemplate($vs_template, $va_label_values, array('removePrefix' => 'nonpreferred_labels.'));
                                     } else {
                                         if ($vb_convert_codes_to_display_text && $vs_disp_field == 'type_id') {
                                             $va_values[] = $va_types[$va_label[$vs_disp_field]]['name_singular'];
                                         } else {
                                             $va_values[] = $va_label[$vs_disp_field];
                                         }
                                     }
                                 }
                             }
                             return join($vs_delimiter, $va_values);
                         } else {
                             $va_labels = $t_instance->getNonPreferredLabels(null, false);
                             if (!$vb_return_all_locales) {
                                 $va_labels = caExtractValuesByUserLocale($va_labels, null, $va_preferred_locales);
                             }
                             if (isset($va_tmp[2]) && $va_tmp[2]) {
                                 // specific field
                                 if ($vb_return_all_locales) {
                                     foreach ($va_labels as $vn_label_id => $va_labels_by_locale) {
                                         foreach ($va_labels_by_locale as $vn_locale_id => $va_label_list) {
                                             foreach ($va_label_list as $vn_i => $va_label) {
                                                 $va_labels[$vn_label_id][$vn_locale_id][$vn_i] = $va_label[$va_tmp[2]];
                                             }
                                         }
                                     }
                                 } else {
                                     // get specified field value
                                     foreach ($va_labels as $vn_label_id => $va_label_info) {
                                         foreach ($va_label_info as $vn_id => $va_label) {
                                             $va_labels[$vn_label_id] = $va_label[$va_tmp[2]];
                                         }
                                     }
                                 }
                             }
                             return $va_labels;
                         }
                         break;
                         # ---------------------------------------------
                 }
             }
             break;
     }
     return parent::get($ps_field, $pa_options);
 }
Ejemplo n.º 2
0
/**
 * Replace "^" prefixed tags (eg. ^forename) in a template with values from an array
 *
 * @param string $ps_template String with embedded tags. Tags are just alphanumeric strings prefixed with a caret ("^")
 * @param string $pm_tablename_or_num Table name or number of table from which values are being formatted
 * @param string $pa_row_ids An array of primary key values in the specified table to be pulled into the template
 * @param array $pa_options Supported options are:
 *		returnAsArray = if true an array of processed template values is returned, otherwise the template values are returned as a string joined together with a delimiter. Default is false.
 *		delimiter = value to string together template values with when returnAsArray is false. Default is ';' (semicolon)
 *		relatedValues = array of field values to return in template when directly referenced. Array should be indexed numerically in parallel with $pa_row_ids
 *		relationshipValues = array of field values to return in template for relationship when directly referenced. Should be indexed by row_id and then by relation_id
 *		placeholderPrefix = attribute container to implicitly place primary record fields into. Ex. if the table is "ca_entities" and the placeholder is "address" then tags like ^city will resolve to ca_entities.address.city
 *		requireLinkTags = if set then links are only added when explicitly defined with <l> tags. Default is to make the entire text a link in the absence of <l> tags.
 *		resolveLinksUsing = 
 *		primaryIDs = row_ids for primary rows in related table, keyed by table name; when resolving ambiguous relationships the row_ids will be excluded from consideration. This option is rarely used and exists primarily to take care of a single
 *						edge case: you are processing a template relative to a self-relationship such as ca_entities_x_entities that includes references to the subject table (ca_entities, in the case of ca_entities_x_entities). There are
 *						two possible paths to take in this situations; primaryIDs lets you specify which ones you *don't* want to take by row_id. For interstitial editors, the ids will be set to a single id: that of the subject (Eg. ca_entities) row
 *						from which the interstitial was launched.
 *		sort = optional list of tag values to sort repeating values within a row template on. The tag must appear in the template. You can specify more than one tag by separating the tags with semicolons.
 *		sortDirection = The direction of the sort of repeating values within a row template. May be either ASC (ascending) or DESC (descending). [Default is ASC]
 *		linkTarget = Optional target to use when generating <l> tag-based links. By default links point to standard detail pages, but plugins may define linkTargets that point elsewhere.
 * 		skipIfExpression = skip the elements in $pa_row_ids for which the given expression does not evaluate true
 *		includeBlankValuesInArray = include blank template values in returned array when returnAsArray is set. If you need the returned array of values to line up with the row_ids in $pa_row_ids this should be set. [Default is false]
 *
 * @return mixed Output of processed templates
 */
function caProcessTemplateForIDs($ps_template, $pm_tablename_or_num, $pa_row_ids, $pa_options = null)
{
    foreach (array('request', 'template', 'restrictToTypes', 'restrict_to_types', 'restrict_to_relationship_types', 'restrictToRelationshipTypes', 'useLocaleCodes') as $vs_k) {
        unset($pa_options[$vs_k]);
    }
    if (!isset($pa_options['convertCodesToDisplayText'])) {
        $pa_options['convertCodesToDisplayText'] = true;
    }
    $pb_return_as_array = (bool) caGetOption('returnAsArray', $pa_options, false);
    if (($pa_sort = caGetOption('sort', $pa_options, null)) && !is_array($pa_sort)) {
        $pa_sort = explode(";", $pa_sort);
    }
    $ps_sort_direction = caGetOption('sortDirection', $pa_options, null, array('forceUppercase' => true));
    if (!in_array($ps_sort_direction, array('ASC', 'DESC'))) {
        $ps_sort_direction = 'ASC';
    }
    $pa_check_access = caGetOption('checkAccess', $pa_options, null);
    if (!is_array($pa_row_ids) || !sizeof($pa_row_ids) || !$ps_template) {
        return $pb_return_as_array ? array() : "";
    }
    unset($pa_options['returnAsArray']);
    if (!isset($pa_options['requireLinkTags'])) {
        $pa_options['requireLinkTags'] = true;
    }
    $ps_skip_if_expression = caGetOption('skipIfExpression', $pa_options, false);
    $va_primary_ids = caGetOption("primaryIDs", $pa_options, null);
    $o_dm = Datamodel::load();
    $ps_tablename = is_numeric($pm_tablename_or_num) ? $o_dm->getTableName($pm_tablename_or_num) : $pm_tablename_or_num;
    $ps_resolve_links_using = caGetOption('resolveLinksUsing', $pa_options, $ps_tablename);
    $t_instance = $o_dm->getInstanceByTableName($ps_tablename, true);
    if ($ps_resolve_links_using != $ps_tablename) {
        $t_resolve_links_instance = $o_dm->getInstanceByTableName($ps_resolve_links_using, true);
        $vs_resolve_links_using_pk = $t_resolve_links_instance->primaryKey();
    }
    $vs_pk = $t_instance->primaryKey();
    $vs_delimiter = isset($pa_options['delimiter']) ? $pa_options['delimiter'] : '; ';
    $ps_template = str_replace("^_parent", "^{$ps_resolve_links_using}.parent.preferred_labels", $ps_template);
    $ps_template = str_replace("^_hierarchy", "^{$ps_resolve_links_using}._hierarchyName", $ps_template);
    $va_related_values = isset($pa_options['relatedValues']) && is_array($pa_options['relatedValues']) ? $pa_options['relatedValues'] : array();
    $va_relationship_values = isset($pa_options['relationshipValues']) && is_array($pa_options['relationshipValues']) ? $pa_options['relationshipValues'] : array();
    $o_doc = str_get_dom($ps_template);
    // parse template
    $ps_template = str_replace("<~root~>", "", str_replace("</~root~>", "", $o_doc->html()));
    // replace template with parsed version; this allows us to do text find/replace later
    // Parse units from template
    $o_units = $o_doc('unit');
    // only process non-nested <unit> tags
    $va_units = array();
    $vn_unit_id = 1;
    foreach ($o_units as $o_unit) {
        if (!$o_unit) {
            continue;
        }
        $vs_html = str_replace("<~root~>", "", str_replace("</~root~>", "", $o_unit->html()));
        $vs_content = $o_unit->getInnerText();
        // is this nested in another unit? We skip these
        foreach ($va_units as $va_tmp) {
            if (strpos($va_tmp['directive'], $vs_html) !== false) {
                continue 2;
            }
        }
        $va_units[] = $va_unit = array('tag' => $vs_unit_tag = "[[#{$vn_unit_id}]]", 'directive' => $vs_html, 'content' => $vs_content, 'relativeTo' => (string) $o_unit->getAttribute("relativeto"), 'delimiter' => ($vs_d = (string) $o_unit->getAttribute("delimiter")) ? $vs_d : null, 'restrictToTypes' => (string) $o_unit->getAttribute("restricttotypes"), 'restrictToRelationshipTypes' => (string) $o_unit->getAttribute("restricttorelationshiptypes"), 'sort' => explode(";", $o_unit->getAttribute("sort")), 'sortDirection' => (string) $o_unit->getAttribute("sortDirection"), 'skipIfExpression' => (string) $o_unit->getAttribute("skipIfExpression"));
        $ps_template = str_ireplace($va_unit['directive'], $vs_unit_tag, $ps_template);
        $vn_unit_id++;
    }
    $o_doc = str_get_dom($ps_template);
    // parse template again with units replaced by unit tags in the format [[#X]]
    $ps_template = str_replace("<~root~>", "", str_replace("</~root~>", "", $o_doc->html()));
    // replace template with parsed version; this allows us to do text find/replace later
    $va_tags = array();
    if (preg_match_all(__CA_BUNDLE_DISPLAY_TEMPLATE_TAG_REGEX__, $ps_template, $va_matches)) {
        $va_tags = $va_matches[1];
    }
    $va_directive_tags = array();
    $va_directive_tag_vals = array();
    $qr_res = caMakeSearchResult($ps_tablename, $pa_row_ids);
    if (!$qr_res) {
        return '';
    }
    $va_proc_templates = array();
    $vn_i = 0;
    $o_ifs = $o_doc("if");
    // if
    $o_ifdefs = $o_doc("ifdef");
    // if defined
    $o_ifnotdefs = $o_doc("ifnotdef");
    // if not defined
    $o_mores = $o_doc("more");
    // more tags - content suppressed if there are no defined values following the tag pair
    $o_betweens = $o_doc("between");
    // between tags - content suppressed if there are not defined values on both sides of the tag pair
    $va_if = array();
    foreach ($o_ifs as $o_if) {
        if (!$o_if) {
            continue;
        }
        $vs_html = $o_if->html();
        $vs_content = $o_if->getInnerText();
        $va_if[] = array('directive' => $vs_html, 'content' => $vs_content, 'rule' => $vs_rule = (string) $o_if->getAttribute('rule'));
    }
    foreach ($o_ifdefs as $o_ifdef) {
        if (!$o_ifdef) {
            continue;
        }
        $vs_code = (string) $o_ifdef->getAttribute('code');
        $vs_code_proc = preg_replace("!%(.*)\$!", '', $vs_code);
        $va_directive_tags = array_merge($va_directive_tags, preg_split('![,\\|]{1}!', $vs_code_proc));
    }
    foreach ($o_ifnotdefs as $o_ifnotdef) {
        if (!$o_ifnotdef) {
            continue;
        }
        $vs_code = (string) $o_ifnotdef->getAttribute('code');
        $vs_code_proc = preg_replace("!%(.*)\$!", '', $vs_code);
        $va_directive_tags = array_merge($va_directive_tags, preg_split('![,\\|]{1}!', $vs_code_proc));
    }
    $va_mores = array();
    foreach ($o_mores as $o_more) {
        if (!$o_more) {
            continue;
        }
        $vs_html = str_replace("<~root~>", "", str_replace("</~root~>", "", $o_more->html()));
        $vs_content = $o_more->getInnerText();
        $va_mores[] = array('directive' => $vs_html, 'content' => $vs_content);
    }
    $va_betweens = array();
    foreach ($o_betweens as $o_between) {
        if (!$o_between) {
            continue;
        }
        $vs_html = str_replace("<~root~>", "", str_replace("</~root~>", "", $o_between->html()));
        $vs_content = $o_between->getInnerText();
        $va_betweens[] = array('directive' => $vs_html, 'content' => $vs_content);
    }
    $va_resolve_links_using_row_ids = array();
    $va_tag_val_list = $va_defined_tag_list = array();
    $va_expression_vars = array();
    /** @var $qr_res SearchResult */
    while ($qr_res->nextHit()) {
        $vs_pk_val = $qr_res->get($vs_pk, array('checkAccess' => $pa_check_access));
        if (is_array($pa_check_access) && sizeof($pa_check_access) && !in_array($qr_res->get("{$ps_tablename}.access"), $pa_check_access)) {
            continue;
        }
        $vs_template = $ps_template;
        // check if we skip this row because of skipIfExpression
        if (strlen($ps_skip_if_expression) > 0) {
            $va_expression_tags = caGetTemplateTags($ps_skip_if_expression);
            foreach ($va_expression_tags as $vs_expression_tag) {
                if (!isset($va_expression_vars[$vs_expression_tag])) {
                    $va_expression_vars[$vs_expression_tag] = $qr_res->get($vs_expression_tag, array('assumeDisplayField' => true, 'returnIdno' => true, 'delimiter' => ';'));
                }
            }
            if (ExpressionParser::evaluate($ps_skip_if_expression, $va_expression_vars)) {
                continue;
            }
        }
        // Grab values for codes used in ifdef and ifnotdef directives
        $va_directive_tag_vals = array();
        foreach ($va_directive_tags as $vs_directive_tag) {
            $va_directive_tag_vals[$vs_directive_tag] = $qr_res->get($vs_directive_tag, array('assumeDisplayField' => true, 'convertCodesToDisplayText' => true, 'dontUseElementTemplate' => true));
        }
        $o_parsed_template = str_get_dom($vs_template);
        while (sizeof($vo_templates = $o_parsed_template('ifcount:not(:has(ifdef,ifndef,ifcount)),ifdef:not(:has(ifdef,ifndef,ifcount)),ifndef:not(:has(ifdef,ifndef,ifcount))')) > 0) {
            foreach ($vo_templates as $vn_index => $vo_element) {
                $vs_code = $vo_element->code;
                switch ($vo_element->tag) {
                    case 'ifdef':
                        if (strpos($vs_code, "|") !== false) {
                            $vs_bool = 'OR';
                            $va_tag_list = explode("|", $vs_code);
                            $vb_output = false;
                        } else {
                            $vs_bool = 'AND';
                            $va_tag_list = explode(",", $vs_code);
                            $vb_output = true;
                        }
                        foreach ($va_tag_list as $vs_tag_to_test) {
                            $vs_tag_to_test = preg_replace("!%.*\$!", "", $vs_tag_to_test);
                            $vb_value_is_set = isset($va_directive_tag_vals[$vs_tag_to_test]) && strlen($va_directive_tag_vals[$vs_tag_to_test]) > 1;
                            switch ($vs_bool) {
                                case 'OR':
                                    if ($vb_value_is_set) {
                                        $vb_output = true;
                                        break 2;
                                    }
                                    // any must be defined; if any is defined output
                                    break;
                                case 'AND':
                                default:
                                    if (!$vb_value_is_set) {
                                        $vb_output = false;
                                        break 2;
                                    }
                                    // all must be defined; if any is not defined don't output
                                    break;
                            }
                        }
                        if ($vb_output) {
                            $vs_template = str_replace($vo_element->html(), $vo_element->getInnerText(), $vs_template);
                        } else {
                            $vs_template = str_replace($vo_element->html(), '', $vs_template);
                        }
                        break;
                    case 'ifndef':
                        if (strpos($vs_code, "|") !== false) {
                            $vs_bool = 'OR';
                            $va_tag_list = explode("|", $vs_code);
                            $vb_output = false;
                        } else {
                            $vs_bool = 'AND';
                            $va_tag_list = explode(",", $vs_code);
                            $vb_output = true;
                        }
                        $vb_output = true;
                        foreach ($va_tag_list as $vs_tag_to_test) {
                            $vb_value_is_set = (bool) (isset($va_directive_tag_vals[$vs_tag_to_test]) && strlen($va_directive_tag_vals[$vs_tag_to_test]) > 0);
                            switch ($vs_bool) {
                                case 'OR':
                                    if (!$vb_value_is_set) {
                                        $vb_output = true;
                                        break 2;
                                    }
                                    // any must be not defined; if anything is not set output
                                    break;
                                case 'AND':
                                default:
                                    if ($vb_value_is_set) {
                                        $vb_output = false;
                                        break 2;
                                    }
                                    // all must be not defined; if anything is set don't output
                                    break;
                            }
                        }
                        if ($vb_output) {
                            $vs_template = str_replace($vo_element->html(), $vo_element->getInnerText(), $vs_template);
                        } else {
                            $vs_template = str_replace($vo_element->html(), '', $vs_template);
                        }
                        break;
                    case 'ifcount':
                        if (is_array($va_if_codes = preg_split("![\\|,;]+!", $vs_code))) {
                            $vn_min = (int) $vo_element->min;
                            $vn_max = (int) $vo_element->max;
                            $va_restrict_to_types = preg_split("![,; ]+!", $vo_element->restrictToTypes);
                            $va_restrict_to_relationship_types = preg_split("![,; ]+!", $vo_element->restrictToRelationshipTypes);
                            $vn_count = 0;
                            foreach ($va_if_codes as $vs_if_code) {
                                if ($t_table = $o_dm->getInstanceByTableName($vs_if_code, true)) {
                                    $va_count_vals = $qr_res->get($vs_if_code . "." . $t_table->primaryKey(), array('restrictToTypes' => $va_restrict_to_types, 'restrictToRelationshipTypes' => $va_restrict_to_relationship_types, 'returnAsArray' => true, 'checkAccess' => $pa_check_access));
                                } else {
                                    $va_count_vals = $qr_res->get($vs_if_code, array('returnAsArray' => true, 'restrictToTypes' => $va_restrict_to_types, 'restrictToRelationshipTypes' => $va_restrict_to_relationship_types, 'checkAccess' => $pa_check_access));
                                }
                                if (is_array($va_count_vals)) {
                                    $va_bits = explode(".", $vs_if_code);
                                    $vs_fld = array_pop($va_bits);
                                    foreach ($va_count_vals as $vs_count_val) {
                                        if (is_array($vs_count_val)) {
                                            if (isset($vs_count_val[$vs_fld]) && !trim($vs_count_val[$vs_fld])) {
                                                continue;
                                            }
                                            $vb_is_set = false;
                                            foreach ($vs_count_val as $vs_f => $vs_v) {
                                                if (trim($vs_v)) {
                                                    $vb_is_set = true;
                                                    break;
                                                }
                                            }
                                            if (!$vb_is_set) {
                                                continue;
                                            }
                                        } else {
                                            if (!trim($vs_count_val)) {
                                                continue;
                                            }
                                        }
                                        $vn_count++;
                                    }
                                }
                            }
                            if ($vn_min <= $vn_count && ($vn_max >= $vn_count || !$vn_max)) {
                                $vs_template = str_replace($vo_element->html(), $vo_element->getInnerText(), $vs_template);
                            } else {
                                $vs_template = str_replace($vo_element->html(), '', $vs_template);
                            }
                        }
                        break;
                }
            }
            $o_parsed_template = str_get_dom($vs_template);
            // reparse
        }
        $va_proc_templates[$vn_i] = $vs_template;
        foreach ($va_units as $k => $va_unit) {
            if (!$va_unit['content']) {
                continue;
            }
            $va_relative_to_tmp = $va_unit['relativeTo'] ? explode(".", $va_unit['relativeTo']) : array($ps_tablename);
            if (!($t_rel_instance = $o_dm->getInstanceByTableName($va_relative_to_tmp[0], true))) {
                continue;
            }
            $vs_unit_delimiter = caGetOption('delimiter', $va_unit, $vs_delimiter);
            $vs_unit_skip_if_expression = caGetOption('skipIfExpression', $va_unit, false);
            // additional get options for pulling related records
            $va_get_options = array('returnAsArray' => true, 'checkAccess' => $pa_check_access);
            if ($va_unit['restrictToTypes'] && strlen($va_unit['restrictToTypes']) > 0) {
                $va_get_options['restrictToTypes'] = preg_split('![\\|,;]+!', $va_unit['restrictToTypes']);
            }
            if ($va_unit['restrictToRelationshipTypes'] && strlen($va_unit['restrictToRelationshipTypes']) > 0) {
                $va_get_options['restrictToRelationshipTypes'] = preg_split('![\\|,;]+!', $va_unit['restrictToRelationshipTypes']);
            }
            if ($va_unit['sort'] && is_array($va_unit['sort'])) {
                $va_get_options['sort'] = $va_unit['sort'];
                $va_get_options['sortDirection'] = $va_unit['sortDirection'];
            }
            if (sizeof($va_relative_to_tmp) == 1 && $va_relative_to_tmp[0] == $ps_tablename || sizeof($va_relative_to_tmp) >= 1 && $va_relative_to_tmp[0] == $ps_tablename && $va_relative_to_tmp[1] != 'related') {
                switch (strtolower($va_relative_to_tmp[1])) {
                    case 'hierarchy':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".hierarchy." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'parent':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".parent." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'children':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".children." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    default:
                        $va_relative_ids = array($vs_pk_val);
                        break;
                }
                // process template for all records selected by unit tag
                $va_tmpl_val = caProcessTemplateForIDs($va_unit['content'], $va_relative_to_tmp[0], $va_relative_ids, array_merge($pa_options, array('sort' => $va_get_options['sort'], 'sortDirection' => $va_get_options['sortDirection'], 'returnAsArray' => true, 'delimiter' => $vs_unit_delimiter, 'resolveLinksUsing' => null, 'skipIfExpression' => $vs_unit_skip_if_expression)));
                $va_proc_templates[$vn_i] = str_ireplace($va_unit['tag'], join($vs_unit_delimiter, $va_tmpl_val), $va_proc_templates[$vn_i]);
            } else {
                switch (strtolower($va_relative_to_tmp[1])) {
                    case 'hierarchy':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".hierarchy." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'parent':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".parent." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'children':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".children." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'related':
                        $va_relative_ids = $qr_res->get($t_rel_instance->tableName() . ".related." . $t_rel_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    default:
                        if (method_exists($t_instance, 'isSelfRelationship') && $t_instance->isSelfRelationship()) {
                            $va_relative_ids = array_values($t_instance->getRelatedIDsForSelfRelationship($va_primary_ids[$t_rel_instance->tableName()], array($vs_pk_val)));
                        } else {
                            $va_relative_ids = array_values($qr_res->get($t_rel_instance->tableName() . "." . $t_rel_instance->primaryKey(), $va_get_options));
                        }
                        break;
                }
                $vs_tmpl_val = caProcessTemplateForIDs($va_unit['content'], $va_relative_to_tmp[0], $va_relative_ids, array_merge($pa_options, array('sort' => $va_unit['sort'], 'sortDirection' => $va_unit['sortDirection'], 'delimiter' => $vs_unit_delimiter, 'resolveLinksUsing' => null, 'skipIfExpression' => $vs_unit_skip_if_expression)));
                $va_proc_templates[$vn_i] = str_ireplace($va_unit['tag'], $vs_tmpl_val, $va_proc_templates[$vn_i]);
            }
        }
        if (!strlen(trim($va_proc_templates[$vn_i]))) {
            $va_proc_templates[$vn_i] = null;
        }
        if (!sizeof($va_tags)) {
            $vn_i++;
            continue;
        }
        // if there are no tags in the template then we don't need to process further
        if ($ps_resolve_links_using !== $ps_tablename) {
            $va_resolve_links_using_row_ids += $qr_res->get("{$ps_resolve_links_using}.{$vs_resolve_links_using_pk}", array('returnAsArray' => true, 'checkAccess' => $pa_check_access));
            // we need to remove "primary_ids" from the list, since for self-relations these will be the side(s) of the relations we're viewing *from*
            if (is_array($va_primary_ids[$ps_resolve_links_using]) && sizeof($va_primary_ids[$ps_resolve_links_using])) {
                $va_resolve_links_using_row_ids = array_values(array_diff($va_resolve_links_using_row_ids, $va_primary_ids[$ps_resolve_links_using]));
            }
        }
        $va_tag_val_list[$vn_i] = array();
        $va_defined_tag_list[$vn_i] = array();
        $va_tag_opts = $va_tag_filters = array();
        foreach ($va_tags as $vs_tag) {
            $va_tmp = explode('.', $vs_tag);
            $vs_last_element = $va_tmp[sizeof($va_tmp) - 1];
            $va_tag_opt_tmp = explode("%", $vs_last_element);
            if (sizeof($va_tag_opt_tmp) > 1) {
                $vs_tag_bit = array_shift($va_tag_opt_tmp);
                // get rid of getspec
                foreach ($va_tag_opt_tmp as $vs_tag_opt_raw) {
                    if (preg_match("!^\\[([^\\]]+)\\]\$!", $vs_tag_opt_raw, $va_matches)) {
                        if (sizeof($va_filter = explode("=", $va_matches[1])) == 2) {
                            $va_tag_filters[$va_filter[0]] = $va_filter[1];
                        }
                        continue;
                    }
                    $va_tag_tmp = explode("=", $vs_tag_opt_raw);
                    $va_tag_tmp[0] = trim($va_tag_tmp[0]);
                    $va_tag_tmp[1] = trim($va_tag_tmp[1]);
                    if (in_array($va_tag_tmp[0], array('delimiter', 'hierarchicalDelimiter'))) {
                        $va_tag_tmp[1] = str_replace("_", " ", $va_tag_tmp[1]);
                    }
                    if (sizeof($va_tag_line_tmp = explode("|", $va_tag_tmp[1])) > 1) {
                        $va_tag_opts[trim($va_tag_tmp[0])] = $va_tag_line_tmp;
                    } else {
                        $va_tag_opts[trim($va_tag_tmp[0])] = $va_tag_tmp[1];
                    }
                }
                $va_tmp[sizeof($va_tmp) - 1] = $vs_tag_bit;
                // remove option from tag-part array
                $vs_tag_proc = join(".", $va_tmp);
                $va_proc_templates[$vn_i] = str_replace($vs_tag, $vs_tag_proc, $va_proc_templates[$vn_i]);
                $vs_tag = $vs_tag_proc;
            }
            switch ($vs_tag) {
                case 'DATE':
                    $vs_format = urldecode(caGetOption('format', $va_tag_opts, 'd M Y'));
                    $va_proc_templates[$vn_i] = str_replace("^{$vs_tag}", date($vs_format), $va_proc_templates[$vn_i]);
                    continue 2;
                    break;
            }
            $pa_options = array_merge($pa_options, $va_tag_opts);
            // Default label tag to hierarchies
            if (isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels'] && $vs_tag == 'label') {
                unset($va_related_values[$vs_pk_val][$vs_tag]);
                unset($va_relationship_values[$vs_pk_val][$vs_tag]);
                $va_tmp = array($ps_tablename, 'hierarchy', 'preferred_labels');
            }
            if (!isset($va_relationship_values[$vs_pk_val])) {
                $va_relationship_values[$vs_pk_val] = array(0 => null);
            }
            foreach ($va_relationship_values[$vs_pk_val] as $vn_relation_id => $va_relationship_value_array) {
                $vb_is_related = false;
                $va_val = null;
                if (isset($va_relationship_value_array[$vs_tag]) && !(isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels'] && $vs_tag == 'label')) {
                    $va_val = array($vs_val = $va_relationship_value_array[$vs_tag]);
                } elseif (isset($va_relationship_value_array[$vs_tag])) {
                    $va_val = array($vs_val = $va_relationship_value_array[$vs_tag]);
                } else {
                    if (isset($va_related_values[$vs_pk_val][$vs_tag])) {
                        $va_val = array($vs_val = $va_related_values[$vs_pk_val][$vs_tag]);
                    } else {
                        //
                        // see if this is a reference to a related table
                        //
                        if (in_array($vs_tag, array("relationship_typename", "relationship_type_id", "relationship_typecode", "relationship_type_code"))) {
                            $vb_is_related = true;
                            switch ($vs_tag) {
                                case 'relationship_typename':
                                    $vs_spec = 'preferred_labels.' . (caGetOption('orientation', $pa_options, 'LTOR') == 'LTOR' ? 'typename' : 'typename_reverse');
                                    break;
                                case 'relationship_type_id':
                                    $vs_spec = 'type_id';
                                    break;
                                case 'relationship_typecode':
                                case 'relationship_type_code':
                                default:
                                    $vs_spec = 'type_code';
                                    break;
                            }
                            $vs_rel = $qr_res->get("ca_relationship_types.{$vs_spec}", array_merge($pa_options, $va_tag_opts, array('returnAsArray' => false)));
                            $va_val = array($vs_rel);
                        } elseif ($ps_tablename != $va_tmp[0] && ($t_tmp = $o_dm->getInstanceByTableName($va_tmp[0], true))) {
                            // if the part of the tag before a "." (or the tag itself if there are no periods) is a related table then try to fetch it as related to the current record
                            if (isset($pa_options['placeholderPrefix']) && $pa_options['placeholderPrefix'] && $va_tmp[0] != $pa_options['placeholderPrefix'] && sizeof($va_tmp) == 1) {
                                $vs_get_spec = array_shift($va_tmp) . "." . $pa_options['placeholderPrefix'];
                                if (sizeof($va_tmp) > 0) {
                                    $vs_get_spec .= "." . join(".", $va_tmp);
                                }
                            } else {
                                $vs_get_spec = $vs_tag;
                            }
                            $va_spec_bits = explode(".", $vs_get_spec);
                            if (sizeof($va_spec_bits) == 1 && $o_dm->getTableNum($va_spec_bits[0])) {
                                $vs_get_spec .= ".preferred_labels";
                            }
                            $va_additional_options = array('returnAsArray' => true, 'checkAccess' => $pa_check_access);
                            $vs_hierarchy_name = null;
                            $vb_is_hierarchy = false;
                            if (in_array($va_spec_bits[1], array('hierarchy', '_hierarchyName'))) {
                                $t_rel = $o_dm->getInstanceByTableName($va_spec_bits[0], true);
                                switch ($t_rel->getProperty('HIERARCHY_TYPE')) {
                                    case __CA_HIER_TYPE_SIMPLE_MONO__:
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                    case __CA_HIER_TYPE_MULTI_MONO__:
                                        $vs_hierarchy_name = $t_rel->getHierarchyName($qr_res->get($t_rel->tableName() . "." . $t_rel->primaryKey(), array('checkAccess' => $pa_check_access)));
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                }
                            }
                            if ($va_spec_bits[1] != '_hierarchyName') {
                                $va_val = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_additional_options, array('returnWithStructure' => true, 'returnBlankValues' => true, 'returnAllLocales' => true, 'useLocaleCodes' => false, 'filters' => $va_tag_filters, 'primaryIDs' => $va_primary_ids)));
                            } else {
                                $va_val = array();
                            }
                            if (is_array($va_primary_ids) && isset($va_primary_ids[$va_spec_bits[0]]) && is_array($va_primary_ids[$va_spec_bits[0]])) {
                                foreach ($va_primary_ids[$va_spec_bits[0]] as $vn_primary_id) {
                                    unset($va_val[$vn_primary_id]);
                                }
                            }
                            if ($va_spec_bits[1] !== 'hierarchy') {
                                $va_val = caExtractValuesByUserLocale($va_val);
                                $va_val_tmp = array();
                                foreach ($va_val as $vn_d => $va_vals) {
                                    if (is_array($va_vals)) {
                                        $va_val_tmp = array_merge($va_val_tmp, array_values($va_vals));
                                    } else {
                                        $va_val_tmp[] = $va_vals;
                                    }
                                }
                                $va_val = $va_val_tmp;
                            }
                            $va_val_proc = array();
                            switch ($va_spec_bits[1]) {
                                case '_hierarchyName':
                                    if ($vs_hierarchy_name) {
                                        $va_val_proc[] = $vs_hierarchy_name;
                                    }
                                    break;
                                case 'hierarchy':
                                    if (is_array($va_val) && sizeof($va_val) > 0) {
                                        $va_hier_list = array();
                                        if ($vs_hierarchy_name) {
                                            array_unshift($va_hier_list, $vs_hierarchy_name);
                                        }
                                        $vs_name = end($va_spec_bits);
                                        foreach ($va_val as $va_hier) {
                                            $va_hier = caExtractValuesByUserLocale($va_hier);
                                            foreach ($va_hier as $va_hier_item) {
                                                foreach ($va_hier_item as $va_hier_value) {
                                                    $va_hier_list[] = $va_hier_value[$vs_name] ? $va_hier_value[$vs_name] : array_shift($va_hier_value);
                                                }
                                            }
                                        }
                                        $va_val_proc[] = join(caGetOption("delimiter", $va_tag_opts, $vs_delimiter), $va_hier_list);
                                    }
                                    break;
                                case 'parent':
                                    if (is_array($va_val)) {
                                        foreach ($va_val as $vm_label) {
                                            if (is_array($vm_label)) {
                                                $t_rel = $o_dm->getInstanceByTableName($va_spec_bits[0], true);
                                                if (!$t_rel || !method_exists($t_rel, "getLabelDisplayField")) {
                                                    $va_val_proc[] = join("; ", $vm_label);
                                                } else {
                                                    $va_val_proc[] = $vm_label[$t_rel->getLabelDisplayField()];
                                                }
                                            } else {
                                                $va_val_proc[] = $vm_label;
                                            }
                                        }
                                    }
                                    break;
                                default:
                                    $vs_terminal = end($va_spec_bits);
                                    foreach ($va_val as $va_val_container) {
                                        if (!is_array($va_val_container)) {
                                            if ($va_val_container) {
                                                $va_val_proc[] = $va_val_container;
                                            }
                                            continue;
                                        }
                                        // Add display field to *_labels terminals
                                        if (in_array($vs_terminal, array('preferred_labels', 'nonpreferred_labels')) && !$va_val_container[$vs_terminal]) {
                                            $t_rel = $o_dm->getInstanceByTableName($va_spec_bits[0], true);
                                            $vs_terminal = $t_rel->getLabelDisplayField();
                                        }
                                        $va_val_proc[] = $va_val_container[$vs_terminal];
                                    }
                                    break;
                            }
                            $va_val = $va_val_proc;
                            $vb_is_related = true;
                        } else {
                            //
                            // Handle non-related gets
                            //
                            // Default specifiers that end with a modifier to preferred labels
                            if (sizeof($va_tmp) == 2 && in_array($va_tmp[1], array('hierarchy', 'children', 'parent', 'related'))) {
                                array_push($va_tmp, 'preferred_labels');
                            }
                            $vs_hierarchy_name = null;
                            if (in_array($va_tmp[1], array('hierarchy', '_hierarchyName'))) {
                                switch ($t_instance->getProperty('HIERARCHY_TYPE')) {
                                    case __CA_HIER_TYPE_SIMPLE_MONO__:
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                    case __CA_HIER_TYPE_MULTI_MONO__:
                                        $vs_hierarchy_name = $t_instance->getHierarchyName($qr_res->get($t_instance->tableName() . "." . $t_instance->primaryKey(), array('checkAccess' => $pa_check_access)));
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                }
                            }
                            if ($va_tmp[0] == $ps_tablename) {
                                array_shift($va_tmp);
                            }
                            // get rid of primary table if it's in the field spec
                            if (!sizeof($va_tmp) && $t_instance->getProperty('LABEL_TABLE_NAME')) {
                                $va_tmp[] = "preferred_labels";
                            }
                            if (isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels']) {
                                if (!in_array($va_tmp[0], array('hierarchy', 'children', 'parent', 'related')) && $va_tmp[1] == 'preferred_labels') {
                                    array_unshift($va_tmp, 'hierarchy');
                                }
                            }
                            if (isset($pa_options['placeholderPrefix']) && $pa_options['placeholderPrefix'] && $va_tmp[0] != $pa_options['placeholderPrefix']) {
                                array_splice($va_tmp, -1, 0, $pa_options['placeholderPrefix']);
                            }
                            $vs_get_spec = "{$ps_tablename}." . join(".", $va_tmp);
                            if (in_array($va_tmp[0], array('parent'))) {
                                $va_val[] = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_tag_opts, array('returnAsArray' => false)));
                            } elseif ($va_tmp[0] == '_hierarchyName') {
                                $va_val[] = $vs_hierarchy_name;
                            } else {
                                $va_val_tmp = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_tag_opts, array('returnAsArray' => true, 'returnBlankValues' => true, 'assumeDisplayField' => true, 'filters' => $va_tag_filters, 'checkAccess' => $pa_check_access)));
                                $va_val = array();
                                if (is_array($va_val_tmp)) {
                                    //$va_val_tmp = array_reverse($va_val_tmp);
                                    if ($va_tmp[0] == 'hierarchy') {
                                        if ($vs_hierarchy_name) {
                                            array_shift($va_val_tmp);
                                            // remove root
                                            array_unshift($va_val_tmp, $vs_hierarchy_name);
                                            // replace with hierarchy name
                                        }
                                        if ($vs_delimiter_tmp = caGetOption('hierarchicalDelimiter', $va_tag_opts)) {
                                            $vs_tag_val_delimiter = $vs_delimiter_tmp;
                                        } elseif ($vs_delimiter_tmp = caGetOption('hierarchicalDelimiter', $pa_options)) {
                                            $vs_tag_val_delimiter = $vs_delimiter_tmp;
                                        } elseif ($vs_delimiter_tmp = caGetOption('delimiter', $va_tag_opts, $vs_delimiter)) {
                                            $vs_tag_val_delimiter = $vs_delimiter_tmp;
                                        } else {
                                            $vs_tag_val_delimiter = $vs_delimiter;
                                        }
                                    } else {
                                        $vs_tag_val_delimiter = caGetOption('delimiter', $va_tag_opts, $vs_delimiter);
                                    }
                                    foreach ($va_val_tmp as $vn_attr_id => $vm_attr_val) {
                                        if (is_array($vm_attr_val)) {
                                            $va_val[] = join($vs_tag_val_delimiter, $vm_attr_val);
                                        } else {
                                            $va_val[] = $vm_attr_val;
                                        }
                                    }
                                }
                                if (sizeof($va_val) > 1 && $va_tmp[0] == 'hierarchy') {
                                    $vs_tag_val_delimiter = caGetOption('delimiter', $va_tag_opts, $vs_delimiter);
                                    $va_val = array(join($vs_tag_val_delimiter, $va_val));
                                }
                            }
                        }
                    }
                }
                if (is_array($va_val)) {
                    if (sizeof($va_val) > 0) {
                        foreach ($va_val as $vn_j => $vs_val) {
                            if (!is_array($va_tag_val_list[$vn_i][$vn_j][$vs_tag]) || !in_array($vs_val, $va_tag_val_list[$vn_i][$vn_j][$vs_tag])) {
                                $va_tag_val_list[$vn_i][$vn_j][$vs_tag][] = $vs_val;
                                if (is_array($vs_val) && sizeof($vs_val) || strlen($vs_val) > 0) {
                                    $va_defined_tag_list[$vn_i][$vn_j][$vs_tag] = true;
                                }
                            }
                        }
                    } else {
                        $va_tag_val_list[$vn_i][0][$vs_tag] = null;
                        $va_defined_tag_list[$vn_i][0][$vs_tag] = false;
                    }
                }
            }
        }
        $vn_i++;
    }
    foreach ($va_tag_val_list as $vn_i => $va_tags_list) {
        // do sorting?
        if (is_array($pa_sort)) {
            $va_sorted_values = $va_sorted_values_tmp = array();
            foreach ($va_tags_list as $vn_j => $va_values_by_field) {
                $vs_key = '';
                foreach ($pa_sort as $vn_k => $vs_sort) {
                    if (!isset($va_values_by_field[$vs_sort])) {
                        continue;
                    }
                    $vs_subkey = null;
                    foreach ($va_values_by_field[$vs_sort] as $vn_x => $vs_sort_subval) {
                        if ($va_date = caDateToHistoricTimestamps($vs_sort_subval)) {
                            // try to treat it as a date
                            if ($ps_sort_direction == 'DESC' && ($va_date[0] < $vs_subkey || is_null($vs_subkey))) {
                                $vs_subkey = $va_date[0];
                            } elseif ($va_date[0] > $vs_subkey || is_null($vs_subkey)) {
                                $vs_subkey = $va_date[0];
                            }
                        } else {
                            $vs_sort_subval = str_pad($vs_sort_subval, 20, ' ', STR_PAD_LEFT);
                            if ($ps_sort_direction == 'DESC' && ($vs_sort_subval < $vs_subkey || is_null($vs_subkey))) {
                                $vs_subkey = $vs_sort_subval;
                            } elseif ($vs_sort_subval > $vs_subkey || is_null($vs_subkey)) {
                                $vs_subkey = $vs_sort_subval;
                            }
                        }
                    }
                    $vs_key .= $vs_subkey;
                    $va_sorted_values_tmp[$vs_key][] = $va_values_by_field;
                }
            }
            ksort($va_sorted_values_tmp);
            foreach ($va_sorted_values_tmp as $vs_key => $va_value_list) {
                foreach ($va_value_list as $vn_x => $va_val) {
                    $va_sorted_values[$vs_key . $vn_x] = $va_val;
                }
            }
            if ($ps_sort_direction == 'DESC') {
                $va_sorted_values = array_reverse($va_sorted_values);
            }
            if (sizeof($va_sorted_values) > 0) {
                $va_tag_val_list[$vn_i] = $va_tags_list = $va_sorted_values;
            }
        }
        $va_acc = array();
        foreach ($va_tags_list as $vn_j => $va_tags) {
            $va_tag_list = array();
            $va_pt_vals = array();
            $vs_template = $va_proc_templates[$vn_i];
            // Process <if>
            foreach ($va_if as $va_def_con) {
                if (ExpressionParser::evaluate($va_def_con['rule'], $va_tags)) {
                    $vs_template = str_replace($va_def_con['directive'], $va_def_con['content'], $vs_template);
                } else {
                    $vs_template = str_replace($va_def_con['directive'], '', $vs_template);
                }
            }
            // Process <more> tags
            foreach ($va_mores as $vn_more_index => $va_more) {
                if (($vn_pos = strpos($vs_template, $va_more['directive'])) !== false) {
                    if (isset($va_mores[$vn_more_index + 1]) && ($vn_next_more_pos = strpos(substr($vs_template, $vn_pos + strlen($va_more['directive'])), $va_mores[$vn_more_index + 1]['directive'])) !== false) {
                        $vn_next_more_pos += $vn_pos;
                        $vs_partial_template = substr($vs_template, $vn_pos + strlen($va_more['directive']), $vn_next_more_pos - $vn_pos);
                    } else {
                        $vs_partial_template = substr($vs_template, $vn_pos + strlen($va_more['directive']));
                    }
                    $vb_output = false;
                    foreach (array_keys($va_defined_tag_list[$vn_i][$vn_j]) as $vs_defined_tag) {
                        if (strpos($vs_partial_template, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output = true;
                            break;
                        }
                    }
                    if ($vb_output) {
                        $vs_template = preg_replace('!' . $va_more['directive'] . '!', $va_more['content'], $vs_template, 1);
                    } else {
                        $vs_template = preg_replace('!' . $va_more['directive'] . '!', '', $vs_template, 1);
                    }
                }
            }
            // Process <between> tags - text to be output if it is between two defined values
            $va_between_positions = array();
            foreach ($va_betweens as $vn_between_index => $va_between) {
                $vb_output_before = $vb_output_after = false;
                if (($vn_cur_pos = strpos($vs_template, $va_between['directive'])) !== false) {
                    $va_between_positions[$vn_between_index] = $vn_cur_pos;
                    // Get parts of template before tag and after tag
                    $vs_partial_template_before = substr($vs_template, 0, $vn_cur_pos);
                    $vs_partial_template_after = substr($vs_template, $vn_cur_pos + strlen($va_between['directive']));
                    // Only get the template between our current position and the next <between> tag
                    if (isset($va_betweens[$vn_between_index + 1]) && ($vn_after_pos_relative = strpos($vs_partial_template_after, $va_betweens[$vn_between_index + 1]['directive'])) !== false) {
                        $vs_partial_template_after = substr($vs_partial_template_after, 0, $vn_after_pos_relative);
                    }
                    // Check for defined value before and after tag
                    foreach (array_keys($va_defined_tag_list[$vn_i][$vn_j]) as $vs_defined_tag) {
                        if (strpos($vs_partial_template_before, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output_after = true;
                        }
                        if (strpos($vs_partial_template_after, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output_before = true;
                            break;
                        }
                        if ($vb_output_before && $vb_output_after) {
                            break;
                        }
                    }
                }
                if ($vb_output_before && $vb_output_after) {
                    $vs_template = preg_replace('!' . $va_between['directive'] . '!', $va_between['content'], $vs_template, 1);
                } else {
                    $vs_template = preg_replace('!' . $va_between['directive'] . '!', '', $vs_template, 1);
                }
            }
            //
            // Need to sort tags by length descending (longest first)
            // so that when we go to substitute and you have a tag followed by itself with a suffix
            // (ex. ^measurements and ^measurements2) we don't substitute both for the stub (ex. ^measurements)
            //
            $va_tags_tmp = array_keys($va_tags);
            usort($va_tags_tmp, function ($a, $b) {
                return strlen($b) - strlen($a);
            });
            $vs_pt = $vs_template;
            foreach ($va_tags_tmp as $vs_tag) {
                $vs_pt = str_replace('^' . $vs_tag, is_array($va_tags[$vs_tag]) ? join(" | ", $va_tags[$vs_tag]) : $va_tags[$vs_tag], $vs_pt);
            }
            if ($vs_pt) {
                $va_pt_vals[] = $vs_pt;
            }
            if ($vs_acc_val = join(isset($pa_options['delimiter']) ? $pa_options['delimiter'] : $vs_delimiter, $va_pt_vals)) {
                $va_acc[] = $vs_acc_val;
            }
        }
        $va_proc_templates[$vn_i] = join($vs_delimiter, $va_acc);
    }
    if ($pb_return_as_array && !caGetOption('includeBlankValuesInArray', $pa_options, false)) {
        foreach ($va_proc_templates as $vn_i => $vs_template) {
            if (!strlen(trim($vs_template))) {
                unset($va_proc_templates[$vn_i]);
            }
        }
    }
    // Transform links
    $va_proc_templates = caCreateLinksFromText($va_proc_templates, $ps_resolve_links_using, $ps_resolve_links_using != $ps_tablename ? $va_resolve_links_using_row_ids : $pa_row_ids, null, caGetOption('linkTarget', $pa_options, null), array_merge(array('addRelParameter' => true), $pa_options));
    // Kill any lingering tags (just in case)
    foreach ($va_proc_templates as $vn_i => $vs_proc_template) {
        $va_proc_templates[$vn_i] = preg_replace("!\\^([A-Za-z0-9_\\.]+[%]{1}[^ \\^\t\r\n\"\\'<>\\(\\)\\{\\}\\/\\[\\]]*|[A-Za-z0-9_\\.]+)!", "", $vs_proc_template);
        $va_proc_templates[$vn_i] = str_replace("<![CDATA[", "", $va_proc_templates[$vn_i]);
        $va_proc_templates[$vn_i] = str_replace("]]>", "", $va_proc_templates[$vn_i]);
    }
    if ($pb_return_as_array) {
        return $va_proc_templates;
    }
    return join($vs_delimiter, $va_proc_templates);
}
 /**
  * Overrides get() to support bundleable-level fields (relationships)
  *
  * Options:
  *		All supported by BaseModelWithAttributes::get() plus:
  *		retrictToRelationshipTypes - array of ca_relationship_types.type_id values to filter related items on. *MUST BE INTEGER TYPE_IDs, NOT type_code's* This limitation is for performance reasons. You have to convert codes to integer type_id's before invoking get
  *		sort = optional array of bundles to sort returned values on. Currently only supported when getting related values via simple related <table_name> and <table_name>.related invokations. Eg. from a ca_objects results you can use the 'sort' option got get('ca_entities'), get('ca_entities.related') or get('ca_objects.related'). The bundle specifiers are fields with or without tablename. Only those fields returned for the related tables (intrinsics and label fields) are sortable. You cannot sort on attributes.
  *		returnAsLink = if true and $ps_field is set to a specific field in a related table, or $ps_field is set to a related table (eg. ca_entities or ca_entities.related) AND the template option is set and returnAllLocales is not set, then returned values will be links. The destination of the link will be the appropriate editor when executed within Providence or the appropriate detail page when executed within Pawtucket or another front-end. Default is false.
  *		returnAsLinkText = text to use a content of HTML link. If omitted the url itself is used as the link content.
  *		returnAsLinkAttributes = array of attributes to include in link <a> tag. Use this to set class, alt and any other link attributes.
  *		returnAsLinkTarget = Optional link target. If any plugin implementing hookGetAsLink() responds to the specified target then the plugin will be used to generate the links rather than CA's default link generator.
  *		filter = optional array of elements to filter returned values on. The element must be part off the container being fetched from. For example, if you're get()'ing a value from a container element (Eg. ca_objects.dates.date_value) you can filter on any other subelement in that container by passing the name of the subelement and a value (Eg. "date_type" => "copyright"). Pass only the name of the subelement, not the full path that includes the table and container element. You can filter on multiple subelements by passing each subelement as a key in the array. Only values that match all filters are returned. You can filter on multiple values for a subelement by passing an array of values rather than a scalar (Eg. "date_type" => array("copyright", "patent")). Values that match *any* of the values will be returned. Only simple equivalance is supported. NOTE: Filters are only available when returnAsArray is set. They will be ignored if returnAsArray is not set.
  */
 public function get($ps_field, $pa_options = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     $vs_template = caGetOption('template', $pa_options, null);
     $vb_return_as_array = caGetOption('returnAsArray', $pa_options, false, array('castTo' => 'bool'));
     $vb_return_as_link = caGetOption('returnAsLink', $pa_options, false, array('castTo' => 'bool'));
     $vs_return_as_link_text = caGetOption('returnAsLinkText', $pa_options, '');
     $vs_return_as_link_target = caGetOption('returnAsLinkTarget', $pa_options, '');
     $va_return_as_link_attributes = caGetOption('returnAsLinkAttributes', $pa_options, array(), array('castTo' => 'array'));
     $vb_return_all_locales = caGetOption('returnAllLocales', $pa_options, false, array('castTo' => 'bool'));
     $vs_delimiter = caGetOption('delimiter', $pa_options, '');
     $va_restrict_to_rel_types = caGetOption('restrictToRelationshipTypes', $pa_options, null);
     $va_filters = caGetOption('filters', $pa_options, array(), array('castTo' => 'array'));
     if ($vb_return_all_locales && !$vb_return_as_array) {
         $vb_return_as_array = true;
     }
     $va_return_values = null;
     // does get refer to an attribute?
     $va_tmp = explode('.', $ps_field);
     if (sizeof($va_tmp) > 1) {
         if ($va_tmp[0] != $this->tableName()) {
             $vs_access_chk_key = $ps_field;
         } else {
             $va_tmp2 = $va_tmp;
             array_shift($va_tmp2);
             $vs_access_chk_key = join(".", $va_tmp2);
         }
     } else {
         $vs_access_chk_key = $ps_field;
     }
     if ($va_tmp[sizeof($va_tmp) - 1] != 'access' && (!$this->hasField($va_tmp[sizeof($va_tmp) - 1]) || $this->getFieldInfo($va_tmp[sizeof($va_tmp) - 1], 'ALLOW_BUNDLE_ACCESS_CHECK'))) {
         // Always allow "access" value to be gotten otherwise none of the Pawtucket access checks will work.
         if (caGetBundleAccessLevel($this->tableName(), $vs_access_chk_key) == __CA_BUNDLE_ACCESS_NONE__) {
             return null;
         }
     }
     switch (sizeof($va_tmp)) {
         # -------------------------------------
         case 1:
             // table_name
             if ($t_instance = $this->_DATAMODEL->getInstanceByTableName($va_tmp[0], true)) {
                 $vs_pk = $t_instance->primaryKey();
                 $va_related_items = $this->getRelatedItems($va_tmp[0], $pa_options);
                 if (!is_array($va_related_items)) {
                     return null;
                 }
                 if (!$vs_template && !$vb_return_all_locales && !$vb_return_as_array) {
                     $vs_template = "^preferred_labels";
                 }
                 if ($vb_return_all_locales) {
                     $va_related_tmp = array();
                     foreach ($va_related_items as $vn_i => $va_related_item) {
                         $va_related_tmp[$vn_i][$va_related_item['locale_id']] = $va_related_item;
                     }
                     $va_related_items = $va_related_tmp;
                 } else {
                     if ($vs_template) {
                         $va_template_opts = $pa_options;
                         unset($va_template_opts['request']);
                         unset($va_template_opts['template']);
                         $va_template_opts['returnAsLink'] = false;
                         $va_template_opts['returnAsArray'] = true;
                         $va_template_opts['requireLinkTags'] = true;
                         $va_ids = array();
                         if (is_array($va_rel_items = $this->get($va_tmp[0], $va_template_opts))) {
                             foreach ($va_rel_items as $vn_rel_id => $va_rel_item) {
                                 $va_ids[] = $va_rel_item[$vs_pk];
                                 $va_template_opts['relationshipValues'][$va_rel_item[$vs_pk]][$va_rel_item['relation_id']]['relationship_typename'] = $va_rel_item['relationship_typename'];
                                 $va_template_opts['relationshipValues'][$va_rel_item[$vs_pk]][$va_rel_item['relation_id']]['relationship_type_id'] = $va_rel_item['relationship_type_id'];
                                 $va_template_opts['relationshipValues'][$va_rel_item[$vs_pk]][$va_rel_item['relation_id']]['label'] = $va_rel_item['label'];
                             }
                         } else {
                             $va_rel_items = array();
                         }
                         $va_text = caProcessTemplateForIDs($vs_template, $va_tmp[0], $va_ids, $va_template_opts);
                         if ($vb_return_as_link) {
                             $va_text = caCreateLinksFromText($va_text, $va_tmp[0], $va_ids, $vs_return_as_link_class, $vs_return_as_link_target);
                         }
                         if ($vb_return_as_array) {
                             $va_return_values = $va_text;
                         }
                         if (is_array($va_text)) {
                             return join($vs_delimiter, $va_text);
                         } else {
                             return null;
                         }
                     }
                 }
                 $va_return_values = $va_related_items;
             }
             break;
             # -------------------------------------
         # -------------------------------------
         case 2:
             // table_name.field_name || table_name.related
         // table_name.field_name || table_name.related
         case 3:
             // table_name.field_name.sub_element || table_name.related.field_name
         // table_name.field_name.sub_element || table_name.related.field_name
         case 4:
             // table_name.related.field_name.sub_element
             //
             // TODO: this code is compact, relatively simple and works but is slow since it
             // generates a lot more identical database queries than we'd like
             // We will need to add some notion of caching so that multiple calls to get()
             // for various fields in the same list of related items don't cause repeated queries
             //
             $vb_is_related = false;
             $vb_is_hierarchy = false;
             if ($va_tmp[1] === 'related') {
                 array_splice($va_tmp, 1, 1);
                 $vb_is_related = true;
             }
             if ($vb_is_related || $va_tmp[0] !== $this->tableName()) {
                 // must be related table
                 if (!($t_instance = $this->_DATAMODEL->getInstanceByTableName($va_tmp[0], true))) {
                     return null;
                 }
                 $vs_pk = $t_instance->primaryKey();
                 if ($vs_template && !$vb_return_all_locales) {
                     $va_template_opts = $pa_options;
                     unset($va_template_opts['request']);
                     unset($va_template_opts['template']);
                     $va_template_opts['returnAsLink'] = false;
                     $va_template_opts['returnAsArray'] = true;
                     $va_template_opts['requireLinkTags'] = true;
                     $va_ids = array();
                     if (is_array($va_rel_items = $this->get($va_tmp[0], $va_template_opts))) {
                         foreach ($va_rel_items as $vn_rel_id => $va_rel_item) {
                             $va_ids[] = $va_rel_item[$vs_pk];
                             $va_template_opts['relationshipValues'][$va_rel_item[$vs_pk]][$va_rel_item['relation_id']]['relationship_typename'] = $va_rel_item['relationship_typename'];
                         }
                     } else {
                         $va_rel_items = array();
                     }
                     $va_text = caProcessTemplateForIDs($vs_template, $va_tmp[0], $va_ids, $va_template_opts);
                     if ($vb_return_as_link) {
                         $va_text = caCreateLinksFromText($va_text, $va_tmp[0], $va_ids, $vs_return_as_link_class, $vs_return_as_link_target);
                     }
                     if ($vb_return_as_array) {
                         $va_return_values = $va_text;
                     }
                     return join($vs_delimiter, $va_text);
                 }
                 $va_related_items = $this->getRelatedItems($va_tmp[0], array_merge($pa_options, array('returnLabelsAsArray' => true)));
                 if (is_array($va_restrict_to_rel_types) && sizeof($va_restrict_to_rel_types)) {
                     require_once __CA_MODELS_DIR__ . '/ca_relationship_types.php';
                     $t_rel_types = new ca_relationship_types();
                     $va_restrict_to_rel_types = $t_rel_types->relationshipTypeListToIDs($t_rel_types->getRelationshipTypeTable($this->tableName(), $va_tmp[0]), $va_restrict_to_rel_types, array('includeChildren' => true));
                 }
                 $vb_field_is_specified = sizeof($va_tmp) == 2 && (!$vb_is_hierarchy && $va_tmp[1] != 'related') || sizeof($va_tmp) >= 3;
                 $va_ids = array();
                 $va_items = array();
                 if (is_array($va_related_items) && sizeof($va_related_items) > 0) {
                     foreach ($va_related_items as $vn_rel_id => $va_related_item) {
                         $va_ids[] = $va_related_item[$vs_pk];
                         if (is_array($va_restrict_to_rel_types) && !in_array($va_related_item['relationship_type_id'], $va_restrict_to_rel_types)) {
                             continue;
                         }
                         if ($va_tmp[1] == 'relationship_typename') {
                             $va_items[] = $va_related_item['relationship_typename'];
                             continue;
                         }
                         if ($va_tmp[1] == 'hierarchy') {
                             $vb_is_hierarchy = true;
                             if ($t_instance->load($va_related_item[$t_instance->primaryKey()])) {
                                 $va_items[] = $t_instance->get(join('.', $va_tmp), $pa_options);
                             }
                             continue;
                         }
                         // is field directly returned by getRelatedItems()?
                         if (isset($va_tmp[1]) && isset($va_related_item[$va_tmp[1]]) && $t_instance->hasField($va_tmp[1])) {
                             if ($vb_return_as_array) {
                                 if ($vb_return_all_locales) {
                                     // for return as locale-index array
                                     $va_items[$va_related_item['relation_id']][$va_related_item['locale_id']][] = $va_related_item[$va_tmp[1]];
                                 } else {
                                     // for return as simple array
                                     $va_items[] = $va_related_item[$va_tmp[1]];
                                 }
                             } else {
                                 // for return as string
                                 $va_items[] = $va_related_item[$va_tmp[1]];
                             }
                             continue;
                         }
                         // is field preferred labels?
                         if ($va_tmp[1] === 'preferred_labels') {
                             if (!isset($va_tmp[2])) {
                                 if ($vb_return_as_array || $vb_return_all_locales) {
                                     if ($vb_return_all_locales) {
                                         // for return as locale-index array
                                         $va_items[$va_related_item['relation_id']][] = $va_related_item['labels'];
                                     } else {
                                         // for return as simple array
                                         $va_item_list = caExtractValuesByUserLocale(array($va_related_item['labels']));
                                         foreach ($va_item_list as $vn_x => $va_item) {
                                             $va_items[] = $va_item[$t_instance->getLabelDisplayField()];
                                         }
                                     }
                                 } else {
                                     // for return as string
                                     $va_items[] = $va_related_item['label'][$t_instance->getLabelDisplayField()];
                                 }
                             } else {
                                 if ($vb_return_all_locales) {
                                     // for return as locale-index array
                                     foreach ($va_related_item['labels'] as $vn_locale_id => $va_label) {
                                         $va_items[$va_related_item['relation_id']][$vn_locale_id][] = $va_label[$va_tmp[2]];
                                     }
                                 } else {
                                     foreach (caExtractValuesByUserLocale(array($va_related_item['labels'])) as $vn_i => $va_label) {
                                         // for return as string or simple array
                                         $va_items[] = $va_label[$va_tmp[2]];
                                     }
                                 }
                             }
                             continue;
                         }
                         // TODO: add support for nonpreferred labels
                         // Get related attributes
                         if ($t_instance->load($va_related_item[$t_instance->primaryKey()])) {
                             if (isset($va_tmp[1])) {
                                 if ($vm_val = $t_instance->get(join(".", $va_tmp), $pa_options)) {
                                     if ($vb_return_as_link && !$vb_return_as_array && !$vb_return_all_locales) {
                                         $va_items[] = $vm_val;
                                     } else {
                                         $va_items[] = $vm_val;
                                         //break;
                                     }
                                 }
                             } else {
                                 $va_items[] = $this->get($va_tmp[0], $pa_options);
                             }
                         } else {
                             $va_items[] = null;
                         }
                     }
                 }
                 if ($vb_return_as_link && !$vb_return_all_locales) {
                     $va_items = caCreateLinksFromText($va_items, $va_tmp[0], $va_ids, $vs_return_as_link_class, $vs_return_as_link_target);
                 }
                 if ($vb_return_as_array) {
                     $va_return_values = $va_items;
                 } else {
                     return join($vs_delimiter, $va_items);
                 }
             }
             break;
             # -------------------------------------
     }
     if (!$va_return_values) {
         $va_return_values = parent::get($ps_field, $pa_options);
     }
     //
     // Perform filtering
     //
     if ($vb_return_as_array && sizeof($va_filters)) {
         $va_tmp = explode(".", $ps_field);
         if (sizeof($va_tmp) > 1) {
             array_pop($va_tmp);
         }
         if ($t_instance = $this->getAppDataModel()->getInstanceByTableName($va_tmp[0], true)) {
             $va_keepers = array();
             foreach ($va_filters as $vs_filter => $va_filter_vals) {
                 if (!is_array($va_filter_vals)) {
                     $va_filter_vals = array($va_filter_vals);
                 }
                 foreach ($va_filter_vals as $vn_index => $vs_filter_val) {
                     // is value a list attribute idno?
                     if (!is_numeric($vs_filter_val) && (($t_element = $t_instance->_getElementInstance($vs_filter)) && $t_element->get('datatype') == 3)) {
                         $va_filter_vals[$vn_index] = caGetListItemID($t_element->get('list_id'), $vs_filter_val);
                     }
                 }
                 $va_filter_values = $this->get(join(".", $va_tmp) . ".{$vs_filter}", array('returnAsArray' => true));
                 if (!is_array($va_filter_values)) {
                     $va_filter_values = array();
                 }
                 foreach ($va_filter_values as $vn_id => $vm_filtered_val) {
                     if ((!isset($va_keepers[$vn_id]) || $va_keepers[$vn_id]) && in_array($vm_filtered_val, $va_filter_vals)) {
                         // any match for the element counts
                         $va_keepers[$vn_id] = true;
                     } else {
                         // if no match on any criteria kill it
                         $va_keepers[$vn_id] = false;
                     }
                 }
             }
             $va_filtered_vals = array();
             foreach ($va_keepers as $vn_id => $vb_include) {
                 if (!$vb_include) {
                     continue;
                 }
                 $va_filtered_vals[$vn_id] = $va_return_values[$vn_id];
             }
             return $va_filtered_vals;
         }
     }
     return $va_return_values;
 }
Ejemplo n.º 4
0
 /**
  *
  */
 private static function _processChildren(SearchResult $pr_res, $po_nodes, array $pa_vals, array $pa_options = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = [];
     }
     if (!$po_nodes) {
         return '';
     }
     $vs_acc = '';
     $ps_tablename = $pr_res->tableName();
     $o_dm = Datamodel::load();
     $t_instance = $o_dm->getInstanceByTableName($ps_tablename, true);
     $ps_delimiter = caGetOption('delimiter', $pa_options, '; ');
     $pb_is_case = caGetOption('isCase', $pa_options, false, ['castTo' => 'boolean']);
     $pb_quote = caGetOption('quote', $pa_options, false, ['castTo' => 'boolean']);
     $pa_primary_ids = caGetOption('primaryIDs', $pa_options, null);
     $pb_include_blanks = caGetOption('includeBlankValuesInArray', $pa_options, false);
     unset($pa_options['quote']);
     $vn_last_unit_omit_count = null;
     foreach ($po_nodes as $vn_index => $o_node) {
         switch ($vs_tag = strtolower($o_node->tag)) {
             case 'case':
                 if (!$pb_is_case) {
                     $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, $pa_vals, array_merge($pa_options, ['isCase' => true]));
                 }
                 break;
             case 'if':
                 if (strlen($vs_rule = $o_node->rule) && ExpressionParser::evaluate($vs_rule, $pa_vals)) {
                     $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, DisplayTemplateParser::_getValues($pr_res, DisplayTemplateParser::_getTags($o_node->children, $pa_options), $pa_options), $pa_options);
                     if ($pb_is_case) {
                         break 2;
                     }
                 }
                 break;
             case 'ifdef':
             case 'ifnotdef':
                 $vb_defined = DisplayTemplateParser::_evaluateCodeAttribute($pr_res, $o_node, ['index' => caGetOption('index', $pa_options, null), 'mode' => $vs_tag == 'ifdef' ? 'present' : 'not_present']);
                 if ($vs_tag == 'ifdef' && $vb_defined || $vs_tag == 'ifnotdef' && $vb_defined) {
                     // Make sure returned values are not empty
                     $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, DisplayTemplateParser::_getValues($pr_res, DisplayTemplateParser::_getTags($o_node->children, $pa_options), $pa_options), $pa_options);
                     if ($pb_is_case) {
                         break 2;
                     }
                 }
                 break;
             case 'ifcount':
                 $vn_min = (int) $o_node->min;
                 $vn_max = (int) $o_node->max;
                 if (!is_array($va_codes = DisplayTemplateParser::_getCodesFromAttribute($o_node)) || !sizeof($va_codes)) {
                     break;
                 }
                 $pa_check_access = $t_instance->hasField('access') ? caGetOption('checkAccess', $pa_options, null) : null;
                 if (!is_array($pa_check_access) || !sizeof($pa_check_access)) {
                     $pa_check_access = null;
                 }
                 $vb_bool = DisplayTemplateParser::_getCodesBooleanModeAttribute($o_node);
                 $va_restrict_to_types = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'restrictToTypes']);
                 $va_exclude_types = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'excludeTypes']);
                 $va_restrict_to_relationship_types = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'restrictToRelationshipTypes']);
                 $va_exclude_to_relationship_types = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'excludeRelationshipTypes']);
                 $vm_count = $vb_bool == 'AND' ? 0 : [];
                 foreach ($va_codes as $vs_code) {
                     $va_vals = $pr_res->get($vs_code, ['checkAccess' => $pa_check_access, 'returnAsArray' => true, 'restrictToTypes' => $va_restrict_to_types, 'excludeTypes' => $va_exclude_types, 'restrictToRelationshipTypes' => $va_restrict_to_relationship_types, 'excludeRelationshipTypes' => $va_exclude_to_relationship_types]);
                     if (is_array($va_vals)) {
                         if ($vb_bool == 'AND') {
                             $vm_count += sizeof($va_vals);
                         } else {
                             $vm_count[$vs_code] = sizeof($va_vals);
                         }
                     }
                 }
                 if ($vb_bool == 'AND') {
                     if ($vn_min <= $vm_count && ($vn_max >= $vm_count || !$vn_max)) {
                         $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, DisplayTemplateParser::_getValues($pr_res, DisplayTemplateParser::_getTags($o_node->children, $pa_options), $pa_options), $pa_options);
                         if ($pb_is_case) {
                             break 2;
                         }
                     }
                 } else {
                     $vb_all_have_count = true;
                     foreach ($vm_count as $vs_code => $vn_count) {
                         if (!($vn_min <= $vn_count && ($vn_max >= $vn_count || !$vn_max))) {
                             $vb_all_have_count = false;
                             break 2;
                         }
                     }
                     if ($vb_all_have_count) {
                         $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, $pa_vals, $pa_options);
                         if ($pb_is_case) {
                             break 2;
                         }
                     }
                 }
                 break;
             case 'more':
                 // Does a placeholder with value follow this tag?
                 // NOTE: 	We don't take into account <ifdef> and friends when finding a set placeholder; it may be set but not visible due to a conditional
                 // 			This case is not covered at the moment on the assumption that if you're using <more> you're not using conditionals. This may or may not be a good assumption.
                 for ($vn_i = $vn_index + 1; $vn_i < sizeof($po_nodes); $vn_i++) {
                     if ($po_nodes[$vn_i] && $po_nodes[$vn_i]->tag == '~text~' && is_array($va_following_tags = caGetTemplateTags($po_nodes[$vn_i]->text))) {
                         foreach ($va_following_tags as $vs_following_tag) {
                             if (isset($pa_vals[$vs_following_tag]) && strlen($pa_vals[$vs_following_tag])) {
                                 $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, $pa_vals, $pa_options);
                                 if ($pb_is_case) {
                                     break 2;
                                 }
                             }
                         }
                     }
                 }
                 break;
             case 'between':
                 // Does a placeholder with value precede this tag?
                 // NOTE: 	We don't take into account <ifdef> and friends when finding a set placeholder; it may be set but not visible due to a conditional
                 // 			This case is not covered at the moment on the assumption that if you're using <between> you're not using conditionals. This may or may not be a good assumption.
                 $vb_has_preceding_value = false;
                 for ($vn_i = 0; $vn_i < $vn_index; $vn_i++) {
                     if ($po_nodes[$vn_i] && $po_nodes[$vn_i]->tag == '~text~' && is_array($va_preceding_tags = caGetTemplateTags($po_nodes[$vn_i]->text))) {
                         foreach ($va_preceding_tags as $vs_preceding_tag) {
                             if (isset($pa_vals[$vs_preceding_tag]) && strlen($pa_vals[$vs_preceding_tag])) {
                                 $vb_has_preceding_value = true;
                             }
                         }
                     }
                 }
                 if ($vb_has_preceding_value) {
                     // Does it have a value immediately following it?
                     for ($vn_i = $vn_index + 1; $vn_i < sizeof($po_nodes); $vn_i++) {
                         if ($po_nodes[$vn_i] && $po_nodes[$vn_i]->tag == '~text~' && is_array($va_following_tags = caGetTemplateTags($po_nodes[$vn_i]->text))) {
                             foreach ($va_following_tags as $vs_following_tag) {
                                 if (isset($pa_vals[$vs_following_tag]) && strlen($pa_vals[$vs_following_tag])) {
                                     $vs_acc .= DisplayTemplateParser::_processChildren($pr_res, $o_node->children, $pa_vals, $pa_options);
                                     if ($pb_is_case) {
                                         break 2;
                                     }
                                 }
                                 break;
                             }
                         }
                     }
                 }
                 break;
             case 'expression':
                 if ($vs_exp = trim($o_node->getInnerText())) {
                     $vs_acc .= ExpressionParser::evaluate(DisplayTemplateParser::_processChildren($pr_res, $o_node->children, DisplayTemplateParser::_getValues($pr_res, DisplayTemplateParser::_getTags($o_node->children, $pa_options), $pa_options), array_merge($pa_options, ['quote' => true])), $pa_vals);
                     if ($pb_is_case) {
                         break 2;
                     }
                 }
                 break;
             case 'unit':
                 $va_relative_to_tmp = $o_node->relativeTo ? explode(".", $o_node->relativeTo) : [$ps_tablename];
                 if ($va_relative_to_tmp[0] && !($t_rel_instance = $o_dm->getInstanceByTableName($va_relative_to_tmp[0], true))) {
                     continue;
                 }
                 $vn_last_unit_omit_count = 0;
                 // <unit> attributes
                 $vs_unit_delimiter = $o_node->delimiter ? (string) $o_node->delimiter : $ps_delimiter;
                 $vb_unique = $o_node->unique ? (bool) $o_node->unique : false;
                 $vb_aggregate_unique = $o_node->aggregateUnique ? (bool) $o_node->aggregateUnique : false;
                 $vs_unit_skip_if_expression = (string) $o_node->skipIfExpression;
                 $vn_start = (int) $o_node->start;
                 $vn_length = (int) $o_node->length;
                 $pa_check_access = $t_instance->hasField('access') ? caGetOption('checkAccess', $pa_options, null) : null;
                 if (!is_array($pa_check_access) || !sizeof($pa_check_access)) {
                     $pa_check_access = null;
                 }
                 // additional get options for pulling related records
                 $va_get_options = ['returnAsArray' => true, 'checkAccess' => $pa_check_access];
                 $va_get_options['restrictToTypes'] = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'restrictToTypes']);
                 $va_get_options['excludeTypes'] = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'excludeTypes']);
                 $va_get_options['restrictToRelationshipTypes'] = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'restrictToRelationshipTypes']);
                 $va_get_options['excludeRelationshipTypes'] = DisplayTemplateParser::_getCodesFromAttribute($o_node, ['attribute' => 'excludeRelationshipTypes']);
                 if ($o_node->sort) {
                     $va_get_options['sort'] = preg_split('![ ,;]+!', $o_node->sort);
                     $va_get_options['sortDirection'] = $o_node->sortDirection;
                 }
                 if (sizeof($va_relative_to_tmp) == 1 && $va_relative_to_tmp[0] == $ps_tablename || sizeof($va_relative_to_tmp) >= 1 && $va_relative_to_tmp[0] == $ps_tablename && $va_relative_to_tmp[1] != 'related') {
                     $vs_relative_to_container = null;
                     switch (strtolower($va_relative_to_tmp[1])) {
                         case 'hierarchy':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".hierarchy." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'parent':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".parent." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'children':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".children." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'siblings':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".siblings." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         default:
                             // If relativeTo is not set to a valid attribute try to guess from template
                             if ($t_rel_instance->isValidMetadataElement(join(".", array_slice($va_relative_to_tmp, 1, 1)), true)) {
                                 $vs_relative_to_container = join(".", array_slice($va_relative_to_tmp, 0, 2));
                             } else {
                                 $va_tags = caGetTemplateTags($o_node->getInnerText());
                                 foreach ($va_tags as $vs_tag) {
                                     $va_tag = explode('.', $vs_tag);
                                     while (sizeof($va_tag) > 1) {
                                         $vs_end = array_pop($va_tag);
                                         if ($t_rel_instance->isValidMetadataElement($vs_end, true)) {
                                             $va_tag[] = $vs_end;
                                             $vs_relative_to_container = join(".", $va_tag);
                                             break 2;
                                         }
                                     }
                                 }
                             }
                             $va_relative_ids = array($pr_res->getPrimaryKey());
                             break;
                     }
                     // process template for all records selected by unit tag
                     $va_tmpl_val = DisplayTemplateParser::evaluate($o_node->getInnerText(), $ps_tablename, $va_relative_ids, array_merge($pa_options, ['sort' => $va_get_options['sort'], 'sortDirection' => $va_get_options['sortDirection'], 'returnAsArray' => true, 'delimiter' => $vs_unit_delimiter, 'skipIfExpression' => $vs_unit_skip_if_expression, 'placeholderPrefix' => (string) $o_node->relativeTo, 'restrictToTypes' => $va_get_options['restrictToTypes'], 'excludeTypes' => $va_get_options['excludeTypes'], 'isUnit' => true, 'unitStart' => $vn_start, 'unitLength' => $vn_length, 'relativeToContainer' => $vs_relative_to_container, 'includeBlankValuesInTopLevelForPrefetch' => false, 'unique' => $vb_unique, 'aggregateUnique' => $vb_aggregate_unique]));
                     if ($vb_unique) {
                         $va_tmpl_val = array_unique($va_tmpl_val);
                     }
                     if ($vn_start > 0 || !is_null($vn_length)) {
                         $vn_last_unit_omit_count = sizeof($va_tmpl_val) - ($vn_length - $vn_start);
                     }
                     if (caGetOption('returnAsArray', $pa_options, false)) {
                         return $va_tmpl_val;
                     }
                     $vs_acc .= join($vs_unit_delimiter, $va_tmpl_val);
                     if ($pb_is_case) {
                         break 2;
                     }
                 } else {
                     switch (strtolower($va_relative_to_tmp[1])) {
                         case 'hierarchy':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".hierarchy." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'parent':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".parent." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'children':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".children." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'siblings':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".siblings." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         case 'related':
                             $va_relative_ids = $pr_res->get($t_rel_instance->tableName() . ".related." . $t_rel_instance->primaryKey(), $va_get_options);
                             $va_relative_ids = array_values($va_relative_ids);
                             break;
                         default:
                             if (method_exists($t_instance, 'isSelfRelationship') && $t_instance->isSelfRelationship() && is_array($pa_primary_ids) && isset($pa_primary_ids[$t_rel_instance->tableName()])) {
                                 $va_relative_ids = array_values($t_instance->getRelatedIDsForSelfRelationship($pa_primary_ids[$t_rel_instance->tableName()], array($pr_res->getPrimaryKey())));
                             } else {
                                 $va_relative_ids = $pr_res->get($t_rel_instance->primaryKey(true), $va_get_options);
                                 $va_relative_ids = is_array($va_relative_ids) ? array_values($va_relative_ids) : array();
                             }
                             break;
                     }
                     $va_tmpl_val = DisplayTemplateParser::evaluate($o_node->getInnerText(), $va_relative_to_tmp[0], $va_relative_ids, array_merge($pa_options, ['sort' => $va_unit['sort'], 'sortDirection' => $va_unit['sortDirection'], 'delimiter' => $vs_unit_delimiter, 'returnAsArray' => true, 'skipIfExpression' => $vs_unit_skip_if_expression, 'placeholderPrefix' => (string) $o_node->relativeTo, 'restrictToTypes' => $va_get_options['restrictToTypes'], 'excludeTypes' => $va_get_options['excludeTypes'], 'isUnit' => true, 'unitStart' => $vn_start, 'unitLength' => $vn_length, 'includeBlankValuesInTopLevelForPrefetch' => false, 'unique' => $vb_unique, 'aggregateUnique' => $vb_aggregate_unique]));
                     if ($vb_unique) {
                         $va_tmpl_val = array_unique($va_tmpl_val);
                     }
                     if ($vn_start > 0 || !is_null($vn_length)) {
                         $vn_num_vals = sizeof($va_tmpl_val);
                         $va_tmpl_val = array_slice($va_tmpl_val, $vn_start, $vn_length > 0 ? $vn_length : null);
                         $vn_last_unit_omit_count = $vn_num_vals - ($vn_length - $vn_start);
                     }
                     if (caGetOption('returnAsArray', $pa_options, false)) {
                         return $va_tmpl_val;
                     }
                     $vs_acc .= join($vs_unit_delimiter, $va_tmpl_val);
                     if ($pb_is_case) {
                         break 2;
                     }
                 }
                 break;
             case 'whenunitomits':
                 if ($vn_last_unit_omit_count > 0) {
                     $vs_proc_template = caProcessTemplate($o_node->getInnerText(), array_merge($pa_vals, ['omitcount' => (int) $vn_last_unit_omit_count]), ['quote' => $pb_quote]);
                     $vs_acc .= $vs_proc_template;
                 }
                 break;
             default:
                 if ($o_node->children && sizeof($o_node->children) > 0) {
                     $vs_proc_template = DisplayTemplateParser::_processChildren($pr_res, $o_node->children, $pa_vals, $pa_options);
                 } else {
                     $vs_proc_template = caProcessTemplate($o_node->html(), $pa_vals, ['quote' => $pb_quote]);
                 }
                 if ($vs_tag === 'l') {
                     $va_proc_templates = caCreateLinksFromText(["{$vs_proc_template}"], $ps_tablename, [$pr_res->getPrimaryKey()], null, caGetOption('linkTarget', $pa_options, null), array_merge(['addRelParameter' => true, 'requireLinkTags' => false], $pa_options));
                     $vs_proc_template = array_shift($va_proc_templates);
                 } elseif (strlen($vs_tag) && $vs_tag[0] !== '~') {
                     if ($o_node->children && sizeof($o_node->children) > 0) {
                         $vs_attr = '';
                         if ($o_node->attributes) {
                             foreach ($o_node->attributes as $attribute => $value) {
                                 $vs_attr .= " {$attribute}=\"" . htmlspecialchars(caProcessTemplate($value, $pa_vals, ['quote' => $pb_quote])) . "\"";
                             }
                         }
                         $vs_proc_template = "<{$vs_tag}{$vs_attr}>{$vs_proc_template}</{$vs_tag}>";
                     } elseif ($o_node->attributes && sizeof($o_node->attributes) > 0) {
                         $vs_attr = '';
                         foreach ($o_node->attributes as $attribute => $value) {
                             $vs_attr .= " {$attribute}=\"" . htmlspecialchars(caProcessTemplate($value, $pa_vals, ['quote' => $pb_quote])) . "\"";
                         }
                         switch (strtolower($vs_tag)) {
                             case 'br':
                             case 'hr':
                             case 'meta':
                             case 'link':
                             case 'base':
                             case 'img':
                             case 'embed':
                             case 'param':
                             case 'area':
                             case 'col':
                             case 'input':
                                 $vs_proc_template = "<{$vs_tag}{$vs_attr} />";
                                 break;
                             default:
                                 $vs_proc_template = "<{$vs_tag}{$vs_attr}></{$vs_tag}>";
                                 break;
                         }
                     } else {
                         $vs_proc_template = $o_node->html();
                     }
                 }
                 $vs_acc .= $vs_proc_template;
                 break;
         }
     }
     return $vs_acc;
 }
Ejemplo n.º 5
0
 /**
  * get() value for intrinsic
  *
  * @param array $pa_value_list
  * @param BaseModel $pt_instance
  * @param array Options
  *
  * @return array|string
  */
 private function _getIntrinsicValue($pa_value_list, $pt_instance, $pa_options)
 {
     $vb_return_as_link = isset($pa_options['returnAsLink']) ? $pa_options['returnAsLink'] : false;
     $vb_get_direct_date = (bool) caGetOption(array('getDirectDate', 'GET_DIRECT_DATE'), $pa_options, false);
     $vb_sortable = isset($pa_options['sortable']) ? $pa_options['sortable'] : false;
     $va_path_components = $pa_options['pathComponents'];
     $va_field_info = $pa_options['fieldInfo'];
     $vs_pk = $pa_options['primaryKey'];
     $vs_table_name = $pt_instance->tableName();
     // Handle specific intrinsic types
     switch ($va_field_info['FIELD_TYPE']) {
         case FT_DATERANGE:
         case FT_HISTORIC_DATERANGE:
         case FT_TIMESTAMP:
         case FT_DATETIME:
         case FT_HISTORIC_DATETIME:
             foreach ($pa_value_list as $vn_locale_id => $va_values) {
                 if ($pa_options['useLocaleCodes']) {
                     if (!$vn_locale_id || !($vm_locale_id = $this->opo_locales->localeIDToCode($vn_locale_id))) {
                         $vm_locale_id = __CA_DEFAULT_LOCALE__;
                     }
                 } else {
                     if (!($vm_locale_id = $vn_locale_id)) {
                         $vm_locale_id = $this->opo_locales->localeCodeToID(__CA_DEFAULT_LOCALE__);
                     }
                 }
                 foreach ($va_values as $vn_i => $va_value) {
                     $va_ids[] = $vn_id = $va_value[$vs_pk];
                     if (in_array($va_field_info['FIELD_TYPE'], array(FT_TIMESTAMP, FT_DATETIME, FT_HISTORIC_DATETIME))) {
                         $vs_prop = $va_value[$va_path_components['field_name']];
                         if (!$vb_get_direct_date && !$vb_sortable) {
                             $this->opo_tep->init();
                             if ($va_field_info['FIELD_TYPE'] !== FT_HISTORIC_DATETIME) {
                                 $this->opo_tep->setUnixTimestamps($vs_prop, $vs_prop);
                             } else {
                                 $this->opo_tep->setHistoricTimestamps($vs_prop, $vs_prop);
                             }
                             $vs_prop = $this->opo_tep->getText($pa_options);
                         }
                     } elseif ($vb_get_direct_date) {
                         $vs_prop = $va_value[$va_field_info['START']];
                     } elseif ($vb_sortable) {
                         $vs_prop = $va_value[$va_field_info['START']];
                     } else {
                         $this->opo_tep->init();
                         if ($va_field_info['FIELD_TYPE'] == FT_DATERANGE) {
                             $this->opo_tep->setUnixTimestamps($va_value[$va_field_info['START']], $va_value[$va_field_info['END']]);
                         } else {
                             $this->opo_tep->setHistoricTimestamps($va_value[$va_field_info['START']], $va_value[$va_field_info['END']]);
                         }
                         $vs_prop = $this->opo_tep->getText($pa_options);
                     }
                     if ($vb_return_as_link) {
                         $vs_prop = array_shift(caCreateLinksFromText(array($vs_prop), $vs_table_name, array($vn_id)));
                     }
                     $va_return_values[$vn_id][$vm_locale_id] = $vs_prop;
                 }
             }
             break;
         case FT_MEDIA:
             if (!($vs_version = $va_path_components['subfield_name'])) {
                 $vs_version = "largeicon";
                 // TODO: fix
             }
             foreach ($pa_value_list as $vn_locale_id => $va_values) {
                 if ($pa_options['useLocaleCodes']) {
                     if (!$vn_locale_id || !($vm_locale_id = $this->opo_locales->localeIDToCode($vn_locale_id))) {
                         $vm_locale_id = __CA_DEFAULT_LOCALE__;
                     }
                 } else {
                     if (!($vm_locale_id = $vn_locale_id)) {
                         $vm_locale_id = $this->opo_locales->localeCodeToID(__CA_DEFAULT_LOCALE__);
                     }
                 }
                 foreach ($va_values as $vn_i => $va_value) {
                     $va_ids[] = $vn_id = $va_value[$vs_pk];
                     $o_media_settings = new MediaProcessingSettings($va_path_components['table_name'], $va_path_components['field_name']);
                     $va_versions = $o_media_settings->getMediaTypeVersions('*');
                     if (!isset($va_versions[$vs_version])) {
                         $va_tmp = array_keys($va_versions);
                         $vs_version = array_shift($va_tmp);
                     }
                     // See if an info element was passed, eg. ca_object_representations.media.icon.width should return the width of the media rather than a tag or url to the media
                     $vs_info_element = $va_path_components['num_components'] == 4 ? $va_path_components['components'][3] : null;
                     if ($pa_options['unserialize']) {
                         $va_return_values[$vn_id][$vm_locale_id] = caUnserializeForDatabase($va_value[$va_path_components['field_name']]);
                     } elseif ($vs_info_element) {
                         $va_return_values[$vn_id][$vm_locale_id] = $this->getMediaInfo($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $vs_info_element, $pa_options);
                     } elseif (isset($pa_options['returnURL']) && $pa_options['returnURL']) {
                         $va_return_values[$vn_id][$vm_locale_id] = $this->getMediaUrl($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $pa_options);
                     } elseif (isset($pa_options['returnPath']) && $pa_options['returnPath']) {
                         $va_return_values[$vn_id][$vm_locale_id] = $this->getMediaPath($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $pa_options);
                     } else {
                         $va_return_values[$vn_id][$vm_locale_id] = $this->getMediaTag($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $pa_options);
                     }
                 }
             }
             break;
         default:
             // is intrinsic field in primary table
             foreach ($pa_value_list as $vn_locale_id => $va_values) {
                 if ($pa_options['useLocaleCodes']) {
                     if (!$vn_locale_id || !($vm_locale_id = $this->opo_locales->localeIDToCode($vn_locale_id))) {
                         $vm_locale_id = __CA_DEFAULT_LOCALE__;
                     }
                 } else {
                     if (!($vm_locale_id = $vn_locale_id)) {
                         $vm_locale_id = $this->opo_locales->localeCodeToID(__CA_DEFAULT_LOCALE__);
                     }
                 }
                 foreach ($va_values as $vn_i => $va_value) {
                     $va_ids[] = $vn_id = $va_value[$vs_pk];
                     $vs_prop = $va_value[$va_path_components['field_name']];
                     if ($pa_options['unserialize']) {
                         $vs_prop = caUnserializeForDatabase($vs_prop);
                     }
                     if ($pa_options['convertCodesToDisplayText']) {
                         $vs_prop = $this->_convertCodeToDisplayText($vs_prop, $va_path_components, $pt_instance, $pa_options);
                     } elseif ($pa_options['convertCodesToIdno']) {
                         $vs_prop = $this->_convertCodeToIdno($vs_prop, $va_path_components, $pt_instance, $pa_options);
                     }
                     $va_return_values[$vn_id][$vm_locale_id] = $vs_prop;
                 }
             }
             break;
     }
     if (!$pa_options['returnAllLocales']) {
         $va_return_values = caExtractValuesByUserLocale($va_return_values);
     }
     if ($pa_options['returnWithStructure']) {
         return is_array($va_return_values) ? $va_return_values : array();
     }
     //
     // Flatten array for return as string or simple array value
     //
     $va_flattened_values = $this->_flattenArray($va_return_values, $pa_options);
     if ($pa_options['returnAsArray']) {
         return $va_flattened_values;
     } else {
         return sizeof($va_flattened_values) > 0 ? join($pa_options['delimiter'], $va_flattened_values) : null;
     }
 }
Ejemplo n.º 6
0
/**
 * Replace "^" tags (eg. ^forename) in a template with values from an array
 *
 * @param string $ps_template String with embedded tags. Tags are just alphanumeric strings prefixed with a caret ("^")
 * @param string $pm_tablename_or_num Table name or number of table from which values are being formatted
 * @param string $pa_row_ids An array of primary key values in the specified table to be pulled into the template
 * @param array $pa_options Supported options are:
 *		returnAsArray = if true an array of processed template values is returned, otherwise the template values are returned as a string joined together with a delimiter. Default is false.
 *		delimiter = value to string together template values with when returnAsArray is false. Default is ';' (semicolon)
 *		relatedValues = array of field values to return in template when directly referenced. Array should be indexed numerically in parallel with $pa_row_ids
 *		relationshipValues = array of field values to return in template for relationship when directly referenced. Should be indexed by row_id and then by relation_id
 *		placeholderPrefix = attribute container to implicitly place primary record fields into. Ex. if the table is "ca_entities" and the placeholder is "address" then tags like ^city will resolve to ca_entities.address.city
 *		requireLinkTags = if set then links are only added when explicitly defined with <l> tags. Default is to make the entire text a link in the absence of <l> tags.
 *		resolveLinksUsing = 
 *		primaryIDs = row_ids for primary rows in related table, keyed by table name; when resolving ambiguous relationships the row_ids will be excluded from consideration. This option is rarely used and exists primarily to take care of a single
 *						edge case: you are processing a template relative to a self-relationship such as ca_entities_x_entities that includes references to the subject table (ca_entities, in the case of ca_entities_x_entities). There are
 *						two possible paths to take in this situations; primaryIDs lets you specify which ones you *don't* want to take by row_id. For interstitial editors, the ids will be set to a single id: that of the subject (Eg. ca_entities) row
 *						from which the interstitial was launched.
 * @return mixed Output of processed templates
 */
function caProcessTemplateForIDs($ps_template, $pm_tablename_or_num, $pa_row_ids, $pa_options = null)
{
    unset($pa_options['request']);
    unset($pa_options['template']);
    // we pass through options to get() and don't want templates
    if (!isset($pa_options['convertCodesToDisplayText'])) {
        $pa_options['convertCodesToDisplayText'] = true;
    }
    $pb_return_as_array = (bool) caGetOption('returnAsArray', $pa_options, false);
    if (!is_array($pa_row_ids) || !sizeof($pa_row_ids) || !$ps_template) {
        return $pb_return_as_array ? array() : "";
    }
    unset($pa_options['returnAsArray']);
    if (!isset($pa_options['requireLinkTags'])) {
        $pa_options['requireLinkTags'] = true;
    }
    $va_primary_ids = caGetOption("primaryIDs", $pa_options, null);
    $o_dm = Datamodel::load();
    $ps_tablename = is_numeric($pm_tablename_or_num) ? $o_dm->getTableName($pm_tablename_or_num) : $pm_tablename_or_num;
    $ps_resolve_links_using = caGetOption('resolveLinksUsing', $pa_options, $ps_tablename);
    $t_instance = $o_dm->getInstanceByTableName($ps_tablename, true);
    if ($ps_resolve_links_using != $ps_tablename) {
        $t_resolve_links_instance = $o_dm->getInstanceByTableName($ps_resolve_links_using, true);
        $vs_resolve_links_using_pk = $t_resolve_links_instance->primaryKey();
    }
    $vs_pk = $t_instance->primaryKey();
    $vs_delimiter = isset($pa_options['delimiter']) ? $pa_options['delimiter'] : '; ';
    $ps_template = str_replace("^_parent", "^{$ps_resolve_links_using}.parent.preferred_labels", $ps_template);
    $ps_template = str_replace("^_hierarchy", "^{$ps_resolve_links_using}._hierarchyName", $ps_template);
    $va_related_values = isset($pa_options['relatedValues']) && is_array($pa_options['relatedValues']) ? $pa_options['relatedValues'] : array();
    $va_relationship_values = isset($pa_options['relationshipValues']) && is_array($pa_options['relationshipValues']) ? $pa_options['relationshipValues'] : array();
    // Set up DomDocument XML parser
    $o_dom = new DOMDocument('1.0', 'utf-8');
    $o_dom->preserveWhiteSpace = true;
    libxml_use_internal_errors(true);
    // don't reported mangled HTML errors
    $o_dom->loadHTML('<?xml encoding="utf-8">' . $ps_template);
    libxml_clear_errors();
    // Parse units from template
    $o_xpath = new DOMXPath($o_dom);
    $o_units = $o_xpath->query('//unit[not(ancestor::unit)]');
    // only find units not nested within other units (allows for units with units...)
    $va_units = array();
    $ps_template = preg_replace("![\r\n\t]+!", "", html_entity_decode($ps_template));
    //DomDocument kills newlines and tabs so we do the same to the template
    $ps_template = preg_replace("!relativeTo[ ]*\\=!i", "relativeto=", $ps_template);
    //DomDocument forces attribute names to all lower case so we need to adjust the template to match
    $ps_template = preg_replace("!restrictToTypes[ ]*\\=!i", "restricttotypes=", $ps_template);
    $ps_template = preg_replace("!restrictToRelationshipTypes[ ]*\\=!i", "restricttorelationshiptypes=", $ps_template);
    $ps_template = preg_replace("!([A-Za-z0-9]+)='([^']*)'!", "\$1=\"\$2\"", $ps_template);
    //DomDocument converts quotes around attributes from single to double quotes, so we need to normalize the template to match
    $ps_template = preg_replace("!\\>[ ]+\\<!", "><", $ps_template);
    $vn_unit_id = 1;
    foreach ($o_units as $o_unit) {
        if (!$o_unit) {
            continue;
        }
        $vs_html = (string) $o_dom->saveXML($o_unit);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        $vs_content = preg_replace("!>[ ]+<\$!", "><", $vs_content);
        // DomDocument messes with white space and encodes entities so we normalize the directive here so the str_ireplace() replacement below doesn't fail
        $va_units[] = $va_unit = array('tag' => $vs_unit_tag = "[[#{$vn_unit_id}]]", 'directive' => preg_replace("![\r\n\t\"]+!", "", html_entity_decode($vs_html)), 'content' => $vs_content, 'relativeTo' => (string) $o_unit->getAttribute("relativeto"), 'delimiter' => (string) $o_unit->getAttribute("delimiter"), 'restrictToTypes' => (string) $o_unit->getAttribute("restricttotypes"), 'restrictToRelationshipTypes' => (string) $o_unit->getAttribute("restricttorelationshiptypes"));
        $ps_template = str_ireplace($va_unit['directive'], $vs_unit_tag, $ps_template);
        $vn_unit_id++;
    }
    $va_tags = array();
    if (preg_match_all("!\\^([A-Za-z0-9_\\.]+[%]{1}[^ \\^\t\r\n\"\\'<>\\(\\)\\{\\}\\/\\[\\]]*|[A-Za-z0-9_\\.]+)!", $ps_template, $va_matches)) {
        $va_tags = $va_matches[1];
    }
    $qr_res = $t_instance->makeSearchResult($ps_tablename, $pa_row_ids);
    if (!$qr_res) {
        return '';
    }
    $va_proc_templates = array();
    $vn_i = 0;
    // Parse template
    $o_dom->loadHTML('<?xml encoding="utf-8">' . $ps_template);
    libxml_clear_errors();
    $o_if = $o_dom->getElementsByTagName("if");
    // if
    $o_ifdefs = $o_dom->getElementsByTagName("ifdef");
    // if defined
    $o_ifnotdefs = $o_dom->getElementsByTagName("ifnotdef");
    // if not defined
    $o_mores = $o_dom->getElementsByTagName("more");
    // more tags – content suppressed if there are no defined values following the tag pair
    $o_betweens = $o_dom->getElementsByTagName("between");
    // between tags – content suppressed if there are not defined values on both sides of the tag pair
    $o_options = $o_dom->getElementsByTagName("options");
    $va_if = array();
    foreach ($o_if as $o_if) {
        if (!$o_if) {
            continue;
        }
        $vs_html = $o_dom->saveXML($o_if);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        //
        // Hack to get around DomDocument trimming leading spaces off of parsed HTML
        // We try here to detect the trimming and shunt those spaces back where they belong. Seems to work :-)
        //
        if (preg_match("!([ ]+){$vs_content}!", $ps_template, $va_match_spaces)) {
            $vs_html = preg_replace("!{$vs_content}!", $va_match_spaces[1] . $vs_content, $vs_html);
            $vs_content = $va_match_spaces[1] . $vs_content;
        }
        $va_if[] = array('directive' => $vs_html, 'content' => $vs_content, 'rule' => $vs_rule = (string) $o_if->getAttribute('rule'));
        //$vs_code = preg_replace("!%(.*)$!", '', $vs_code);
        //if (!in_array($vs_code, $va_tags)) { $va_tags[] = $vs_code; }
    }
    //print_r($va_if);
    $va_ifdefs = array();
    foreach ($o_ifdefs as $o_ifdef) {
        if (!$o_ifdef) {
            continue;
        }
        $vs_html = $o_dom->saveXML($o_ifdef);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        //
        // Hack to get around DomDocument trimming leading spaces off of parsed HTML
        // We try here to detect the trimming and shunt those spaces back where they belong. Seems to work :-)
        //
        if (preg_match("!([ ]+){$vs_content}!", $ps_template, $va_match_spaces)) {
            $vs_html = preg_replace("!{$vs_content}!", $va_match_spaces[1] . $vs_content, $vs_html);
            $vs_content = $va_match_spaces[1] . $vs_content;
        }
        $va_ifdefs[$vs_code = (string) $o_ifdef->getAttribute('code')][] = array('directive' => $vs_html, 'content' => $vs_content);
        $vs_code = preg_replace("!%(.*)\$!", '', $vs_code);
        if (!in_array($vs_code, $va_tags)) {
            $va_tags[] = $vs_code;
        }
    }
    $va_ifnotdefs = array();
    foreach ($o_ifnotdefs as $o_ifnotdef) {
        if (!$o_ifnotdef) {
            continue;
        }
        $vs_html = $o_dom->saveXML($o_ifnotdef);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        //
        // Hack to get around DomDocument trimming leading spaces off of parsed HTML
        // We try here to detect the trimming and shunt those spaces back where they belong. Seems to work :-)
        //
        if (preg_match("!([ ]+){$vs_content}!", $ps_template, $va_match_spaces)) {
            $vs_html = preg_replace("!{$vs_content}!", $va_match_spaces[1] . $vs_content, $vs_html);
            $vs_content = $va_match_spaces[1] . $vs_content;
        }
        $va_ifnotdefs[$vs_code = (string) $o_ifnotdef->getAttribute('code')][] = array('directive' => $vs_html, 'content' => $vs_content);
        $vs_code = preg_replace("!%(.*)\$!", '', $vs_code);
        if (!in_array($vs_code, $va_tags)) {
            $va_tags[] = $vs_code;
        }
    }
    $va_mores = array();
    foreach ($o_mores as $o_more) {
        if (!$o_more) {
            continue;
        }
        $vs_html = $o_dom->saveXML($o_more);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        $va_mores[] = array('directive' => $vs_html, 'content' => $vs_content);
    }
    $va_betweens = array();
    foreach ($o_betweens as $o_between) {
        if (!$o_between) {
            continue;
        }
        $vs_html = $o_dom->saveXML($o_between);
        $vs_content = preg_replace("!^<[^\\>]+>!", "", $vs_html);
        $vs_content = preg_replace("!<[^\\>]+>\$!", "", $vs_content);
        $va_betweens[] = array('directive' => $vs_html, 'content' => $vs_content);
    }
    $va_resolve_links_using_row_ids = array();
    $va_tag_val_list = $va_defined_tag_list = array();
    while ($qr_res->nextHit()) {
        $vs_pk_val = $qr_res->get($vs_pk);
        $va_proc_templates[$vn_i] = preg_replace("![\r\n\t]+!", "", html_entity_decode($ps_template));
        // DomDocument messes with white space and encodes entities so we normalize things here so the str_ireplace() replacement below doesn't fail
        foreach ($va_units as $va_unit) {
            if (!$va_unit['content']) {
                continue;
            }
            $va_relative_to_tmp = $va_unit['relativeTo'] ? explode(".", $va_unit['relativeTo']) : array($ps_tablename);
            if (!($t_instance = $o_dm->getInstanceByTableName($va_relative_to_tmp[0], true))) {
                continue;
            }
            $vs_unit_delimiter = caGetOption('delimiter', $va_unit, '; ');
            // additional get options for pulling related records
            $va_get_options = array('returnAsArray' => true);
            if ($va_unit['restrictToTypes'] && strlen($va_unit['restrictToTypes']) > 0) {
                $va_get_options['restrictToTypes'] = explode('|', $va_unit['restrictToTypes']);
            }
            if ($va_unit['restrictToRelationshipTypes'] && strlen($va_unit['restrictToRelationshipTypes']) > 0) {
                $va_get_options['restrictToRelationshipTypes'] = explode('|', $va_unit['restrictToRelationshipTypes']);
            }
            if (sizeof($va_relative_to_tmp) == 1 && $va_relative_to_tmp[0] == $ps_tablename || sizeof($va_relative_to_tmp) >= 1 && $va_relative_to_tmp[0] == $ps_tablename && $va_relative_to_tmp[1] != 'related') {
                switch (strtolower($va_relative_to_tmp[1])) {
                    case 'hierarchy':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".hierarchy." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'parent':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".parent." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'children':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".children." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    default:
                        $va_relative_ids = $pa_row_ids;
                        break;
                }
            } else {
                switch (strtolower($va_relative_to_tmp[1])) {
                    case 'hierarchy':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".hierarchy." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'parent':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".parent." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'children':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".children." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    case 'related':
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . ".related." . $t_instance->primaryKey(), $va_get_options);
                        $va_relative_ids = array_values($va_relative_ids);
                        break;
                    default:
                        $va_relative_ids = $qr_res->get($t_instance->tableName() . "." . $t_instance->primaryKey(), $va_get_options);
                        break;
                }
            }
            $vs_tmpl_val = caProcessTemplateForIDs($va_unit['content'], $va_relative_to_tmp[0], $va_relative_ids, array_merge($pa_options, array('restrictToTypes' => caGetOption('restrictToTypes', $va_get_options, null), 'restrictToRelationshipTypes' => caGetOption('restrictToRelationshipTypes', $va_get_options, null)), array('delimiter' => $vs_unit_delimiter, 'resolveLinksUsing' => null)));
            $va_proc_templates[$vn_i] = str_ireplace($va_unit['tag'], $vs_tmpl_val, $va_proc_templates[$vn_i]);
        }
        if (!strlen(trim($va_proc_templates[$vn_i]))) {
            $va_proc_templates[$vn_i] = null;
        }
        if (!sizeof($va_tags)) {
            continue;
        }
        // if there are no tags in the template then we don't need to process further
        if ($ps_resolve_links_using != $ps_tablename) {
            $va_resolve_links_using_row_ids[] = $qr_res->get("{$ps_resolve_links_using}.{$vs_resolve_links_using_pk}");
        }
        $va_tag_val_list[$vn_i] = array();
        $va_defined_tag_list[$vn_i] = array();
        $va_tag_opts = array();
        foreach ($va_tags as $vs_tag) {
            $va_tmp = explode('.', $vs_tag);
            $vs_last_element = $va_tmp[sizeof($va_tmp) - 1];
            $va_tag_opt_tmp = explode("%", $vs_last_element);
            if (sizeof($va_tag_opt_tmp) > 1) {
                $vs_tag_bit = array_shift($va_tag_opt_tmp);
                // get rid of getspec
                foreach ($va_tag_opt_tmp as $vs_tag_opt_raw) {
                    $va_tag_tmp = explode("=", $vs_tag_opt_raw);
                    $va_tag_tmp[0] = trim($va_tag_tmp[0]);
                    $va_tag_tmp[1] = trim($va_tag_tmp[1]);
                    if (in_array($va_tag_tmp[0], array('delimiter', 'hierarchicalDelimiter'))) {
                        $va_tag_tmp[1] = str_replace("_", " ", $va_tag_tmp[1]);
                    }
                    if (sizeof($va_tag_line_tmp = explode("|", $va_tag_tmp[1])) > 1) {
                        $va_tag_opts[trim($va_tag_tmp[0])] = $va_tag_line_tmp;
                    } else {
                        $va_tag_opts[trim($va_tag_tmp[0])] = $va_tag_tmp[1];
                    }
                }
                $va_tmp[sizeof($va_tmp) - 1] = $vs_tag_bit;
                // remove option from tag-part array
                $vs_tag_proc = join(".", $va_tmp);
                $va_proc_templates[$vn_i] = str_replace($vs_tag, $vs_tag_proc, $va_proc_templates[$vn_i]);
                $vs_tag = $vs_tag_proc;
            }
            $pa_options = array_merge($pa_options, $va_tag_opts);
            // Default label tag to hierarchies
            if (isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels'] && $vs_tag == 'label') {
                unset($va_related_values[$vs_pk_val][$vs_tag]);
                unset($va_relationship_values[$vs_pk_val][$vs_tag]);
                $va_tmp = array($ps_tablename, 'hierarchy', 'preferred_labels');
            }
            if (!isset($va_relationship_values[$vs_pk_val])) {
                $va_relationship_values[$vs_pk_val] = array(0 => null);
            }
            foreach ($va_relationship_values[$vs_pk_val] as $vn_relation_id => $va_relationship_value_array) {
                $vb_is_related = false;
                $va_val = null;
                if (isset($va_relationship_value_array[$vs_tag]) && !(isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels'] && $vs_tag == 'label')) {
                    $va_val = array($vs_val = $va_relationship_value_array[$vs_tag]);
                } else {
                    if (isset($va_related_values[$vs_pk_val][$vs_tag])) {
                        $va_val = array($vs_val = $va_related_values[$vs_pk_val][$vs_tag]);
                    } else {
                        //
                        // see if this is a reference to a related table
                        //
                        if ($ps_tablename != $va_tmp[0] && ($t_tmp = $o_dm->getInstanceByTableName($va_tmp[0], true))) {
                            // if the part of the tag before a "." (or the tag itself if there are no periods) is a related table then try to fetch it as related to the current record
                            if (isset($pa_options['placeholderPrefix']) && $pa_options['placeholderPrefix'] && $va_tmp[0] != $pa_options['placeholderPrefix'] && sizeof($va_tmp) == 1) {
                                $vs_get_spec = array_shift($va_tmp) . "." . $pa_options['placeholderPrefix'];
                                if (sizeof($va_tmp) > 0) {
                                    $vs_get_spec .= "." . join(".", $va_tmp);
                                }
                            } else {
                                $vs_get_spec = $vs_tag;
                            }
                            $va_spec_bits = explode(".", $vs_get_spec);
                            if (sizeof($va_spec_bits) == 1 && $o_dm->getTableNum($va_spec_bits[0])) {
                                $vs_get_spec .= ".preferred_labels";
                            }
                            $va_additional_options = array('returnAsArray' => true);
                            $vs_hierarchy_name = null;
                            if (in_array($va_spec_bits[1], array('hierarchy', '_hierarchyName'))) {
                                $t_rel = $o_dm->getInstanceByTableName($va_spec_bits[0], true);
                                switch ($t_rel->getProperty('HIERARCHY_TYPE')) {
                                    case __CA_HIER_TYPE_SIMPLE_MONO__:
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                    case __CA_HIER_TYPE_MULTI_MONO__:
                                        $vs_hierarchy_name = $t_rel->getHierarchyName($qr_res->get($t_rel->tableName() . "." . $t_rel->primaryKey()));
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                }
                            }
                            if ($va_spec_bits[1] != '_hierarchyName') {
                                $va_val = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_additional_options, array("returnAsArray" => true, "returnAllLocales" => true)));
                            } else {
                                $va_val = array();
                            }
                            if (is_array($va_primary_ids) && isset($va_primary_ids[$va_spec_bits[0]]) && is_array($va_primary_ids[$va_spec_bits[0]])) {
                                foreach ($va_primary_ids[$va_spec_bits[0]] as $vn_primary_id) {
                                    unset($va_val[$vn_primary_id]);
                                }
                            }
                            $va_val = caExtractValuesByUserLocale($va_val);
                            $va_val_tmp = array();
                            foreach ($va_val as $vn_d => $va_vals) {
                                $va_val_tmp = array_merge($va_val_tmp, $va_vals);
                            }
                            $va_val = $va_val_tmp;
                            $va_val_proc = array();
                            switch ($va_spec_bits[1]) {
                                case '_hierarchyName':
                                    if ($vs_hierarchy_name) {
                                        $va_val_proc[] = $vs_hierarchy_name;
                                    }
                                    break;
                                case 'hierarchy':
                                    if (is_array($va_val) && sizeof($va_val) > 0) {
                                        if ($vs_hierarchy_name) {
                                            array_unshift($va_val, $vs_hierarchy_name);
                                        }
                                        foreach ($va_val as $va_hier) {
                                            if (!is_array($va_hier)) {
                                                $va_hier = array($va_hier);
                                            }
                                            $va_val_proc[] = join(caGetOption("delimiter", $va_tag_opts, "; "), $va_hier);
                                        }
                                    }
                                    break;
                                case 'parent':
                                    if (is_array($va_val)) {
                                        foreach ($va_val as $va_label) {
                                            $va_val_proc[] = $va_label['name'];
                                        }
                                    }
                                    break;
                                default:
                                    $vs_terminal = end($va_spec_bits);
                                    foreach ($va_val as $va_val_container) {
                                        if (!is_array($va_val_container)) {
                                            if ($va_val_container) {
                                                $va_val_proc[] = $va_val_container;
                                            }
                                            continue;
                                        }
                                        $va_val_proc[] = $va_val_container[$vs_terminal];
                                    }
                                    break;
                            }
                            $va_val = $va_val_proc;
                            $vb_is_related = true;
                        } else {
                            //
                            // Handle non-related gets
                            //
                            // Default specifiers that end with a modifier to preferred labels
                            if (sizeof($va_tmp) == 2 && in_array($va_tmp[1], array('hierarchy', 'children', 'parent', 'related'))) {
                                array_push($va_tmp, 'preferred_labels');
                            }
                            $vs_hierarchy_name = null;
                            if (in_array($va_tmp[1], array('hierarchy', '_hierarchyName'))) {
                                switch ($t_instance->getProperty('HIERARCHY_TYPE')) {
                                    case __CA_HIER_TYPE_SIMPLE_MONO__:
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                    case __CA_HIER_TYPE_MULTI_MONO__:
                                        $vs_hierarchy_name = $t_instance->getHierarchyName($qr_res->get($t_instance->tableName() . "." . $t_instance->primaryKey()));
                                        $va_additional_options['removeFirstItems'] = 1;
                                        break;
                                }
                            }
                            if ($va_tmp[0] == $ps_tablename) {
                                array_shift($va_tmp);
                            }
                            // get rid of primary table if it's in the field spec
                            if (!sizeof($va_tmp) && $t_instance->getProperty('LABEL_TABLE_NAME')) {
                                $va_tmp[] = "preferred_labels";
                            }
                            if (isset($pa_options['showHierarchicalLabels']) && $pa_options['showHierarchicalLabels']) {
                                if (!in_array($va_tmp[0], array('hierarchy', 'children', 'parent', 'related')) && $va_tmp[1] == 'preferred_labels') {
                                    array_unshift($va_tmp, 'hierarchy');
                                }
                            }
                            if (isset($pa_options['placeholderPrefix']) && $pa_options['placeholderPrefix'] && $va_tmp[0] != $pa_options['placeholderPrefix']) {
                                array_splice($va_tmp, -1, 0, $pa_options['placeholderPrefix']);
                            }
                            $vs_get_spec = "{$ps_tablename}." . join(".", $va_tmp);
                            if (in_array($va_tmp[0], array('parent'))) {
                                $va_val[] = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_tag_opts, array('returnAsArray' => false)));
                            } else {
                                $va_val_tmp = $qr_res->get($vs_get_spec, array_merge($pa_options, $va_tag_opts, array('returnAsArray' => true)));
                                $va_val = array();
                                if (is_array($va_val_tmp)) {
                                    foreach ($va_val_tmp as $vn_attr_id => $vm_attr_val) {
                                        if (is_array($vm_attr_val)) {
                                            if ($va_tmp[0] == 'hierarchy') {
                                                if ($vs_hierarchy_name) {
                                                    array_shift($vm_attr_val);
                                                    // remove root
                                                    array_unshift($vm_attr_val, $vs_hierarchy_name);
                                                    // replace with hierarchy name
                                                }
                                                if ($vs_delimiter_tmp = caGetOption('hierarchicalDelimiter', $va_tag_opts)) {
                                                    $vs_tag_val_delimiter = $vs_delimiter_tmp;
                                                } elseif ($vs_delimiter_tmp = caGetOption('hierarchicalDelimiter', $pa_options)) {
                                                    $vs_tag_val_delimiter = $vs_delimiter_tmp;
                                                } else {
                                                    $vs_tag_val_delimiter = caGetOption('delimiter', $va_tag_opts, $vs_delimiter);
                                                }
                                            } else {
                                                $vs_tag_val_delimiter = caGetOption('delimiter', $va_tag_opts, $vs_delimiter);
                                            }
                                            $va_val[] = join($vs_tag_val_delimiter, $vm_attr_val);
                                        } else {
                                            $va_val[] = $vm_attr_val;
                                        }
                                    }
                                }
                                if (sizeof($va_val) > 1 && $va_tmp[0] == 'hierarchy') {
                                    $vs_tag_val_delimiter = caGetOption('delimiter', $va_tag_opts, $vs_delimiter);
                                    $va_val = array(join($vs_tag_val_delimiter, $va_val));
                                }
                            }
                        }
                    }
                }
                if (is_array($va_val)) {
                    if (sizeof($va_val) > 0) {
                        foreach ($va_val as $vn_j => $vs_val) {
                            if (!is_array($va_tag_val_list[$vn_i][$vn_j][$vs_tag]) || !in_array($vs_val, $va_tag_val_list[$vn_i][$vn_j][$vs_tag])) {
                                $va_tag_val_list[$vn_i][$vn_j][$vs_tag][] = $vs_val;
                                if (is_array($vs_val) && sizeof($vs_val) || strlen($vs_val) > 0) {
                                    $va_defined_tag_list[$vn_i][$vn_j][$vs_tag] = true;
                                }
                            }
                        }
                    } else {
                        $va_tag_val_list[$vn_i][0][$vs_tag] = null;
                        $va_defined_tag_list[$vn_i][0][$vs_tag] = false;
                    }
                }
            }
        }
        $vn_i++;
    }
    foreach ($va_tag_val_list as $vn_i => $va_tags_list) {
        $va_acc = array();
        foreach ($va_tags_list as $vn_j => $va_tags) {
            $va_tag_list = array();
            $va_pt_vals = array();
            $vs_template = $va_proc_templates[$vn_i];
            // Process <if>
            foreach ($va_if as $va_def_con) {
                if (ExpressionParser::evaluate($va_def_con['rule'], $va_tags)) {
                    $vs_template = str_replace($va_def_con['directive'], $va_def_con['content'], $vs_template);
                } else {
                    $vs_template = str_replace($va_def_con['directive'], '', $vs_template);
                }
            }
            // Process <ifdef> (IF DEFined)
            foreach ($va_ifdefs as $vs_code => $va_def_con) {
                if (strpos($vs_code, "|") !== false) {
                    $vs_bool = 'OR';
                    $va_tag_list = explode("|", $vs_code);
                    $vb_output = false;
                } else {
                    $vs_bool = 'AND';
                    $va_tag_list = explode(",", $vs_code);
                    $vb_output = true;
                }
                foreach ($va_tag_list as $vs_tag_to_test) {
                    $vb_value_is_set = (bool) (isset($va_tags[$vs_tag_to_test]) && sizeof($va_tags[$vs_tag_to_test]) > 1 || sizeof($va_tags[$vs_tag_to_test]) == 1 && strlen($va_tags[$vs_tag_to_test][0]) > 0);
                    switch ($vs_bool) {
                        case 'OR':
                            if ($vb_value_is_set) {
                                $vb_output = true;
                                break 2;
                            }
                            // any must be defined; if any is defined output
                            break;
                        case 'AND':
                        default:
                            if (!$vb_value_is_set) {
                                $vb_output = false;
                                break 2;
                            }
                            // all must be defined; if any is not defined don't output
                            break;
                    }
                }
                foreach ($va_def_con as $va_ifdef) {
                    if ($vb_output) {
                        $vs_template = str_replace($va_ifdef['directive'], $va_ifdef['content'], $vs_template);
                    } else {
                        $vs_template = str_replace($va_ifdef['directive'], '', $vs_template);
                    }
                }
            }
            // Process <ifnotdef> (IF NOT DEFined)
            foreach ($va_ifnotdefs as $vs_code => $va_notdef_con) {
                if (strpos($vs_code, "|") !== false) {
                    $vs_bool = 'OR';
                    $va_tag_list = explode("|", $vs_code);
                    $vb_output = false;
                } else {
                    $vs_bool = 'AND';
                    $va_tag_list = explode(",", $vs_code);
                    $vb_output = true;
                }
                $vb_output = true;
                foreach ($va_tag_list as $vs_tag_to_test) {
                    $vb_value_is_set = (bool) (isset($va_tags[$vs_tag_to_test]) && sizeof($va_tags[$vs_tag_to_test]) > 1 || sizeof($va_tags[$vs_tag_to_test]) == 1 && strlen($va_tags[$vs_tag_to_test][0]) > 0);
                    switch ($vs_bool) {
                        case 'OR':
                            if (!$vb_value_is_set) {
                                $vb_output = true;
                                break 2;
                            }
                            // any must be not defined; if anything is not set output
                            break;
                        case 'AND':
                        default:
                            if ($vb_value_is_set) {
                                $vb_output = false;
                                break 2;
                            }
                            // all must be not defined; if anything is set don't output
                            break;
                    }
                }
                foreach ($va_notdef_con as $va_ifnotdef) {
                    if ($vb_output) {
                        $vs_template = str_replace($va_ifnotdef['directive'], $va_ifnotdef['content'], $vs_template);
                    } else {
                        $vs_template = str_replace($va_ifnotdef['directive'], '', $vs_template);
                    }
                }
            }
            // Process <more> tags
            foreach ($va_mores as $vn_more_index => $va_more) {
                if (($vn_pos = strpos($vs_template, $va_more['directive'])) !== false) {
                    if (isset($va_mores[$vn_more_index + 1]) && ($vn_next_more_pos = strpos(substr($vs_template, $vn_pos + strlen($va_more['directive'])), $va_mores[$vn_more_index + 1]['directive'])) !== false) {
                        $vn_next_more_pos += $vn_pos;
                        $vs_partial_template = substr($vs_template, $vn_pos + strlen($va_more['directive']), $vn_next_more_pos - $vn_pos);
                    } else {
                        $vs_partial_template = substr($vs_template, $vn_pos + strlen($va_more['directive']));
                    }
                    $vb_output = false;
                    foreach (array_keys($va_defined_tag_list[$vn_i][$vn_j]) as $vs_defined_tag) {
                        if (strpos($vs_partial_template, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output = true;
                            break;
                        }
                    }
                    if ($vb_output) {
                        $vs_template = preg_replace('!' . $va_more['directive'] . '!', $va_more['content'], $vs_template, 1);
                    } else {
                        $vs_template = preg_replace('!' . $va_more['directive'] . '!', '', $vs_template, 1);
                    }
                }
            }
            // Process <between> tags - text to be output if it is between two defined values
            $va_between_positions = array();
            foreach ($va_betweens as $vn_between_index => $va_between) {
                $vb_output_before = $vb_output_after = false;
                if (($vn_cur_pos = strpos($vs_template, $va_between['directive'])) !== false) {
                    $va_between_positions[$vn_between_index] = $vn_cur_pos;
                    // Get parts of template before tag and after tag
                    $vs_partial_template_before = substr($vs_template, 0, $vn_cur_pos);
                    $vs_partial_template_after = substr($vs_template, $vn_cur_pos + strlen($va_between['directive']));
                    // Only get the template between our current position and the next <between> tag
                    if (isset($va_betweens[$vn_between_index + 1]) && ($vn_after_pos_relative = strpos($vs_partial_template_after, $va_betweens[$vn_between_index + 1]['directive'])) !== false) {
                        $vs_partial_template_after = substr($vs_partial_template_after, 0, $vn_after_pos_relative);
                    }
                    // Check for defined value before and after tag
                    foreach (array_keys($va_defined_tag_list[$vn_i][$vn_j]) as $vs_defined_tag) {
                        if (strpos($vs_partial_template_before, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output_after = true;
                        }
                        if (strpos($vs_partial_template_after, $vs_defined_tag) !== false) {
                            // content is defined
                            $vb_output_before = true;
                            break;
                        }
                        if ($vb_output_before && $vb_output_after) {
                            break;
                        }
                    }
                }
                if ($vb_output_before && $vb_output_after) {
                    $vs_template = preg_replace('!' . $va_between['directive'] . '!', $va_between['content'], $vs_template, 1);
                } else {
                    $vs_template = preg_replace('!' . $va_between['directive'] . '!', '', $vs_template, 1);
                }
            }
            //
            // Need to sort tags by length descending (longest first)
            // so that when we go to substitute and you have a tag followed by itself with a suffix
            // (ex. ^measurements and ^measurements2) we don't substitute both for the stub (ex. ^measurements)
            //
            $va_tags_tmp = array_keys($va_tags);
            usort($va_tags_tmp, function ($a, $b) {
                return strlen($b) - strlen($a);
            });
            $vs_pt = $vs_template;
            foreach ($va_tags_tmp as $vs_tag) {
                $vs_pt = str_replace('^' . $vs_tag, is_array($va_tags[$vs_tag]) ? join(" | ", $va_tags[$vs_tag]) : $va_tags[$vs_tag], $vs_pt);
            }
            $va_pt_vals[] = $vs_pt;
            $va_acc[] = join(isset($pa_options['delimiter']) ? $pa_options['delimiter'] : $vs_delimiter, $va_pt_vals);
        }
        $va_proc_templates[$vn_i] = join($vs_delimiter, $va_acc);
    }
    foreach ($va_proc_templates as $vn_i => $vs_template) {
        if (!strlen(trim($vs_template))) {
            unset($va_proc_templates[$vn_i]);
        }
    }
    // Transform links
    $va_proc_templates = caCreateLinksFromText($va_proc_templates, $ps_resolve_links_using, $ps_resolve_links_using != $ps_tablename ? $va_resolve_links_using_row_ids : $pa_row_ids, null, null, $pa_options);
    // Kill any lingering tags (just in case)
    foreach ($va_proc_templates as $vn_i => $vs_proc_template) {
        $va_proc_templates[$vn_i] = preg_replace("!\\^([A-Za-z0-9_\\.]+[%]{1}[^ \\^\t\r\n\"\\'<>\\(\\)\\{\\}\\/\\[\\]]*|[A-Za-z0-9_\\.]+)!", "", $vs_proc_template);
    }
    if ($pb_return_as_array) {
        return $va_proc_templates;
    }
    return join($vs_delimiter, $va_proc_templates);
}
Ejemplo n.º 7
0
 /**
  * Implementation of primary get() functionality
  */
 private function _get($ps_field, $pa_options = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     if (isset($pa_options['restrictToType']) && (!isset($pa_options['restrict_to_type']) || !$pa_options['restrict_to_type'])) {
         $pa_options['restrict_to_type'] = $pa_options['restrictToType'];
     }
     if (isset($pa_options['restrictToTypes']) && (!isset($pa_options['restrict_to_types']) || !$pa_options['restrict_to_types'])) {
         $pa_options['restrict_to_types'] = $pa_options['restrictToTypes'];
     }
     if (isset($pa_options['restrictToRelationshipTypes']) && (!isset($pa_options['restrict_to_relationship_types']) || !$pa_options['restrict_to_relationship_types'])) {
         $pa_options['restrict_to_relationship_types'] = $pa_options['restrictToRelationshipTypes'];
     }
     if (isset($pa_options['excludeType']) && (!isset($pa_options['exclude_type']) || !$pa_options['exclude_type'])) {
         $pa_options['exclude_type'] = $pa_options['excludeType'];
     }
     if (isset($pa_options['excludeTypes']) && (!isset($pa_options['exclude_types']) || !$pa_options['exclude_types'])) {
         $pa_options['exclude_types'] = $pa_options['excludeTypes'];
     }
     if (isset($pa_options['excludeRelationshipTypes']) && (!isset($pa_options['exclude_relationship_types']) || !$pa_options['exclude_relationship_types'])) {
         $pa_options['exclude_relationship_types'] = $pa_options['excludeRelationshipTypes'];
     }
     $vb_return_as_array = caGetOption('returnAsArray', $pa_options, false, array('castTo' => 'bool'));
     $vb_return_all_locales = caGetOption('returnAllLocales', $pa_options, false, array('castTo' => 'bool'));
     $vb_return_as_link = caGetOption('returnAsLink', $pa_options, false, array('castTo' => 'bool'));
     $vs_return_as_link_text = caGetOption('returnAsLinkText', $pa_options, '');
     $vs_return_as_link_target = caGetOption('returnAsLinkTarget', $pa_options, '');
     $vs_return_as_link_attributes = caGetOption('returnAsLinkAttributes', $pa_options, array(), array('castTo' => 'array'));
     $va_original_path_components = $va_path_components = $this->getFieldPathComponents($ps_field);
     if ($va_path_components['table_name'] != $this->ops_table_name) {
         $vs_access_chk_key = $va_path_components['table_name'] . ($va_path_components['field_name'] ? '.' . $va_path_components['field_name'] : '');
     } else {
         $vs_access_chk_key = $va_path_components['field_name'];
     }
     if (caGetBundleAccessLevel($this->ops_table_name, $vs_access_chk_key) == __CA_BUNDLE_ACCESS_NONE__) {
         return null;
     }
     $vo_request = caGetOption('request', $pa_options, null);
     unset($pa_options['request']);
     // first see if the search engine can provide the field value directly (fastest)
     if (!(($vs_value = $this->opo_engine_result->get($ps_field, $pa_options)) === false)) {
         if ($vb_return_as_array) {
             if ($vb_return_all_locales) {
                 return array(1 => $vs_value);
             } else {
                 return array($vs_value);
             }
         } else {
             return $vs_value;
         }
     }
     $vs_template = caGetOption('template', $pa_options, null);
     $vs_delimiter = caGetOption('delimiter', $pa_options, ' ');
     $vs_hierarchical_delimiter = caGetOption('hierarchicalDelimiter', $pa_options, ' ');
     if ($vb_return_all_locales && !$vb_return_as_array) {
         $vb_return_as_array = true;
     }
     if (isset($pa_options['sort']) && !is_array($pa_options['sort'])) {
         $pa_options['sort'] = array($pa_options['sort']);
     }
     if (is_array($va_sort_fields = isset($pa_options['sort']) && is_array($pa_options['sort']) ? $pa_options['sort'] : null)) {
         foreach ($va_sort_fields as $vn_i => $vs_sort_fld) {
             if (!trim($vs_sort_fld)) {
                 unset($va_sort_fields[$vn_i]);
             }
         }
     }
     $vn_row_id = $this->opo_engine_result->get($this->ops_table_pk);
     // try to lazy load (slower)...
     //
     // Are we getting timestamp (created on or last modified) info?
     //
     if ($va_path_components['table_name'] == $this->ops_table_name && $va_path_components['field_name'] == 'created') {
         if (!isset($this->opa_timestamp_cache['created_on'][$this->ops_table_name][$vn_row_id])) {
             $this->prefetchChangeLogData($this->ops_table_name, $this->opo_engine_result->currentRow(), $this->getOption('prefetch'));
         }
         if ($vb_return_as_array) {
             return $this->opa_timestamp_cache['created_on'][$this->ops_table_name][$vn_row_id];
         } else {
             $vs_subfield = $va_path_components['subfield_name'] ? $va_path_components['subfield_name'] : 'timestamp';
             $vm_val = $this->opa_timestamp_cache['created_on'][$this->ops_table_name][$vn_row_id][$vs_subfield];
             if ($vs_subfield == 'timestamp') {
                 $o_tep = new TimeExpressionParser();
                 $o_tep->setUnixTimestamps($vm_val, $vm_val);
                 $vm_val = $o_tep->getText($pa_options);
             }
             return $vm_val;
         }
     }
     if ($va_path_components['table_name'] == $this->ops_table_name && $va_path_components['field_name'] == 'lastModified') {
         if (!isset($this->opa_timestamp_cache['last_changed'][$this->ops_table_name][$vn_row_id])) {
             $this->prefetchChangeLogData($this->ops_table_name, $this->opo_engine_result->currentRow(), $this->getOption('prefetch'));
         }
         if ($vb_return_as_array) {
             return $this->opa_timestamp_cache['last_changed'][$this->ops_table_name][$vn_row_id];
         } else {
             $vs_subfield = $va_path_components['subfield_name'] ? $va_path_components['subfield_name'] : 'timestamp';
             $vm_val = $this->opa_timestamp_cache['last_changed'][$this->ops_table_name][$vn_row_id][$vs_subfield];
             if ($vs_subfield == 'timestamp') {
                 $o_tep = new TimeExpressionParser();
                 $o_tep->setUnixTimestamps($vm_val, $vm_val);
                 $vm_val = $o_tep->getText($pa_options);
             }
             return $vm_val;
         }
     }
     if (!($t_instance = $this->opo_datamodel->getInstanceByTableName($va_path_components['table_name'], true))) {
         return null;
     }
     // Bad table
     $t_original_instance = $t_instance;
     // $t_original_instance will always be the as-called subject; optimizations may results in $t_instance being transformed into a different model
     //
     // Simple related table get:
     //			<table>
     //			<table>.related
     //			<table>.hierarchy
     //			<table>.related.hierarchy
     //
     if ($va_path_components['num_components'] == 1 && $va_path_components['table_name'] !== $this->ops_table_name || $va_path_components['num_components'] == 2 && $va_path_components['field_name'] == 'related' || $va_path_components['num_components'] == 2 && $va_path_components['field_name'] == 'hierarchy' || $va_path_components['num_components'] == 3 && $va_path_components['field_name'] == 'related' && $va_path_components['subfield_name'] == 'hierarchy') {
         if (!($t_table = $this->opo_datamodel->getInstanceByTableName($this->ops_table_name, true))) {
             return null;
         }
         $vb_show_hierarachy = (bool) ($va_path_components['field_name'] == 'hierarchy' && $t_instance->isHierarchical());
         if ($va_path_components['num_components'] == 2) {
             $va_path_components['num_components'] = 1;
             $va_path_components['field_name'] = null;
         }
         $vs_opt_md5 = caMakeCacheKeyFromOptions($pa_options);
         if (!isset($this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5])) {
             $this->prefetchRelated($va_path_components['table_name'], $this->opo_engine_result->currentRow(), $this->getOption('prefetch'), $pa_options);
         }
         $va_related_items = $this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5];
         if (!is_array($va_related_items)) {
             return null;
         }
         if (is_array($va_sort_fields) && sizeof($va_sort_fields)) {
             $va_related_items = caSortArrayByKeyInValue($va_related_items, $va_sort_fields);
         }
         // Return as array
         if ($vs_template) {
             return caProcessTemplateForIDs($vs_template, $this->opo_subject_instance->tableName(), array($vn_row_id), array_merge($pa_options, array('placeholderPrefix' => $va_path_components['field_name'])));
         }
         if ($vb_return_as_array || $vb_return_all_locales) {
             if ($vb_return_all_locales) {
                 $va_related_tmp = array();
                 foreach ($va_related_items as $vn_i => $va_related_item) {
                     $va_related_tmp[$vn_i][$va_related_item['locale_id']] = $va_related_item;
                 }
                 return $va_related_tmp;
             } else {
                 if (!$vs_template && !$va_path_components['field_name']) {
                     return $va_related_items;
                 }
                 $vs_pk = $t_instance->primaryKey();
                 $va_links = array();
                 foreach ($va_related_items as $vn_relation_id => $va_relation_info) {
                     $va_relation_info['labels'] = caExtractValuesByUserLocale(array(0 => $va_relation_info['labels']));
                     if ($vb_return_as_link) {
                         $va_template_opts = array();
                         $va_template_opts['relationshipValues'][$va_relation_info[$vs_pk]][$va_relation_info['relation_id']]['relationship_typename'] = $va_relation_info['relationship_typename'];
                         $vs_text = $vs_template ? caProcessTemplateForIDs($vs_template, $t_instance->tableName(), array($va_relation_info[$vs_pk]), $va_template_opts) : join("; ", $va_relation_info['labels']);
                         $va_link = caCreateLinksFromText(array($vs_text), $va_original_path_components['table_name'], array($va_relation_info[$vs_pk]), $vs_return_as_link_class, $vs_return_as_link_target);
                         $va_links[$vn_relation_id] = array_pop($va_link);
                     } else {
                         $va_related_items[$vn_relation_id]['labels'] = $va_relation_info['labels'];
                     }
                 }
                 if ($vb_return_as_link) {
                     return $va_links;
                 }
                 return $va_related_items;
             }
         } else {
             // Return scalar
             $va_proc_labels = array();
             $va_row_ids = array();
             $vs_rel_pk = $t_instance->primaryKey();
             $va_relationship_values = array();
             foreach ($va_related_items as $vn_relation_id => $va_relation_info) {
                 $va_row_ids[] = $va_relation_info[$vs_rel_pk];
                 $va_relationship_values[$va_relation_info[$vs_rel_pk]][$vn_relation_id] = array('relationship_typename' => $va_relation_info['relationship_typename'], 'relationship_type_id' => $va_relation_info['relationship_type_id'], 'relationship_type_code' => $va_relation_info['relationship_type_code'], 'relationship_typecode' => $va_relation_info['relationship_type_code'], 'label' => $va_relation_info['label']);
             }
             if (!sizeof($va_row_ids)) {
                 return '';
             }
             if (!$vs_template) {
                 $vs_template = "^label";
             }
             $va_template_opts = $pa_options;
             unset($va_template_opts['request']);
             unset($va_template_opts['template']);
             $va_template_opts['returnAsLink'] = false;
             $va_template_opts['returnAsArray'] = true;
             $va_text = caProcessTemplateForIDs($vs_template, $t_instance->tableNum(), $va_row_ids, array_merge($va_template_opts, array('relationshipValues' => $va_relationship_values, 'showHierarchicalLabels' => $vb_show_hierarachy)));
             if ($vb_return_as_link) {
                 $va_links = caCreateLinksFromText($va_text, $va_original_path_components['table_name'], $va_row_ids, $vs_return_as_link_class, $vs_return_as_link_target);
                 return join($vs_delimiter, $va_links);
             }
             return join($vs_delimiter, $va_text);
         }
     }
     $vb_need_parent = false;
     $vb_need_children = false;
     //
     // Transform "preferred_labels" into tables for pre-fetching
     //
     $vb_is_get_for_labels = $vb_return_all_label_values = $vb_get_preferred_labels_only = $vb_get_nonpreferred_labels_only = false;
     if (in_array($va_path_components['field_name'], array('preferred_labels', 'nonpreferred_labels'))) {
         if ($t_instance->getProperty('LABEL_TABLE_NAME')) {
             $vb_get_preferred_labels_only = $va_path_components['field_name'] == 'preferred_labels' ? true : false;
             $vb_get_nonpreferred_labels_only = $va_path_components['field_name'] == 'nonpreferred_labels' ? true : false;
             if ($va_path_components['num_components'] == 2) {
                 // if it's just <table_name>.preferred_labels then return an array of fields from the label table
                 $vb_return_all_label_values = true;
             }
             $va_path_components['table_name'] = $t_instance->getLabelTableName();
             $t_label_instance = $t_instance->getLabelTableInstance();
             if (!$va_path_components['subfield_name'] || !$t_label_instance->hasField($va_path_components['subfield_name'])) {
                 $va_path_components['field_name'] = $t_instance->getLabelDisplayField();
             } else {
                 $va_path_components['field_name'] = $va_path_components['subfield_name'];
             }
             $va_path_components['subfield_name'] = null;
             $va_path_components = $this->getFieldPathComponents($va_path_components['table_name'] . '.' . $va_path_components['field_name']);
             // Ok, convert the table instance to the label table since that's the table we'll be grabbing data from
             $t_instance = $t_label_instance;
             $vb_is_get_for_labels = true;
         }
     }
     //
     // Handle modifiers (parent, children, related, hierarchy) with and without fields
     //
     if ($va_path_components['num_components'] >= 2) {
         switch ($va_path_components['field_name']) {
             case 'parent':
                 if ($t_instance->isHierarchical() && ($vn_parent_id = $this->get($va_path_components['table_name'] . '.' . $t_instance->getProperty('HIERARCHY_PARENT_ID_FLD')))) {
                     //
                     // TODO: support some kind of prefetching of parents?
                     //
                     unset($va_path_components['components'][1]);
                     if ($t_instance->load($vn_parent_id)) {
                         return $t_instance->get(join('.', array_values($va_path_components['components'])), $pa_options);
                     }
                     return null;
                 }
                 break;
             case 'children':
                 if ($t_instance->isHierarchical()) {
                     //unset($va_path_components['components'][1]);	// remove 'children' from field path
                     $vs_field_spec = join('.', array_values($va_path_components['components']));
                     if ($vn_id = $this->get($va_path_components['table_name'] . '.' . $t_instance->primaryKey(), array('returnAsArray' => false))) {
                         if ($t_instance->load($vn_id)) {
                             return $t_instance->get($vs_field_spec, $pa_options);
                         }
                     }
                     return null;
                 }
                 break;
             case 'related':
                 // Regular related table call
                 if ($va_path_components['table_name'] != $this->ops_table_name) {
                     // just remove "related" from name and be on our way
                     $va_tmp = $va_path_components['components'];
                     array_splice($va_tmp, 1, 1);
                     return $this->get(join('.', $va_tmp), $pa_options);
                 }
                 // Self-relations need special handling
                 $vs_opt_md5 = caMakeCacheKeyFromOptions($pa_options);
                 if (!isset($this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5])) {
                     $this->prefetchRelated($va_path_components['table_name'], $this->opo_engine_result->currentRow(), $this->getOption('prefetch'), $pa_options);
                 }
                 $va_related_items = $this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5];
                 if (!($t_table = $this->opo_datamodel->getInstanceByTableName($va_path_components['table_name'], true))) {
                     return null;
                 }
                 $va_ids = array();
                 foreach ($va_related_items as $vn_relation_id => $va_item) {
                     $va_ids[] = $va_item[$t_table->primaryKey()];
                 }
                 $va_vals = array();
                 if ($qr_res = $t_table->makeSearchResult($va_path_components['table_name'], $va_ids)) {
                     $va_tmp = $va_path_components['components'];
                     unset($va_tmp[1]);
                     $vs_rel_field = join('.', $va_tmp);
                     while ($qr_res->nextHit()) {
                         if ($vb_return_as_array) {
                             $va_vals = array_merge($va_vals, $qr_res->get($vs_rel_field, $pa_options));
                         } else {
                             $va_vals[] = $qr_res->get($vs_rel_field, $pa_options);
                         }
                     }
                 }
                 //if (is_array($va_sort_fields) && sizeof($va_sort_fields)) {
                 //	$va_vals = caSortArrayByKeyInValue($va_vals, $va_sort_fields);
                 //}
                 if ($vb_return_as_link) {
                     if (!$vb_return_all_locales) {
                         $va_vals = caCreateLinksFromText($va_vals, $va_original_path_components['table_name'], $va_ids, $vs_return_as_link_class, $vs_return_as_link_target);
                     }
                 }
                 if ($vb_return_as_array) {
                     return $va_vals;
                 } else {
                     return join($vs_delimiter, $va_vals);
                 }
                 break;
             case 'hierarchy':
                 $vn_max_levels_from_bottom = caGetOption('maxLevelsFromBottom', $pa_options, caGetOption('maxLevels', $pa_options, null));
                 $vn_max_levels_from_top = caGetOption('maxLevelsFromTop', $pa_options, null);
                 if ($t_instance->isHierarchical()) {
                     $vs_field_spec = join('.', array_values($va_path_components['components']));
                     $vs_hier_pk_fld = $t_instance->primaryKey();
                     if ($va_ids = $this->get($va_path_components['table_name'] . '.' . $vs_hier_pk_fld, array_merge($pa_options, array('returnAsArray' => true, 'returnAsLink' => false, 'returnAllLocales' => false)))) {
                         $va_vals = array();
                         if ($va_path_components['subfield_name'] == $vs_hier_pk_fld) {
                             foreach ($va_ids as $vn_id) {
                                 // TODO: This is too slow
                                 if ($t_instance->load($vn_id)) {
                                     $va_vals = array_merge($va_vals, $t_instance->get($va_path_components['table_name'] . ".hierarchy." . $vs_hier_pk_fld, array_merge($pa_options, array('returnAsArray' => true))));
                                 }
                             }
                         } else {
                             foreach ($va_ids as $vn_id) {
                                 // TODO: This is too slow
                                 if ($t_instance->load($vn_id)) {
                                     $va_vals = $t_instance->get($vs_field_spec, array_merge($pa_options, array('returnAsArray' => true)));
                                     if (is_array($va_vals)) {
                                         $va_vals = array_reverse($va_vals);
                                     }
                                     // Add/replace hierarchy name
                                     if ($t_instance->getProperty('HIERARCHY_TYPE') == __CA_HIER_TYPE_MULTI_MONO__ && $t_instance->getHierarchyName()) {
                                         $vn_first_key = array_shift(array_keys($va_vals));
                                         if ($vb_return_all_locales) {
                                             $va_vals[$vn_first_key] = array(0 => array($t_instance->getHierarchyName()));
                                         } else {
                                             $va_vals[$vn_first_key] = $t_instance->getHierarchyName();
                                         }
                                     }
                                     if ($vn_max_levels_from_bottom > 0) {
                                         if (($vn_start = sizeof($va_vals) - $vn_max_levels_from_bottom) < 0) {
                                             $vn_start = 0;
                                         }
                                         $va_vals = array_slice($va_vals, $vn_start, $vn_max_levels_from_bottom, true);
                                     } elseif ($vn_max_levels_from_top > 0) {
                                         $va_vals = array_slice($va_vals, 0, $vn_max_levels_from_top, true);
                                     }
                                 }
                             }
                         }
                         if ($vb_return_as_array) {
                             return $va_vals;
                         } else {
                             return join($vs_hierarchical_delimiter, $va_vals);
                         }
                     }
                     return null;
                 }
                 break;
         }
     }
     // If the requested table was not added to the query via SearchEngine::addTable()
     // then auto-add it here. It's better to explicitly add it with addTables() as that call
     // gives you precise control over which fields are autoloaded and also lets you specify limiting criteria
     // for selection of related field data; and it also lets you explicitly define the tables used to join the
     // related table. Autoloading guesses and usually does what you want, but not always.
     if (!isset($this->opa_tables[$va_path_components['table_name']]) || !$this->opa_tables[$va_path_components['table_name']]) {
         $va_join_tables = $this->opo_datamodel->getPath($this->ops_table_name, $va_path_components['table_name']);
         array_shift($va_join_tables);
         // remove subject table
         array_pop($va_join_tables);
         // remove content table (we only need linking tables here)
         $va_join_criteria = array();
         if (is_array($va_primary_ids)) {
             foreach ($va_primary_ids as $vs_t => $va_t_ids) {
                 if (isset($va_join_tables[$vs_t]) && sizeof($va_t_ids) > 0) {
                     $vs_t_pk = $this->opo_datamodel->getTablePrimaryKeyName($vs_t);
                     $va_join_criteria[] = "{$vs_t}.{$vs_t_pk} NOT IN (" . join(",", $va_t_ids) . ")";
                 }
             }
         }
         $this->opa_tables[$va_path_components['table_name']] = array('fieldList' => array($va_path_components['table_name'] . '.*'), 'joinTables' => array_keys($va_join_tables), 'criteria' => $va_join_criteria);
     }
     if ($va_path_components['table_name'] === $this->ops_table_name && !$t_instance->hasField($va_path_components['field_name']) && method_exists($t_instance, 'getAttributes')) {
         //
         // Return attribute values for primary table
         //
         if ($va_path_components['field_name'] && ($t_element = $t_instance->_getElementInstance($va_path_components['field_name']))) {
             $vn_element_id = $t_element->getPrimaryKey();
         } else {
             $vn_element_id = null;
         }
         if (!isset(ca_attributes::$s_get_attributes_cache[$this->opn_table_num . '/' . $vn_row_id][$vn_element_id])) {
             ca_attributes::prefetchAttributes($this->opo_db, $this->opn_table_num, $this->getRowIDsToPrefetch($va_path_components['table_name'], $this->opo_engine_result->currentRow(), $this->getOption('prefetch')), $vn_element_id ? array($vn_element_id) : null, array('dontFetchAlreadyCachedValues' => true));
         }
         if (!$vb_return_as_array && !$vb_return_all_locales) {
             // return scalar
             //
             // Handle "hierarchy" modifier on list elements
             //
             if ($va_hier = $this->_getElementHierarchy($t_instance, $va_path_components)) {
                 return join($vs_hierarchical_delimiter, $va_hier);
             }
             if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && $va_path_components['field_name']) {
                 $vs_template = null;
                 if ($va_path_components['subfield_name']) {
                     $va_values = $t_instance->getAttributeDisplayValues($va_path_components['field_name'], $vn_row_id, $pa_options);
                     $va_value_list = array();
                     foreach ($va_values as $vn_id => $va_attr_val_list) {
                         foreach ($va_attr_val_list as $vn_value_id => $va_value_array) {
                             $va_value_list[] = $va_value_array[$va_path_components['subfield_name']];
                         }
                     }
                     return join(" ", $va_value_list);
                 } else {
                     if (isset($pa_options['template'])) {
                         $vs_template = $pa_options['template'];
                     }
                 }
                 unset($pa_options['template']);
                 if (!$vs_template) {
                     $vs_template = "^" . $va_path_components['subfield_name'] ? $va_path_components['subfield_name'] : $va_path_components['field_name'];
                 }
                 return $t_instance->getAttributesForDisplay($va_path_components['field_name'], $vs_template, array_merge(array('row_id' => $vn_row_id), $pa_options));
             }
             if ($t_element && !$va_path_components['subfield_name'] && $t_element->get('datatype') == 0) {
                 return $t_instance->getAttributesForDisplay($va_path_components['field_name'], $vs_template, array_merge($pa_options, array('row_id' => $vn_row_id)));
             } else {
                 if (!$vs_template) {
                     return $t_instance->getRawValue($vn_row_id, $va_path_components['field_name'], $va_path_components['subfield_name'], ',', $pa_options);
                 } else {
                     return caProcessTemplateForIDs($vs_template, $va_path_components['table_name'], array($vn_row_id), array());
                 }
             }
         } else {
             // return array
             //
             // Handle "hierarchy" modifier on list elements
             //
             if ($va_hier = $this->_getElementHierarchy($t_instance, $va_path_components)) {
                 return $va_hier;
             }
             $va_values = $t_instance->getAttributeDisplayValues($va_path_components['field_name'], $vn_row_id, $pa_options);
             if ($vs_template && !$vb_return_all_locales) {
                 $va_values_tmp = array();
                 foreach ($va_values as $vn_i => $va_value_list) {
                     foreach ($va_value_list as $vn_attr_id => $va_attr_data) {
                         $va_values_tmp[] = caProcessTemplateForIDs($vs_template, $va_path_components['table_name'], array($vn_row_id), array_merge($pa_options, array('placeholderPrefix' => $va_path_components['field_name'])));
                     }
                 }
                 $va_values = $va_values_tmp;
             } else {
                 if ($va_path_components['subfield_name']) {
                     if ($vb_return_all_locales) {
                         foreach ($va_values as $vn_row_id => $va_values_by_locale) {
                             foreach ($va_values_by_locale as $vn_locale_id => $va_value_list) {
                                 foreach ($va_value_list as $vn_attr_id => $va_attr_data) {
                                     $va_values[$vn_row_id][$vn_locale_id][$vn_attr_id] = $va_attr_data[$va_path_components['subfield_name']];
                                 }
                             }
                         }
                     } else {
                         $va_processed_value_list = array();
                         foreach ($va_values as $vn_row_id => $va_value_list) {
                             foreach ($va_value_list as $vn_attr_id => $va_attr_data) {
                                 $va_processed_value_list[$vn_attr_id] = $va_attr_data[$va_path_components['subfield_name']];
                             }
                         }
                         $va_values = $va_processed_value_list;
                     }
                 } else {
                     if (!$vb_return_all_locales) {
                         $va_values = array_shift($va_values);
                     }
                 }
             }
             return $va_values;
         }
     } else {
         // Prefetch intrinsic fields in primary and related tables
         if (!isset($this->opa_prefetch_cache[$va_path_components['table_name']][$vn_row_id])) {
             $this->prefetch($va_path_components['table_name'], $this->opo_engine_result->currentRow(), $this->getOption('prefetch'), $pa_options);
             // try to prefetch ahead (usually doesn't hurt and very often helps performance)
         }
     }
     $va_return_values = array();
     if ($va_path_components['table_name'] !== $this->ops_table_name && $va_path_components['field_name'] !== 'relationship_typename' && !$t_instance->hasField($va_path_components['field_name']) && method_exists($t_instance, 'getAttributes')) {
         //
         // Return metadata attributes in a related table
         //
         $vs_pk = $t_instance->primaryKey();
         $vb_is_related = $this->ops_table_name !== $va_path_components['table_name'];
         $va_ids = array();
         $vs_opt_md5 = caMakeCacheKeyFromOptions($pa_options);
         if (!isset($this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5])) {
             $this->prefetchRelated($va_path_components['table_name'], $this->opo_engine_result->currentRow(), $this->getOption('prefetch'), $pa_options);
         }
         if (is_array($this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5])) {
             foreach ($this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5] as $vn_i => $va_values) {
                 //$vn_locale_id => $va_values_by_locale) {
                 $va_ids[] = $va_values[$vs_pk];
                 if (!$vb_return_as_array) {
                     $vs_val = $t_instance->getAttributesForDisplay($va_path_components['field_name'], $vs_template, array_merge(array('row_id' => $va_values[$vs_pk]), $pa_options));
                 } else {
                     $vs_val = $t_instance->getAttributeDisplayValues($va_path_components['field_name'], $va_values[$vs_pk], $pa_options);
                 }
                 if ($vs_val) {
                     if ($vb_return_as_array) {
                         if (!$vb_return_all_locales) {
                             foreach ($vs_val as $vn_i => $va_values_list) {
                                 foreach ($va_values_list as $vn_j => $va_values) {
                                     $va_return_values[] = $va_values;
                                 }
                             }
                         } else {
                             foreach ($vs_val as $vn_i => $va_values_list) {
                                 $va_return_values[] = $va_values_list;
                             }
                         }
                     } else {
                         $va_return_values[] = $vs_val;
                     }
                 }
             }
         }
         if ($vb_return_as_array || $vb_return_all_locales) {
             // return array
             if ($vb_return_as_link && $vb_is_related) {
                 $vs_table_name = $t_instance->tableName();
                 $vs_fld_key = $va_path_components['subfield_name'] ? $va_path_components['subfield_name'] : $va_path_components['field_name'];
                 if (!$vb_return_all_locales) {
                     $va_return_values_tmp = array();
                     foreach ($va_return_values as $vn_i => $va_value) {
                         if ($vs_template) {
                             $vs_value = caProcessTemplateForIDs($vs_template, $va_path_components['table_name'], array($va_ids[$vn_i][$vs_pk]), array('returnAsArray' => false));
                         } else {
                             $vs_value = $va_value[$vs_fld_key];
                         }
                         if ($vb_return_as_link) {
                             $va_return_values_tmp[$vn_i] = array_pop(caCreateLinksFromText(array($vs_value), $va_original_path_components['table_name'], array($va_ids[$vn_i]), $vs_return_as_link_class, $vs_return_as_link_target));
                         } else {
                             $va_return_values_tmp[$vn_i] = $vs_value;
                         }
                     }
                     $va_return_values = $va_return_values_tmp;
                 }
             }
             return $va_return_values;
         } else {
             // return scalar
             if ($vb_return_as_link && $vb_is_related) {
                 $va_return_values = caCreateLinksFromText($va_return_values, $va_original_path_components['table_name'], $va_ids, $vs_return_as_link_class, $vs_return_as_link_target);
             }
             if (isset($pa_options['convertLineBreaks']) && $pa_options['convertLineBreaks']) {
                 return caConvertLineBreaks(join($vs_delimiter, $va_return_values));
             } else {
                 return join($vs_delimiter, $va_return_values);
             }
         }
     } else {
         if ($vs_template) {
             return caProcessTemplateForIDs($vs_template, $this->opo_subject_instance->tableName(), array($vn_row_id), array_merge($pa_options, array('placeholderPrefix' => $va_path_components['field_name'])));
         }
         //
         // Return fields (intrinsics, labels) in primary or related table
         //
         $t_list = $this->opo_datamodel->getInstanceByTableName('ca_lists', true);
         $va_value_list = array($vn_row_id => $this->opa_prefetch_cache[$va_path_components['table_name']][$vn_row_id]);
         // Restrict to relationship types (related)
         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'])) {
                 $t_rel_type = $this->opo_datamodel->getInstanceByTableName('ca_relationship_types', true);
                 $va_rel_types = array();
                 $va_rel_path = array_keys($this->opo_datamodel->getPath($this->ops_table_name, $va_path_components['table_name']));
                 foreach ($pa_options['restrict_to_relationship_types'] as $vm_type) {
                     if (!$vm_type) {
                         continue;
                     }
                     if ($vn_type_id = $t_rel_type->getRelationshipTypeID($va_rel_path[1], $vm_type)) {
                         $va_rel_types[] = $vn_type_id;
                         if (is_array($va_children = $t_rel_type->getHierarchyChildren($vn_type_id, array('idsOnly' => true)))) {
                             $va_rel_types = array_merge($va_rel_types, $va_children);
                         }
                     }
                 }
                 if (sizeof($va_rel_types)) {
                     $va_tmp = array();
                     foreach ($va_value_list as $vn_id => $va_by_locale) {
                         foreach ($va_by_locale as $vn_locale_id => $va_values) {
                             foreach ($va_values as $vn_i => $va_value) {
                                 if (!$va_value['rel_type_id'] || in_array($va_value['rel_type_id'], $va_rel_types)) {
                                     $va_tmp[$vn_id][$vn_locale_id][$vn_i] = $va_value;
                                 }
                             }
                         }
                     }
                     $va_value_list = $va_tmp;
                 }
             }
         }
         // Exclude relationship types (related)
         if (isset($pa_options['exclude_relationship_types']) && $pa_options['exclude_relationship_types']) {
             if (!is_array($pa_options['exclude_relationship_types'])) {
                 $pa_options['exclude_relationship_types'] = array($pa_options['exclude_relationship_types']);
             }
             if (sizeof($pa_options['exclude_relationship_types'])) {
                 $t_rel_type = $this->opo_datamodel->getInstanceByTableName('ca_relationship_types', true);
                 $va_rel_types = array();
                 $va_rel_path = array_keys($this->opo_datamodel->getPath($this->ops_table_name, $va_path_components['table_name']));
                 foreach ($pa_options['exclude_relationship_types'] as $vm_type) {
                     if ($vn_type_id = $t_rel_type->getRelationshipTypeID($va_rel_path[1], $vm_type)) {
                         $va_rel_types[] = $vn_type_id;
                         if (is_array($va_children = $t_rel_type->getHierarchyChildren($vn_type_id, array('idsOnly' => true)))) {
                             $va_rel_types = array_merge($va_rel_types, $va_children);
                         }
                     }
                 }
                 if (sizeof($va_rel_types)) {
                     $va_tmp = array();
                     foreach ($va_value_list as $vn_id => $va_by_locale) {
                         foreach ($va_by_locale as $vn_locale_id => $va_values) {
                             foreach ($va_values as $vn_i => $va_value) {
                                 if (!in_array($va_value['rel_type_id'], $va_rel_types)) {
                                     $va_tmp[$vn_id][$vn_locale_id][$vn_i] = $va_value;
                                 }
                             }
                         }
                     }
                     $va_value_list = $va_tmp;
                 }
             }
         }
         // Restrict to types (related)
         $va_type_ids = $vs_type_fld = null;
         if (method_exists($t_instance, "getTypeFieldName")) {
             $va_type_ids = caMergeTypeRestrictionLists($t_instance, $pa_options);
             $vs_type_fld = $t_instance->getTypeFieldName();
         } else {
             if (method_exists($t_instance, "getSubjectTableInstance")) {
                 $t_label_subj_instance = $t_instance->getSubjectTableInstance();
                 if (method_exists($t_label_subj_instance, "getTypeFieldName")) {
                     $va_type_ids = caMergeTypeRestrictionLists($t_label_subj_instance, $pa_options);
                     $vs_type_fld = 'item_type_id';
                 }
             }
         }
         if (is_array($va_type_ids) && sizeof($va_type_ids)) {
             $va_tmp = array();
             foreach ($va_value_list as $vn_id => $va_by_locale) {
                 foreach ($va_by_locale as $vn_locale_id => $va_values) {
                     foreach ($va_values as $vn_i => $va_value) {
                         if (!$va_value[$vs_type_fld ? $vs_type_fld : 'item_type_id'] || in_array($va_value[$vs_type_fld ? $vs_type_fld : 'item_type_id'], $va_type_ids)) {
                             $va_tmp[$vn_id][$vn_locale_id][$vn_i] = $va_value;
                         }
                     }
                 }
             }
             $va_value_list = $va_tmp;
         }
         // Restrict to sources (related)
         $va_source_ids = $vs_source_id_fld = null;
         if (method_exists($t_instance, "getSourceFieldName")) {
             $va_source_ids = caMergeSourceRestrictionLists($t_instance, $pa_options);
             $vs_source_id_fld = $t_instance->getSourceFieldName();
         } else {
             if (method_exists($t_instance, "getSubjectTableInstance")) {
                 $t_label_subj_instance = $t_instance->getSubjectTableInstance();
                 if (method_exists($t_label_subj_instance, "getSourceFieldName")) {
                     $va_source_ids = caMergeSourceRestrictionLists($t_label_subj_instance, $pa_options);
                     $vs_source_id_fld = 'item_source_id';
                 }
             }
         }
         if (is_array($va_source_ids) && sizeof($va_source_ids)) {
             $va_tmp = array();
             foreach ($va_value_list as $vn_id => $va_by_locale) {
                 foreach ($va_by_locale as $vn_locale_id => $va_values) {
                     foreach ($va_values as $vn_i => $va_value) {
                         if (!$va_value[$vs_source_id_fld ? $vs_source_id_fld : 'item_source_id'] || in_array($va_value[$vs_source_id_fld ? $vs_source_id_fld : 'item_source_id'], $va_source_ids)) {
                             $va_tmp[$vn_id][$vn_locale_id][$vn_i] = $va_value;
                         }
                     }
                 }
             }
             $va_value_list = $va_tmp;
         }
         // Exclude types (related)
         if (isset($pa_options['exclude_type']) && $pa_options['exclude_type']) {
             if (!isset($pa_options['exclude_types']) || !is_array($pa_options['exclude_types'])) {
                 $pa_options['exclude_types'] = array();
             }
             $pa_options['exclude_types'][] = $pa_options['exclude_type'];
         }
         if (isset($pa_options['exclude_types']) && is_array($pa_options['exclude_types'])) {
             $va_ids = caMakeTypeIDList($va_path_components['table_name'], $pa_options['exclude_types']);
             if (is_array($va_ids) && sizeof($va_ids) > 0) {
                 $va_tmp = array();
                 foreach ($va_value_list as $vn_id => $va_by_locale) {
                     foreach ($va_by_locale as $vn_locale_id => $va_values) {
                         foreach ($va_values as $vn_i => $va_value) {
                             if (!in_array($va_value[$vs_type_fld ? $vs_type_fld : 'item_type_id'], $va_type_ids)) {
                                 $va_tmp[$vn_id][$vn_locale_id][$vn_i] = $va_value;
                             }
                         }
                     }
                 }
                 $va_value_list = $va_tmp;
             }
         }
         // Handle 'relationship_typename' (related)
         $vb_get_relationship_typename = false;
         if ($va_path_components['field_name'] == 'relationship_typename') {
             $va_path_components['field_name'] = 'rel_type_id';
             $vb_get_relationship_typename = true;
         }
         if ($vb_return_as_array) {
             // return array (intrinsics or labels in primary or related table)
             if ($t_instance->hasField($va_path_components['field_name']) && $va_path_components['table_name'] === $t_instance->tableName()) {
                 // Intrinsic
                 $va_field_info = $t_instance->getFieldInfo($va_path_components['field_name']);
                 $vs_pk = $t_original_instance->primaryKey();
                 // Handle specific intrinsic types
                 switch ($va_field_info['FIELD_TYPE']) {
                     case FT_DATERANGE:
                     case FT_HISTORIC_DATERANGE:
                         foreach ($va_value_list as $vn_id => $va_values_by_locale) {
                             foreach ($va_values_by_locale as $vn_locale_id => $va_values) {
                                 foreach ($va_values as $vn_i => $va_value) {
                                     $va_ids[] = $va_value[$vs_pk];
                                     if (caGetOption('GET_DIRECT_DATE', $pa_options, false) || caGetOption('getDirectDate', $pa_options, false)) {
                                         if (caGetOption('sortable', $pa_options, false)) {
                                             $vs_prop = $va_value[$va_field_info['START']] . '/' . $va_value[$va_field_info['END']];
                                         } else {
                                             $vs_prop = $va_value[$va_field_info['START']];
                                         }
                                     } else {
                                         $this->opo_tep->init();
                                         if ($va_field_info['FIELD_TYPE'] == FT_DATERANGE) {
                                             $this->opo_tep->setUnixTimestamps($va_value[$va_field_info['START']], $va_value[$va_field_info['END']]);
                                         } else {
                                             $this->opo_tep->setHistoricTimestamps($va_value[$va_field_info['START']], $va_value[$va_field_info['END']]);
                                         }
                                         $vs_prop = $this->opo_tep->getText($pa_options);
                                     }
                                     if ($vb_return_all_locales) {
                                         $va_return_values[$vn_row_id][$vn_locale_id][] = $vs_prop;
                                     } else {
                                         $va_return_values[] = $vs_prop;
                                     }
                                 }
                             }
                         }
                         break;
                     case FT_MEDIA:
                         if (!($vs_version = $va_path_components['subfield_name'])) {
                             $vs_version = "largeicon";
                         }
                         foreach ($va_value_list as $vn_id => $va_values_by_locale) {
                             foreach ($va_values_by_locale as $vn_locale_id => $va_values) {
                                 foreach ($va_values as $vn_i => $va_value) {
                                     $va_ids[] = $va_value[$vs_pk];
                                     if (isset($pa_options['unserialize']) && $pa_options['unserialize']) {
                                         $vs_prop = caUnserializeForDatabase($va_value[$va_path_components['field_name']]);
                                         if ($vb_return_all_locales) {
                                             $va_return_values[$vn_row_id][$vn_locale_id][] = $vs_prop;
                                         } else {
                                             $va_return_values[] = $vs_prop;
                                         }
                                     } else {
                                         $o_media_settings = new MediaProcessingSettings($va_path_components['table_name'], $va_path_components['field_name']);
                                         $va_versions = $o_media_settings->getMediaTypeVersions('*');
                                         if (!isset($va_versions[$vs_version])) {
                                             $va_tmp = array_keys($va_versions);
                                             $vs_version = array_shift($va_tmp);
                                         }
                                         // See if an info element was passed, eg. ca_object_representations.media.icon.width should return the width of the media rather than a tag or url to the media
                                         $vs_info_element = $va_path_components['num_components'] == 4 ? $va_path_components['components'][3] : null;
                                         if ($vb_return_all_locales) {
                                             if ($vs_info_element) {
                                                 $va_return_values[$vn_row_id][$vn_locale_id][] = $this->getMediaInfo($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $vs_info_element, $pa_options);
                                             } elseif (isset($pa_options['returnURL']) && $pa_options['returnURL']) {
                                                 $va_return_values[$vn_row_id][$vn_locale_id][] = $this->getMediaUrl($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $pa_options);
                                             } else {
                                                 $va_return_values[$vn_row_id][$vn_locale_id][] = $this->getMediaTag($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $pa_options);
                                             }
                                         } else {
                                             if ($vs_info_element) {
                                                 $va_return_values[] = $this->getMediaInfo($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $vs_info_element, $pa_options);
                                             } elseif (isset($pa_options['returnURL']) && $pa_options['returnURL']) {
                                                 $va_return_values[] = $this->getMediaUrl($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $pa_options);
                                             } else {
                                                 $va_return_values[] = $this->getMediaTag($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $pa_options);
                                             }
                                         }
                                     }
                                 }
                             }
                         }
                         break;
                     default:
                         // is intrinsic field in primary table
                         $vb_supports_preferred = (bool) $t_instance->hasField('is_preferred');
                         foreach ($va_value_list as $vn_id => $va_values_by_locale) {
                             foreach ($va_values_by_locale as $vn_locale_id => $va_values) {
                                 foreach ($va_values as $vn_i => $va_value) {
                                     $va_ids[] = $vn_id = $va_value[$vs_pk];
                                     if ($vb_get_preferred_labels_only && $vb_supports_preferred && !$va_value['is_preferred']) {
                                         continue;
                                     }
                                     if ($vb_get_nonpreferred_labels_only && $vb_supports_preferred && $va_value['is_preferred']) {
                                         continue;
                                     }
                                     $vs_prop = $va_value[$va_path_components['field_name']];
                                     if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($vs_list_code = $t_instance->getFieldInfo($va_path_components['field_name'], "LIST_CODE"))) {
                                         $vs_prop = $t_list->getItemFromListForDisplayByItemID($vs_list_code, $vs_prop);
                                     } else {
                                         if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($vs_list_code = $t_instance->getFieldInfo($va_path_components['field_name'], "LIST"))) {
                                             $vs_prop = $t_list->getItemFromListForDisplayByItemValue($vs_list_code, $vs_prop);
                                         } else {
                                             if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && $va_path_components['field_name'] === 'locale_id' && (int) $vs_prop > 0) {
                                                 $t_locale = new ca_locales($vs_prop);
                                                 $vs_prop = $t_locale->getName();
                                             } else {
                                                 if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && is_array($va_list = $t_instance->getFieldInfo($va_path_components['field_name'], "BOUNDS_CHOICE_LIST"))) {
                                                     foreach ($va_list as $vs_option => $vs_value) {
                                                         if ($vs_value == $vs_prop) {
                                                             $vs_prop = $vs_option;
                                                             break;
                                                         }
                                                     }
                                                 }
                                             }
                                         }
                                     }
                                     if ($vb_return_all_locales) {
                                         $va_return_values[$vn_id][$vn_locale_id][] = $vs_prop;
                                     } else {
                                         $va_return_values[$vn_id][$vn_locale_id] = $vs_prop;
                                     }
                                 }
                             }
                         }
                         if (!$vb_return_all_locales) {
                             $va_return_values = array_values(caExtractValuesByUserLocale($va_return_values));
                         }
                         break;
                 }
             } else {
                 // Attributes
                 $vs_pk = $t_original_instance->primaryKey();
                 $vb_is_related = $this->ops_table_name !== $va_path_components['table_name'];
                 $va_ids = array();
                 $t_instance = $this->opo_datamodel->getInstanceByTableName($va_path_components['table_name'], true);
                 foreach ($va_value_list as $vn_i => $va_values_by_locale) {
                     foreach ($va_values_by_locale as $vn_locale_id => $va_values) {
                         foreach ($va_values as $vn_i => $va_value) {
                             if ($vb_is_related) {
                                 $va_ids[] = $va_value[$vs_pk];
                             }
                             if ($vb_get_preferred_labels_only && !$va_value['is_preferred']) {
                                 continue;
                             }
                             if ($vb_get_nonpreferred_labels_only && $va_value['is_preferred']) {
                                 continue;
                             }
                             // do we need to translate foreign key and choice list codes to display text?
                             $vs_prop = $vb_return_all_label_values && !$vb_return_as_link ? $va_value : $va_value[$va_path_components['field_name']];
                             if ($vb_get_relationship_typename) {
                                 if (!$t_rel_type) {
                                     $t_rel_type = $this->opo_datamodel->getInstanceByTableName('ca_relationship_types', true);
                                 }
                                 if (is_array($va_labels = $t_rel_type->getDisplayLabels(false, array('row_id' => (int) $vs_prop)))) {
                                     $va_label = array_shift($va_labels);
                                     $vs_prop = $va_label[0]['typename'];
                                 } else {
                                     $vs_prop = "?";
                                 }
                             } else {
                                 // Decode list items to text
                                 if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($vs_list_code = $t_instance->getFieldInfo($va_path_components['field_name'], "LIST_CODE"))) {
                                     $vs_prop = $t_list->getItemFromListForDisplayByItemID($vs_list_code, $vs_prop);
                                 } else {
                                     if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($vs_list_code = $t_instance->getFieldInfo($va_path_components['field_name'], "LIST"))) {
                                         $vs_prop = $t_list->getItemFromListForDisplayByItemValue($vs_list_code, $vs_prop);
                                     } else {
                                         if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && $va_path_components['field_name'] === 'locale_id' && (int) $vs_prop > 0) {
                                             $t_locale = new ca_locales($vs_prop);
                                             $vs_prop = $t_locale->getName();
                                         } else {
                                             if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && is_array($va_list = $t_instance->getFieldInfo($va_path_components['field_name'], "BOUNDS_CHOICE_LIST"))) {
                                                 foreach ($va_list as $vs_option => $vs_value) {
                                                     if ($vs_value == $vs_prop) {
                                                         $vs_prop = $vs_option;
                                                         break;
                                                     }
                                                 }
                                             }
                                         }
                                     }
                                 }
                             }
                             if ($vb_return_all_locales) {
                                 $va_return_values[$vn_row_id][$vn_locale_id][] = $vs_prop;
                             } else {
                                 if ($vb_get_nonpreferred_labels_only && is_array($vs_prop)) {
                                     // non-preferred labels are lists of lists because they can repeat
                                     $va_return_values[][] = $vs_prop;
                                 } else {
                                     $va_return_values[] = $vs_prop;
                                 }
                             }
                         }
                     }
                 }
             }
             if ($vb_return_as_link) {
                 if (!$vb_return_all_locales) {
                     $va_return_values = caCreateLinksFromText($va_return_values, $va_original_path_components['table_name'], $va_ids, $vs_return_as_link_class, $vs_return_as_link_target);
                 }
             }
             return $va_return_values;
         } else {
             // Return scalar (intrinsics or labels in primary or related table)
             if ($vb_get_preferred_labels_only || $vb_get_nonpreferred_labels_only) {
                 // We have to distinguish between preferred and non-preferred labels here
                 // so that only appropriate labels are passed for output.
                 $va_filtered_values = array();
                 foreach ($va_value_list as $vn_label_id => $va_labels_by_locale) {
                     foreach ($va_labels_by_locale as $vn_locale_id => $va_labels) {
                         foreach ($va_labels as $vn_i => $va_label) {
                             if ($vb_get_preferred_labels_only && (!isset($va_label['is_preferred']) || $va_label['is_preferred']) || $vb_get_nonpreferred_labels_only && !$va_label['is_preferred']) {
                                 $va_filtered_values[$vn_label_id][$vn_locale_id][] = $va_label;
                             }
                         }
                     }
                 }
                 $va_value_list = $va_filtered_values;
             }
             $va_value_list = caExtractValuesByUserLocale($va_value_list);
             // do we need to translate foreign key and choice list codes to display text?
             $t_instance = $this->opo_datamodel->getInstanceByTableName($va_path_components['table_name'], true);
             $va_field_info = $t_instance->getFieldInfo($va_path_components['field_name']);
             $vs_pk = $t_instance->primaryKey();
             $vb_is_related = $this->ops_table_name !== $va_path_components['table_name'];
             $va_ids = array();
             foreach ($va_value_list as $vn_i => $va_values) {
                 if (!is_array($va_values)) {
                     continue;
                 }
                 // Handle specific intrinsic types
                 $vs_template_value = $vs_template;
                 foreach ($va_values as $vn_j => $va_value) {
                     switch ($va_field_info['FIELD_TYPE']) {
                         case FT_BIT:
                             if ($pa_options['convertCodesToDisplayText']) {
                                 $va_value[$va_path_components['field_name']] = (bool) $vs_prop ? _t('yes') : _t('no');
                             }
                             break;
                         case FT_DATERANGE:
                             if (caGetOption('GET_DIRECT_DATE', $pa_options, false) || caGetOption('getDirectDate', $pa_options, false)) {
                                 if (isset($pa_options['sortable']) && $pa_options['sortable']) {
                                     $va_value[$va_path_components['field_name']] = $va_value[$va_field_info['START']] . '/' . $va_value[$va_field_info['END']];
                                 } else {
                                     $va_value[$va_path_components['field_name']] = $va_value[$va_field_info['START']];
                                 }
                             } else {
                                 $this->opo_tep->init();
                                 $this->opo_tep->setUnixTimestamps($va_value[$va_field_info['START']], $va_value[$va_field_info['END']]);
                                 $va_value[$va_path_components['field_name']] = $this->opo_tep->getText($pa_options);
                             }
                             break;
                         case FT_HISTORIC_DATERANGE:
                             if (caGetOption('GET_DIRECT_DATE', $pa_options, false) || caGetOption('getDirectDate', $pa_options, false)) {
                                 if (caGetOption('sortable', $pa_options, false)) {
                                     $va_value[$va_path_components['field_name']] = $va_value[$va_field_info['START']] . '/' . $va_value[$va_field_info['END']];
                                 } else {
                                     $va_value[$va_path_components['field_name']] = $va_value[$va_field_info['START']];
                                 }
                             } else {
                                 $this->opo_tep->init();
                                 $this->opo_tep->setHistoricTimestamps($va_value[$va_field_info['START']], $va_value[$va_field_info['END']]);
                                 $va_value[$va_path_components['field_name']] = $this->opo_tep->getText($pa_options);
                             }
                             break;
                         case FT_MEDIA:
                             if (!($vs_version = $va_path_components['subfield_name'])) {
                                 $vs_version = "largeicon";
                             }
                             // See if an info element was passed, eg. ca_object_representations.media.icon.width should return the width of the media rather than a tag or url to the media
                             $vs_info_element = $va_path_components['num_components'] == 4 ? $va_path_components['components'][3] : null;
                             if (isset($pa_options['unserialize']) && $pa_options['unserialize']) {
                                 return caUnserializeForDatabase($va_value[$va_path_components['field_name']]);
                             } else {
                                 $o_media_settings = new MediaProcessingSettings($va_path_components['table_name'], $va_path_components['field_name']);
                                 $va_versions = $o_media_settings->getMediaTypeVersions('*');
                                 if (!isset($va_versions[$vs_version])) {
                                     $va_tmp = array_keys($va_versions);
                                     $vs_version = array_shift($va_tmp);
                                 }
                                 if ($vs_info_element) {
                                     // Return media info
                                     $va_value[$va_path_components['field_name']] = $this->getMediaInfo($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $vs_info_element, $pa_options);
                                 } elseif (isset($pa_options['returnURL']) && $pa_options['returnURL']) {
                                     $va_value[$va_path_components['field_name']] = $this->getMediaUrl($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $pa_options);
                                 } else {
                                     $va_value[$va_path_components['field_name']] = $this->getMediaTag($va_path_components['table_name'] . '.' . $va_path_components['field_name'], $vs_version, $pa_options);
                                 }
                             }
                             break;
                         default:
                             // noop
                             break;
                     }
                     $vs_prop = $va_value[$va_path_components['field_name']];
                     // Decode list items to text
                     if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($vs_list_code = $t_instance->getFieldInfo($va_path_components['field_name'], "LIST_CODE"))) {
                         $va_value[$va_path_components['field_name']] = $t_list->getItemFromListForDisplayByItemID($vs_list_code, $vs_prop);
                     } else {
                         if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($vs_list_code = $t_instance->getFieldInfo($va_path_components['field_name'], "LIST"))) {
                             $va_value[$va_path_components['field_name']] = $t_list->getItemFromListForDisplayByItemValue($vs_list_code, $vs_prop);
                         } else {
                             if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && $va_path_components['field_name'] === 'locale_id' && (int) $vs_prop > 0) {
                                 $t_locale = new ca_locales($vs_prop);
                                 $va_value[$va_path_components['field_name']] = $t_locale->getName();
                             } else {
                                 if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && is_array($va_list = $t_instance->getFieldInfo($va_path_components['field_name'], "BOUNDS_CHOICE_LIST"))) {
                                     foreach ($va_list as $vs_option => $vs_value) {
                                         if ($vs_value == $vs_prop) {
                                             $va_value[$va_path_components['field_name']] = $vs_option;
                                             break;
                                         }
                                     }
                                 }
                             }
                         }
                     }
                     $vs_pk = $this->opo_datamodel->getTablePrimaryKeyName($va_original_path_components['table_name']);
                     if ($vs_template) {
                         foreach ($va_value_list as $vn_id => $va_values) {
                             foreach ($va_values as $vn_i => $va_value) {
                                 $vs_prop = caProcessTemplateForIDs($vs_template, $va_original_path_components['table_name'], array($va_value[$vs_pk]), array('returnAsArray' => false));
                                 $va_return_values[] = $vs_prop;
                                 $va_ids[] = $va_value[$vs_pk];
                             }
                         }
                     } else {
                         $vs_prop = $va_value[$va_path_components['field_name']];
                         $va_return_values[] = $vs_prop;
                         if ($vb_is_related) {
                             $va_ids[] = $va_value[$vs_pk];
                         }
                     }
                 }
             }
             if ($vb_return_as_link && $vb_is_related) {
                 $va_return_values = caCreateLinksFromText($va_return_values, $va_original_path_components['table_name'], $va_ids, $vs_return_as_link_class, $vs_return_as_link_target);
             }
             if (isset($pa_options['convertLineBreaks']) && $pa_options['convertLineBreaks']) {
                 return caConvertLineBreaks(join($vs_delimiter, $va_return_values));
             } else {
                 return join($vs_delimiter, $va_return_values);
             }
         }
     }
     return null;
 }