PageRenderTime 117ms CodeModel.GetById 66ms app.highlight 39ms RepoModel.GetById 1ms app.codeStats 1ms

/app/lib/core/Search/SearchResult.php

https://bitbucket.org/Sinfin/pawtucket
PHP | 1602 lines | 1196 code | 142 blank | 264 comment | 355 complexity | 99792584399b0f02c697a18fb6449536 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/** ---------------------------------------------------------------------
   3 * app/lib/core/Search/SearchResult.php : implements interface to results from a search
   4 * ----------------------------------------------------------------------
   5 * CollectiveAccess
   6 * Open-source collections management software
   7 * ----------------------------------------------------------------------
   8 *
   9 * Software by Whirl-i-Gig (http://www.whirl-i-gig.com)
  10 * Copyright 2008-2011 Whirl-i-Gig
  11 *
  12 * For more information visit http://www.CollectiveAccess.org
  13 *
  14 * This program is free software; you may redistribute it and/or modify it under
  15 * the terms of the provided license as published by Whirl-i-Gig
  16 *
  17 * CollectiveAccess is distributed in the hope that it will be useful, but
  18 * WITHOUT ANY WARRANTIES whatsoever, including any implied warranty of 
  19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
  20 *
  21 * This source code is free and modifiable under the terms of 
  22 * GNU General Public License. (http://www.gnu.org/copyleft/gpl.html). See
  23 * the "license.txt" file for details, or visit the CollectiveAccess web site at
  24 * http://www.CollectiveAccess.org
  25 *
  26 * @package CollectiveAccess
  27 * @subpackage Search
  28 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License version 3
  29 *
  30 * ----------------------------------------------------------------------
  31 */
  32 
  33 /**
  34  *
  35  */
  36
  37# ----------------------------------------------------------------------
  38# --- Import classes
  39# ----------------------------------------------------------------------
  40include_once(__CA_LIB_DIR__."/core/BaseObject.php");
  41include_once(__CA_LIB_DIR__."/core/Datamodel.php");
  42include_once(__CA_LIB_DIR__."/core/Media/MediaInfoCoder.php");
  43include_once(__CA_LIB_DIR__."/core/File/FileInfoCoder.php");
  44include_once(__CA_LIB_DIR__."/core/Parsers/TimeExpressionParser.php");
  45include_once(__CA_LIB_DIR__."/core/Parsers/TimecodeParser.php");
  46include_once(__CA_LIB_DIR__."/core/ApplicationChangeLog.php");
  47
  48
  49# ----------------------------------------------------------------------
  50class SearchResult extends BaseObject {
  51	
  52	private $opo_datamodel;
  53	private $opo_search_config;
  54	private $opo_db;
  55	private $opn_table_num;
  56	private $ops_table_name;
  57	private $ops_table_pk;
  58	// ----
  59	
  60	private $opa_options;
  61	
  62	private $opo_engine_result;
  63	protected $opa_tables;
  64	
  65	private $opo_row_instance;
  66	
  67	private $opa_prefetch_cache;
  68	private $opa_rel_prefetch_cache;
  69	private $opa_timestamp_cache;
  70	private $opa_row_ids_to_prefetch_cache;
  71	
  72	private $opo_tep; // timeexpression parser
  73	
  74	# ------------------------------------------------------------------
  75	public function __construct($pn_subject_table_num=null, $po_engine_result=null, $pa_tables=null) {
  76		
  77		$this->opo_db = new Db();
  78		$this->opo_datamodel = Datamodel::load();
  79		
  80		$this->opa_prefetch_cache = array();
  81		$this->opa_rel_prefetch_cache = array();
  82		$this->opa_timestamp_cache = array();
  83		$this->opa_row_ids_to_prefetch_cache = array();
  84		
  85		if ($pn_subject_table_num) {
  86			$this->init($pn_subject_table_num, $po_engine_result, $pa_tables);
  87		}
  88		
  89		if (!$GLOBALS["_DbResult_time_expression_parser"]) { $GLOBALS["_DbResult_time_expression_parser"] = new TimeExpressionParser(); }
  90		if (!$GLOBALS["_DbResult_timecodeparser"]) { $GLOBALS["_DbResult_timecodeparser"] = new TimecodeParser(); }
  91		
  92		if (!$GLOBALS["_DbResult_mediainfocoder"]) { $GLOBALS["_DbResult_mediainfocoder"] = MediaInfoCoder::load(); }
  93		if (!$GLOBALS["_DbResult_fileinfocoder"]) { $GLOBALS["_DbResult_fileinfocoder"] = FileInfoCoder::load(); }
  94		
  95		
  96		// valid options and defaults
  97		$this->opa_options = array(
  98				// SearchResult::get() can load field data from database when it is not available directly from the search index (most fields are *not* available from the index)
  99				// It is almost always more efficient to grab multiple field values from a table in one query, and to do so for multiple rows, than to generate and execute queries 
 100				// each time get() is called. Thus get() automatically "prefetches" field values for a given table when it is called; the "prefetch" option defined how many rows
 101				// beyond the current row are pre-loaded. You ideally want this value to match the number of rows you actually plan to use. If you're generating lists of search
 102				// results and page the results with 50 results per page then you'd want to the prefetch to be 50. If the number of rows you need is very large (> 200?) then it might
 103				// make sense to use a value less than the total number of rows since queries with many enumerated row_ids (which is what the prefetch mechanism uses) may run slowly
 104				// when a large number of ids are specified. The default for this is 50.
 105				// 
 106				'prefetch' => 50
 107		);
 108		
 109		
 110		$this->opo_tep = new TimeExpressionParser();
 111
 112	}
 113	# ------------------------------------------------------------------
 114	public function init($pn_subject_table_num, $po_engine_result, $pa_tables) {
 115		$this->opn_table_num = $pn_subject_table_num;
 116		$this->opo_engine_result = $po_engine_result;
 117		$this->opa_tables = $pa_tables;
 118		
 119		$this->errors = array();
 120	
 121		$this->opo_row_instance = $this->opo_datamodel->getInstanceByTableNum($this->opn_table_num, true);
 122		$this->ops_table_name =  $this->opo_row_instance->tableName();
 123		$this->ops_table_pk = $this->opo_row_instance->primaryKey();
 124	}
 125	# ------------------------------------------------------------------
 126	public function tableNum() {
 127		return $this->opn_table_num;
 128	}
 129	# ------------------------------------------------------------------
 130	public function tableName() {
 131		return $this->ops_table_name;
 132	}
 133	# ------------------------------------------------------------------
 134	public function numHits() {
 135		return $this->opo_engine_result->numHits();
 136	}
 137	# ------------------------------------------------------------------
 138	public function nextHit() {
 139		return $this->opo_engine_result->nextHit();
 140	}
 141	# ------------------------------------------------------------------
 142	public function previousHit() {
 143		$vn_index = $this->opo_engine_result->currentRow();
 144		if ($vn_index >= 0) {
 145			$this->opo_engine_result->seek($vn_index);
 146		}
 147	}
 148	# ------------------------------------------------------------------
 149	/**
 150  	 * Returns true if this current hit is the last in the set
 151  	 *
 152  	 * @return boolean True if current hit is the last in the results set, false otherwise
 153	 */
 154	public function isLastHit() {
 155		$vn_index = $this->opo_engine_result->currentRow();
 156		$vn_num_hits = $this->opo_engine_result->numHits();
 157		
 158		if ($vn_index == ($vn_num_hits - 1)) { return true; }
 159		
 160		return false;
 161	}
 162	# ------------------------------------------------------------------
 163	/**
 164	 *
 165	 */
 166	private function getRowIDsToPrefetch($ps_tablename, $pn_start, $pn_num_rows) {
 167		if ($this->opa_row_ids_to_prefetch_cache[$ps_tablename.'/'.$pn_start.'/'.$pn_num_rows]) { return $this->opa_row_ids_to_prefetch_cache[$ps_tablename.'/'.$pn_start.'/'.$pn_num_rows]; }
 168		$va_row_ids = array();
 169		
 170		$vn_cur_row_index = $this->opo_engine_result->currentRow();
 171		$this->seek($pn_start);
 172		
 173		$vn_i=0;
 174		while(self::nextHit() && ($vn_i < $pn_num_rows)) {
 175			$va_row_ids[] = $this->opo_engine_result->get($this->ops_table_pk);
 176			$vn_i++;
 177		}
 178		$this->seek($vn_cur_row_index + 1);
 179		
 180		return $this->opa_row_ids_to_prefetch_cache[$ps_tablename.'/'.$pn_start.'/'.$pn_num_rows] = $va_row_ids;
 181	}
 182	# ------------------------------------------------------------------
 183	/**
 184	 * TODO: implement prefetch of related and non-indexed-stored fields. Basically, instead of doing a query for every row via get() [which will still be an option if you're lazy]
 185	 * prefetch() will allow you to tell SearchResult to preload values for a set of hits starting at $pn_start 
 186	 * Because this can be done in a single query it'll presumably be faster than lazy loading lots of rows
 187	 */
 188	public function prefetch($ps_tablename, $pn_start, $pn_num_rows, $pa_element_ids=null) {
 189		//print "PREFETCH: ".$ps_tablename.' - '. $pn_start.' - '. $pn_num_rows."<br>";
 190		
 191		// get row_ids to fetch
 192		if (sizeof($va_row_ids = $this->getRowIDsToPrefetch($ps_tablename, $pn_start, $pn_num_rows)) == 0) { return false; }
 193		
 194		// do join
 195		$va_joins = array();
 196		
 197		$t_rel_instance = $this->opo_datamodel->getInstanceByTableName($ps_tablename, true);
 198		
 199		if ($ps_tablename != $this->ops_table_name) {
 200			$va_fields = $this->opa_tables[$ps_tablename]['fieldList'];
 201			$va_fields[] = $this->ops_table_name.'.'.$this->ops_table_pk;
 202			
 203			// Include type_id field for item table (eg. ca_entities.type_id)
 204			if (method_exists($t_rel_instance, "getTypeFieldName") && ($t_rel_instance->getTypeFieldName()) && ($vs_type_fld_name = $t_rel_instance->getTypeFieldName())) {
 205				$va_fields[] = $t_rel_instance->tableName().'.'.$vs_type_fld_name.' item_type_id';
 206			} else {
 207				// Include type_id field for item table (eg. ca_entities.type_id) when fetching labels
 208				if (method_exists($t_rel_instance, "getSubjectTableInstance")) {
 209					$t_label_subj_instance = $t_rel_instance->getSubjectTableInstance();
 210					if (method_exists($t_label_subj_instance, "getTypeFieldName") && ($vs_type_fld_name = $t_label_subj_instance->getTypeFieldName())) {
 211						$va_fields[] = $t_label_subj_instance->tableName().'.'.$vs_type_fld_name.' item_type_id';
 212					}
 213				}
 214			}
 215			
 216			$va_joined_table_info = $this->opa_tables[$ps_tablename];
 217			$va_linking_tables = $va_joined_table_info['joinTables'];
 218			if (!is_array($va_linking_tables)) { $va_linking_tables = array(); }
 219			array_push($va_linking_tables, $ps_tablename);
 220			
 221			$vs_left_table = $this->ops_table_name;
 222	
 223			foreach($va_linking_tables as $vs_right_table) {
 224				if ($va_rel = $this->opo_datamodel->getOneToManyRelations($vs_left_table, $vs_right_table)) {
 225					$va_joins[] = 'INNER JOIN '.$va_rel['many_table'].' ON '.$va_rel['one_table'].'.'.$va_rel['one_table_field'].' = '.$va_rel['many_table'].'.'.$va_rel['many_table_field'];
 226					$t_link = $this->opo_datamodel->getInstanceByTableName($va_rel['many_table'], true);
 227					if (is_a($t_link, 'BaseRelationshipModel') && $t_link->hasField('type_id')) {
 228						$va_fields[] = $va_rel['many_table'].'.type_id rel_type_id';
 229					}
 230				} else {
 231					if ($va_rel = $this->opo_datamodel->getOneToManyRelations($vs_right_table, $vs_left_table)) {
 232						$va_joins[] = 'INNER JOIN '.$va_rel['one_table'].' ON '.$va_rel['one_table'].'.'.$va_rel['one_table_field'].' = '.$va_rel['many_table'].'.'.$va_rel['many_table_field'];
 233					}
 234				}
 235				$vs_left_table = $vs_right_table;
 236			}
 237		} else {
 238			$va_fields = array('*');
 239		}
 240		
 241		$vs_criteria_sql = '';
 242		if (is_array($this->opa_tables[$ps_tablename]['criteria']) && (sizeof($this->opa_tables[$ps_tablename]['criteria']) > 0)) {
 243			$vs_criteria_sql = ' AND ('.join(' AND ', $this->opa_tables[$ps_tablename]['criteria']).')';
 244		}
 245	
 246		$vb_has_locale_id = true;
 247		if ($this->opo_row_instance->hasField('locale_id') && (!$t_rel_instance->hasField('locale_id'))) {
 248			$va_fields[] = $this->ops_table_name.'.locale_id';
 249			$vb_has_locale_id = true;
 250		}
 251		
 252		$vs_order_by = '';
 253		if ($t_rel_instance->hasField('idno_sort')) {
 254			$vs_order_by = " ORDER BY ".$t_rel_instance->tableName().".idno_sort";
 255		}
 256	
 257		$vs_rel_pk = $t_rel_instance->primaryKey();
 258		
 259		$vs_sql = "
 260			SELECT ".join(',', $va_fields)."
 261			FROM ".$this->ops_table_name."
 262			".join("\n", $va_joins)."
 263			WHERE
 264				".$this->ops_table_name.'.'.$this->ops_table_pk." IN (".join(',', $va_row_ids).") $vs_criteria_sql
 265			{$vs_order_by}
 266		";
 267		//print "<pre>$vs_sql</pre>";
 268		$qr_rel = $this->opo_db->query($vs_sql);
 269		
 270		$va_rel_row_ids = array();
 271		while($qr_rel->nextRow()) {
 272			$va_row = $qr_rel->getRow();
 273			$vn_row_id = $va_row[$this->ops_table_pk];
 274			if ($vb_has_locale_id) {
 275				$vn_locale_id = $va_row['locale_id'];
 276				$this->opa_prefetch_cache[$ps_tablename][$vn_row_id][$vn_locale_id][] = $va_row;
 277			} else {
 278				$this->opa_prefetch_cache[$ps_tablename][$vn_row_id][1][] = $va_row;
 279			}
 280		}
 281		
 282		// Fill row_id values for which there is nothing to prefetch with an empty lists
 283		// otherwise we'll try and prefetch these again later wasting time.
 284		foreach($va_row_ids as $vn_row_id) {
 285			if (!isset($this->opa_prefetch_cache[$ps_tablename][$vn_row_id])) {
 286				$this->opa_prefetch_cache[$ps_tablename][$vn_row_id] = array();
 287			}
 288		}
 289		
 290		//print "<pre>".print_r($this->opa_prefetch_cache[$ps_tablename], true)."</pre>";
 291	}
 292	# ------------------------------------------------------------------
 293	/**
 294	 * 
 295	 */
 296	public function prefetchRelated($ps_tablename, $pn_start, $pn_num_rows, $pa_options) {
 297		//print "PREFETCH RELATED: ".$ps_tablename.' - '. $pn_start.' - '. $pn_num_rows."<br>";
 298		unset($pa_options['request']);
 299		if (sizeof($va_row_ids = $this->getRowIDsToPrefetch($ps_tablename, $pn_start, $pn_num_rows)) == 0) { return false; }
 300		$vs_md5 = md5(print_r($pa_options, true));
 301		$va_rel_items = $this->opo_row_instance->getRelatedItems($ps_tablename, array_merge($pa_options, array('row_ids' => $va_row_ids, 'limit' => 100000)));		// if there are more than 100,000 then we have a problem
 302		foreach($va_rel_items as $vn_relation_id => $va_rel_item) {
 303			$this->opa_rel_prefetch_cache[$ps_tablename][$va_rel_item['row_id']][$vs_md5][] = $va_rel_item;
 304		}
 305		// Fill row_id values for which there is nothing to prefetch with an empty lists
 306		// otherwise we'll try and prefetch these again later wasting time.
 307		foreach($va_row_ids as $vn_row_id) {
 308			if (!isset($this->opa_rel_prefetch_cache[$ps_tablename][$vn_row_id][$vs_md5])) {
 309				$this->opa_rel_prefetch_cache[$ps_tablename][$vn_row_id][$vs_md5] = array();
 310			}
 311		}
 312		return true;
 313	}
 314	# ------------------------------------------------------------------
 315	/**
 316	 * 
 317	 */
 318	public function prefetchChangeLogData($ps_tablename, $pn_start, $pn_num_rows) {
 319		if (sizeof($va_row_ids = $this->getRowIDsToPrefetch($ps_tablename, $pn_start, $pn_num_rows)) == 0) { return false; }
 320		$vs_key = md5($ps_tablename.'/'.print_r($va_row_ids, true));
 321		if ($this->opa_timestamp_cache['fetched'][$vs_key]) { return true; }
 322		
 323		$o_log = new ApplicationChangeLog();
 324	
 325		if (!is_array($this->opa_timestamp_cache['created_on'][$ps_tablename])) { $this->opa_timestamp_cache['created_on'][$ps_tablename] = array(); }
 326		$this->opa_timestamp_cache['created_on'][$ps_tablename] += $o_log->getCreatedOnTimestampsForIDs($ps_tablename, $va_row_ids);
 327		if (!is_array($this->opa_timestamp_cache['last_changed'][$ps_tablename])) { $this->opa_timestamp_cache['last_changed'][$ps_tablename] = array(); }
 328		$this->opa_timestamp_cache['last_changed'][$ps_tablename] += $o_log->getLastChangeTimestampsForIDs($ps_tablename, $va_row_ids);
 329
 330		$this->opa_timestamp_cache['fetched'][$vs_key] = true;
 331		return true;
 332	}
 333	# ------------------------------------------------------------------
 334	/**
 335	 *
 336	 */
 337	public function getPrimaryKeyValues($vn_limit=4000000000) {
 338		$vs_pk = $this->opo_row_instance->primaryKey();
 339		$this->opo_engine_result->seek(0);
 340		
 341		$va_ids = array();
 342		$vn_c = 0;
 343		while($this->opo_engine_result->nextHit()) {
 344			$va_ids[] = $this->opo_engine_result->get($vs_pk);
 345			$vn_c++;
 346			if ($vn_c >= $vn_limit) { break; }
 347		}
 348		return $va_ids;
 349	}
 350	# ------------------------------------------------------------------
 351	/**
 352	 * Returns a value from the query result. This can be a single value if it is a field in the subject table (eg. objects table in an objects search), or
 353	 * perhaps multiple related values (eg. related entities in an objects search). By default get() always returns a single value; for fields with multiple values
 354	 * the value will be the first value encountered when loading the field data. 
 355	 *
 356	 * You can fetch the values of attributes attached to the subject row (ie. if you're searching for ca_objects rows, the subject row is the ca_objects row)
 357	 * by use the "virtual" field name <subject_table_name>.<element_code> (ex. ca_objects.date_created)
 358	 * If the attribute is a multi-value container then you can fetch a specific value using the format <subject_table_name>.<attribute_element_code>/<value_element_code>
 359	 * For example, if you want to get the "date_value" value out of a "date" attribute attached to a ca_objects row, then you'd call get()
 360	 * with this fieldname: ca_objects.date/date_value
 361	 *
 362	 * If you want to get the other values for a multiple-value fields use the following options:
 363	 *
 364	 *				returnAsArray = if true, return an array, otherwise return a string (default is false)
 365	 *				template = formats attribute values; precede element codes with a caret ("^"). Eg. "^address1<br/>^city, ^state ^postalcode ^country"; only used when returnAsArray is false and a scalar is therefore to be returned.
 366	 *				delimiter = 
 367	 *				returnAllLocales = 
 368	 *				convertCodesToDisplayText = if true then list_ids are automatically converted to display text in the current locale; default is false (return list_ids raw)
 369	 *				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.
 370 	 *				exclude_relationship_types - omits any items related to the current row with any of the specified types from the returned set of its. You can pass either an array of types or a single type. The types can be relationship type_code's or type_id's.
 371 	 *				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.
 372	 *				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).
 373	 */
 374	function get($ps_field, $pa_options=null) {
 375		if (!is_array($pa_options)) { $pa_options = array(); }
 376		$vb_return_as_array = 		(isset($pa_options['returnAsArray'])) ? (bool)$pa_options['returnAsArray'] : false;
 377		$vb_return_all_locales = 	(isset($pa_options['returnAllLocales'])) ? (bool)$pa_options['returnAllLocales'] : false;
 378		$va_get_where = 			(isset($pa_options['where']) && is_array($pa_options['where']) && sizeof($pa_options['where'])) ? $pa_options['where'] : null;
 379		
 380		$vo_request = $pa_options['request'];
 381		unset($pa_options['request']);
 382		
 383		// first see if the search engine can provide the field value directly (fastest)
 384		if(!(($vs_value = $this->opo_engine_result->get($ps_field, $pa_options)) === false)) {
 385			if ($vb_return_as_array) {
 386				if ($vb_return_all_locales) {
 387					return array(1 => $vs_value);
 388				} else {
 389					return array($vs_value);
 390				}
 391			} else {
 392				return $vs_value;
 393			}
 394		}
 395		
 396		$vs_template = 				(isset($pa_options['template'])) ? $pa_options['template'] : null;
 397		$vs_delimiter = 				(isset($pa_options['delimiter'])) ? $pa_options['delimiter'] : ' ';
 398		if ($vb_return_all_locales && !$vb_return_as_array) { $vb_return_as_array = true; }
 399		
 400		if(isset($pa_options['sort']) && !is_array($pa_options['sort'])) { $pa_options['sort'] = array($pa_options['sort']); }
 401		$va_sort_fields = (isset($pa_options['sort']) && is_array($pa_options['sort'])) ? $pa_options['sort'] : null;
 402		
 403		$vn_row_id = $this->opo_engine_result->get($this->ops_table_pk);	
 404		
 405		
 406		// try to lazy load (slower)
 407		$va_path_components = $this->getFieldPathComponents($ps_field);
 408		
 409		//
 410		// are we getting timestamp (created on or last modified) info?
 411		//
 412		if (($va_path_components['table_name'] == $this->ops_table_name) && ($va_path_components['field_name'] == 'created')) {
 413			if (!isset($this->opa_timestamp_cache['created_on'][$this->ops_table_name][$vn_row_id])) {
 414				$this->prefetchChangeLogData($this->ops_table_name, $this->opo_engine_result->currentRow(), $this->getOption('prefetch'));
 415			}
 416			
 417			if ($vb_return_as_array) {
 418				return $this->opa_timestamp_cache['created_on'][$this->ops_table_name][$vn_row_id];
 419			} else {
 420				$vs_subfield = $va_path_components['subfield_name'] ? $va_path_components['subfield_name'] : 'timestamp';
 421				$vm_val = $this->opa_timestamp_cache['created_on'][$this->ops_table_name][$vn_row_id][$vs_subfield];
 422				
 423				if ($vs_subfield == 'timestamp') {
 424					$o_tep = new TimeExpressionParser();
 425					$o_tep->setUnixTimestamps($vm_val, $vm_val);
 426					$vm_val = $o_tep->getText($pa_options);
 427				}
 428				return $vm_val;
 429			}
 430		}
 431		
 432		if (($va_path_components['table_name'] == $this->ops_table_name) && ($va_path_components['field_name'] == 'lastModified')) {
 433			if (!isset($this->opa_timestamp_cache['last_changed'][$this->ops_table_name][$vn_row_id])) {
 434				$this->prefetchChangeLogData($this->ops_table_name, $this->opo_engine_result->currentRow(), $this->getOption('prefetch'));
 435			}
 436			
 437			if ($vb_return_as_array) {
 438				return $this->opa_timestamp_cache['last_changed'][$this->ops_table_name][$vn_row_id];
 439			} else {
 440				$vs_subfield = $va_path_components['subfield_name'] ? $va_path_components['subfield_name'] : 'timestamp';
 441				$vm_val = $this->opa_timestamp_cache['last_changed'][$this->ops_table_name][$vn_row_id][$vs_subfield];
 442				
 443				if ($vs_subfield == 'timestamp') {
 444					$o_tep = new TimeExpressionParser();
 445					$o_tep->setUnixTimestamps($vm_val, $vm_val);
 446					$vm_val = $o_tep->getText($pa_options);
 447				}
 448				return $vm_val;
 449			}
 450		}
 451		
 452		if (!($t_instance = $this->opo_datamodel->getInstanceByTableName($va_path_components['table_name'], true))) { return null; }
 453		
 454		// Simple related table get
 455		if (
 456			(($va_path_components['num_components'] == 1) && ($va_path_components['table_name'] !== $this->ops_table_name))
 457			||
 458			(($va_path_components['num_components'] == 2) && ($va_path_components['field_name'] == 'related'))
 459			||
 460			(($va_path_components['num_components'] == 2) && ($va_path_components['field_name'] == 'hierarchy'))
 461		) {
 462			if (!($t_table = $this->opo_datamodel->getInstanceByTableName($this->ops_table_name, true))) { return null; }
 463			
 464			$vb_show_hierarachy = (bool)$va_path_components['field_name'];
 465			
 466			if ($va_path_components['num_components'] == 2) {
 467				$va_path_components['num_components'] = 1;
 468				$va_path_components['field_name'] = null;
 469			}
 470			
 471			$vs_opt_md5 = md5(print_R($pa_options, true));
 472			if (!isset($this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5])) {
 473				$this->prefetchRelated($va_path_components['table_name'], $this->opo_engine_result->currentRow(), $this->getOption('prefetch'), $pa_options);
 474			}
 475			$va_related_items = $this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5];
 476			if (!is_array($va_related_items)) { return null; }
 477			
 478			if (is_array($va_sort_fields) && sizeof($va_sort_fields)) {
 479				$va_related_items = caSortArrayByKeyInValue($va_related_items, $va_sort_fields);
 480			}
 481			if($vb_return_as_array) {
 482				 if ($vb_return_all_locales) {
 483					return $va_related_items;
 484				 } else {
 485					foreach($va_related_items as $vn_relation_id => $va_relation_info) {
 486						$va_relation_info['labels'] = caExtractValuesByUserLocale(array(0 => $va_relation_info['labels']));	
 487						$va_related_items[$vn_relation_id]['labels'] = $va_relation_info['labels'];
 488					}
 489					return $va_related_items;
 490				 }
 491			} else {
 492				$va_proc_labels = array();
 493				
 494				$va_row_ids = array();
 495				$vs_rel_pk = $t_instance->primaryKey();
 496				
 497				foreach($va_related_items as $vn_relation_id => $va_relation_info) {
 498					$va_row_ids[] = $va_relation_info[$vs_rel_pk];
 499				}
 500				if (!sizeof($va_row_ids)) { return ''; }
 501				$va_tags = array();
 502				if (preg_match_all("!\^([A-Za-z0-9_\.]+)!", $vs_template, $va_matches)) {
 503					$va_tags = $va_matches[1];
 504				}
 505				
 506				$qr_rel_items = $t_instance->makeSearchResult($va_path_components['table_name'], $va_row_ids);
 507				$qr_rel_items->setOption('prefetch', 1000);
 508				
 509				$va_values = array();
 510				while($qr_rel_items->nextHit()) {
 511					$va_relation_info = array_shift($va_related_items);
 512					if (sizeof($va_tags)) {
 513						$vs_value = $vs_template;
 514						foreach($va_tags as $vs_tag) {
 515							$vs_field_val = null;
 516							switch($vs_tag) {
 517								case 'label':
 518								case 'preferred_labels':
 519								case $va_path_components['table_name'].'.preferred_labels':
 520									$vs_field_val = $va_relation_info['label'];
 521									if ($vb_show_hierarachy && $t_instance->isHierarchical()) {
 522										if ($va_ids = $this->get($va_path_components['table_name'].'.hierarchy.'.$t_instance->primaryKey(), array_merge($pa_options, array('returnAsArray' => true)))) {
 523											$va_vals = array();
 524											foreach($va_ids as $vn_id) {
 525												if($t_instance->load($vn_id)) {
 526													$va_vals[] = $t_instance->get($va_path_components['table_name'].".preferred_labels", $pa_options);
 527												}
 528											}
 529											
 530											$vs_field_val = join($vs_delimiter, $va_vals);
 531										}
 532									}
 533									$vs_value = str_replace("^{$vs_tag}", $vs_field_val, $vs_value);
 534									break;
 535								case 'relationship_typename':
 536									$vs_field_val = $va_relation_info['relationship_typename'];
 537									break;
 538								default:
 539									$vs_field_val = $qr_rel_items->get($va_path_components['table_name'].".".$vs_tag);
 540									break;
 541							}
 542							
 543							if ($vs_field_val) {
 544								$vs_value = str_replace("^{$vs_tag}", $vs_field_val, $vs_value);
 545							} else {
 546								$vs_value = preg_replace("![^\^A-Za-z0-9_ ]*\^{$vs_tag}[ ]*[^A-Za-z0-9_ ]*[ ]*!", '', $vs_value);
 547							}
 548						}
 549						
 550						$va_values[] = $vs_value;
 551					} else {
 552						$va_values[] = $qr_rel_items->get($va_path_components['table_name'].'.preferred_labels');
 553					}
 554				}
 555				
 556				return join($vs_delimiter, $va_values);
 557			}
 558		}
 559		
 560		$vb_need_parent = false;
 561		$vb_need_children = false;
 562		
 563		
 564		//
 565		// Transform "preferred_labels" into tables for pre-fetching
 566		//
 567		$vb_is_get_for_labels = $vb_return_all_label_values = $vb_get_preferred_labels_only = $vb_get_nonpreferred_labels_only = false;
 568		if(in_array($va_path_components['field_name'], array('preferred_labels', 'nonpreferred_labels'))) {
 569			if (is_subclass_of($t_instance, 'LabelableBaseModelWithAttributes')) {
 570				
 571				$vb_get_preferred_labels_only = ($va_path_components['field_name'] == 'preferred_labels') ? true : false;
 572				$vb_get_nonpreferred_labels_only = ($va_path_components['field_name'] == 'nonpreferred_labels') ? true : false;
 573				
 574				if ($va_path_components['num_components'] == 2) {
 575					$vb_return_all_label_values = true;
 576				}
 577				
 578				$va_path_components['table_name'] = $t_instance->getLabelTableName();
 579				$t_label_instance = $t_instance->getLabelTableInstance();
 580				if (!$va_path_components['subfield_name'] || !$t_label_instance->hasField($va_path_components['subfield_name'])) {
 581					$va_path_components['field_name'] = $t_instance->getLabelDisplayField();
 582				} else {
 583					$va_path_components['field_name'] = $va_path_components['subfield_name'];
 584				}
 585				$va_path_components['subfield_name'] = null;
 586				
 587				$va_path_components = $this->getFieldPathComponents($va_path_components['table_name'].'.'.$va_path_components['field_name']);
 588				// Ok, convert the table instance to the label table since that's the table we'll be grabbing data from
 589				$t_instance = $t_label_instance;
 590				
 591				$vb_is_get_for_labels = true;
 592			}
 593		}
 594		
 595		if ($va_path_components['num_components'] >= 2) {
 596			switch($va_path_components['field_name']) {
 597				case 'parent':
 598					if (($t_instance->isHierarchical()) && ($vn_parent_id = $this->get($va_path_components['table_name'].'.'.$t_instance->getProperty('HIERARCHY_PARENT_ID_FLD')))) {
 599						//
 600						// TODO: support some kind of prefetching of parents?
 601						//
 602						unset($va_path_components['components'][1]);
 603						if ($t_instance->load($vn_parent_id)) {
 604							return $t_instance->get(join('.', array_values($va_path_components['components'])), $pa_options);
 605						}
 606						return null;
 607					}
 608					break;
 609				case 'children':
 610					if ($t_instance->isHierarchical()) {
 611						//unset($va_path_components['components'][1]);	// remove 'children' from field path
 612						
 613						$vs_field_spec = join('.', array_values($va_path_components['components']));
 614						if ($vn_id = $this->get($va_path_components['table_name'].'.'.$t_instance->primaryKey(), array('returnAsArray' => false))) {
 615							if($t_instance->load($vn_id)) {
 616								return $t_instance->get($vs_field_spec, $pa_options);
 617							}
 618						}
 619						return null;
 620					} 
 621					break;
 622				case 'related':
 623					// Regular related table call
 624					if ($va_path_components['table_name'] != $this->ops_table_name) {
 625						// just remove "related" from name and be on our way
 626						$va_tmp = $va_path_components['components'];
 627						array_splice($va_tmp, 1, 1);
 628						return $this->get(join('.', $va_tmp), $pa_options);
 629					}
 630					
 631					// Self-relations need special handling
 632					$vs_opt_md5 = md5(print_R($pa_options, true));
 633					if (!isset($this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5])) {
 634						$this->prefetchRelated($va_path_components['table_name'], $this->opo_engine_result->currentRow(), $this->getOption('prefetch'), $pa_options);
 635					}
 636					
 637					$va_related_items = $this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5];
 638					if (!($t_table = $this->opo_datamodel->getInstanceByTableName($va_path_components['table_name'], true))) { return null; }
 639					
 640					$va_ids = array();
 641					foreach($va_related_items as $vn_relation_id => $va_item) {
 642						$va_ids[] = $va_item[$t_table->primaryKey()];
 643					}
 644					
 645					$va_vals = array();
 646					
 647					if ($qr_res = $t_table->makeSearchResult($va_path_components['table_name'], $va_ids)) {
 648						$va_tmp = $va_path_components['components'];
 649						unset($va_tmp[1]);
 650						$vs_rel_field = join('.', $va_tmp);
 651						
 652						while($qr_res->nextHit()) {
 653							if ($vb_return_as_array) {
 654								$va_vals = array_merge($va_vals, $qr_res->get($vs_rel_field, $pa_options));
 655							} else {
 656								$va_vals[] = $qr_res->get($vs_rel_field, $pa_options);
 657							}
 658						}
 659					}
 660					
 661					if (is_array($va_sort_fields) && sizeof($va_sort_fields)) {
 662						$va_vals = caSortArrayByKeyInValue($va_vals, $va_sort_fields);
 663					}
 664					
 665					if ($vb_return_as_array) {
 666						return $va_vals;
 667					} else {
 668						return join($vs_delimiter, $va_vals);
 669					}
 670					break;
 671				case 'hierarchy':
 672					if ($t_instance->isHierarchical()) {
 673						$vs_field_spec = join('.', array_values($va_path_components['components']));
 674						$vs_hier_pk_fld = $t_instance->primaryKey();
 675						if ($va_ids = $this->get($va_path_components['table_name'].'.'.$vs_hier_pk, array_merge($pa_options, array('returnAsArray' => true)))) {
 676							if ($va_path_components['field_name'] == $vs_hier_pk_fld) {
 677								$va_vals = $va_ids;
 678							} else {
 679								$va_vals = array();
 680								foreach($va_ids as $vn_id) {
 681									if($t_instance->load($vn_id)) {
 682										$va_vals[] = $t_instance->get($vs_field_spec.".preferred_labels", $pa_options);
 683									}
 684								}
 685							}
 686							if ($vb_return_as_array) {
 687								return $va_vals;
 688							} else {
 689								return join($vs_delimiter, $va_vals);
 690							}
 691						}
 692						return null;
 693					} 
 694					break;
 695			}
 696		}
 697
 698		// If the requested table was not added to the query via SearchEngine::addTable()
 699		// then auto-add it here. It's better to explicitly add it with addTables() as that call
 700		// gives you precise control over which fields are autoloaded and also lets you specify limiting criteria 
 701		// for selection of related field data; and it also lets you explicitly define the tables used to join the
 702		// related table. Autoloading guesses and usually does what you want, but not always.
 703		if (!isset($this->opa_tables[$va_path_components['table_name']]) || !$this->opa_tables[$va_path_components['table_name']]) {
 704			$va_join_tables = $this->opo_datamodel->getPath($this->ops_table_name, $va_path_components['table_name']);
 705			array_shift($va_join_tables); 	// remove subject table
 706			array_pop($va_join_tables);		// remove content table (we only need linking tables here)
 707			$this->opa_tables[$va_path_components['table_name']] = array(
 708				'fieldList' => array($va_path_components['table_name'].'.*'),
 709				'joinTables' => array_keys($va_join_tables),
 710				'criteria' => array()
 711			);
 712		}
 713		
 714		
 715		if (($va_path_components['table_name'] === $this->ops_table_name) && !$t_instance->hasField($va_path_components['field_name']) && method_exists($t_instance, 'getAttributes')) {
 716			//
 717			// Return attribute values for primary table 
 718			//
 719			if ($t_element = $t_instance->_getElementInstance($va_path_components['field_name'])) {
 720				$vn_element_id = $t_element->getPrimaryKey();
 721			} else {
 722				$vn_element_id = null;
 723			}
 724			if (!isset(ca_attributes::$s_get_attributes_cache[$this->opn_table_num.'/'.$vn_row_id][$vn_element_id])) {
 725				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));
 726			}
 727			if (!$vb_return_as_array) {
 728				if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($va_path_components['field_name'])) {
 729					$vs_template = '';
 730					if ($va_path_components['subfield_name']) { 
 731						$vs_template = '^'.$va_path_components['subfield_name']; 
 732					} else {
 733						if (isset($pa_options['template'])) { $vs_template = $pa_options['template']; }
 734					}
 735					
 736					return $t_instance->getAttributesForDisplay($va_path_components['field_name'], $vs_template, array_merge(array('row_id' => $vn_row_id), $pa_options));
 737				}
 738				if ($t_element && !$va_path_components['subfield_name'] && ($t_element->get('datatype') == 0)) {
 739					return $t_instance->getAttributesForDisplay($va_path_components['field_name'], null, array_merge($pa_options, array('row_id' => $vn_row_id)));
 740				} else {
 741					return $t_instance->getRawValue($vn_row_id, $va_path_components['field_name'], $va_path_components['subfield_name'], ',', $pa_options);
 742				}
 743			} else {
 744				$va_values = $t_instance->getAttributeDisplayValues($va_path_components['field_name'], $vn_row_id, $pa_options);
 745				
 746				if ($va_path_components['subfield_name']) {
 747					if ($vb_return_all_locales) {
 748						foreach($va_values as $vn_row_id => $va_values_by_locale) {
 749							foreach($va_values_by_locale as $vn_locale_id => $va_value_list) {
 750								foreach($va_value_list as $vn_attr_id => $va_attr_data) {
 751									$va_values[$vn_row_id][$vn_locale_id][$vn_attr_id] = $va_attr_data[$va_path_components['subfield_name']];
 752								}
 753							}
 754						}
 755					} else {
 756						$va_processed_value_list = array();
 757						foreach($va_values as $vn_row_id => $va_value_list) {
 758							foreach($va_value_list as $vn_attr_id => $va_attr_data) {
 759								$va_processed_value_list[$vn_attr_id] = $va_attr_data[$va_path_components['subfield_name']];
 760							}
 761						}
 762						$va_values = $va_processed_value_list;
 763					}
 764				} else {
 765					if (!$vb_return_all_locales) {
 766						$va_values = array_shift($va_values);
 767					}
 768				}
 769				return $va_values;
 770			}
 771		} else {
 772			// Prefetch intrinsic fields in primary and related tables
 773			if (!isset($this->opa_prefetch_cache[$va_path_components['table_name']][$vn_row_id])) {
 774				$this->prefetch($va_path_components['table_name'], $this->opo_engine_result->currentRow(), $this->getOption('prefetch'));	// try to prefetch ahead (usually doesn't hurt and very often helps performance)
 775			}
 776		}
 777		
 778		//
 779		// Prepare return value
 780		//
 781		$va_return_values = array();
 782		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')) {
 783			//
 784			// Return attributes in a related table
 785			//
 786			$vs_pk = $t_instance->primaryKey();
 787			
 788			$vs_opt_md5 = md5(print_R($pa_options, true));
 789			if (!isset($this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_opt_md5])) {
 790				$this->prefetchRelated($va_path_components['table_name'], $this->opo_engine_result->currentRow(), $this->getOption('prefetch'), $pa_options);
 791			}
 792			
 793			if (is_array($this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_md5])) {
 794				foreach($this->opa_rel_prefetch_cache[$va_path_components['table_name']][$vn_row_id][$vs_md5] as $vn_i => $va_values) { //$vn_locale_id => $va_values_by_locale) {
 795					if (!$vb_return_as_array) {
 796						$vs_val = $t_instance->getAttributesForDisplay($va_path_components['field_name'], $vs_template, array_merge(array('row_id' => $va_values[$vs_pk]), $pa_options));
 797					} else {
 798						$vs_val = $t_instance->getAttributeDisplayValues($va_path_components['field_name'], $va_values[$vs_pk], $pa_options);
 799					}
 800					if ($vs_val) {
 801						if ($vb_return_as_array) {
 802							if (!$vb_return_all_locales) {
 803								foreach($vs_val as $vn_i => $va_values_list) {
 804									foreach($va_values_list as $vn_j => $va_values) {
 805										$va_return_values[] = $va_values;
 806									}
 807								}
 808							} else {
 809								foreach($vs_val as $vn_i => $va_values_list) {
 810									$va_return_values[] = $va_values_list;
 811								}
 812							}
 813						} else {
 814							$va_return_values[] = $vs_val;
 815						}
 816					}
 817				}
 818			}
 819			if ($vb_return_as_array) {
 820				return $va_return_values;
 821			} else {
 822				if (isset($pa_options['convertLineBreaks']) && $pa_options['convertLineBreaks']) {
 823					return caConvertLineBreaks(join($vs_delimiter, $va_return_values));
 824				} else {
 825					return join($vs_delimiter, $va_return_values);
 826				}
 827			}
 828		} else {
 829			//
 830			// Return fields in primary or related table
 831			//
 832			$t_list = $this->opo_datamodel->getInstanceByTableName('ca_lists', true);
 833			$va_value_list = array($vn_row_id => $this->opa_prefetch_cache[$va_path_components['table_name']][$vn_row_id]);
 834
 835				if (is_array($va_get_where)) {
 836					$va_tmp = array();
 837					foreach($va_value_list as $vn_id => $va_by_locale) {
 838						foreach($va_by_locale as $vn_locale_id => $va_values) {
 839							foreach($va_values as $vn_i => $va_value) {
 840								foreach($va_get_where as $vs_fld => $vm_val) {
 841									if ($va_value[$vs_fld] != $vm_val) { continue(2); }
 842								}
 843								$va_tmp[$vn_id][$vn_locale_id][$vn_i] = $va_value;
 844							}
 845						}
 846					}
 847					$va_value_list = $va_tmp;
 848				}
 849
 850				if (isset($pa_options['restrict_to_relationship_types']) && $pa_options['restrict_to_relationship_types']) {
 851				if (!is_array($pa_options['restrict_to_relationship_types'])) {
 852					$pa_options['restrict_to_relationship_types'] = array($pa_options['restrict_to_relationship_types']);
 853				}
 854				if (sizeof($pa_options['restrict_to_relationship_types'])) {
 855					$t_rel_type = $this->opo_datamodel->getInstanceByTableName('ca_relationship_types', true);
 856					$va_rel_types = array();
 857					$va_rel_path = array_keys($this->opo_datamodel->getPath($this->ops_table_name,  $va_path_components['table_name']));
 858					foreach($pa_options['restrict_to_relationship_types'] as $vm_type) {
 859						if (!$vm_type) { continue; }
 860						if ($vn_type_id = $t_rel_type->getRelationshipTypeID($va_rel_path[1], $vm_type)) {
 861							$va_rel_types[] = $vn_type_id;
 862							if (is_array($va_children = $t_rel_type->getHierarchyChildren($vn_type_id, array('idsOnly' => true)))) {
 863								$va_rel_types = array_merge($va_rel_types, $va_children);
 864							}
 865						}
 866					}
 867					if (sizeof($va_rel_types)) {
 868						$va_tmp = array();
 869						foreach($va_value_list as $vn_id => $va_by_locale) {
 870							foreach($va_by_locale as $vn_locale_id => $va_values) {
 871								foreach($va_values as $vn_i => $va_value) {
 872									if (in_array($va_value['rel_type_id'], $va_rel_types)) {
 873										$va_tmp[$vn_id][$vn_locale_id][$vn_i] = $va_value;
 874									}
 875								}
 876							}
 877						}
 878						$va_value_list = $va_tmp;
 879					}
 880				}
 881			}
 882			if (isset($pa_options['exclude_relationship_types']) && $pa_options['exclude_relationship_types']) {
 883				if (!is_array($pa_options['exclude_relationship_types'])) {
 884					$pa_options['exclude_relationship_types'] = array($pa_options['exclude_relationship_types']);
 885				}
 886				
 887				if (sizeof($pa_options['exclude_relationship_types'])) {
 888					$t_rel_type = $this->opo_datamodel->getInstanceByTableName('ca_relationship_types', true);
 889					$va_rel_types = array();
 890					$va_rel_path = array_keys($this->opo_datamodel->getPath($this->ops_table_name,  $va_path_components['table_name']));
 891					foreach($pa_options['exclude_relationship_types'] as $vm_type) {
 892						if ($vn_type_id = $t_rel_type->getRelationshipTypeID($va_rel_path[1], $vm_type)) {
 893							$va_rel_types[] = $vn_type_id;
 894							if (is_array($va_children = $t_rel_type->getHierarchyChildren($vn_type_id, array('idsOnly' => true)))) {
 895								$va_rel_types = array_merge($va_rel_types, $va_children);
 896							}
 897						}
 898					}
 899					if (sizeof($va_rel_types)) {
 900						$va_tmp = array();
 901						foreach($va_value_list as $vn_id => $va_by_locale) {
 902							foreach($va_by_locale as $vn_locale_id => $va_values) {
 903								foreach($va_values as $vn_i => $va_value) {
 904									if (!in_array($va_value['rel_type_id'], $va_rel_types)) {
 905										$va_tmp[$vn_id][$vn_locale_id][$vn_i] = $va_value;
 906									}
 907								}
 908							}
 909						}
 910						$va_value_list = $va_tmp;
 911					}
 912				}
 913			}
 914			
 915			if (isset($pa_options['restrict_to_types']) && $pa_options['restrict_to_types']) {
 916				if (!is_array($pa_options['restrict_to_types'])) {
 917					$pa_options['restrict_to_types'] = array($pa_options['restrict_to_types']);
 918				}
 919				
 920				if (sizeof($pa_options['restrict_to_types'])) {
 921					$vs_type_list_code = null;
 922					if (method_exists($t_instance, "getTypeFieldName")) {
 923						$vs_type_list_code = $t_instance->getTypeListCode();
 924					} else {
 925						if (method_exists($t_instance, "getSubjectTableInstance")) {
 926							$t_label_subj_instance = $t_instance->getSubjectTableInstance();
 927							if (method_exists($t_label_subj_instance, "getTypeFieldName")) {
 928								$vs_type_list_code = $t_label_subj_instance->getTypeListCode();
 929							}
 930						}
 931					}
 932					
 933					if ($vs_type_list_code) {
 934						$va_types = array();
 935						$t_item = $this->opo_datamodel->getInstanceByTableName('ca_list_items', true);
 936						foreach($pa_options['restrict_to_types'] as $vm_type) {
 937							$vn_type_id = null;
 938							if (is_numeric($vm_type)) { 
 939								$vn_type_id = (int)$vm_type; 
 940							} else {
 941								$vn_type_id = $t_list->getItemIDFromList($vs_type_list_code, $vm_type);
 942							}
 943							
 944							if ($vn_type_id) {
 945								$va_types[] = $vn_type_id;
 946								if (is_array($va_children = $t_item->getHierarchyChildren($vn_type_id, array('idsOnly' => true)))) {
 947									$va_types = array_merge($va_types, $va_children);
 948								}
 949							}
 950						}
 951						if (sizeof($va_types)) {
 952							$va_tmp = array(); 
 953							foreach($va_value_list as $vn_id => $va_by_locale) {
 954								foreach($va_by_locale as $vn_locale_id => $va_values) {
 955									foreach($va_values as $vn_i => $va_value) {
 956										if (in_array($va_value['item_type_id'], $va_types)) {
 957											$va_tmp[$vn_id][$vn_locale_id][$vn_i] = $va_value;
 958										}
 959									}
 960								}
 961							}
 962							$va_value_list = $va_tmp;
 963						}
 964					}
 965				}
 966			}
 967			
 968			// handle 'relationship_typename' call
 969			$vb_get_relationship_typename = false;
 970			if ($va_path_components['field_name'] == 'relationship_typename') {
 971				$va_path_components['field_name'] = 'rel_type_id';
 972				$vb_get_relationship_typename = true;
 973			}
 974	
 975			if ($vb_return_as_array) {
 976				if ($t_instance->hasField($va_path_components['field_name']) && ($va_path_components['table_name'] === $this->ops_table_name)) {
 977					$va_field_info = $t_instance->getFieldInfo($va_path_components['field_name']);
 978					
 979					switch($va_field_info['FIELD_TYPE']) {
 980						case FT_DATERANGE:
 981						case FT_HISTORIC_DATERANGE:
 982							$this->opo_tep->init();
 983							if ($va_field_info['FIELD_TYPE'] == FT_DATERANGE) {
 984								$this->opo_tep->setUnixTimestamps($va_value[$va_field_info['START']], $va_value[$va_field_info['END']]);
 985							} else {
 986								$this->opo_tep->setHistoricTimestamps($va_value[$va_field_info['START']], $va_value[$va_field_info['END']]);
 987							}
 988							$vs_prop = $this->opo_tep->getText();
 989							if ($vb_return_all_locales) {
 990								$va_return_values[$vn_row_id][$vn_locale_id][] = $vs_prop;
 991							} else {
 992								$va_return_values[] = $vs_prop;
 993							}
 994							break;
 995						default:
 996							// is intrinsic field in primary table
 997							foreach($va_value_list as $vn_id => $va_values_by_locale) {
 998								foreach($va_values_by_locale as $vn_locale_id => $va_values) {
 999									foreach($va_values as $vn_i => $va_value) {
1000										if (($vb_get_preferred_labels_only) && (!$va_value['is_preferred'])) { continue; }
1001										if (($vb_get_nonpreferred_labels_only) && ($va_value['is_preferred'])) { continue; }
1002										
1003										if ($vb_return_all_locales) {
1004											$va_return_values[$vn_row_id][$vn_locale_id][] = $va_value[$va_path_components['field_name']];
1005										} else {
1006											$va_return_values[] = $va_value[$va_path_components['field_name']];
1007										}
1008									}
1009								}
1010							}
1011							break;
1012					}
1013				} else {
1014					foreach($va_value_list as $vn_i => $va_values_by_locale) {
1015						foreach($va_values_by_locale as $vn_locale_id => $va_values) {
1016							foreach($va_values as $vn_i => $va_value) {
1017								if (($vb_get_preferred_labels_only) && (!$va_value['is_preferred'])) { continue; }
1018								if (($vb_get_nonpreferred_labels_only) && ($va_value['is_preferred'])) { continue; }
1019								
1020								// do we need to translate foreign key and choice list codes to display text?
1021								$t_instance = $this->opo_datamodel->getInstanceByTableName($va_path_components['table_name'], true);
1022								$vs_prop = ($vb_return_all_label_values) ? $va_value[$t_instance->getProperty('LABEL_DISPLAY_FIELD')] : $va_value[$va_path_components['field_name']];
1023								
1024								if ($vb_get_relationship_typename) {
1025									if (!$t_rel_type) { $t_rel_type = $this->opo_datamodel->getInstanceByTableName('ca_relationship_types', true); }
1026									if (is_array($va_labels = $t_rel_type->getDisplayLabels(false, array('row_id' => (int)$vs_prop)))) {
1027										$va_label = array_shift($va_labels);
1028										$vs_prop = $va_label[0]['typename'];
1029									} else {
1030										$vs_prop = "?";
1031									}
1032								} else {
1033									if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($vs_list_code = $t_instance->getFieldInfo($va_path_components['field_name'],"LIST_CODE"))) {
1034										$vs_prop = $t_list->getItemFromListForDisplayByItemID($vs_list_code, $vs_prop);
1035									} else {
1036										if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($vs_list_code = $t_instance->getFieldInfo($va_path_components['field_name'],"LIST"))) {
1037											$vs_prop = $t_list->getItemFromListForDisplayByItemValue($vs_list_code, $vs_prop);
1038										} else {
1039											if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && ($va_path_components['field_name'] === 'locale_id') && ((int)$vs_prop > 0)) {
1040												$t_locale = new ca_locales($vs_prop);
1041												$vs_prop = $t_locale->getName();
1042											} else {
1043												if (isset($pa_options['convertCodesToDisplayText']) && $pa_options['convertCodesToDisplayText'] && (is_array($va_list = $t_instance->getFieldInfo($va_path_components['field_name'],"BOUNDS_CHOICE_LIST")))) {
1044													foreach($va_list as $vs_option => $vs_value) {
1045														if ($vs_value == $vs_prop) {
1046															$vs_prop = $vs_option;
1047															break;
1048														}
1049													}
1050												}
1051											}
1052										}
1053									}
1054								}
1055								if ($vb_return_all_locales) {
1056									$va_return_values[$vn_row_id][$vn_locale_id][] = $vs_prop;
1057								} else {
1058									$va_return_values[] = $vs_prop;
1059								}
1060							}
1061						}
1062					}
1063				}
1064				return $va_return_values;
1065			} else {
1066				//
1067				// Return scalar
1068				//
1069				if ($vb_get_preferred_labels_only || $vb_get_nonpreferred_labels_only) {
1070					// We have to distinguish between preferred and non-preferred labels here
1071					// so that only appropriate labels are passed for output.
1072					$va_filtered_values = array();
1073					foreach($va_value_list as $vn_label_id => $va_labels_by_locale) {
1074						foreach($va_labels_by_locale as $vn_locale_id => $va_labels) {
1075							foreach($va_labels as $vn_i => $va_label) {
1076								if (	
1077									($vb_get_preferred_labels_only && ((!isset($va_label['is_preferred']) || $va_label['is_preferred'])))
1078									||
1079									($vb_get_nonpreferred_labels_only && !$va_label['is_preferred'])
1080								) {
1081									$va_filtered_values[$vn_label_id][$vn_locale_id][] = $va_label;
1082								}
1083							}
1084						}
1085					}
1086					$va_value_list = $va_filtered_values;
1087				}
1088				$va_value_list = caExtractValuesByUserLocale($va_value_list);
1089				
1090				// do we need to translate foreign key and choice list codes to display text?
1091				$t_instance = $this->opo_datamodel->getInstanceByTableName($va_path_components['table_name'], true);
1092				$va_field_info = $t_instance->getFieldInfo($va_path_components['field_name']);
1093						
1094				foreach($va_value_list as $vn_i => $va_values) {
1095					if (!is_array($va…

Large files files are truncated, but you can click here to view the full file