/** * Checks if the given menu link should be marked as active. * * If `$item->activation` is a callable function it will be used to determinate * if the link should be active or not, returning true from callable indicates * link should be active, false indicates it should not be marked as active. * Callable receives current request object as first argument and $item as second. * * `$item->url` property MUST exists if "activation" is not a callable, and can * be either: * * - A string representing an external or internal URL (all internal links must * starts with "/"). e.g. `/user/login` * * - An array compatible with \Cake\Routing\Router::url(). e.g. `['controller' * => 'users', 'action' => 'login']` * * Both examples are equivalent. * * @param \Cake\Datasource\EntityInterface $item A menu's item * @return bool */ public function isActive(EntityInterface $item) { if ($item->has('activation') && is_callable($item->get('activation'))) { $callable = $item->get('activation'); return $callable($this->_View->request, $item); } $itemUrl = $this->sanitize($item->get('url')); if (!str_starts_with($itemUrl, '/')) { return false; } switch ($item->get('activation')) { case 'any': return $this->_requestMatches($item->get('active')); case 'none': return !$this->_requestMatches($item->get('active')); case 'php': return php_eval($item->get('active'), ['view', &$this->_View, 'item', &$item]) === true; case 'auto': default: static $requestUri = null; static $requestUrl = null; if ($requestUri === null) { $requestUri = urldecode(env('REQUEST_URI')); $requestUrl = str_replace('//', '/', '/' . urldecode($this->_View->request->url) . '/'); } $isInternal = $itemUrl !== '/' && str_ends_with($itemUrl, str_replace_once($this->baseUrl(), '', $requestUri)); $isIndex = $itemUrl === '/' && $this->_View->request->isHome(); $isExact = str_replace('//', '/', "{$itemUrl}/") === $requestUrl || $itemUrl == $requestUri; if ($this->config('breadcrumbGuessing')) { return $isInternal || $isIndex || $isExact || in_array($itemUrl, $this->_crumbs()); } return $isInternal || $isIndex || $isExact; } }
/** * Gets the image version data used to generate the versions. * * @param \Cake\Datasource\EntityInterface $entity An ImageStorage database record. * @param array $options Options for the version. * @return array Version data information as array. */ protected function _getImageVersionData(EntityInterface $entity, array $options = []) { $versionData = (array) Configure::read('FileStorage.imageSizes.' . $entity->get('model')); if (isset($options['originalVersion'])) { $versionData['original'] = $options['originalVersion']; } else { Configure::write('FileStorage.imageSizes.' . $entity->get('model') . '.original', []); $versionData['original'] = []; } $versionData['original'] = isset($options['originalVersion']) ? $options['originalVersion'] : 'original'; return $versionData; }
/** * Performs the existence check * * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * @param array $options Options passed to the check, * where the `repository` key is required. * @return bool */ public function __invoke(EntityInterface $entity, array $options) { if (is_string($this->_repository)) { $this->_repository = $options['repository']->association($this->_repository); } $source = !empty($options['repository']) ? $options['repository'] : $this->_repository; $source = $source instanceof Association ? $source->source() : $source; $target = $this->_repository; if ($target instanceof Association) { $bindingKey = (array) $target->bindingKey(); $target = $this->_repository->target(); } else { $bindingKey = (array) $target->primaryKey(); } if (!empty($options['_sourceTable']) && $target === $options['_sourceTable']) { return true; } if (!$entity->extract($this->_fields, true)) { return true; } $nulls = 0; $schema = $source->schema(); foreach ($this->_fields as $field) { if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { $nulls++; } } if ($nulls === count($this->_fields)) { return true; } $primary = array_map([$this->_repository, 'aliasField'], $bindingKey); $conditions = array_combine($primary, $entity->extract($this->_fields)); return $this->_repository->exists($conditions); }
/** * afterSave Event * * @param Event $event Event * @param EntityInterface $entity Entity to be saved * @return void */ public function afterSave(Event $event, EntityInterface $entity) { $uploads = $entity->get($this->config('formFieldName')); if (!empty($uploads)) { $this->Attachments->addUploads($entity, $uploads); } }
/** * Performs the existence check * * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * @param array $options Options passed to the check, * where the `repository` key is required. * @return bool */ public function __invoke(EntityInterface $entity, array $options) { if (is_string($this->_repository)) { $this->_repository = $options['repository']->association($this->_repository); } if (!empty($options['_sourceTable'])) { $source = $this->_repository instanceof Association ? $this->_repository->target() : $this->_repository; if ($source === $options['_sourceTable']) { return true; } } if (!$entity->extract($this->_fields, true)) { return true; } $nulls = 0; $schema = $this->_repository->schema(); foreach ($this->_fields as $field) { if ($schema->isNullable($field) && $entity->get($field) === null) { $nulls++; } } if ($nulls === count($this->_fields)) { return true; } $alias = $this->_repository->alias(); $primary = array_map(function ($key) use($alias) { return "{$alias}.{$key}"; }, (array) $this->_repository->primaryKey()); $conditions = array_combine($primary, $entity->extract($this->_fields)); return $this->_repository->exists($conditions); }
/** * Triggers the callback "afterDetach", it also deletes all associated records * in the "field_values" table. * * @param \Cake\Event\Event $event The event that was triggered * @param \Field\Model\Entity\FieldInstance $instance The Field Instance that was deleted * @param \ArrayObject $options the options passed to the delete method * @return void */ public function afterDelete(Event $event, FieldInstance $instance, ArrayObject $options = null) { if (!empty($this->_deleted)) { TableRegistry::get('Eav.EavAttributes')->delete($this->_deleted->get('eav_attribute')); $instance->afterDetach(); $this->_deleted = null; } }
/** * Callback before save entity. * * @param Event $event * @param EntityInterface $entity * @param \ArrayObject $options * @return bool */ public function beforeSave(Event $event, EntityInterface $entity, \ArrayObject $options) { // Hack for test fieldToggle. if ($entity->get('id') == 4) { return false; } return true; }
/** * Event listener to encrypt data. * * @param \Cake\Event\Event $event Event. * @param \Cake\Datasource\EntityInterface $entity Entity. * @return void */ public function beforeSave(Event $event, EntityInterface $entity) { $driver = $this->_table->connection()->driver(); foreach ($this->config('fields') as $field => $type) { if (!$entity->has($field)) { continue; } $raw = $entity->get($field); $plain = Type::build($type)->toDatabase($raw, $driver); $entity->set($field, $this->encrypt($plain)); } }
/** * Save also related model data * * @param \Cake\Event\Event * @param \Cake\ORM\Entity; * @return void */ public function beforeSave(Event $event, EntityInterface $entity, \ArrayObject $options) { foreach ($this->config('fields') as $field => $mapped) { list($mappedTable, $mappedField) = explode('.', $mapped); if (!isset($this->_table->{$mappedTable}) || $this->_table->{$mappedTable}->isOwningSide($this->_table)) { throw new Exception(sprintf('Incorrect definition of related data to persist for %s', $mapped)); } $foreign = $entity->get($this->_table->{$mappedTable}->foreignKey()); if (!is_null($foreign)) { // get related entity $related = $this->_table->{$mappedTable}->get($foreign); // set field value $entity->set($field, $related->get($mappedField)); } } }
/** * Takes an entity from the source table and looks if there is a field * matching the property name for this association. The found entity will be * saved on the target table for this association by passing supplied * `$options` * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array|\ArrayObject $options options to be passed to the save method in * the target table * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity * @see Table::save() * @throws \InvalidArgumentException when the association data cannot be traversed. */ public function saveAssociated(EntityInterface $entity, array $options = []) { $targetEntities = $entity->get($this->property()); if (empty($targetEntities)) { return $entity; } if (!is_array($targetEntities) && !$targetEntities instanceof \Traversable) { $name = $this->property(); $message = sprintf('Could not save %s, it cannot be traversed', $name); throw new InvalidArgumentException($message); } $foreignKey = (array) $this->foreignKey(); $properties = array_combine($foreignKey, $entity->extract((array) $this->bindingKey())); $target = $this->target(); $original = $targetEntities; $options['_sourceTable'] = $this->source(); foreach ($targetEntities as $k => $targetEntity) { if (!$targetEntity instanceof EntityInterface) { break; } if (!empty($options['atomic'])) { $targetEntity = clone $targetEntity; } if ($properties !== $targetEntity->extract($foreignKey)) { $targetEntity->set($properties, ['guard' => false]); } if ($target->save($targetEntity, $options)) { $targetEntities[$k] = $targetEntity; continue; } if (!empty($options['atomic'])) { $original[$k]->errors($targetEntity->errors()); $entity->set($this->property(), $original); return false; } } $entity->set($this->property(), $targetEntities); return $entity; }
/** * Returns the depth level of a node in the tree. * * @param int|string|\Cake\Datasource\EntityInterface $entity The entity or primary key get the level of. * @return int|bool Integer of the level or false if the node does not exist. */ public function getLevel($entity) { $primaryKey = $this->_getPrimaryKey(); $id = $entity; if ($entity instanceof EntityInterface) { $id = $entity->get($primaryKey); } $config = $this->config(); $entity = $this->_table->find('all')->select([$config['path']])->where([$primaryKey => $id])->first(); if ($entity === null) { return false; } return substr_count($entity[$config['path']], '-') + 1; }
/** * Helper method used to generated multiple translated field entities * out of the data found in the `_translations` property in the passed * entity. The result will be put into its `_i18n` property * * @param \Cake\Datasource\EntityInterface $entity Entity * @return void */ protected function _bundleTranslatedFields($entity) { $translations = (array) $entity->get('_translations'); if (empty($translations) && !$entity->dirty('_translations')) { return; } $fields = $this->_config['fields']; $primaryKey = (array) $this->_table->primaryKey(); $key = $entity->get(current($primaryKey)); $find = []; foreach ($translations as $lang => $translation) { foreach ($fields as $field) { if (!$translation->dirty($field)) { continue; } $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key]; $contents[] = new Entity(['content' => $translation->get($field)], ['useSetters' => false]); } } if (empty($find)) { return; } $results = $this->_findExistingTranslations($find); foreach ($find as $i => $translation) { if (!empty($results[$i])) { $contents[$i]->set('id', $results[$i], ['setter' => false]); $contents[$i]->isNew(false); } else { $translation['model'] = $this->_config['referenceName']; $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); $contents[$i]->isNew(true); } } $entity->set('_i18n', $contents); }
/** * checkRules rule. * * @param \Cake\Datasource\EntityInterface $entity Entity. * @return bool */ public function checkRules(EntityInterface $entity) { $field = $this->_config['discriminatorField']; if ($entity->dirty($field)) { return $this->_matches($entity->get($field), $this->acceptedDiscriminators()); } return true; }
/** * Takes an entity from the source table and looks if there is a field * matching the property name for this association. The found entity will be * saved on the target table for this association by passing supplied * `$options` * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array|\ArrayObject $options options to be passed to the save method in * the target table * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity * @see Table::save() */ public function saveAssociated(EntityInterface $entity, array $options = []) { $targetEntity = $entity->get($this->property()); if (empty($targetEntity) || !$targetEntity instanceof EntityInterface) { return $entity; } $properties = array_combine((array) $this->foreignKey(), $entity->extract((array) $this->bindingKey())); $targetEntity->set($properties, ['guard' => false]); if (!$this->target()->save($targetEntity, $options)) { $targetEntity->unsetProperty(array_keys($properties)); return false; } return $entity; }
/** * Removes all links between the passed source entity and each of the provided * target entities. This method assumes that all passed objects are already persisted * in the database and that each of them contain a primary key value. * * ### Options * * Additionally to the default options accepted by `Table::delete()`, the following * keys are supported: * * - cleanProperty: Whether or not to remove all the objects in `$targetEntities` that * are stored in `$sourceEntity` (default: true) * * By default this method will unset each of the entity objects stored inside the * source entity. * * Changes are persisted in the database and also in the source entity. * * ### Example: * * ``` * $user = $users->get(1); * $user->articles = [$article1, $article2, $article3, $article4]; * $users->save($user, ['Associated' => ['Articles']]); * $allArticles = [$article1, $article2, $article3]; * $users->Articles->unlink($user, $allArticles); * ``` * * `$article->get('articles')` will contain only `[$article4]` after deleting in the database * * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for * this association * @param array $targetEntities list of entities persisted in the target table for * this association * @param array $options list of options to be passed to the internal `delete` call * @throws \InvalidArgumentException if non persisted entities are passed or if * any of them is lacking a primary key value * @return void */ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []) { if (is_bool($options)) { $options = ['cleanProperty' => $options]; } else { $options += ['cleanProperty' => true]; } $foreignKey = (array) $this->foreignKey(); $target = $this->target(); $targetPrimaryKey = array_merge((array) $target->primaryKey(), $foreignKey); $property = $this->property(); $conditions = ['OR' => (new Collection($targetEntities))->map(function ($entity) use($targetPrimaryKey) { return $entity->extract($targetPrimaryKey); })->toList()]; $this->_unlink($foreignKey, $target, $conditions, $options); if ($options['cleanProperty']) { $sourceEntity->set($property, (new Collection($sourceEntity->get($property)))->reject(function ($assoc) use($targetEntities) { return in_array($assoc, $targetEntities); })->toList()); } $sourceEntity->dirty($property, false); }
/** * Takes an entity from the source table and looks if there is a field * matching the property name for this association. The found entity will be * saved on the target table for this association by passing supplied * `$options` * * When using the 'append' strategy, this function will only create new links * between each side of this association. It will not destroy existing ones even * though they may not be present in the array of entities to be saved. * * When using the 'replace' strategy, existing links will be removed and new links * will be created in the joint table. If there exists links in the database to some * of the entities intended to be saved by this method, they will be updated, * not deleted. * * @param \Cake\Datasource\EntityInterface $entity an entity from the source table * @param array|\ArrayObject $options options to be passed to the save method in * the target table * @throws \InvalidArgumentException if the property representing the association * in the parent entity cannot be traversed * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns * the saved entity * @see Table::save() * @see BelongsToMany::replaceLinks() */ public function saveAssociated(EntityInterface $entity, array $options = []) { $targetEntity = $entity->get($this->property()); $strategy = $this->saveStrategy(); $isEmpty = in_array($targetEntity, [null, [], '', false], true); if ($isEmpty && $entity->isNew()) { return $entity; } if ($isEmpty) { $targetEntity = []; } if ($strategy === self::SAVE_APPEND) { return $this->_saveTarget($entity, $targetEntity, $options); } if ($this->replaceLinks($entity, $targetEntity, $options)) { return $entity; } return false; }
/** * Merges `$data` into `$entity` and recursively does the same for each one of * the association names passed in `$include`. When merging associations, if an * entity is not present in the parent entity for a given association, a new one * will be created. * * When merging HasMany or BelongsToMany associations, all the entities in the * `$data` array will appear, those that can be matched by primary key will get * the data merged, but those that cannot, will be discarded. * * @param \Cake\Datasource\EntityInterface $entity the entity that will get the * data merged in * @param array $data key value list of fields to be merged into the entity * @param array $include The list of associations to be merged * @return \Cake\Datasource\EntityInterface */ public function merge(EntityInterface $entity, array $data, array $include = []) { $propertyMap = $this->_buildPropertyMap($include); $tableName = $this->_table->alias(); if (isset($data[$tableName])) { $data = $data[$tableName]; } $schema = $this->_table->schema(); $properties = []; foreach ($data as $key => $value) { $columnType = $schema->columnType($key); $original = $entity->get($key); if (isset($propertyMap[$key])) { $assoc = $propertyMap[$key]['association']; $nested = $propertyMap[$key]['nested']; $value = $this->_mergeAssociation($original, $assoc, $value, $nested); } elseif ($columnType) { $converter = Type::build($columnType); $value = $converter->marshal($value); if ($original == $value) { continue; } } $properties[$key] = $value; } $entity->set($properties); return $entity; }
/** * Prepares entity's cache-columns (those defined using `cache` option). * * @param \Cake\Datasource\EntityInterface $entity The entity to prepare * @return \Cake\Datasource\EntityInterfa Modified entity */ protected function _prepareCachedColumns(EntityInterface $entity) { if ($this->config('cacheMap')) { foreach ((array) $this->config('cacheMap') as $column => $fields) { if (in_array($column, $entity->visibleProperties())) { $string = $entity->get($column); if ($string == serialize(false) || @unserialize($string) !== false) { $entity->set($column, unserialize($string)); } else { $entity->set($column, new CachedColumn()); } } } } return $entity; }
/** * Generates a unique hash for the given entity. * * Used by revision system to detect if an entity has changed or not. * * @param \Cake\Datasource\EntityInterface $entity The entity for which calculate its hash * @return string MD5 hash for this particular entity */ protected function _calculateHash($entity) { $hash = []; foreach ($entity->visibleProperties() as $property) { if (strpos($property, 'created') === false && strpos($property, 'created_by') === false && strpos($property, 'modified') === false && strpos($property, 'modified_by') === false) { if ($property == '_fields') { foreach ($entity->get('_fields') as $field) { if ($field instanceof \Field\Model\Entity\Field) { $hash[] = is_object($field->value) || is_array($field->value) ? md5(serialize($field->value)) : md5($field->value); } } } else { $hash[] = $entity->get($property); } } } return md5(serialize($hash)); }
/** * After the main file was deleted remove the the thumbnails * * Note that we do not call the parent::afterDelete(), we just want to trigger the ImageStorage.afterDelete event but not the FileStorage.afterDelete at the same time! * * @param \Cake\Event\Event $event * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return boolean */ public function afterDelete(Event $event, EntityInterface $entity, $options) { $this->dispatchEvent('ImageStorage.afterDelete', ['record' => $entity, 'storage' => $this->storageAdapter($entity->get('adapter'))]); return true; }
/** * Check whether or not the entity fields are nullable and null. * * @param \Cake\Datasource\EntityInterface $entity The entity to check. * @param \Cake\ORM\Table $source The table to use schema from. * @return bool */ protected function _fieldsAreNull($entity, $source) { $nulls = 0; $schema = $source->schema(); foreach ($this->_fields as $field) { if ($schema->column($field) && $schema->isNullable($field) && $entity->get($field) === null) { $nulls++; } } return $nulls === count($this->_fields); }
/** * Merges `$data` into `$entity` and recursively does the same for each one of * the association names passed in `$options`. When merging associations, if an * entity is not present in the parent entity for a given association, a new one * will be created. * * When merging HasMany or BelongsToMany associations, all the entities in the * `$data` array will appear, those that can be matched by primary key will get * the data merged, but those that cannot, will be discarded. * * ### Options: * * * associated: Associations listed here will be marshalled as well. * * fieldList: A whitelist of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. * * @param \Cake\Datasource\EntityInterface $entity the entity that will get the * data merged in * @param array $data key value list of fields to be merged into the entity * @param array $options List of options. * @return \Cake\Datasource\EntityInterface */ public function merge(EntityInterface $entity, array $data, array $options = []) { $propertyMap = $this->_buildPropertyMap($options); $tableName = $this->_table->alias(); if (isset($data[$tableName])) { $data = $data[$tableName]; } $schema = $this->_table->schema(); $properties = []; foreach ($data as $key => $value) { $columnType = $schema->columnType($key); $original = $entity->get($key); if (isset($propertyMap[$key])) { $assoc = $propertyMap[$key]['association']; $value = $this->_mergeAssociation($original, $assoc, $value, $propertyMap[$key]); } elseif ($columnType) { $converter = Type::build($columnType); $value = $converter->marshal($value); $isObject = is_object($value); if (!$isObject && $original === $value || $isObject && $original == $value) { continue; } } $properties[$key] = $value; } if (!isset($options['fieldList'])) { $entity->set($properties); return $entity; } foreach ((array) $options['fieldList'] as $field) { if (isset($properties[$field])) { $entity->set($field, $properties[$field]); } } return $entity; }
/** * Checks if the entity can be deleted. * * @param EntityInterface $entity * @return bool */ protected function _deleteAllowed(EntityInterface $entity) { $type = $this->_formatTypeName(); if ($type !== false) { $fieldName = $this->config('field_name'); if ($entity->has($fieldName) && strpos($entity->get($fieldName), $type) === false) { return false; } } return true; }
/** * Delete a single resource. * * @param \Cake\Datasource\EntityInterface $resource The resource to remove. * @param array|\ArrayAccess $options The options for the delete. * @return bool success */ public function delete(EntityInterface $resource, $options = []) { return (bool) $this->query()->delete()->where([$this->primaryKey() => $resource->get($this->primaryKey())])->execute(); }
/** * Calculates entity's primary key. * * If PK is composed of multiple columns they will be merged with `:` symbol. * For example, consider `Users` table with composed PK <nick, email>, then for * certain User entity this method could return: * * john-locke:john@the-island.com * * @param \Cake\Datasource\EntityInterface $entity The entity * @return string */ public function getEntityId(EntityInterface $entity) { $pk = []; $keys = $this->_table->primaryKey(); $keys = !is_array($keys) ? [$keys] : $keys; foreach ($keys as $key) { $pk[] = $entity->get($key); } return implode(':', $pk); }
/** * Helper method used to generated multiple translated field entities * out of the data found in the `_translations` property in the passed * entity. The result will be put into its `_i18n` property * * @param \Cake\Datasource\EntityInterface $entity Entity * @return void */ protected function _bundleTranslatedFields($entity) { $translations = (array) $entity->get('_translations'); if (empty($translations) && !$entity->dirty('_translations')) { return; } $primaryKey = (array) $this->_table->primaryKey(); $key = $entity->get(current($primaryKey)); foreach ($translations as $lang => $translation) { if (!$translation->id) { $update = ['id' => $key, 'locale' => $lang]; $translation->set($update, ['guard' => false]); } } $entity->set('_i18n', $translations); }
/** * Merges `$data` into `$entity` and recursively does the same for each one of * the association names passed in `$options`. When merging associations, if an * entity is not present in the parent entity for a given association, a new one * will be created. * * When merging HasMany or BelongsToMany associations, all the entities in the * `$data` array will appear, those that can be matched by primary key will get * the data merged, but those that cannot, will be discarded. `ids` option can be used * to determine whether the association must use the `_ids` format. * * ### Options: * * - associated: Associations listed here will be marshalled as well. * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. * - fieldList: A whitelist of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * * The above options can be used in each nested `associated` array. In addition to the above * options you can also use the `onlyIds` option for HasMany and BelongsToMany associations. * When true this option restricts the request data to only be read from `_ids`. * * ``` * $result = $marshaller->merge($entity, $data, [ * 'associated' => ['Tags' => ['onlyIds' => true]] * ]); * ``` * * @param \Cake\Datasource\EntityInterface $entity the entity that will get the * data merged in * @param array $data key value list of fields to be merged into the entity * @param array $options List of options. * @return \Cake\Datasource\EntityInterface */ public function merge(EntityInterface $entity, array $data, array $options = []) { list($data, $options) = $this->_prepareDataAndOptions($data, $options); $propertyMap = $this->_buildPropertyMap($options); $isNew = $entity->isNew(); $keys = []; if (!$isNew) { $keys = $entity->extract((array) $this->_table->primaryKey()); } if (isset($options['accessibleFields'])) { foreach ((array) $options['accessibleFields'] as $key => $value) { $entity->accessible($key, $value); } } $errors = $this->_validate($data + $keys, $options, $isNew); $schema = $this->_table->schema(); $properties = $marshalledAssocs = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { if ($entity instanceof InvalidPropertyInterface) { $entity->invalid($key, $value); } continue; } $columnType = $schema->columnType($key); $original = $entity->get($key); if (isset($propertyMap[$key])) { $assoc = $propertyMap[$key]['association']; $value = $this->_mergeAssociation($original, $assoc, $value, $propertyMap[$key]); $marshalledAssocs[$key] = true; } elseif ($columnType) { $converter = Type::build($columnType); $value = $converter->marshal($value); $isObject = is_object($value); if (!$isObject && $original === $value || $isObject && $original == $value) { continue; } } $properties[$key] = $value; } if (!isset($options['fieldList'])) { $entity->set($properties); $entity->errors($errors); foreach (array_keys($marshalledAssocs) as $field) { if ($properties[$field] instanceof EntityInterface) { $entity->dirty($field, $properties[$field]->dirty()); } } return $entity; } foreach ((array) $options['fieldList'] as $field) { if (array_key_exists($field, $properties)) { $entity->set($field, $properties[$field]); if ($properties[$field] instanceof EntityInterface && isset($marshalledAssocs[$field])) { $entity->dirty($field, $properties[$field]->dirty()); } } } $entity->errors($errors); return $entity; }
/** * Sets up hashid for model. * * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return bool True if save should proceed, false otherwise */ public function encode(EntityInterface $entity) { $idField = $this->_primaryKey; $id = $entity->get($idField); if (!$id) { return false; } $field = $this->_config['field']; if (!$field) { return false; } $hashid = $this->encodeId($id); $entity->set($field, $hashid); $entity->dirty($field, false); return true; }
/** * Returns the depth level of a node in the tree. * * @param int|string|\Cake\Datasource\EntityInterface $entity The entity or primary key get the level of. * @return int|bool Integer of the level or false if the node does not exist. */ public function getLevel($entity) { $primaryKey = $this->_getPrimaryKey(); $id = $entity; if ($entity instanceof EntityInterface) { $id = $entity->get($primaryKey); } $config = $this->config(); $entity = $this->_table->find('all')->select([$config['left'], $config['right']])->where([$primaryKey => $id])->first(); if ($entity === null) { return false; } $query = $this->_table->find('all')->where([$config['left'] . ' <' => $entity[$config['left']], $config['right'] . ' >' => $entity[$config['right']]]); return $this->_scope($query)->count(); }
/** * Merges `$data` into `$entity` and recursively does the same for each one of * the association names passed in `$options`. When merging associations, if an * entity is not present in the parent entity for a given association, a new one * will be created. * * When merging HasMany or BelongsToMany associations, all the entities in the * `$data` array will appear, those that can be matched by primary key will get * the data merged, but those that cannot, will be discarded. `ids` option can be used * to determine whether the association must use the `_ids` format. * * ### Options: * * - associated: Associations listed here will be marshalled as well. * - validate: Whether or not to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. * - fieldList: A whitelist of fields to be assigned to the entity. If not present * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. * * The above options can be used in each nested `associated` array. In addition to the above * options you can also use the `onlyIds` option for HasMany and BelongsToMany associations. * When true this option restricts the request data to only be read from `_ids`. * * ``` * $result = $marshaller->merge($entity, $data, [ * 'associated' => ['Tags' => ['onlyIds' => true]] * ]); * ``` * * @param \Cake\Datasource\EntityInterface $entity the entity that will get the * data merged in * @param array $data key value list of fields to be merged into the entity * @param array $options List of options. * @return \Cake\Datasource\EntityInterface */ public function merge(EntityInterface $entity, array $data, array $options = []) { list($data, $options) = $this->_prepareDataAndOptions($data, $options); $isNew = $entity->isNew(); $keys = []; if (!$isNew) { $keys = $entity->extract((array) $this->_table->primaryKey()); } if (isset($options['accessibleFields'])) { foreach ((array) $options['accessibleFields'] as $key => $value) { $entity->accessible($key, $value); } } $errors = $this->_validate($data + $keys, $options, $isNew); $schema = $this->_table->schema(); $options['isMerge'] = true; $propertyMap = $this->_buildPropertyMap($data, $options); $properties = $marshalledAssocs = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { if ($entity instanceof InvalidPropertyInterface) { $entity->invalid($key, $value); } continue; } $original = $entity->get($key); if (isset($propertyMap[$key])) { $value = $propertyMap[$key]($value, $entity); // Don't dirty scalar values and objects that didn't // change. Arrays will always be marked as dirty because // the original/updated list could contain references to the // same objects, even though those objects may have changed internally. if (is_scalar($value) && $original === $value || $value === null && $original === $value || is_object($value) && !$value instanceof EntityInterface && $original == $value) { continue; } } $properties[$key] = $value; } $entity->errors($errors); if (!isset($options['fieldList'])) { $entity->set($properties); foreach ($properties as $field => $value) { if ($value instanceof EntityInterface) { $entity->dirty($field, $value->dirty()); } } return $entity; } foreach ((array) $options['fieldList'] as $field) { if (array_key_exists($field, $properties)) { $entity->set($field, $properties[$field]); if ($properties[$field] instanceof EntityInterface) { $entity->dirty($field, $properties[$field]->dirty()); } } } return $entity; }