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; } }