diff options
Diffstat (limited to 'engine/classes/ElggAttributeLoader.php')
| -rw-r--r-- | engine/classes/ElggAttributeLoader.php | 248 | 
1 files changed, 248 insertions, 0 deletions
| diff --git a/engine/classes/ElggAttributeLoader.php b/engine/classes/ElggAttributeLoader.php new file mode 100644 index 000000000..ffc80b02d --- /dev/null +++ b/engine/classes/ElggAttributeLoader.php @@ -0,0 +1,248 @@ +<?php + +/** + * Loads ElggEntity attributes from DB or validates those passed in via constructor + * + * @access private + * + * @package    Elgg.Core + * @subpackage DataModel + */ +class ElggAttributeLoader { + +	/** +	 * @var array names of attributes in all entities +	 */ +	protected static $primary_attr_names = array( +		'guid', +		'type', +		'subtype', +		'owner_guid', +		'container_guid', +		'site_guid', +		'access_id', +		'time_created', +		'time_updated', +		'last_action', +		'enabled', +	); + +	/** +	 * @var array names of secondary attributes required for the entity +	 */ +	protected $secondary_attr_names = array(); + +	/** +	 * @var string entity type (not class) required for fetched primaries +	 */ +	protected $required_type; + +	/** +	 * @var array +	 */ +	protected $initialized_attributes; + +	/** +	 * @var string class of object being loaded +	 */ +	protected $class; + +	/** +	 * @var bool should access control be considered when fetching entity? +	 */ +	public $requires_access_control = true; + +	/** +	 * @var callable function used to load attributes from {prefix}entities table +	 */ +	public $primary_loader = 'get_entity_as_row'; + +	/** +	 * @var callable function used to load attributes from secondary table +	 */ +	public $secondary_loader = ''; + +	/** +	 * @var callable function used to load all necessary attributes +	 */ +	public $full_loader = ''; + +	/** +	 * Constructor +	 * +	 * @param string $class             class of object being loaded +	 * @param string $required_type     entity type this is being used to populate +	 * @param array  $initialized_attrs attributes after initializeAttributes() has been run +	 * @throws InvalidArgumentException +	 */ +	public function __construct($class, $required_type, array $initialized_attrs) { +		if (!is_string($class)) { +			throw new InvalidArgumentException('$class must be a class name.'); +		} +		$this->class = $class; + +		if (!is_string($required_type)) { +			throw new InvalidArgumentException('$requiredType must be a system entity type.'); +		} +		$this->required_type = $required_type; + +		$this->initialized_attributes = $initialized_attrs; +		unset($initialized_attrs['tables_split'], $initialized_attrs['tables_loaded']); +		$all_attr_names = array_keys($initialized_attrs); +		$this->secondary_attr_names = array_diff($all_attr_names, self::$primary_attr_names); +	} + +	/** +	 * Get primary attributes missing that are missing +	 * +	 * @param stdClass $row Database row +	 * @return array +	 */ +	protected function isMissingPrimaries($row) { +		return array_diff(self::$primary_attr_names, array_keys($row)) !== array(); +	} + +	/** +	 * Get secondary attributes that are missing +	 * +	 * @param stdClass $row Database row +	 * @return array +	 */ +	protected function isMissingSecondaries($row) { +		return array_diff($this->secondary_attr_names, array_keys($row)) !== array(); +	} + +	/** +	 * Check that the type is correct +	 * +	 * @param stdClass $row Database row +	 * @return void +	 * @throws InvalidClassException +	 */ +	protected function checkType($row) { +		if ($row['type'] !== $this->required_type) { +			$msg = elgg_echo('InvalidClassException:NotValidElggStar', array($row['guid'], $this->class)); +			throw new InvalidClassException($msg); +		} +	} + +	/** +	 * Get all required attributes for the entity, validating any that are passed in. Returns empty array +	 * if can't be loaded (Check $failure_reason). +	 * +	 * This function splits loading between "primary" attributes (those in {prefix}entities table) and +	 * "secondary" attributes (e.g. those in {prefix}objects_entity), but can load all at once if a +	 * combined loader is available. +	 * +	 * @param mixed $row a row loaded from DB (array or stdClass) or a GUID +	 * @return array will be empty if failed to load all attributes (access control or entity doesn't exist) +	 * +	 * @throws InvalidArgumentException|LogicException|IncompleteEntityException +	 */ +	public function getRequiredAttributes($row) { +		if (!is_array($row) && !($row instanceof stdClass)) { +			// assume row is the GUID +			$row = array('guid' => $row); +		} +		$row = (array) $row; +		if (empty($row['guid'])) { +			throw new InvalidArgumentException('$row must be or contain a GUID'); +		} + +		// these must be present to support isFullyLoaded() +		foreach (array('tables_split', 'tables_loaded') as $key) { +			if (isset($this->initialized_attributes[$key])) { +				$row[$key] = $this->initialized_attributes[$key]; +			} +		} + +		$was_missing_primaries = $this->isMissingPrimaries($row); +		$was_missing_secondaries = $this->isMissingSecondaries($row); + +		// some types have a function to load all attributes at once, it should be faster +		if (($was_missing_primaries || $was_missing_secondaries) && is_callable($this->full_loader)) { +			$fetched = (array) call_user_func($this->full_loader, $row['guid']); +			if (!$fetched) { +				return array(); +			} +			$row = array_merge($row, $fetched); +			$this->checkType($row); +		} else { +			if ($was_missing_primaries) { +				if (!is_callable($this->primary_loader)) { +					throw new LogicException('Primary attribute loader must be callable'); +				} +				if ($this->requires_access_control) { +					$fetched = (array) call_user_func($this->primary_loader, $row['guid']); +				} else { +					$ignoring_access = elgg_set_ignore_access(); +					$fetched = (array) call_user_func($this->primary_loader, $row['guid']); +					elgg_set_ignore_access($ignoring_access); +				} +				if (!$fetched) { +					return array(); +				} +				$row = array_merge($row, $fetched); +			} + +			// We must test type before trying to load the secondaries so that InvalidClassException +			// gets thrown. Otherwise the secondary loader will fail and return false. +			$this->checkType($row); + +			if ($was_missing_secondaries) { +				if (!is_callable($this->secondary_loader)) { +					throw new LogicException('Secondary attribute loader must be callable'); +				} +				$fetched = (array) call_user_func($this->secondary_loader, $row['guid']); +				if (!$fetched) { +					if ($row['type'] === 'site') { +						// A special case is needed for sites: When vanilla ElggEntities are created and +						// saved, these are stored w/ type "site", but with no sites_entity row. These +						// are probably only created in the unit tests. +						// @todo Don't save vanilla ElggEntities with type "site" + +						$row = $this->filterAddedColumns($row); +						$row['guid'] = (int) $row['guid']; +						return $row; +					} +					throw new IncompleteEntityException("Secondary loader failed to return row for {$row['guid']}"); +				} +				$row = array_merge($row, $fetched); +			} +		} + +		$row = $this->filterAddedColumns($row); + +		// Note: If there are still missing attributes, we're running on a 1.7 or earlier schema. We let +		// this pass so the upgrades can run. + +		// guid needs to be an int  https://github.com/elgg/elgg/issues/4111 +		$row['guid'] = (int) $row['guid']; + +		return $row; +	} + +	/** +	 * Filter out keys returned by the query which should not appear in the entity's attributes +	 * +	 * @param array $row All columns from the query +	 * @return array Columns acceptable for the entity's attributes +	 */ +	protected function filterAddedColumns($row) { +		// make an array with keys as acceptable attribute names +		$acceptable_attrs = self::$primary_attr_names; +		array_splice($acceptable_attrs, count($acceptable_attrs), 0, $this->secondary_attr_names); +		$acceptable_attrs = array_combine($acceptable_attrs, $acceptable_attrs); + +		// @todo remove these when #4584 is in place +		$acceptable_attrs['tables_split'] = true; +		$acceptable_attrs['tables_loaded'] = true; + +		foreach ($row as $key => $val) { +			if (!isset($acceptable_attrs[$key])) { +				unset($row[$key]); +			} +		} +		return $row; +	} +} | 
