/**
  * Options:
  * 		rawDate - if true, returns date as an array of start and end historic timestames
  *		sortable - if true a language-independent sortable representation is returned.
  *		getDirectDate - get underlying historic timestamp (floatval)
  */
 public function getDisplayValue($pa_options = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     if (isset($pa_options['rawDate']) && $pa_options['rawDate']) {
         return array(0 => $this->opn_start_date, 1 => $this->opn_end_date, 'start' => $this->opn_start_date, 'end' => $this->opn_end_date);
     }
     if (caGetOption('GET_DIRECT_DATE', $pa_options, false) || caGetOption('getDirectDate', $pa_options, false)) {
         return $this->opn_start_date;
     }
     if (isset($pa_options['sortable']) && $pa_options['sortable']) {
         if (!$this->opn_start_date || !$this->opn_end_date) {
             return null;
         }
         return $this->opn_start_date . '/' . $this->opn_end_date;
     }
     $o_date_config = Configuration::load(__CA_CONF_DIR__ . '/datetime.conf');
     $vs_date_format = $o_date_config->get('dateFormat');
     $vs_cache_key = md5($vs_date_format . $this->opn_start_date . $this->opn_end_date);
     // pull from cache
     if (isset(DateRangeAttributeValue::$s_date_cache[$vs_cache_key])) {
         return DateRangeAttributeValue::$s_date_cache[$vs_cache_key];
     }
     // if neither start nor end date are set, the setHistoricTimestamps() call below will
     // fail and the TEP will return the text for whatever happened to be parsed previously
     // so we have to init() before trying
     DateRangeAttributeValue::$o_tep->init();
     if ($vs_date_format == 'original') {
         return DateRangeAttributeValue::$s_date_cache[$vs_cache_key] = $this->ops_text_value;
     } else {
         if (!is_array($va_settings = MemoryCache::fetch($this->getElementID(), 'ElementSettings'))) {
             $t_element = new ca_metadata_elements($this->getElementID());
             $va_settings = MemoryCache::fetch($this->getElementID(), 'ElementSettings');
         }
         DateRangeAttributeValue::$o_tep->setHistoricTimestamps($this->opn_start_date, $this->opn_end_date);
         return DateRangeAttributeValue::$s_date_cache[$vs_cache_key] = DateRangeAttributeValue::$o_tep->getText(array_merge(array('isLifespan' => $va_settings['isLifespan']), $pa_options));
         //$this->ops_text_value;
     }
 }
/**
 * Returns date range as a localized string for display, subject to the settings in the app/conf/datetime.conf configuration 
 *
 * @param int $pn_start_timestamp Historic start timestamp for date range to localize
 * @param int $pn_end_timestamp Historic end timestamp for date range to localize
 * @param array $pa_options All options supported by TimeExpressionParser::getText() are supported
 *
 * @return string Localized date/time expression
 */
function caGetLocalizedHistoricDateRange($pn_start_timestamp, $pn_end_timestamp, $pa_options = null)
{
    $o_tep = new TimeExpressionParser();
    $o_tep->setHistoricTimestamps($pn_start_timestamp, $pn_end_timestamp);
    return $o_tep->getText($pa_options);
}
Exemple #3
0
 /**
  * Get a field value of the table row that is represented by this object.
  *
  * @param string $ps_field field name
  * @param array $pa_options options array; can be omitted.
  * It should be an associative array of boolean (except one of the options) flags. In case that some of the options are not set, they are treated as 'false'.
  * Possible options (keys) are:
  * 		BINARY: return field value as is
  * 		FILTER_HTML_SPECIAL_CHARS: convert all applicable chars to their html entities
  * 		DONT_PROCESS_GLOSSARY_TAGS: ?
  * 		CONVERT_HTML_BREAKS: similar to nl2br()
  * 		convertLineBreaks: same as CONVERT_HTML_BREAKS
  * 		getDirectDate: return raw date value from database if $ps_field adresses a date field, otherwise the value will be parsed using the TimeExpressionParser::getText() method
  * 		getDirectTime: return raw time value from database if $ps_field adresses a time field, otherwise the value will be parsed using the TimeExpressionParser::getText() method
  * 		TIMECODE_FORMAT: set return format for fields representing time ranges possible (string) values: COLON_DELIMITED, HOURS_MINUTES_SECONDS, RAW; data will be passed through floatval() by default
  * 		QUOTE: set return value into quotes
  * 		URL_ENCODE: value will be passed through urlencode()
  * 		ESCAPE_FOR_XML: convert <, >, &, ' and " characters for XML use
  * 		DONT_STRIP_SLASHES: if set to true, return value will not be passed through stripslashes()
  * 		template: formatting string to use for returned value; ^<fieldname> placeholder is used to represent field value in template
  * 		returnAsArray: if true, fields that can return multiple values [currently only <table_name>.children.<field>] will return values in an indexed array; default is false
  * 		returnAllLocales:
  * 		delimiter: if set, value is used as delimiter when fields that can return multiple fields are returned as strings; default is a single space
  * 		convertCodesToDisplayText: if set, id values refering to foreign keys are returned as preferred label text in the current locale
  * 		returnURL: if set then url is returned for media, otherwise an HTML tag for display is returned
  * 		dateFormat: format to return created or lastModified dates in. Valid values are iso8601 or text
  *		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 that have an "access" field.
  *		sort = optional field to sort returned values on. The field specifiers are fields with or without table name specified.	 
  *		sort_direction = direction to sort results by, either 'asc' for ascending order or 'desc' for descending order; default is 'asc'	 
  */
 public function get($ps_field, $pa_options = null)
 {
     if (!$ps_field) {
         return null;
     }
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     $vb_return_as_array = caGetOption('returnAsArray', $pa_options, false);
     $vb_return_with_structure = caGetOption('returnWithStructure', $pa_options, false);
     $vb_return_all_locales = caGetOption('returnAllLocales', $pa_options, false);
     $vs_delimiter = caGetOption('delimiter', $pa_options, ' ');
     if (($vb_return_with_structure || $vb_return_all_locales) && !$vb_return_as_array) {
         $vb_return_as_array = true;
     }
     $vn_row_id = $this->getPrimaryKey();
     if ($vb_return_as_array && $vb_return_as_array) {
         $vn_locale_id = $this->hasField('locale_id') ? $this->get('locale_id') : null;
     }
     $va_tmp = explode('.', $ps_field);
     if (sizeof($va_tmp) > 1) {
         if ($va_tmp[0] === $this->tableName()) {
             //
             // Return created on or last modified
             //
             if (in_array($va_tmp[1], array('created', 'lastModified'))) {
                 if ($vb_return_as_array) {
                     return $va_tmp[1] == 'lastModified' ? $this->getLastChangeTimestamp() : $this->getCreationTimestamp();
                 } else {
                     $va_data = $va_tmp[1] == 'lastModified' ? $this->getLastChangeTimestamp() : $this->getCreationTimestamp();
                     $vs_subfield = isset($va_tmp[2]) && isset($va_data[$va_tmp[2]]) ? $va_tmp[2] : 'timestamp';
                     $vm_val = $va_data[$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;
                 }
             }
             //
             // Generalized get
             //
             $va_field_info = $this->getFieldInfo($va_tmp[1]);
             switch (sizeof($va_tmp)) {
                 case 2:
                     // support <table_name>.<field_name> syntax
                     $ps_field = $va_tmp[1];
                     break;
                 default:
                     // > 2 elements
                     // media field?
                     if ($va_field_info['FIELD_TYPE'] === FT_MEDIA && !isset($pa_options['returnAsArray']) && !$pa_options['returnAsArray']) {
                         $o_media_settings = new MediaProcessingSettings($va_tmp[0], $va_tmp[1]);
                         $va_versions = $o_media_settings->getMediaTypeVersions('*');
                         $vs_version = $va_tmp[2];
                         if (!isset($va_versions[$vs_version])) {
                             $va_available_versions = array_keys($va_versions);
                             $vs_version = $va_tmp[2] = array_shift($va_available_versions);
                         }
                         if (isset($va_tmp[3])) {
                             return $this->getMediaInfo($va_tmp[1], $vs_version, 'width');
                         } else {
                             if (isset($pa_options['returnURL']) && $pa_options['returnURL']) {
                                 return $this->getMediaUrl($va_tmp[1], $vs_version, $pa_options);
                             } else {
                                 return $this->getMediaTag($va_tmp[1], $vs_version, $pa_options);
                             }
                         }
                     }
                     if ($va_tmp[1] == 'parent' && $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)) {
                             return $vb_return_as_array ? array() : null;
                         } else {
                             unset($va_tmp[1]);
                             $va_tmp = array_values($va_tmp);
                             if ($vb_return_as_array) {
                                 if ($vb_return_all_locales) {
                                     return array($vn_row_id => array($vn_locale_id => array($t_instance->get(join('.', $va_tmp)))));
                                 } else {
                                     return array($vn_row_id => $t_instance->get(join('.', $va_tmp)));
                                 }
                             } else {
                                 return $t_instance->get(join('.', $va_tmp));
                             }
                         }
                     } else {
                         if ($va_tmp[1] == 'children' && $this->isHierarchical()) {
                             $vb_check_access = is_array($pa_options['checkAccess']) && $this->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';
                             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());
                                 if ($va_tmp[1] == $this->primaryKey() && !$vs_sort) {
                                     foreach ($va_children_ids as $vn_child_id) {
                                         $va_data[$vn_child_id] = $vn_child_id;
                                     }
                                 } else {
                                     if (method_exists($this, "makeSearchResult")) {
                                         // Use SearchResult lazy loading when available
                                         $vs_table = $this->tableName();
                                         $vs_pk = $vs_table . '.' . $this->primaryKey();
                                         $qr_children = $this->makeSearchResult($this->tableName(), $va_children_ids);
                                         while ($qr_children->nextHit()) {
                                             if ($vb_check_access && !in_array($qr_children->get("{$vs_table}.access"), $pa_options['checkAccess'])) {
                                                 continue;
                                             }
                                             $vn_child_id = $qr_children->get($vs_pk);
                                             $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();
                                             }
                                             if ($vb_return_as_array) {
                                                 $va_data[$vs_sort_key][$vn_child_id] = array_shift($qr_children->get($vs_childless_path, array_merge($pa_options, array('returnAsArray' => $vb_return_as_array, 'returnAllLocales' => $vb_return_all_locales))));
                                             } else {
                                                 $va_data[$vs_sort_key][$vn_child_id] = $qr_children->get($vs_childless_path, array_merge($pa_options, array('returnAsArray' => false, 'returnAllLocales' => false)));
                                             }
                                         }
                                         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;
                                     } else {
                                         // Fall-back to loading records row-by-row (slow)
                                         foreach ($va_children_ids as $vn_child_id) {
                                             if ($t_instance->load($vn_child_id)) {
                                                 if ($vb_check_access && !in_array($t_instance->get("access"), $pa_options['checkAccess'])) {
                                                     continue;
                                                 }
                                                 $vs_sort_key = $vs_sort ? $t_instance->get($vs_sort) : 0;
                                                 if (!is_array($va_data[$vs_sort_key])) {
                                                     $va_data[$vs_sort_key] = array();
                                                 }
                                                 if ($vb_return_as_array) {
                                                     $va_data[$vs_sort_key][$vn_child_id] = array_shift($t_instance->get($vs_childless_path, array_merge($pa_options, array('returnAsArray' => $vb_return_as_array, 'returnAllLocales' => $vb_return_all_locales))));
                                                 } else {
                                                     $va_data[$vs_sort_key][$vn_child_id] = $t_instance->get($vs_childless_path, array_merge($pa_options, array('returnAsArray' => false, 'returnAllLocales' => false)));
                                                 }
                                             }
                                         }
                                         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;
             }
         } else {
             $va_rels = $this->getRelatedItems($va_tmp[0]);
             $va_vals = array();
             if (is_array($va_rels)) {
                 foreach ($va_rels as $va_rel_item) {
                     if (isset($va_rel_item[$va_tmp[1]])) {
                         $va_vals[] = $va_rel_item[$va_tmp[1]];
                     }
                 }
                 return $vb_return_as_array ? $va_vals : join($vs_delimiter, $va_vals);
             }
             // can't pull fields from other tables!
             return $vb_return_as_array ? array() : null;
         }
     }
     if (isset($pa_options["BINARY"]) && $pa_options["BINARY"]) {
         return $this->_FIELD_VALUES[$ps_field];
     }
     if (array_key_exists($ps_field, $this->FIELDS)) {
         $ps_field_type = $this->getFieldInfo($ps_field, "FIELD_TYPE");
         if ($this->getFieldInfo($ps_field, 'IS_LIFESPAN')) {
             $pa_options['isLifespan'] = true;
         }
         switch ($ps_field_type) {
             case FT_BIT:
                 $vs_prop = isset($this->_FIELD_VALUES[$ps_field]) ? $this->_FIELD_VALUES[$ps_field] : "";
                 if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText']) {
                     $vs_prop = (bool) $vs_prop ? _t('yes') : _t('no');
                 }
                 return $vs_prop;
                 break;
             case FT_TEXT:
             case FT_NUMBER:
             case FT_PASSWORD:
                 $vs_prop = isset($this->_FIELD_VALUES[$ps_field]) ? $this->_FIELD_VALUES[$ps_field] : null;
                 if (isset($pa_options["FILTER_HTML_SPECIAL_CHARS"]) && $pa_options["FILTER_HTML_SPECIAL_CHARS"]) {
                     $vs_prop = htmlentities(html_entity_decode($vs_prop));
                 }
                 //
                 // Convert foreign keys and choice list values to display text is needed
                 //
                 if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($vs_list_code = $this->getFieldInfo($ps_field, "LIST_CODE"))) {
                     $t_list = new ca_lists();
                     $vs_prop = $t_list->getItemFromListForDisplayByItemID($vs_list_code, $vs_prop);
                 } else {
                     if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($vs_list_code = $this->getFieldInfo($ps_field, "LIST"))) {
                         $t_list = new ca_lists();
                         if (!($vs_tmp = $t_list->getItemFromListForDisplayByItemValue($vs_list_code, $vs_prop))) {
                             if ($vs_tmp = $this->getChoiceListValue($ps_field, $vs_prop)) {
                                 $vs_prop = $vs_tmp;
                             }
                         } else {
                             $vs_prop = $vs_tmp;
                         }
                     } else {
                         if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && $ps_field === '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 = $this->getFieldInfo($ps_field, "BOUNDS_CHOICE_LIST"))) {
                                 foreach ($va_list as $vs_option => $vs_value) {
                                     if ($vs_value == $vs_prop) {
                                         $vs_prop = $vs_option;
                                         break;
                                     }
                                 }
                             }
                         }
                     }
                 }
                 if (isset($pa_options["CONVERT_HTML_BREAKS"]) && $pa_options["CONVERT_HTML_BREAKS"] || isset($pa_options["convertLineBreaks"]) && $pa_options["convertLineBreaks"]) {
                     $vs_prop = caConvertLineBreaks($vs_prop);
                 }
                 break;
             case FT_DATETIME:
             case FT_TIMESTAMP:
             case FT_HISTORIC_DATETIME:
             case FT_HISTORIC_DATE:
             case FT_DATE:
                 $vn_timestamp = isset($this->_FIELD_VALUES[$ps_field]) ? $this->_FIELD_VALUES[$ps_field] : 0;
                 if ($vb_return_with_structure) {
                     $vs_prop = array('start' => $this->_FIELD_VALUES[$ps_field], 'end' => $this->_FIELD_VALUES[$ps_field]);
                 } elseif (caGetOption('GET_DIRECT_DATE', $pa_options, false) || caGetOption('getDirectDate', $pa_options, false) || caGetOption('rawDate', $pa_options, false)) {
                     $vs_prop = $this->_FIELD_VALUES[$ps_field];
                 } elseif (isset($pa_options['sortable']) && $pa_options['sortable']) {
                     $vs_prop = $vn_timestamp . "/" . $vn_timestamp;
                 } else {
                     $o_tep = new TimeExpressionParser();
                     if ($ps_field_type == FT_HISTORIC_DATETIME || $ps_field_type == FT_HISTORIC_DATE) {
                         $o_tep->setHistoricTimestamps($vn_timestamp, $vn_timestamp);
                     } else {
                         $o_tep->setUnixTimestamps($vn_timestamp, $vn_timestamp);
                     }
                     if ($ps_field_type == FT_DATE || $ps_field_type == FT_HISTORIC_DATE) {
                         $vs_prop = $o_tep->getText(array_merge(array('timeOmit' => true), $pa_options));
                     } else {
                         $vs_prop = $o_tep->getText($pa_options);
                     }
                 }
                 break;
             case FT_TIME:
                 if ($vb_return_with_structure) {
                     $vs_prop = array('start' => $this->_FIELD_VALUES[$ps_field], 'end' => $this->_FIELD_VALUES[$ps_field]);
                 } elseif (caGetOption('GET_DIRECT_TIME', $pa_options, false) || caGetOption('getDirectTime', $pa_options, false) || caGetOption('rawTime', $pa_options, false)) {
                     $vs_prop = $this->_FIELD_VALUES[$ps_field];
                 } else {
                     $o_tep = new TimeExpressionParser();
                     $vn_timestamp = isset($this->_FIELD_VALUES[$ps_field]) ? $this->_FIELD_VALUES[$ps_field] : 0;
                     $o_tep->setTimes($vn_timestamp, $vn_timestamp);
                     $vs_prop = $o_tep->getText($pa_options);
                 }
                 break;
             case FT_DATERANGE:
             case FT_HISTORIC_DATERANGE:
                 $vs_start_field_name = $this->getFieldInfo($ps_field, "START");
                 $vs_end_field_name = $this->getFieldInfo($ps_field, "END");
                 $vn_start_date = isset($this->_FIELD_VALUES[$vs_start_field_name]) ? $this->_FIELD_VALUES[$vs_start_field_name] : null;
                 $vn_end_date = isset($this->_FIELD_VALUES[$vs_end_field_name]) ? $this->_FIELD_VALUES[$vs_end_field_name] : null;
                 if ($vb_return_with_structure) {
                     $vs_prop = array('start' => $vn_start_date, 'end' => $vn_end_date);
                 } elseif (!caGetOption('GET_DIRECT_DATE', $pa_options, false) && !caGetOption('getDirectDate', $pa_options, false) && !caGetOption('rawDate', $pa_options, false)) {
                     $o_tep = new TimeExpressionParser();
                     if ($ps_field_type == FT_HISTORIC_DATERANGE) {
                         $o_tep->setHistoricTimestamps($vn_start_date, $vn_end_date);
                     } else {
                         $o_tep->setUnixTimestamps($vn_start_date, $vn_end_date);
                     }
                     $vs_prop = $o_tep->getText($pa_options);
                 } elseif (isset($pa_options['sortable']) && $pa_options['sortable']) {
                     $vs_prop = $vn_start_date;
                     //."/".$vn_timestamp;
                 } else {
                     $vs_prop = $vn_start_date;
                     //array($vn_start_date, $vn_end_date);
                 }
                 break;
             case FT_TIMERANGE:
                 $vs_start_field_name = $this->getFieldInfo($ps_field, "START");
                 $vs_end_field_name = $this->getFieldInfo($ps_field, "END");
                 $vn_start_date = isset($this->_FIELD_VALUES[$vs_start_field_name]) ? $this->_FIELD_VALUES[$vs_start_field_name] : null;
                 $vn_end_date = isset($this->_FIELD_VALUES[$vs_end_field_name]) ? $this->_FIELD_VALUES[$vs_end_field_name] : null;
                 if ($vb_return_with_structure) {
                     $vs_prop = array('start' => $vn_start_date, 'end' => $vn_end_date);
                 } elseif (!caGetOption('GET_DIRECT_TIME', $pa_options, false) && !caGetOption('getDirectTime', $pa_options, false) && !caGetOption('rawTime', $pa_options, false)) {
                     $o_tep = new TimeExpressionParser();
                     $o_tep->setTimes($vn_start_date, $vn_end_date);
                     $vs_prop = $o_tep->getText($pa_options);
                 } else {
                     $vs_prop = array($vn_start_date, $vn_end_date);
                 }
                 break;
             case FT_TIMECODE:
                 $o_tp = new TimecodeParser();
                 $o_tp->setParsedValueInSeconds($this->_FIELD_VALUES[$ps_field]);
                 $vs_prop = $o_tp->getText(isset($pa_options["TIMECODE_FORMAT"]) ? $pa_options["TIMECODE_FORMAT"] : null);
                 break;
             case FT_MEDIA:
             case FT_FILE:
                 if ($vb_return_with_structure || $pa_options["USE_MEDIA_FIELD_VALUES"]) {
                     if (isset($pa_options["USE_MEDIA_FIELD_VALUES"]) && $pa_options["USE_MEDIA_FIELD_VALUES"]) {
                         $vs_prop = $this->_FIELD_VALUES[$ps_field];
                     } else {
                         $vs_prop = isset($this->_SET_FILES[$ps_field]['tmp_name']) && $this->_SET_FILES[$ps_field]['tmp_name'] ? $this->_SET_FILES[$ps_field]['tmp_name'] : $this->_FIELD_VALUES[$ps_field];
                     }
                 } else {
                     $va_versions = $this->getMediaVersions($ps_field);
                     $vs_tag = $this->getMediaTag($ps_field, array_shift($va_versions));
                     if ($vb_return_as_array) {
                         return array($vs_tag);
                     } else {
                         return $vs_tag;
                     }
                 }
                 break;
             case FT_VARS:
                 $vs_prop = isset($this->_FIELD_VALUES[$ps_field]) && $this->_FIELD_VALUES[$ps_field] ? $this->_FIELD_VALUES[$ps_field] : null;
                 break;
         }
         if (isset($pa_options["QUOTE"]) && $pa_options["QUOTE"]) {
             $vs_prop = $this->quote($ps_field, $vs_prop);
         }
     } else {
         $this->postError(710, _t("'%1' does not exist in this object", $ps_field), "BaseModel->get()");
         return $vb_return_as_array ? array() : null;
     }
     if (isset($pa_options["URL_ENCODE"]) && $pa_options["URL_ENCODE"]) {
         $vs_prop = urlEncode($vs_prop);
     }
     if (isset($pa_options["ESCAPE_FOR_XML"]) && $pa_options["ESCAPE_FOR_XML"]) {
         $vs_prop = caEscapeForXML($vs_prop);
     }
     if (!(isset($pa_options["DONT_STRIP_SLASHES"]) && $pa_options["DONT_STRIP_SLASHES"])) {
         if (is_string($vs_prop)) {
             $vs_prop = stripSlashes($vs_prop);
         }
     }
     if (isset($pa_options["template"]) && $pa_options["template"]) {
         $vs_template_with_substitution = str_replace("^" . $ps_field, $vs_prop, $pa_options["template"]);
         $vs_prop = str_replace("^" . $this->tableName() . "." . $ps_field, $vs_prop, $vs_template_with_substitution);
     }
     if ($vb_return_as_array) {
         if ($vb_return_all_locales) {
             return array($vn_row_id => array($vn_locale_id => array($vs_prop)));
         } else {
             return array($vn_row_id => $vs_prop);
         }
     } else {
         return $vs_prop;
     }
 }
 /**
  * Returns list of items in the specified table related to the currently loaded row.
  * 
  * @param $pm_rel_table_name_or_num - the table name or table number of the item type you want to get a list of (eg. if you are calling this on an ca_objects instance passing 'ca_entities' here will get you a list of entities related to the object)
  * @param $pa_options - array of options. Supported options are:
  *
  * 		restrict_to_type = restricts returned items to those of the specified type; only supports a single type which can be specified as a list item_code or item_id
  *		restrictToType = synonym for restrict_to_type
  *		restrict_to_types = restricts returned items to those of the specified types; pass an array of list item_codes or item_ids
  *		restrictToTypes = synonym for restrict_to_types
  *		dont_include_subtypes_in_type_restriction = if set subtypes are not included when enforcing restrict_to_types. Note that restrict_to_relationship_types always includes subtypes in its restriction.
  *		dontIncludeSubtypesInTypeRestriction = synonym for dont_include_subtypes_in_type_restriction
  *		restrict_to_relationship_types = restricts returned items to those related to the current row by the specified relationship type(s). You can pass either an array of types or a single type. The types can be relationship type_code's or type_id's.
  *		restrictToRelationshipTypes = synonym for restrict_to_relationship_types
  *
  * 		restrictToBundleValues = restricts returned items to bundle values in related table. pass an associative array with elements like this: bundle_name => list of values
  *
  *		exclude_relationship_types = omits any items related to the current row with any of the specified types from the returned set of ids. You can pass either an array of types or a single type. The types can be relationship type_code's or type_id's.
  *		excludeRelationshipTypes = synonym for exclude_relationship_types
  * 		exclude_type = excludes returned items of the specified type; only supports a single type which can be specified as a list item_code or item_id
  *		excludeType = synonym for exclude_type
  *		exclude_types = omits any items related to the current row that are of any of the specified types from the returned set of ids. You can pass either an array of types or a single type. The types can be type_code's or type_id's.
  *		excludeTypes = synonym for exclude_types
  *
  *		restrict_to_lists = when fetching related ca_list_items restricts returned items to those that are in the specified lists; pass an array of list list_codes or list_ids
  *		restrictToLists = synonym for restrict_to_lists
  *
  *		fields = array of fields (in table.fieldname format) to include in returned data
  *		return_non_preferred_labels = if set to true, non-preferred labels are included in returned data
  *		returnNonPreferredLabels = synonym for return_non_preferred_labels
  *		checkAccess = array of access values to filter results by; if defined only items with the specified access code(s) are returned
  *		return_labels_as_array = if set to true then all labels associated with row are returned in an array, otherwise only a text value in the current locale is returned; default is false - return single label in current locale
  *		returnLabelsAsArray = synonym for return_labels_as_array
  * 		row_ids = array of primary key values to use when fetching related items; if omitted or set to a null value the 'row_id' option (single value) will be used; if row_id is also not set then the currently loaded primary key value will be used
  *		row_id = primary key value to use when fetching related items; if omitted or set to a false value (eg. null, false, 0) then the currently loaded primary key value is used [default]
  *		start = item to start return set at; first item is numbered zero; default is 0
  *		limit = number of items to limit return set to; default is 1000
  *		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, label fields and attributes) are sortable.
  *		sortDirection = direction of sort. Valid values as "ASC" (ascending) and "DESC" (descending). Default is ASC.
  *		showDeleted = if set to true, related items that have been deleted are returned. Default is false.
  *		where = optional array of fields and field values to filter returned values on. The fields must be intrinsic and in the same table as the field being "get()'ed" Can be used to filter returned values from primary and related tables. This option can be useful when you want to fetch certain values from a related table. For example, you want to get the relationship source_info values, but only for relationships going to a specific related record. Note that multiple fields/values are effectively AND'ed together - all must match for a row to be returned - and that only equivalence is supported (eg. field equals value).
  *		user_id = If set item level access control is performed relative to specified user_id, otherwise defaults to logged in user
  *		groupFields = Groups together fields in an arrangement that is easier for import to another system. Used by the ItemInfo web service when in "import" mode. Default is false.
  *		returnLocaleCodes = Return locale values as codes (Ex. en_US) rather than numeric database-specific locale_ids. Default is false.
  * @return array - list of related items
  */
 public function getRelatedItems($pm_rel_table_name_or_num, $pa_options = null)
 {
     global $AUTH_CURRENT_USER_ID;
     $vn_user_id = isset($pa_options['user_id']) && $pa_options['user_id'] ? $pa_options['user_id'] : (int) $AUTH_CURRENT_USER_ID;
     $vb_show_if_no_acl = (bool) ($this->getAppConfig()->get('default_item_access_level') > __CA_ACL_NO_ACCESS__);
     // convert options
     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'];
     }
     if (isset($pa_options['dontIncludeSubtypesInTypeRestriction']) && (!isset($pa_options['dont_include_subtypes_in_type_restriction']) || !$pa_options['dont_include_subtypes_in_type_restriction'])) {
         $pa_options['dont_include_subtypes_in_type_restriction'] = $pa_options['dontIncludeSubtypesInTypeRestriction'];
     }
     if (isset($pa_options['returnNonPreferredLabels']) && (!isset($pa_options['return_non_preferred_labels']) || !$pa_options['return_non_preferred_labels'])) {
         $pa_options['return_non_preferred_labels'] = $pa_options['returnNonPreferredLabels'];
     }
     if (isset($pa_options['returnLabelsAsArray']) && (!isset($pa_options['return_labels_as_array']) || !$pa_options['return_labels_as_array'])) {
         $pa_options['return_labels_as_array'] = $pa_options['returnLabelsAsArray'];
     }
     if (isset($pa_options['restrictToLists']) && (!isset($pa_options['restrict_to_lists']) || !$pa_options['restrict_to_lists'])) {
         $pa_options['restrict_to_lists'] = $pa_options['restrictToLists'];
     }
     if (isset($pa_options['groupFields'])) {
         $pa_options['groupFields'] = (bool) $pa_options['groupFields'];
     } else {
         $pa_options['groupFields'] = false;
     }
     $o_db = $this->getDb();
     $t_locale = new ca_locales();
     $o_tep = new TimeExpressionParser();
     $vb_uses_effective_dates = false;
     $va_get_where = isset($pa_options['where']) && is_array($pa_options['where']) && sizeof($pa_options['where']) ? $pa_options['where'] : null;
     $va_row_ids = isset($pa_options['row_ids']) && is_array($pa_options['row_ids']) ? $pa_options['row_ids'] : null;
     $vn_row_id = isset($pa_options['row_id']) && $pa_options['row_id'] ? $pa_options['row_id'] : $this->getPrimaryKey();
     if (isset($pa_options['sort']) && !is_array($pa_options['sort'])) {
         $pa_options['sort'] = array($pa_options['sort']);
     }
     $va_sort_fields = isset($pa_options['sort']) && is_array($pa_options['sort']) ? $pa_options['sort'] : null;
     $vs_sort_direction = isset($pa_options['sortDirection']) && $pa_options['sortDirection'] ? $pa_options['sortDirection'] : null;
     if (!$va_row_ids && $vn_row_id > 0) {
         $va_row_ids = array($vn_row_id);
     }
     if (!$va_row_ids || !is_array($va_row_ids) || !sizeof($va_row_ids)) {
         return array();
     }
     $vb_return_labels_as_array = isset($pa_options['return_labels_as_array']) && $pa_options['return_labels_as_array'] ? true : false;
     $vn_limit = isset($pa_options['limit']) && (int) $pa_options['limit'] > 0 ? (int) $pa_options['limit'] : 1000;
     $vn_start = isset($pa_options['start']) && (int) $pa_options['start'] > 0 ? (int) $pa_options['start'] : 0;
     if (is_numeric($pm_rel_table_name_or_num)) {
         if (!($vs_related_table_name = $this->getAppDataModel()->getTableName($pm_rel_table_name_or_num))) {
             return null;
         }
     } else {
         if (sizeof($va_tmp = explode(".", $pm_rel_table_name_or_num)) > 1) {
             $pm_rel_table_name_or_num = array_shift($va_tmp);
         }
         if (!($o_instance = $this->getAppDataModel()->getInstanceByTableName($pm_rel_table_name_or_num, true))) {
             return null;
         }
         $vs_related_table_name = $pm_rel_table_name_or_num;
     }
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     switch (sizeof($va_path = array_keys($this->getAppDatamodel()->getPath($this->tableName(), $vs_related_table_name)))) {
         case 3:
             $t_item_rel = $this->getAppDatamodel()->getTableInstance($va_path[1]);
             $t_rel_item = $this->getAppDatamodel()->getTableInstance($va_path[2]);
             $vs_key = $t_item_rel->primaryKey();
             //'relation_id';
             break;
         case 2:
             $t_item_rel = null;
             $t_rel_item = $this->getAppDatamodel()->getTableInstance($va_path[1]);
             $vs_key = $t_rel_item->primaryKey();
             break;
         default:
             // bad related table
             return null;
             break;
     }
     // check for self relationship
     $vb_self_relationship = false;
     if ($this->tableName() == $vs_related_table_name) {
         $vb_self_relationship = true;
         $t_rel_item = $this->getAppDatamodel()->getTableInstance($va_path[0]);
         $t_item_rel = $this->getAppDatamodel()->getTableInstance($va_path[1]);
     }
     $va_wheres = array();
     $va_selects = array();
     $va_joins_post_add = array();
     $vs_related_table = $t_rel_item->tableName();
     if ($t_rel_item->hasField('type_id')) {
         $va_selects[] = $vs_related_table . '.type_id item_type_id';
     }
     if ($t_rel_item->hasField('source_id')) {
         $va_selects[] = $vs_related_table . '.source_id item_source_id';
     }
     // TODO: get these field names from models
     if ($t_item_rel) {
         //define table names
         $vs_linking_table = $t_item_rel->tableName();
         $va_selects[] = $vs_related_table . '.' . $t_rel_item->primaryKey();
         // include dates in returned data
         if ($t_item_rel->hasField('effective_date')) {
             $va_selects[] = $vs_linking_table . '.sdatetime';
             $va_selects[] = $vs_linking_table . '.edatetime';
             $vb_uses_effective_dates = true;
         }
         if ($t_rel_item->hasField('is_enabled')) {
             $va_selects[] = $vs_related_table . '.is_enabled';
         }
         if ($t_item_rel->hasField('type_id')) {
             $va_selects[] = $vs_linking_table . '.type_id relationship_type_id';
             require_once __CA_MODELS_DIR__ . '/ca_relationship_types.php';
             $t_rel = new ca_relationship_types();
             $vb_uses_relationship_types = true;
         }
         // limit related items to a specific type
         if ($vb_uses_relationship_types && isset($pa_options['restrict_to_relationship_types']) && $pa_options['restrict_to_relationship_types']) {
             if (!is_array($pa_options['restrict_to_relationship_types'])) {
                 $pa_options['restrict_to_relationship_types'] = array($pa_options['restrict_to_relationship_types']);
             }
             if (sizeof($pa_options['restrict_to_relationship_types'])) {
                 $va_rel_types = array();
                 foreach ($pa_options['restrict_to_relationship_types'] as $vm_type) {
                     if (!$vm_type) {
                         continue;
                     }
                     if (!($vn_type_id = $t_rel->getRelationshipTypeID($vs_linking_table, $vm_type))) {
                         $vn_type_id = (int) $vm_type;
                     }
                     if ($vn_type_id > 0) {
                         $va_rel_types[] = $vn_type_id;
                         if (is_array($va_children = $t_rel->getHierarchyChildren($vn_type_id, array('idsOnly' => true)))) {
                             $va_rel_types = array_merge($va_rel_types, $va_children);
                         }
                     }
                 }
                 if (sizeof($va_rel_types)) {
                     $va_wheres[] = '(' . $vs_linking_table . '.type_id IN (' . join(',', $va_rel_types) . '))';
                 }
             }
         }
         if ($vb_uses_relationship_types && 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'])) {
                 $va_rel_types = array();
                 foreach ($pa_options['exclude_relationship_types'] as $vm_type) {
                     if ($vn_type_id = $t_rel->getRelationshipTypeID($vs_linking_table, $vm_type)) {
                         $va_rel_types[] = $vn_type_id;
                         if (is_array($va_children = $t_rel->getHierarchyChildren($vn_type_id, array('idsOnly' => true)))) {
                             $va_rel_types = array_merge($va_rel_types, $va_children);
                         }
                     }
                 }
                 if (sizeof($va_rel_types)) {
                     $va_wheres[] = '(' . $vs_linking_table . '.type_id NOT IN (' . join(',', $va_rel_types) . '))';
                 }
             }
         }
     }
     // limit related items to a specific type
     if (isset($pa_options['restrict_to_type']) && $pa_options['restrict_to_type']) {
         if (!isset($pa_options['restrict_to_types']) || !is_array($pa_options['restrict_to_types'])) {
             $pa_options['restrict_to_types'] = array();
         }
         $pa_options['restrict_to_types'][] = $pa_options['restrict_to_type'];
     }
     $va_type_ids = caMergeTypeRestrictionLists($t_rel_item, $pa_options);
     if (is_array($va_type_ids) && sizeof($va_type_ids) > 0) {
         $va_wheres[] = "({$vs_related_table}.type_id IN (" . join(',', $va_type_ids) . ')' . ($t_rel_item->getFieldInfo('type_id', 'IS_NULL') ? " OR ({$vs_related_table}.type_id IS NULL)" : '') . ')';
     }
     $va_source_ids = caMergeSourceRestrictionLists($t_rel_item, $pa_options);
     if (method_exists($t_rel_item, "getSourceFieldName") && ($vs_source_id_fld = $t_rel_item->getSourceFieldName()) && is_array($va_source_ids) && sizeof($va_source_ids) > 0) {
         $va_wheres[] = "({$vs_related_table}.{$vs_source_id_fld} IN (" . join(',', $va_source_ids) . "))";
     }
     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_type_ids = caMakeTypeIDList($t_rel_item->tableName(), $pa_options['exclude_types']);
         if (is_array($va_type_ids) && sizeof($va_type_ids) > 0) {
             $va_wheres[] = '(' . $vs_related_table . '.type_id NOT IN (' . join(',', $va_type_ids) . '))';
         }
     }
     if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
         $t_user = new ca_users($vn_user_id, true);
         if (is_array($va_groups = $t_user->getUserGroups()) && sizeof($va_groups)) {
             $va_group_ids = array_keys($va_groups);
         } else {
             $va_group_ids = array();
         }
         // Join to limit what browse table items are used to generate facet
         $va_joins_post_add[] = 'LEFT JOIN ca_acl ON ' . $t_rel_item->tableName() . '.' . $t_rel_item->primaryKey() . ' = ca_acl.row_id AND ca_acl.table_num = ' . $t_rel_item->tableNum() . "\n";
         $va_wheres[] = "(\n\t\t\t\t((\n\t\t\t\t\t(ca_acl.user_id = " . (int) $vn_user_id . ")\n\t\t\t\t\t" . (sizeof($va_group_ids) > 0 ? "OR\n\t\t\t\t\t(ca_acl.group_id IN (" . join(",", $va_group_ids) . "))" : "") . "\n\t\t\t\t\tOR\n\t\t\t\t\t(ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)\n\t\t\t\t) AND ca_acl.access >= " . __CA_ACL_READONLY_ACCESS__ . ")\n\t\t\t\t" . ($vb_show_if_no_acl ? "OR ca_acl.acl_id IS NULL" : "") . "\n\t\t\t)";
     }
     if (is_array($va_get_where)) {
         foreach ($va_get_where as $vs_fld => $vm_val) {
             if ($t_rel_item->hasField($vs_fld)) {
                 $va_wheres[] = "({$vs_related_table_name}.{$vs_fld} = " . (!is_numeric($vm_val) ? "'" . $this->getDb()->escape($vm_val) . "'" : $vm_val) . ")";
             }
         }
     }
     if ($vs_idno_fld = $t_rel_item->getProperty('ID_NUMBERING_ID_FIELD')) {
         $va_selects[] = $t_rel_item->tableName() . '.' . $vs_idno_fld;
     }
     if ($vs_idno_sort_fld = $t_rel_item->getProperty('ID_NUMBERING_SORT_FIELD')) {
         $va_selects[] = $t_rel_item->tableName() . '.' . $vs_idno_sort_fld;
     }
     $va_selects[] = $va_path[1] . '.' . $vs_key;
     if (isset($pa_options['fields']) && is_array($pa_options['fields'])) {
         $va_selects = array_merge($va_selects, $pa_options['fields']);
     }
     // if related item is labelable then include the label table in the query as well
     $vs_label_display_field = null;
     if (method_exists($t_rel_item, "getLabelTableName")) {
         if ($vs_label_table_name = $t_rel_item->getLabelTableName()) {
             // make sure it actually has a label table...
             $va_path[] = $vs_label_table_name;
             $t_rel_item_label = $this->getAppDatamodel()->getTableInstance($vs_label_table_name);
             $vs_label_display_field = $t_rel_item_label->getDisplayField();
             if ($vb_return_labels_as_array || is_array($va_sort_fields) && sizeof($va_sort_fields)) {
                 $va_selects[] = $vs_label_table_name . '.*';
             } else {
                 $va_selects[] = $vs_label_table_name . '.' . $vs_label_display_field;
                 $va_selects[] = $vs_label_table_name . '.locale_id';
                 if ($t_rel_item_label->hasField('surname')) {
                     // hack to include fields we need to sort entity labels properly
                     $va_selects[] = $vs_label_table_name . '.surname';
                     $va_selects[] = $vs_label_table_name . '.forename';
                 }
             }
             if ($t_rel_item_label->hasField('is_preferred') && (!isset($pa_options['return_non_preferred_labels']) || !$pa_options['return_non_preferred_labels'])) {
                 $va_wheres[] = "(" . $vs_label_table_name . '.is_preferred = 1)';
             }
         }
     }
     // return source info in returned data
     if ($t_item_rel && $t_item_rel->hasField('source_info')) {
         $va_selects[] = $vs_linking_table . '.source_info';
     }
     if (isset($pa_options['checkAccess']) && is_array($pa_options['checkAccess']) && sizeof($pa_options['checkAccess']) && $t_rel_item->hasField('access')) {
         $va_wheres[] = "(" . $t_rel_item->tableName() . '.access IN (' . join(',', $pa_options['checkAccess']) . '))';
     }
     if ((!isset($pa_options['showDeleted']) || !$pa_options['showDeleted']) && $t_rel_item->hasField('deleted')) {
         $va_wheres[] = "(" . $t_rel_item->tableName() . '.deleted = 0)';
     }
     if ($vb_self_relationship) {
         //
         // START - self relation
         //
         $va_rel_info = $this->getAppDatamodel()->getRelationships($va_path[0], $va_path[1]);
         if ($vs_label_table_name) {
             $va_label_rel_info = $this->getAppDatamodel()->getRelationships($va_path[0], $vs_label_table_name);
         }
         $va_rels = array();
         $vn_i = 0;
         foreach ($va_rel_info[$va_path[0]][$va_path[1]] as $va_possible_keys) {
             $va_joins = array();
             $va_joins[] = "INNER JOIN " . $va_path[1] . " ON " . $va_path[1] . '.' . $va_possible_keys[1] . ' = ' . $va_path[0] . '.' . $va_possible_keys[0] . "\n";
             if ($vs_label_table_name) {
                 $va_joins[] = "INNER JOIN " . $vs_label_table_name . " ON " . $vs_label_table_name . '.' . $va_label_rel_info[$va_path[0]][$vs_label_table_name][0][1] . ' = ' . $va_path[0] . '.' . $va_label_rel_info[$va_path[0]][$vs_label_table_name][0][0] . "\n";
             }
             $vs_other_field = $vn_i == 0 ? $va_rel_info[$va_path[0]][$va_path[1]][1][1] : $va_rel_info[$va_path[0]][$va_path[1]][0][1];
             $vs_direction = preg_match('!left!', $vs_other_field) ? 'ltor' : 'rtol';
             $va_selects['row_id'] = $va_path[1] . '.' . $vs_other_field . ' AS row_id';
             $vs_order_by = '';
             $vs_sort_fld = '';
             if ($t_item_rel && $t_item_rel->hasField('rank')) {
                 $vs_order_by = ' ORDER BY ' . $t_item_rel->tableName() . '.rank';
                 $vs_sort_fld = 'rank';
                 $va_selects[] = $t_item_rel->tableName() . ".rank";
             } else {
                 if ($t_rel_item && ($vs_sort = $t_rel_item->getProperty('ID_NUMBERING_SORT_FIELD'))) {
                     $vs_order_by = " ORDER BY " . $t_rel_item->tableName() . ".{$vs_sort}";
                     $vs_sort_fld = $vs_sort;
                     $va_selects[] = $t_rel_item->tableName() . ".{$vs_sort}";
                 }
             }
             $vs_sql = "\n\t\t\t\t\tSELECT " . join(', ', $va_selects) . "\n\t\t\t\t\tFROM " . $va_path[0] . "\n\t\t\t\t\t" . join("\n", array_merge($va_joins, $va_joins_post_add)) . "\n\t\t\t\t\tWHERE\n\t\t\t\t\t\t" . join(' AND ', array_merge($va_wheres, array('(' . $va_path[1] . '.' . $vs_other_field . ' IN (' . join(',', $va_row_ids) . '))'))) . "\n\t\t\t\t\t{$vs_order_by}";
             //print "<pre>$vs_sql</pre>\n";
             $qr_res = $o_db->query($vs_sql);
             if ($vb_uses_relationship_types) {
                 $va_rel_types = $t_rel->getRelationshipInfo($va_path[1]);
             }
             $vn_c = 0;
             if ($vn_start > 0) {
                 $qr_res->seek($vn_start);
             }
             while ($qr_res->nextRow()) {
                 if ($vn_c >= $vn_limit) {
                     break;
                 }
                 $va_row = $qr_res->getRow();
                 $vn_id = $va_row[$vs_key] . '/' . $va_row['row_id'];
                 $vs_sort_key = $qr_res->get($vs_sort_fld);
                 $vs_display_label = $va_row[$vs_label_display_field];
                 //unset($va_row[$vs_label_display_field]);
                 if (!$va_rels[$vs_sort_key][$vn_id]) {
                     $va_rels[$vs_sort_key][$vn_id] = $qr_res->getRow();
                 }
                 if ($vb_uses_effective_dates) {
                     // return effective dates as display/parse-able text
                     if ($va_rels[$vs_sort_key][$vn_id]['sdatetime'] || $va_rels[$vs_sort_key][$vn_id]['edatetime']) {
                         $o_tep->setHistoricTimestamps($va_rels[$vs_sort_key][$vn_id]['sdatetime'], $va_rels[$vs_sort_key][$vn_id]['edatetime']);
                         $va_rels[$vs_sort_key][$vn_id]['effective_date'] = $o_tep->getText();
                     }
                 }
                 $vn_locale_id = $qr_res->get('locale_id');
                 if (isset($pa_options['returnLocaleCodes']) && $pa_options['returnLocaleCodes']) {
                     $va_rels[$vs_v]['locale_id'] = $vn_locale_id = $t_locale->localeIDToCode($vn_locale_id);
                 }
                 $va_rels[$vs_sort_key][$vn_id]['labels'][$vn_locale_id] = $vb_return_labels_as_array ? $va_row : $vs_display_label;
                 $va_rels[$vs_sort_key][$vn_id]['_key'] = $vs_key;
                 $va_rels[$vs_sort_key][$vn_id]['direction'] = $vs_direction;
                 $vn_c++;
                 if ($vb_uses_relationship_types) {
                     $va_rels[$vs_sort_key][$vn_id]['relationship_typename'] = $vs_direction == 'ltor' ? $va_rel_types[$va_row['relationship_type_id']]['typename'] : $va_rel_types[$va_row['relationship_type_id']]['typename_reverse'];
                     $va_rels[$vs_sort_key][$vn_id]['relationship_type_code'] = $va_rel_types[$va_row['relationship_type_id']]['type_code'];
                 }
                 //
                 // Return data in an arrangement more convenient for the data importer
                 //
                 if ($pa_options['groupFields']) {
                     $vs_rel_pk = $t_rel_item->primaryKey();
                     if ($t_rel_item_label) {
                         foreach ($t_rel_item_label->getFormFields() as $vs_field => $va_field_info) {
                             if (!isset($va_rels[$vs_v][$vs_field]) || $vs_field == $vs_rel_pk) {
                                 continue;
                             }
                             $va_rels[$vs_v]['preferred_labels'][$vs_field] = $va_rels[$vs_v][$vs_field];
                             unset($va_rels[$vs_v][$vs_field]);
                         }
                     }
                     foreach ($t_rel_item->getFormFields() as $vs_field => $va_field_info) {
                         if (!isset($va_rels[$vs_v][$vs_field]) || $vs_field == $vs_rel_pk) {
                             continue;
                         }
                         $va_rels[$vs_v]['intrinsic'][$vs_field] = $va_rels[$vs_v][$vs_field];
                         unset($va_rels[$vs_v][$vs_field]);
                     }
                     unset($va_rels[$vs_v]['_key']);
                     unset($va_rels[$vs_v]['row_id']);
                 }
             }
             $vn_i++;
         }
         ksort($va_rels);
         // sort by sort key... we'll remove the sort key in the next loop while we add the labels
         // Set 'label' entry - display label in current user's locale
         $va_sorted_rels = array();
         foreach ($va_rels as $vs_sort_key => $va_rels_by_sort_key) {
             foreach ($va_rels_by_sort_key as $vn_id => $va_rel) {
                 $va_tmp = array(0 => $va_rel['labels']);
                 $va_sorted_rels[$vn_id] = $va_rel;
                 $va_values_filtered_by_locale = caExtractValuesByUserLocale($va_tmp);
                 $va_sorted_rels[$vn_id]['label'] = array_shift($va_values_filtered_by_locale);
             }
         }
         $va_rels = $va_sorted_rels;
         //
         // END - self relation
         //
     } else {
         //
         // BEGIN - non-self relation
         //
         $va_wheres[] = "(" . $this->tableName() . '.' . $this->primaryKey() . " IN (" . join(",", $va_row_ids) . "))";
         $vs_cur_table = array_shift($va_path);
         $va_joins = array();
         // Enforce restrict_to_lists for related list items
         if ($vs_related_table_name == 'ca_list_items' && is_array($pa_options['restrict_to_lists'])) {
             $va_list_ids = array();
             foreach ($pa_options['restrict_to_lists'] as $vm_list) {
                 if ($vn_list_id = ca_lists::getListID($vm_list)) {
                     $va_list_ids[] = $vn_list_id;
                 }
             }
             if (sizeof($va_list_ids)) {
                 $va_wheres[] = "(ca_list_items.list_id IN (" . join(",", $va_list_ids) . "))";
             }
         }
         foreach ($va_path as $vs_join_table) {
             $va_rel_info = $this->getAppDatamodel()->getRelationships($vs_cur_table, $vs_join_table);
             $va_joins[] = 'INNER JOIN ' . $vs_join_table . ' ON ' . $vs_cur_table . '.' . $va_rel_info[$vs_cur_table][$vs_join_table][0][0] . ' = ' . $vs_join_table . '.' . $va_rel_info[$vs_cur_table][$vs_join_table][0][1] . "\n";
             $vs_cur_table = $vs_join_table;
         }
         // If we're getting ca_set_items, we can't rename our primary key "row_id" because that table
         // has a field called row_id and it's important and should be in the selected fields. Hence, this hack.
         if ($vs_related_table_name == 'ca_set_items') {
             $va_selects[] = $this->tableName() . '.' . $this->primaryKey();
             $va_selects[] = 'ca_set_items.row_id';
         } else {
             $va_selects[] = $this->tableName() . '.' . $this->primaryKey() . ' AS row_id';
         }
         $vs_order_by = '';
         if ($t_item_rel && $t_item_rel->hasField('rank')) {
             $vs_order_by = ' ORDER BY ' . $t_item_rel->tableName() . '.rank';
         } else {
             if ($t_rel_item && ($vs_sort = $t_rel_item->getProperty('ID_NUMBERING_SORT_FIELD'))) {
                 $vs_order_by = " ORDER BY " . $t_rel_item->tableName() . ".{$vs_sort}";
             }
         }
         $vs_sql = "\n\t\t\t\tSELECT DISTINCT " . join(', ', $va_selects) . "\n\t\t\t\tFROM " . $this->tableName() . "\n\t\t\t\t" . join("\n", array_merge($va_joins, $va_joins_post_add)) . "\n\t\t\t\tWHERE\n\t\t\t\t\t" . join(' AND ', $va_wheres) . "\n\t\t\t\t{$vs_order_by}\n\t\t\t";
         //print "<pre>$vs_sql</pre>\n";
         $qr_res = $o_db->query($vs_sql);
         if ($vb_uses_relationship_types) {
             $va_rel_types = $t_rel->getRelationshipInfo($t_item_rel->tableName());
             $vs_left_table = $t_item_rel->getLeftTableName();
             $vs_direction = $vs_left_table == $this->tableName() ? 'ltor' : 'rtol';
         }
         $va_rels = array();
         $vn_c = 0;
         if ($vn_start > 0) {
             $qr_res->seek($vn_start);
         }
         while ($qr_res->nextRow()) {
             if ($vn_c >= $vn_limit) {
                 break;
             }
             if (isset($pa_options['returnAsSearchResult']) && $pa_options['returnAsSearchResult']) {
                 $va_rels[] = $qr_res->get($t_rel_item->primaryKey());
                 continue;
             }
             $va_row = $qr_res->getRow();
             $vs_v = $va_row[$vs_key];
             $vs_display_label = $va_row[$vs_label_display_field];
             //unset($va_row[$vs_label_display_field]);
             if (!isset($va_rels[$vs_v]) || !$va_rels[$vs_v]) {
                 $va_rels[$vs_v] = $va_row;
             }
             if ($vb_uses_effective_dates) {
                 // return effective dates as display/parse-able text
                 if ($va_rels[$vs_v]['sdatetime'] || $va_rels[$vs_v]['edatetime']) {
                     $o_tep->setHistoricTimestamps($va_rels[$vs_v]['sdatetime'], $va_rels[$vs_v]['edatetime']);
                     $va_rels[$vs_v]['effective_date'] = $o_tep->getText();
                 }
             }
             $vn_locale_id = $qr_res->get('locale_id');
             if (isset($pa_options['returnLocaleCodes']) && $pa_options['returnLocaleCodes']) {
                 $va_rels[$vs_v]['locale_id'] = $vn_locale_id = $t_locale->localeIDToCode($vn_locale_id);
             }
             $va_rels[$vs_v]['labels'][$vn_locale_id] = $vb_return_labels_as_array ? $va_row : $vs_display_label;
             $va_rels[$vs_v]['_key'] = $vs_key;
             $va_rels[$vs_v]['direction'] = $vs_direction;
             $vn_c++;
             if ($vb_uses_relationship_types) {
                 $va_rels[$vs_v]['relationship_typename'] = $vs_direction == 'ltor' ? $va_rel_types[$va_row['relationship_type_id']]['typename'] : $va_rel_types[$va_row['relationship_type_id']]['typename_reverse'];
                 $va_rels[$vs_v]['relationship_type_code'] = $va_rel_types[$va_row['relationship_type_id']]['type_code'];
             }
             if ($pa_options['groupFields']) {
                 $vs_rel_pk = $t_rel_item->primaryKey();
                 if ($t_rel_item_label) {
                     foreach ($t_rel_item_label->getFormFields() as $vs_field => $va_field_info) {
                         if (!isset($va_rels[$vs_v][$vs_field]) || $vs_field == $vs_rel_pk) {
                             continue;
                         }
                         $va_rels[$vs_v]['preferred_labels'][$vs_field] = $va_rels[$vs_v][$vs_field];
                         unset($va_rels[$vs_v][$vs_field]);
                     }
                 }
                 foreach ($t_rel_item->getFormFields() as $vs_field => $va_field_info) {
                     if (!isset($va_rels[$vs_v][$vs_field]) || $vs_field == $vs_rel_pk) {
                         continue;
                     }
                     $va_rels[$vs_v]['intrinsic'][$vs_field] = $va_rels[$vs_v][$vs_field];
                     unset($va_rels[$vs_v][$vs_field]);
                 }
                 unset($va_rels[$vs_v]['_key']);
                 unset($va_rels[$vs_v]['row_id']);
             }
         }
         if (!isset($pa_options['returnAsSearchResult']) || !$pa_options['returnAsSearchResult']) {
             // Set 'label' entry - display label in current user's locale
             foreach ($va_rels as $vs_v => $va_rel) {
                 $va_tmp = array(0 => $va_rel['labels']);
                 $va_tmp2 = caExtractValuesByUserLocale($va_tmp);
                 $va_rels[$vs_v]['label'] = array_shift($va_tmp2);
             }
         }
         //
         // END - non-self relation
         //
     }
     // Apply restrictToBundleValues
     $va_filters = caGetOption('restrictToBundleValues', $pa_options, array());
     if (is_array($va_filters) && sizeof($va_filters) > 0) {
         foreach ($va_rels as $vn_pk => $va_related_item) {
             foreach ($va_filters as $vs_filter => $va_filter_vals) {
                 if (!$vs_filter) {
                     continue;
                 }
                 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?
                     $va_tmp = explode('.', $vs_filter);
                     $vs_element = array_pop($va_tmp);
                     if (!is_numeric($vs_filter_val) && (($t_element = $t_rel_item->_getElementInstance($vs_element)) && $t_element->get('datatype') == 3)) {
                         $va_filter_vals[$vn_index] = caGetListItemID($t_element->get('list_id'), $vs_filter_val);
                     }
                 }
                 $t_rel_item->load($va_related_item[$t_rel_item->primaryKey()]);
                 $va_filter_values = $t_rel_item->get($vs_filter, array('returnAsArray' => true, 'alwaysReturnItemID' => true));
                 $vb_keep = false;
                 if (is_array($va_filter_values)) {
                     foreach ($va_filter_values as $vm_filtered_val) {
                         if (!is_array($vm_filtered_val)) {
                             $vm_filtered_val = array($vm_filtered_val);
                         }
                         foreach ($vm_filtered_val as $vs_val) {
                             if (in_array($vs_val, $va_filter_vals)) {
                                 // one match is enough to keep it
                                 $vb_keep = true;
                             }
                         }
                     }
                 }
                 if (!$vb_keep) {
                     unset($va_rels[$vn_pk]);
                 }
             }
         }
     }
     //
     // Sort on fields if specified
     //
     if (is_array($va_sort_fields) && sizeof($va_rels)) {
         $va_ids = array();
         $vs_rel_pk = $t_rel_item->primaryKey();
         foreach ($va_rels as $vn_i => $va_rel) {
             $va_ids[] = $va_rel[$vs_rel_pk];
         }
         // Handle sorting on attribute values
         $vs_rel_pk = $t_rel_item->primaryKey();
         foreach ($va_sort_fields as $vn_x => $vs_sort_field) {
             if ($vs_sort_field == 'relation_id') {
                 // sort by relationship primary key
                 if ($t_item_rel) {
                     $va_sort_fields[$vn_x] = $vs_sort_field = $t_item_rel->tableName() . '.' . $t_item_rel->primaryKey();
                 }
                 continue;
             }
             $va_tmp = explode('.', $vs_sort_field);
             if ($va_tmp[0] == $vs_related_table_name) {
                 $qr_rel = $t_rel_item->makeSearchResult($va_tmp[0], $va_ids);
                 $vs_table = array_shift($va_tmp);
                 $vs_key = join(".", $va_tmp);
                 while ($qr_rel->nextHit()) {
                     $vn_pk_val = $qr_rel->get($vs_table . "." . $vs_rel_pk);
                     foreach ($va_rels as $vn_rel_id => $va_rel) {
                         if ($va_rel[$vs_rel_pk] == $vn_pk_val) {
                             $va_rels[$vn_rel_id][$vs_key] = $qr_rel->get($vs_sort_field, array("delimiter" => ";", 'sortable' => 1));
                             break;
                         }
                     }
                 }
             }
         }
         // Perform sort
         $va_rels = caSortArrayByKeyInValue($va_rels, $va_sort_fields, $vs_sort_direction);
     }
     return $va_rels;
 }
 /**
  * Options:
  * 		rawDate - if true, returns date as an array of start and end historic timestames
  *		sortable - if true a language-independent sortable representation is returned.
  *		getDirectDate - get underlying historic timestamp (floatval)
  */
 public function getDisplayValue($pa_options = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     if (isset($pa_options['rawDate']) && $pa_options['rawDate']) {
         return array(0 => $this->opn_start_date, 1 => $this->opn_end_date, 'start' => $this->opn_start_date, 'end' => $this->opn_end_date);
     }
     if (isset($pa_options['GET_DIRECT_DATE']) && $pa_options['GET_DIRECT_DATE'] || isset($pa_options['getDirectDate']) && $pa_options['getDirectDate']) {
         return $this->opn_start_date;
     }
     if (isset($pa_options['sortable']) && $pa_options['sortable']) {
         if (!$this->opn_start_date || !$this->opn_end_date) {
             return null;
         }
         return $this->opn_start_date . '/' . $this->opn_end_date;
     }
     $o_config = Configuration::load();
     $o_date_config = Configuration::load($o_config->get('datetime_config'));
     if ($o_date_config->get('dateFormat') == 'original') {
         return $this->ops_text_value;
     } else {
         $t_element = new ca_metadata_elements($this->getElementID());
         $va_settings = $this->getSettingValuesFromElementArray($t_element->getFieldValuesArray(), array('isLifespan'));
         $o_tep = new TimeExpressionParser();
         $o_tep->setHistoricTimestamps($this->opn_start_date, $this->opn_end_date);
         return $o_tep->getText(array_merge(array('isLifespan' => $va_settings['isLifespan']), $pa_options));
         //$this->ops_text_value;
     }
 }