annotate sites/all/modules/entity/includes/entity.wrapper.inc @ 4:ce11bbd8f642

added modules
author danieleb <danielebarchiesi@me.com>
date Thu, 19 Sep 2013 10:38:44 +0100
parents
children
rev   line source
danielebarchiesi@4 1 <?php
danielebarchiesi@4 2
danielebarchiesi@4 3 /**
danielebarchiesi@4 4 * @file
danielebarchiesi@4 5 * Provides wrappers allowing easy usage of the entity metadata.
danielebarchiesi@4 6 */
danielebarchiesi@4 7
danielebarchiesi@4 8 /**
danielebarchiesi@4 9 * A common base class for all wrappers.
danielebarchiesi@4 10 */
danielebarchiesi@4 11 abstract class EntityMetadataWrapper {
danielebarchiesi@4 12
danielebarchiesi@4 13 protected $type;
danielebarchiesi@4 14 protected $data;
danielebarchiesi@4 15 protected $info;
danielebarchiesi@4 16 protected $cache = array();
danielebarchiesi@4 17
danielebarchiesi@4 18 /**
danielebarchiesi@4 19 * Construct a new wrapper object.
danielebarchiesi@4 20 *
danielebarchiesi@4 21 * @param $type
danielebarchiesi@4 22 * The type of the passed data.
danielebarchiesi@4 23 * @param $data
danielebarchiesi@4 24 * Optional. The data to wrap.
danielebarchiesi@4 25 * @param $info
danielebarchiesi@4 26 * Optional. Used internally to pass info about properties down the tree.
danielebarchiesi@4 27 */
danielebarchiesi@4 28 public function __construct($type, $data = NULL, $info = array()) {
danielebarchiesi@4 29 $this->type = $type;
danielebarchiesi@4 30 $this->info = $info + array(
danielebarchiesi@4 31 'langcode' => NULL,
danielebarchiesi@4 32 );
danielebarchiesi@4 33 $this->info['type'] = $type;
danielebarchiesi@4 34 if (isset($data)) {
danielebarchiesi@4 35 $this->set($data);
danielebarchiesi@4 36 }
danielebarchiesi@4 37 }
danielebarchiesi@4 38
danielebarchiesi@4 39 /**
danielebarchiesi@4 40 * Gets info about the wrapped data.
danielebarchiesi@4 41 *
danielebarchiesi@4 42 * @return Array
danielebarchiesi@4 43 * Keys set are all keys as specified for a property in hook_entity_info()
danielebarchiesi@4 44 * as well as possible the following keys:
danielebarchiesi@4 45 * - name: If this wraps a property, the name of the property.
danielebarchiesi@4 46 * - parent: The parent wrapper, if any.
danielebarchiesi@4 47 * - langcode: The language code, if this data is language specific.
danielebarchiesi@4 48 */
danielebarchiesi@4 49 public function info() {
danielebarchiesi@4 50 return $this->info;
danielebarchiesi@4 51 }
danielebarchiesi@4 52
danielebarchiesi@4 53 /**
danielebarchiesi@4 54 * Gets the (entity)type of the wrapped data.
danielebarchiesi@4 55 */
danielebarchiesi@4 56 public function type() {
danielebarchiesi@4 57 return $this->type;
danielebarchiesi@4 58 }
danielebarchiesi@4 59
danielebarchiesi@4 60 /**
danielebarchiesi@4 61 * Returns the wrapped data. If no options are given the data is returned as
danielebarchiesi@4 62 * described in the info.
danielebarchiesi@4 63 *
danielebarchiesi@4 64 * @param $options
danielebarchiesi@4 65 * (optional) A keyed array of options:
danielebarchiesi@4 66 * - sanitize: A boolean flag indicating that textual properties should be
danielebarchiesi@4 67 * sanitized for display to a web browser. Defaults to FALSE.
danielebarchiesi@4 68 * - decode: If set to TRUE and some textual data is already sanitized, it
danielebarchiesi@4 69 * strips HTML tags and decodes HTML entities. Defaults to FALSE.
danielebarchiesi@4 70 *
danielebarchiesi@4 71 * @return
danielebarchiesi@4 72 * The value of the wrapped data. If the data property is not set, NULL
danielebarchiesi@4 73 * is returned.
danielebarchiesi@4 74 *
danielebarchiesi@4 75 * @throws EntityMetadataWrapperException
danielebarchiesi@4 76 * In case there are no data values available to the wrapper, an exception
danielebarchiesi@4 77 * is thrown. E.g. if the value for an entity property is to be retrieved
danielebarchiesi@4 78 * and there is no entity available, the exception is thrown. However, if
danielebarchiesi@4 79 * an entity is available but the property is not set, NULL is returned.
danielebarchiesi@4 80 */
danielebarchiesi@4 81 public function value(array $options = array()) {
danielebarchiesi@4 82 if (!$this->dataAvailable() && isset($this->info['parent'])) {
danielebarchiesi@4 83 throw new EntityMetadataWrapperException('Missing data values.');
danielebarchiesi@4 84 }
danielebarchiesi@4 85 if (!isset($this->data) && isset($this->info['name'])) {
danielebarchiesi@4 86 $this->data = $this->info['parent']->getPropertyValue($this->info['name'], $this->info);
danielebarchiesi@4 87 }
danielebarchiesi@4 88 return $this->data;
danielebarchiesi@4 89 }
danielebarchiesi@4 90
danielebarchiesi@4 91 /**
danielebarchiesi@4 92 * Returns the raw, unprocessed data. Most times this is the same as returned
danielebarchiesi@4 93 * by value(), however for already processed and sanitized textual data, this
danielebarchiesi@4 94 * will return the unprocessed data in contrast to value().
danielebarchiesi@4 95 */
danielebarchiesi@4 96 public function raw() {
danielebarchiesi@4 97 if (!$this->dataAvailable()) {
danielebarchiesi@4 98 throw new EntityMetadataWrapperException('Missing data values.');
danielebarchiesi@4 99 }
danielebarchiesi@4 100 if (isset($this->info['name']) && isset($this->info['parent'])) {
danielebarchiesi@4 101 return $this->info['parent']->getPropertyRaw($this->info['name'], $this->info);
danielebarchiesi@4 102 }
danielebarchiesi@4 103 // Else return the usual value, which should be raw in this case.
danielebarchiesi@4 104 return $this->value();
danielebarchiesi@4 105 }
danielebarchiesi@4 106
danielebarchiesi@4 107 /**
danielebarchiesi@4 108 * Returns whether data is available to work with.
danielebarchiesi@4 109 *
danielebarchiesi@4 110 * @return
danielebarchiesi@4 111 * If we operate without any data FALSE, else TRUE.
danielebarchiesi@4 112 */
danielebarchiesi@4 113 protected function dataAvailable() {
danielebarchiesi@4 114 return isset($this->data) || (isset($this->info['parent']) && $this->info['parent']->dataAvailable());
danielebarchiesi@4 115 }
danielebarchiesi@4 116
danielebarchiesi@4 117 /**
danielebarchiesi@4 118 * Set a new data value.
danielebarchiesi@4 119 */
danielebarchiesi@4 120 public function set($value) {
danielebarchiesi@4 121 if (!$this->validate($value)) {
danielebarchiesi@4 122 throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
danielebarchiesi@4 123 }
danielebarchiesi@4 124 $this->clear();
danielebarchiesi@4 125 $this->data = $value;
danielebarchiesi@4 126 $this->updateParent($value);
danielebarchiesi@4 127 return $this;
danielebarchiesi@4 128 }
danielebarchiesi@4 129
danielebarchiesi@4 130 /**
danielebarchiesi@4 131 * Updates the parent data structure of a data property with the latest data value.
danielebarchiesi@4 132 */
danielebarchiesi@4 133 protected function updateParent($value) {
danielebarchiesi@4 134 if (isset($this->info['parent'])) {
danielebarchiesi@4 135 $this->info['parent']->setProperty($this->info['name'], $value);
danielebarchiesi@4 136 }
danielebarchiesi@4 137 }
danielebarchiesi@4 138
danielebarchiesi@4 139 /**
danielebarchiesi@4 140 * Returns whether $value is a valid value to set.
danielebarchiesi@4 141 */
danielebarchiesi@4 142 public function validate($value) {
danielebarchiesi@4 143 if (isset($value) && !entity_property_verify_data_type($value, $this->type)) {
danielebarchiesi@4 144 return FALSE;
danielebarchiesi@4 145 }
danielebarchiesi@4 146 // Only proceed with further checks if this is not a list item. If this is
danielebarchiesi@4 147 // a list item, the checks are performed on the list property level.
danielebarchiesi@4 148 if (isset($this->info['parent']) && $this->info['parent'] instanceof EntityListWrapper) {
danielebarchiesi@4 149 return TRUE;
danielebarchiesi@4 150 }
danielebarchiesi@4 151 if (!isset($value) && !empty($this->info['required'])) {
danielebarchiesi@4 152 // Do not allow NULL values if the property is required.
danielebarchiesi@4 153 return FALSE;
danielebarchiesi@4 154 }
danielebarchiesi@4 155 return !isset($this->info['validation callback']) || call_user_func($this->info['validation callback'], $value, $this->info);
danielebarchiesi@4 156 }
danielebarchiesi@4 157
danielebarchiesi@4 158 public function __toString() {
danielebarchiesi@4 159 return isset($this->info) ? 'Property ' . $this->info['name'] : $this->type;
danielebarchiesi@4 160 }
danielebarchiesi@4 161
danielebarchiesi@4 162 /**
danielebarchiesi@4 163 * Clears the data value and the wrapper cache.
danielebarchiesi@4 164 */
danielebarchiesi@4 165 protected function clear() {
danielebarchiesi@4 166 $this->data = NULL;
danielebarchiesi@4 167 foreach ($this->cache as $wrapper) {
danielebarchiesi@4 168 $wrapper->clear();
danielebarchiesi@4 169 }
danielebarchiesi@4 170 }
danielebarchiesi@4 171
danielebarchiesi@4 172 /**
danielebarchiesi@4 173 * Returns the options list specifying possible values for the property, if
danielebarchiesi@4 174 * defined.
danielebarchiesi@4 175 *
danielebarchiesi@4 176 * @param $op
danielebarchiesi@4 177 * (optional) One of 'edit' or 'view'. In case the list of possible values
danielebarchiesi@4 178 * a user could set for a property differs from the list of values a
danielebarchiesi@4 179 * property could have, $op determines which options should be returned.
danielebarchiesi@4 180 * Defaults to 'edit'.
danielebarchiesi@4 181 * E.g. all possible roles a user could have include the anonymous and the
danielebarchiesi@4 182 * authenticated user roles, while those roles cannot be added to a user
danielebarchiesi@4 183 * account. So their options would be included for 'view', but for 'edit'
danielebarchiesi@4 184 * not.
danielebarchiesi@4 185 *
danielebarchiesi@4 186 * @return
danielebarchiesi@4 187 * An array as used by hook_options_list() or FALSE.
danielebarchiesi@4 188 */
danielebarchiesi@4 189 public function optionsList($op = 'edit') {
danielebarchiesi@4 190 if (isset($this->info['options list']) && is_callable($this->info['options list'])) {
danielebarchiesi@4 191 $name = isset($this->info['name']) ? $this->info['name'] : NULL;
danielebarchiesi@4 192 return call_user_func($this->info['options list'], $name, $this->info, $op);
danielebarchiesi@4 193 }
danielebarchiesi@4 194 return FALSE;
danielebarchiesi@4 195 }
danielebarchiesi@4 196
danielebarchiesi@4 197 /**
danielebarchiesi@4 198 * Returns the label for the currently set property value if there is one
danielebarchiesi@4 199 * available, i.e. if an options list has been specified.
danielebarchiesi@4 200 */
danielebarchiesi@4 201 public function label() {
danielebarchiesi@4 202 if ($options = $this->optionsList('view')) {
danielebarchiesi@4 203 $options = entity_property_options_flatten($options);
danielebarchiesi@4 204 $value = $this->value();
danielebarchiesi@4 205 if (is_scalar($value) && isset($options[$value])) {
danielebarchiesi@4 206 return $options[$value];
danielebarchiesi@4 207 }
danielebarchiesi@4 208 }
danielebarchiesi@4 209 }
danielebarchiesi@4 210
danielebarchiesi@4 211 /**
danielebarchiesi@4 212 * Determines whether the given user has access to view or edit this property.
danielebarchiesi@4 213 * Apart from relying on access metadata of properties, this takes into
danielebarchiesi@4 214 * account information about entity level access, if available:
danielebarchiesi@4 215 * - Referenced entities can only be viewed, when the user also has
danielebarchiesi@4 216 * permission to view the entity.
danielebarchiesi@4 217 * - A property may be only edited, if the user has permission to update the
danielebarchiesi@4 218 * entity containing the property.
danielebarchiesi@4 219 *
danielebarchiesi@4 220 * @param $op
danielebarchiesi@4 221 * The operation being performed. One of 'view' or 'edit.
danielebarchiesi@4 222 * @param $account
danielebarchiesi@4 223 * The user to check for. Leave it to NULL to check for the global user.
danielebarchiesi@4 224 * @return boolean
danielebarchiesi@4 225 * Whether access to entity property is allowed for the given operation.
danielebarchiesi@4 226 * However if we wrap no data, it returns whether access is allowed to the
danielebarchiesi@4 227 * property of all entities of this type.
danielebarchiesi@4 228 * If there is no access information for this property, TRUE is returned.
danielebarchiesi@4 229 */
danielebarchiesi@4 230 public function access($op, $account = NULL) {
danielebarchiesi@4 231 if (empty($this->info['parent']) && $this instanceof EntityDrupalWrapper) {
danielebarchiesi@4 232 // If there is no parent just incorporate entity based access.
danielebarchiesi@4 233 return $this->entityAccess($op == 'edit' ? 'update' : 'view', $account);
danielebarchiesi@4 234 }
danielebarchiesi@4 235 return !empty($this->info['parent']) ? $this->info['parent']->propertyAccess($this->info['name'], $op, $account) : TRUE;
danielebarchiesi@4 236 }
danielebarchiesi@4 237
danielebarchiesi@4 238 /**
danielebarchiesi@4 239 * Prepare for serializiation.
danielebarchiesi@4 240 */
danielebarchiesi@4 241 public function __sleep() {
danielebarchiesi@4 242 $vars = get_object_vars($this);
danielebarchiesi@4 243 unset($vars['cache']);
danielebarchiesi@4 244 return drupal_map_assoc(array_keys($vars));
danielebarchiesi@4 245 }
danielebarchiesi@4 246 }
danielebarchiesi@4 247
danielebarchiesi@4 248 /**
danielebarchiesi@4 249 * Wraps a single value.
danielebarchiesi@4 250 */
danielebarchiesi@4 251 class EntityValueWrapper extends EntityMetadataWrapper {
danielebarchiesi@4 252
danielebarchiesi@4 253 /**
danielebarchiesi@4 254 * Overrides EntityMetadataWrapper#value().
danielebarchiesi@4 255 * Sanitizes or decode textual data if necessary.
danielebarchiesi@4 256 */
danielebarchiesi@4 257 public function value(array $options = array()) {
danielebarchiesi@4 258 $data = parent::value();
danielebarchiesi@4 259 if ($this->type == 'text' && isset($data)) {
danielebarchiesi@4 260 $info = $this->info + array('sanitized' => FALSE, 'sanitize' => 'check_plain');
danielebarchiesi@4 261 $options += array('sanitize' => FALSE, 'decode' => FALSE);
danielebarchiesi@4 262 if ($options['sanitize'] && !$info['sanitized']) {
danielebarchiesi@4 263 return call_user_func($info['sanitize'], $data);
danielebarchiesi@4 264 }
danielebarchiesi@4 265 elseif ($options['decode'] && $info['sanitized']) {
danielebarchiesi@4 266 return decode_entities(strip_tags($data));
danielebarchiesi@4 267 }
danielebarchiesi@4 268 }
danielebarchiesi@4 269 return $data;
danielebarchiesi@4 270 }
danielebarchiesi@4 271 }
danielebarchiesi@4 272
danielebarchiesi@4 273 /**
danielebarchiesi@4 274 * Provides a general wrapper for any data structure. For this to work the
danielebarchiesi@4 275 * metadata has to be passed during construction.
danielebarchiesi@4 276 */
danielebarchiesi@4 277 class EntityStructureWrapper extends EntityMetadataWrapper implements IteratorAggregate {
danielebarchiesi@4 278
danielebarchiesi@4 279 protected $propertyInfo = array(), $propertyInfoAltered = FALSE;
danielebarchiesi@4 280 protected $langcode = LANGUAGE_NONE;
danielebarchiesi@4 281
danielebarchiesi@4 282 protected $propertyInfoDefaults = array(
danielebarchiesi@4 283 'type' => 'text',
danielebarchiesi@4 284 'getter callback' => 'entity_property_verbatim_get',
danielebarchiesi@4 285 'clear' => array(),
danielebarchiesi@4 286 );
danielebarchiesi@4 287
danielebarchiesi@4 288 /**
danielebarchiesi@4 289 * Construct a new EntityStructureWrapper object.
danielebarchiesi@4 290 *
danielebarchiesi@4 291 * @param $type
danielebarchiesi@4 292 * The type of the passed data.
danielebarchiesi@4 293 * @param $data
danielebarchiesi@4 294 * Optional. The data to wrap.
danielebarchiesi@4 295 * @param $info
danielebarchiesi@4 296 * Used to for specifying metadata about the data and internally to pass
danielebarchiesi@4 297 * info about properties down the tree. For specifying metadata known keys
danielebarchiesi@4 298 * are:
danielebarchiesi@4 299 * - property info: An array of info about the properties of the wrapped
danielebarchiesi@4 300 * data structure. It has to contain an array of property info in the same
danielebarchiesi@4 301 * structure as used by hook_entity_property_info().
danielebarchiesi@4 302 */
danielebarchiesi@4 303 public function __construct($type, $data = NULL, $info = array()) {
danielebarchiesi@4 304 parent::__construct($type, $data, $info);
danielebarchiesi@4 305 $this->info += array('property defaults' => array());
danielebarchiesi@4 306 $info += array('property info' => array());
danielebarchiesi@4 307 $this->propertyInfo['properties'] = $info['property info'];
danielebarchiesi@4 308 }
danielebarchiesi@4 309
danielebarchiesi@4 310 /**
danielebarchiesi@4 311 * May be used to lazy-load additional info about the data, depending on the
danielebarchiesi@4 312 * concrete passed data.
danielebarchiesi@4 313 */
danielebarchiesi@4 314 protected function spotInfo() {
danielebarchiesi@4 315 // Apply the callback if set, such that the caller may alter the info.
danielebarchiesi@4 316 if (!empty($this->info['property info alter']) && !$this->propertyInfoAltered) {
danielebarchiesi@4 317 $this->propertyInfo = call_user_func($this->info['property info alter'], $this, $this->propertyInfo);
danielebarchiesi@4 318 $this->propertyInfoAltered = TRUE;
danielebarchiesi@4 319 }
danielebarchiesi@4 320 }
danielebarchiesi@4 321
danielebarchiesi@4 322 /**
danielebarchiesi@4 323 * Gets the info about the given property.
danielebarchiesi@4 324 *
danielebarchiesi@4 325 * @param $name
danielebarchiesi@4 326 * The name of the property. If not given, info about all properties will
danielebarchiesi@4 327 * be returned.
danielebarchiesi@4 328 * @throws EntityMetadataWrapperException
danielebarchiesi@4 329 * If there is no such property.
danielebarchiesi@4 330 * @return
danielebarchiesi@4 331 * An array of info about the property.
danielebarchiesi@4 332 */
danielebarchiesi@4 333 public function getPropertyInfo($name = NULL) {
danielebarchiesi@4 334 $this->spotInfo();
danielebarchiesi@4 335 if (!isset($name)) {
danielebarchiesi@4 336 return $this->propertyInfo['properties'];
danielebarchiesi@4 337 }
danielebarchiesi@4 338 if (!isset($this->propertyInfo['properties'][$name])) {
danielebarchiesi@4 339 throw new EntityMetadataWrapperException('Unknown data property ' . check_plain($name) . '.');
danielebarchiesi@4 340 }
danielebarchiesi@4 341 return $this->propertyInfo['properties'][$name] + $this->info['property defaults'] + $this->propertyInfoDefaults;
danielebarchiesi@4 342 }
danielebarchiesi@4 343
danielebarchiesi@4 344 /**
danielebarchiesi@4 345 * Returns a reference on the property info.
danielebarchiesi@4 346 *
danielebarchiesi@4 347 * If possible, use the property info alter callback for spotting metadata.
danielebarchiesi@4 348 * The reference may be used to alter the property info for any remaining
danielebarchiesi@4 349 * cases, e.g. if additional metadata has been asserted.
danielebarchiesi@4 350 */
danielebarchiesi@4 351 public function &refPropertyInfo() {
danielebarchiesi@4 352 return $this->propertyInfo;
danielebarchiesi@4 353 }
danielebarchiesi@4 354
danielebarchiesi@4 355 /**
danielebarchiesi@4 356 * Sets a new language to use for retrieving properties.
danielebarchiesi@4 357 *
danielebarchiesi@4 358 * @param $langcode
danielebarchiesi@4 359 * The language code of the language to set.
danielebarchiesi@4 360 * @return EntityWrapper
danielebarchiesi@4 361 */
danielebarchiesi@4 362 public function language($langcode = LANGUAGE_NONE) {
danielebarchiesi@4 363 if ($langcode != $this->langcode) {
danielebarchiesi@4 364 $this->langcode = $langcode;
danielebarchiesi@4 365 $this->cache = array();
danielebarchiesi@4 366 }
danielebarchiesi@4 367 return $this;
danielebarchiesi@4 368 }
danielebarchiesi@4 369
danielebarchiesi@4 370 /**
danielebarchiesi@4 371 * Gets the language used for retrieving properties.
danielebarchiesi@4 372 *
danielebarchiesi@4 373 * @return String
danielebarchiesi@4 374 * The language object of the language or NULL for the default language.
danielebarchiesi@4 375 *
danielebarchiesi@4 376 * @see EntityStructureWrapper::language()
danielebarchiesi@4 377 */
danielebarchiesi@4 378 public function getPropertyLanguage() {
danielebarchiesi@4 379 if ($this->langcode != LANGUAGE_NONE && $list = language_list()) {
danielebarchiesi@4 380 if (isset($list[$this->langcode])) {
danielebarchiesi@4 381 return $list[$this->langcode];
danielebarchiesi@4 382 }
danielebarchiesi@4 383 }
danielebarchiesi@4 384 return NULL;
danielebarchiesi@4 385 }
danielebarchiesi@4 386
danielebarchiesi@4 387 /**
danielebarchiesi@4 388 * Get the wrapper for a property.
danielebarchiesi@4 389 *
danielebarchiesi@4 390 * @return
danielebarchiesi@4 391 * An instance of EntityMetadataWrapper.
danielebarchiesi@4 392 */
danielebarchiesi@4 393 public function get($name) {
danielebarchiesi@4 394 // Look it up in the cache if possible.
danielebarchiesi@4 395 if (!array_key_exists($name, $this->cache)) {
danielebarchiesi@4 396 if ($info = $this->getPropertyInfo($name)) {
danielebarchiesi@4 397 $info += array('parent' => $this, 'name' => $name, 'langcode' => $this->langcode, 'property defaults' => array());
danielebarchiesi@4 398 $info['property defaults'] += $this->info['property defaults'];
danielebarchiesi@4 399 $this->cache[$name] = entity_metadata_wrapper($info['type'], NULL, $info);
danielebarchiesi@4 400 }
danielebarchiesi@4 401 else {
danielebarchiesi@4 402 throw new EntityMetadataWrapperException('There is no property ' . check_plain($name) . " for this entity.");
danielebarchiesi@4 403 }
danielebarchiesi@4 404 }
danielebarchiesi@4 405 return $this->cache[$name];
danielebarchiesi@4 406 }
danielebarchiesi@4 407
danielebarchiesi@4 408 /**
danielebarchiesi@4 409 * Magic method: Get a wrapper for a property.
danielebarchiesi@4 410 */
danielebarchiesi@4 411 public function __get($name) {
danielebarchiesi@4 412 if (strpos($name, 'krumo') === 0) {
danielebarchiesi@4 413 // #914934 Ugly workaround to allow krumo to write its recursion property.
danielebarchiesi@4 414 // This is necessary to make dpm() work without throwing exceptions.
danielebarchiesi@4 415 return NULL;
danielebarchiesi@4 416 }
danielebarchiesi@4 417 $get = $this->get($name);
danielebarchiesi@4 418 return $get;
danielebarchiesi@4 419 }
danielebarchiesi@4 420
danielebarchiesi@4 421 /**
danielebarchiesi@4 422 * Magic method: Set a property.
danielebarchiesi@4 423 */
danielebarchiesi@4 424 public function __set($name, $value) {
danielebarchiesi@4 425 if (strpos($name, 'krumo') === 0) {
danielebarchiesi@4 426 // #914934 Ugly workaround to allow krumo to write its recursion property.
danielebarchiesi@4 427 // This is necessary to make dpm() work without throwing exceptions.
danielebarchiesi@4 428 $this->$name = $value;
danielebarchiesi@4 429 }
danielebarchiesi@4 430 else {
danielebarchiesi@4 431 $this->get($name)->set($value);
danielebarchiesi@4 432 }
danielebarchiesi@4 433 }
danielebarchiesi@4 434
danielebarchiesi@4 435 /**
danielebarchiesi@4 436 * Gets the value of a property.
danielebarchiesi@4 437 */
danielebarchiesi@4 438 protected function getPropertyValue($name, &$info) {
danielebarchiesi@4 439 $options = array('language' => $this->getPropertyLanguage(), 'absolute' => TRUE);
danielebarchiesi@4 440 $data = $this->value();
danielebarchiesi@4 441 if (!isset($data)) {
danielebarchiesi@4 442 throw new EntityMetadataWrapperException('Unable to get the data property ' . check_plain($name) . ' as the parent data structure is not set.');
danielebarchiesi@4 443 }
danielebarchiesi@4 444 return $info['getter callback']($data, $options, $name, $this->type, $info);
danielebarchiesi@4 445 }
danielebarchiesi@4 446
danielebarchiesi@4 447 /**
danielebarchiesi@4 448 * Gets the raw value of a property.
danielebarchiesi@4 449 */
danielebarchiesi@4 450 protected function getPropertyRaw($name, &$info) {
danielebarchiesi@4 451 if (!empty($info['raw getter callback'])) {
danielebarchiesi@4 452 $options = array('language' => $this->getPropertyLanguage(), 'absolute' => TRUE);
danielebarchiesi@4 453 $data = $this->value();
danielebarchiesi@4 454 if (!isset($data)) {
danielebarchiesi@4 455 throw new EntityMetadataWrapperException('Unable to get the data property ' . check_plain($name) . ' as the parent data structure is not set.');
danielebarchiesi@4 456 }
danielebarchiesi@4 457 return $info['raw getter callback']($data, $options, $name, $this->type, $info);
danielebarchiesi@4 458 }
danielebarchiesi@4 459 return $this->getPropertyValue($name, $info);
danielebarchiesi@4 460 }
danielebarchiesi@4 461
danielebarchiesi@4 462 /**
danielebarchiesi@4 463 * Sets a property.
danielebarchiesi@4 464 */
danielebarchiesi@4 465 protected function setProperty($name, $value) {
danielebarchiesi@4 466 $info = $this->getPropertyInfo($name);
danielebarchiesi@4 467 if (!empty($info['setter callback'])) {
danielebarchiesi@4 468 $data = $this->value();
danielebarchiesi@4 469
danielebarchiesi@4 470 // In case the data structure is not set, support simple auto-creation
danielebarchiesi@4 471 // for arrays. Else an exception is thrown.
danielebarchiesi@4 472 if (!isset($data)) {
danielebarchiesi@4 473 if (!empty($this->info['auto creation']) && !($this instanceof EntityDrupalWrapper)) {
danielebarchiesi@4 474 $data = $this->info['auto creation']($name, $this->info);
danielebarchiesi@4 475 }
danielebarchiesi@4 476 else {
danielebarchiesi@4 477 throw new EntityMetadataWrapperException('Unable to set the data property ' . check_plain($name) . ' as the parent data structure is not set.');
danielebarchiesi@4 478 }
danielebarchiesi@4 479 }
danielebarchiesi@4 480
danielebarchiesi@4 481 // Invoke the setter callback for updating our data.
danielebarchiesi@4 482 $info['setter callback']($data, $name, $value, $this->langcode, $this->type, $info);
danielebarchiesi@4 483
danielebarchiesi@4 484 // If the setter has not thrown any exceptions, proceed and apply the
danielebarchiesi@4 485 // update to the current and any parent wrappers as necessary.
danielebarchiesi@4 486 $data = $this->info['type'] == 'entity' ? $this : $data;
danielebarchiesi@4 487 $this->set($data);
danielebarchiesi@4 488
danielebarchiesi@4 489 // Clear the cache of properties dependent on this value.
danielebarchiesi@4 490 foreach ($info['clear'] as $name) {
danielebarchiesi@4 491 if (isset($this->cache[$name])) {
danielebarchiesi@4 492 $this->cache[$name]->clear();
danielebarchiesi@4 493 }
danielebarchiesi@4 494 }
danielebarchiesi@4 495 }
danielebarchiesi@4 496 else {
danielebarchiesi@4 497 throw new EntityMetadataWrapperException('Entity property ' . check_plain($name) . " doesn't support writing.");
danielebarchiesi@4 498 }
danielebarchiesi@4 499 }
danielebarchiesi@4 500
danielebarchiesi@4 501 protected function propertyAccess($name, $op, $account = NULL) {
danielebarchiesi@4 502 $info = $this->getPropertyInfo($name);
danielebarchiesi@4 503 // If the property should be accessed and it's an entity, make sure the user
danielebarchiesi@4 504 // is allowed to view that entity.
danielebarchiesi@4 505 if ($op == 'view' && $this->$name instanceof EntityDrupalWrapper && !$this->$name->entityAccess($op, $account)) {
danielebarchiesi@4 506 return FALSE;
danielebarchiesi@4 507 }
danielebarchiesi@4 508 // If a property should be edited and this is an entity, make sure the user
danielebarchiesi@4 509 // has update access for this entity.
danielebarchiesi@4 510 if ($op == 'edit') {
danielebarchiesi@4 511 $entity = $this;
danielebarchiesi@4 512 while (!($entity instanceof EntityDrupalWrapper) && isset($entity->info['parent'])) {
danielebarchiesi@4 513 $entity = $entity->info['parent'];
danielebarchiesi@4 514 }
danielebarchiesi@4 515 if ($entity instanceof EntityDrupalWrapper && !$entity->entityAccess('update', $account)) {
danielebarchiesi@4 516 return FALSE;
danielebarchiesi@4 517 }
danielebarchiesi@4 518 }
danielebarchiesi@4 519 if (!empty($info['access callback'])) {
danielebarchiesi@4 520 $data = $this->dataAvailable() ? $this->value() : NULL;
danielebarchiesi@4 521 return call_user_func($info['access callback'], $op, $name, $data, $account, $this->type);
danielebarchiesi@4 522 }
danielebarchiesi@4 523 elseif ($op == 'edit' && isset($info['setter permission'])) {
danielebarchiesi@4 524 return user_access($info['setter permission'], $account);
danielebarchiesi@4 525 }
danielebarchiesi@4 526 return TRUE;
danielebarchiesi@4 527 }
danielebarchiesi@4 528
danielebarchiesi@4 529 /**
danielebarchiesi@4 530 * Magic method: Can be used to check if a property is known.
danielebarchiesi@4 531 */
danielebarchiesi@4 532 public function __isset($name) {
danielebarchiesi@4 533 $this->spotInfo();
danielebarchiesi@4 534 return isset($this->propertyInfo['properties'][$name]);
danielebarchiesi@4 535 }
danielebarchiesi@4 536
danielebarchiesi@4 537 public function getIterator() {
danielebarchiesi@4 538 $this->spotInfo();
danielebarchiesi@4 539 return new EntityMetadataWrapperIterator($this, array_keys($this->propertyInfo['properties']));
danielebarchiesi@4 540 }
danielebarchiesi@4 541
danielebarchiesi@4 542 /**
danielebarchiesi@4 543 * Returns the identifier of the data structure. If there is none, NULL is
danielebarchiesi@4 544 * returned.
danielebarchiesi@4 545 */
danielebarchiesi@4 546 public function getIdentifier() {
danielebarchiesi@4 547 return isset($this->id) && $this->dataAvailable() ? $this->id->value() : NULL;
danielebarchiesi@4 548 }
danielebarchiesi@4 549
danielebarchiesi@4 550 /**
danielebarchiesi@4 551 * Prepare for serializiation.
danielebarchiesi@4 552 */
danielebarchiesi@4 553 public function __sleep() {
danielebarchiesi@4 554 $vars = parent::__sleep();
danielebarchiesi@4 555 unset($vars['propertyInfoDefaults']);
danielebarchiesi@4 556 return $vars;
danielebarchiesi@4 557 }
danielebarchiesi@4 558
danielebarchiesi@4 559 public function clear() {
danielebarchiesi@4 560 $this->propertyInfoAltered = FALSE;
danielebarchiesi@4 561 parent::clear();
danielebarchiesi@4 562 }
danielebarchiesi@4 563 }
danielebarchiesi@4 564
danielebarchiesi@4 565 /**
danielebarchiesi@4 566 * Provides a wrapper for entities registrered in hook_entity_info().
danielebarchiesi@4 567 *
danielebarchiesi@4 568 * The wrapper eases applying getter and setter callbacks of entity properties
danielebarchiesi@4 569 * specified in hook_entity_property_info().
danielebarchiesi@4 570 */
danielebarchiesi@4 571 class EntityDrupalWrapper extends EntityStructureWrapper {
danielebarchiesi@4 572
danielebarchiesi@4 573 /**
danielebarchiesi@4 574 * Contains the entity id.
danielebarchiesi@4 575 */
danielebarchiesi@4 576 protected $id = FALSE;
danielebarchiesi@4 577 protected $bundle;
danielebarchiesi@4 578 protected $entityInfo;
danielebarchiesi@4 579
danielebarchiesi@4 580 /**
danielebarchiesi@4 581 * Construct a new EntityDrupalWrapper object.
danielebarchiesi@4 582 *
danielebarchiesi@4 583 * @param $type
danielebarchiesi@4 584 * The type of the passed data.
danielebarchiesi@4 585 * @param $data
danielebarchiesi@4 586 * Optional. The entity to wrap or its identifier.
danielebarchiesi@4 587 * @param $info
danielebarchiesi@4 588 * Optional. Used internally to pass info about properties down the tree.
danielebarchiesi@4 589 */
danielebarchiesi@4 590 public function __construct($type, $data = NULL, $info = array()) {
danielebarchiesi@4 591 parent::__construct($type, $data, $info);
danielebarchiesi@4 592 $this->setUp();
danielebarchiesi@4 593 }
danielebarchiesi@4 594
danielebarchiesi@4 595 protected function setUp() {
danielebarchiesi@4 596 $this->propertyInfo = entity_get_property_info($this->type) + array('properties' => array());
danielebarchiesi@4 597 $info = $this->info + array('property info' => array(), 'bundle' => NULL);
danielebarchiesi@4 598 $this->propertyInfo['properties'] += $info['property info'];
danielebarchiesi@4 599 $this->bundle = $info['bundle'];
danielebarchiesi@4 600 $this->entityInfo = entity_get_info($this->type);
danielebarchiesi@4 601 if (isset($this->bundle)) {
danielebarchiesi@4 602 $this->spotBundleInfo(FALSE);
danielebarchiesi@4 603 }
danielebarchiesi@4 604 }
danielebarchiesi@4 605
danielebarchiesi@4 606 /**
danielebarchiesi@4 607 * Sets the entity internally accepting both the entity id and object.
danielebarchiesi@4 608 */
danielebarchiesi@4 609 protected function setEntity($data) {
danielebarchiesi@4 610 // For entities we allow getter callbacks to return FALSE, which we
danielebarchiesi@4 611 // interpret like NULL values as unset properties.
danielebarchiesi@4 612 if (isset($data) && $data !== FALSE && !is_object($data)) {
danielebarchiesi@4 613 $this->id = $data;
danielebarchiesi@4 614 $this->data = FALSE;
danielebarchiesi@4 615 }
danielebarchiesi@4 616 elseif (is_object($data) && $data instanceof EntityDrupalWrapper) {
danielebarchiesi@4 617 // We got a wrapped entity passed, so take over its values.
danielebarchiesi@4 618 $this->id = $data->id;
danielebarchiesi@4 619 $this->data = $data->data;
danielebarchiesi@4 620 // For generic entity references, also update the entity type accordingly.
danielebarchiesi@4 621 if ($this->info['type'] == 'entity') {
danielebarchiesi@4 622 $this->type = $data->type;
danielebarchiesi@4 623 }
danielebarchiesi@4 624 }
danielebarchiesi@4 625 elseif (is_object($data)) {
danielebarchiesi@4 626 // We got the entity object passed.
danielebarchiesi@4 627 $this->data = $data;
danielebarchiesi@4 628 $id = entity_id($this->type, $data);
danielebarchiesi@4 629 $this->id = isset($id) ? $id : FALSE;
danielebarchiesi@4 630 }
danielebarchiesi@4 631 else {
danielebarchiesi@4 632 $this->id = FALSE;
danielebarchiesi@4 633 $this->data = NULL;
danielebarchiesi@4 634 }
danielebarchiesi@4 635 }
danielebarchiesi@4 636
danielebarchiesi@4 637 /**
danielebarchiesi@4 638 * Used to lazy-load bundle info. So the wrapper can be loaded e.g. just
danielebarchiesi@4 639 * for setting without the data being loaded.
danielebarchiesi@4 640 */
danielebarchiesi@4 641 protected function spotInfo() {
danielebarchiesi@4 642 if (!$this->propertyInfoAltered) {
danielebarchiesi@4 643 if ($this->info['type'] == 'entity' && $this->dataAvailable() && $this->value()) {
danielebarchiesi@4 644 // Add in entity-type specific details.
danielebarchiesi@4 645 $this->setUp();
danielebarchiesi@4 646 }
danielebarchiesi@4 647 $this->spotBundleInfo(TRUE);
danielebarchiesi@4 648 parent::spotInfo();
danielebarchiesi@4 649 $this->propertyInfoAltered = TRUE;
danielebarchiesi@4 650 }
danielebarchiesi@4 651 }
danielebarchiesi@4 652
danielebarchiesi@4 653 /**
danielebarchiesi@4 654 * Tries to determine the bundle and adds in the according property info.
danielebarchiesi@4 655 *
danielebarchiesi@4 656 * @param $load
danielebarchiesi@4 657 * Whether the entity should be loaded to spot the info if necessary.
danielebarchiesi@4 658 */
danielebarchiesi@4 659 protected function spotBundleInfo($load = TRUE) {
danielebarchiesi@4 660 // Like entity_extract_ids() assume the entity type if no key is given.
danielebarchiesi@4 661 if (empty($this->entityInfo['entity keys']['bundle']) && $this->type != 'entity') {
danielebarchiesi@4 662 $this->bundle = $this->type;
danielebarchiesi@4 663 }
danielebarchiesi@4 664 // Detect the bundle if not set yet and add in properties from the bundle.
danielebarchiesi@4 665 elseif (!$this->bundle && $load && $this->dataAvailable()) {
danielebarchiesi@4 666 try {
danielebarchiesi@4 667 if ($entity = $this->value()) {
danielebarchiesi@4 668 list($id, $vid, $bundle) = entity_extract_ids($this->type, $entity);
danielebarchiesi@4 669 $this->bundle = $bundle;
danielebarchiesi@4 670 }
danielebarchiesi@4 671 }
danielebarchiesi@4 672 catch (EntityMetadataWrapperException $e) {
danielebarchiesi@4 673 // Loading data failed, so we cannot derive the used bundle.
danielebarchiesi@4 674 }
danielebarchiesi@4 675 }
danielebarchiesi@4 676
danielebarchiesi@4 677 if ($this->bundle && isset($this->propertyInfo['bundles'][$this->bundle])) {
danielebarchiesi@4 678 $bundle_info = (array) $this->propertyInfo['bundles'][$this->bundle] + array('properties' => array());
danielebarchiesi@4 679 // Allow bundles to re-define existing properties, such that the bundle
danielebarchiesi@4 680 // can add in more bundle-specific details like the bundle of a referenced
danielebarchiesi@4 681 // entity.
danielebarchiesi@4 682 $this->propertyInfo['properties'] = $bundle_info['properties'] + $this->propertyInfo['properties'];
danielebarchiesi@4 683 }
danielebarchiesi@4 684 }
danielebarchiesi@4 685
danielebarchiesi@4 686 /**
danielebarchiesi@4 687 * Returns the identifier of the wrapped entity.
danielebarchiesi@4 688 *
danielebarchiesi@4 689 * @see entity_id()
danielebarchiesi@4 690 */
danielebarchiesi@4 691 public function getIdentifier() {
danielebarchiesi@4 692 return $this->dataAvailable() ? $this->value(array('identifier' => TRUE)) : NULL;
danielebarchiesi@4 693 }
danielebarchiesi@4 694
danielebarchiesi@4 695 /**
danielebarchiesi@4 696 * Returns the bundle of an entity, or FALSE if it has no bundles.
danielebarchiesi@4 697 */
danielebarchiesi@4 698 public function getBundle() {
danielebarchiesi@4 699 if ($this->dataAvailable()) {
danielebarchiesi@4 700 $this->spotInfo();
danielebarchiesi@4 701 return $this->bundle;
danielebarchiesi@4 702 }
danielebarchiesi@4 703 }
danielebarchiesi@4 704
danielebarchiesi@4 705 /**
danielebarchiesi@4 706 * Overridden.
danielebarchiesi@4 707 *
danielebarchiesi@4 708 * @param $options
danielebarchiesi@4 709 * An array of options. Known keys:
danielebarchiesi@4 710 * - identifier: If set to TRUE, the entity identifier is returned.
danielebarchiesi@4 711 */
danielebarchiesi@4 712 public function value(array $options = array()) {
danielebarchiesi@4 713 // Try loading the data via the getter callback if there is none yet.
danielebarchiesi@4 714 if (!isset($this->data)) {
danielebarchiesi@4 715 $this->setEntity(parent::value());
danielebarchiesi@4 716 }
danielebarchiesi@4 717 if (!empty($options['identifier'])) {
danielebarchiesi@4 718 return $this->id;
danielebarchiesi@4 719 }
danielebarchiesi@4 720 elseif (!$this->data && !empty($this->id)) {
danielebarchiesi@4 721 // Lazy load the entity if necessary.
danielebarchiesi@4 722 $return = entity_load($this->type, array($this->id));
danielebarchiesi@4 723 // In case the entity cannot be loaded, we return NULL just as for empty
danielebarchiesi@4 724 // properties.
danielebarchiesi@4 725 $this->data = $return ? reset($return) : NULL;
danielebarchiesi@4 726 }
danielebarchiesi@4 727 return $this->data;
danielebarchiesi@4 728 }
danielebarchiesi@4 729
danielebarchiesi@4 730 /**
danielebarchiesi@4 731 * Returns the entity prepared for rendering.
danielebarchiesi@4 732 *
danielebarchiesi@4 733 * @see entity_view()
danielebarchiesi@4 734 */
danielebarchiesi@4 735 public function view($view_mode = 'full', $langcode = NULL, $page = NULL) {
danielebarchiesi@4 736 return entity_view($this->type(), array($this->value()), $view_mode, $langcode, $page);
danielebarchiesi@4 737 }
danielebarchiesi@4 738
danielebarchiesi@4 739 /**
danielebarchiesi@4 740 * Overridden to support setting the entity by either the object or the id.
danielebarchiesi@4 741 */
danielebarchiesi@4 742 public function set($value) {
danielebarchiesi@4 743 if (!$this->validate($value)) {
danielebarchiesi@4 744 throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
danielebarchiesi@4 745 }
danielebarchiesi@4 746 if ($this->info['type'] == 'entity' && $value === $this) {
danielebarchiesi@4 747 // Nothing to do.
danielebarchiesi@4 748 return $this;
danielebarchiesi@4 749 }
danielebarchiesi@4 750 $previous_id = $this->id;
danielebarchiesi@4 751 $previous_type = $this->type;
danielebarchiesi@4 752 // Set value, so we get the identifier and pass it to the normal setter.
danielebarchiesi@4 753 $this->clear();
danielebarchiesi@4 754 $this->setEntity($value);
danielebarchiesi@4 755 // Generally, we have to update the parent only if the entity reference
danielebarchiesi@4 756 // has changed. In case of a generic entity reference, we pass the entity
danielebarchiesi@4 757 // wrapped. Else we just pass the id of the entity to the setter callback.
danielebarchiesi@4 758 if ($this->info['type'] == 'entity' && ($previous_id != $this->id || $previous_type != $this->type)) {
danielebarchiesi@4 759 // We need to clone the wrapper we pass through as value, so it does not
danielebarchiesi@4 760 // get cleared when the current wrapper instance gets cleared.
danielebarchiesi@4 761 $this->updateParent(clone $this);
danielebarchiesi@4 762 }
danielebarchiesi@4 763 // In case the entity has been unset, we cannot properly detect changes as
danielebarchiesi@4 764 // the previous id defaults to FALSE for unloaded entities too. So in that
danielebarchiesi@4 765 // case we just always update the parent.
danielebarchiesi@4 766 elseif ($this->id === FALSE && !$this->data) {
danielebarchiesi@4 767 $this->updateParent(NULL);
danielebarchiesi@4 768 }
danielebarchiesi@4 769 elseif ($previous_id != $this->id) {
danielebarchiesi@4 770 $this->updateParent($this->id);
danielebarchiesi@4 771 }
danielebarchiesi@4 772 return $this;
danielebarchiesi@4 773 }
danielebarchiesi@4 774
danielebarchiesi@4 775 /**
danielebarchiesi@4 776 * Overridden.
danielebarchiesi@4 777 */
danielebarchiesi@4 778 public function clear() {
danielebarchiesi@4 779 $this->id = NULL;
danielebarchiesi@4 780 $this->bundle = isset($this->info['bundle']) ? $this->info['bundle'] : NULL;
danielebarchiesi@4 781 if ($this->type != $this->info['type']) {
danielebarchiesi@4 782 // Reset entity info / property info based upon the info provided during
danielebarchiesi@4 783 // the creation of the wrapper.
danielebarchiesi@4 784 $this->type = $this->info['type'];
danielebarchiesi@4 785 $this->setUp();
danielebarchiesi@4 786 }
danielebarchiesi@4 787 parent::clear();
danielebarchiesi@4 788 }
danielebarchiesi@4 789
danielebarchiesi@4 790 /**
danielebarchiesi@4 791 * Overridden.
danielebarchiesi@4 792 */
danielebarchiesi@4 793 public function type() {
danielebarchiesi@4 794 // In case of a generic entity wrapper, load the data first to determine
danielebarchiesi@4 795 // the type of the concrete entity.
danielebarchiesi@4 796 if ($this->dataAvailable() && $this->info['type'] == 'entity') {
danielebarchiesi@4 797 try {
danielebarchiesi@4 798 $this->value(array('identifier' => TRUE));
danielebarchiesi@4 799 }
danielebarchiesi@4 800 catch (EntityMetadataWrapperException $e) {
danielebarchiesi@4 801 // If loading data fails, we cannot determine the concrete entity type.
danielebarchiesi@4 802 }
danielebarchiesi@4 803 }
danielebarchiesi@4 804 return $this->type;
danielebarchiesi@4 805 }
danielebarchiesi@4 806
danielebarchiesi@4 807 /**
danielebarchiesi@4 808 * Checks whether the operation $op is allowed on the entity.
danielebarchiesi@4 809 *
danielebarchiesi@4 810 * @see entity_access()
danielebarchiesi@4 811 */
danielebarchiesi@4 812 public function entityAccess($op, $account = NULL) {
danielebarchiesi@4 813 $entity = $this->dataAvailable() ? $this->value() : NULL;
danielebarchiesi@4 814 return entity_access($op, $this->type, $entity, $account);
danielebarchiesi@4 815 }
danielebarchiesi@4 816
danielebarchiesi@4 817 /**
danielebarchiesi@4 818 * Permanently save the wrapped entity.
danielebarchiesi@4 819 *
danielebarchiesi@4 820 * @throws EntityMetadataWrapperException
danielebarchiesi@4 821 * If the entity type does not support saving.
danielebarchiesi@4 822 *
danielebarchiesi@4 823 * @return EntityDrupalWrapper
danielebarchiesi@4 824 */
danielebarchiesi@4 825 public function save() {
danielebarchiesi@4 826 if ($this->data) {
danielebarchiesi@4 827 if (!entity_type_supports($this->type, 'save')) {
danielebarchiesi@4 828 throw new EntityMetadataWrapperException("There is no information about how to save entities of type " . check_plain($this->type) . '.');
danielebarchiesi@4 829 }
danielebarchiesi@4 830 entity_save($this->type, $this->data);
danielebarchiesi@4 831 // On insert, update the identifier afterwards.
danielebarchiesi@4 832 if (!$this->id) {
danielebarchiesi@4 833 list($this->id, , ) = entity_extract_ids($this->type, $this->data);
danielebarchiesi@4 834 }
danielebarchiesi@4 835 }
danielebarchiesi@4 836 // If the entity hasn't been loaded yet, don't bother saving it.
danielebarchiesi@4 837 return $this;
danielebarchiesi@4 838 }
danielebarchiesi@4 839
danielebarchiesi@4 840 /**
danielebarchiesi@4 841 * Permanently delete the wrapped entity.
danielebarchiesi@4 842 *
danielebarchiesi@4 843 * @return EntityDrupalWrapper
danielebarchiesi@4 844 */
danielebarchiesi@4 845 public function delete() {
danielebarchiesi@4 846 if ($this->dataAvailable() && $this->value()) {
danielebarchiesi@4 847 $return = entity_delete($this->type, $this->id);
danielebarchiesi@4 848 if ($return === FALSE) {
danielebarchiesi@4 849 throw new EntityMetadataWrapperException("There is no information about how to delete entities of type " . check_plain($this->type) . '.');
danielebarchiesi@4 850 }
danielebarchiesi@4 851 }
danielebarchiesi@4 852 return $this;
danielebarchiesi@4 853 }
danielebarchiesi@4 854
danielebarchiesi@4 855 /**
danielebarchiesi@4 856 * Gets the info about the wrapped entity.
danielebarchiesi@4 857 */
danielebarchiesi@4 858 public function entityInfo() {
danielebarchiesi@4 859 return $this->entityInfo;
danielebarchiesi@4 860 }
danielebarchiesi@4 861
danielebarchiesi@4 862 /**
danielebarchiesi@4 863 * Returns the name of the key used by the entity for given entity key.
danielebarchiesi@4 864 *
danielebarchiesi@4 865 * @param $name
danielebarchiesi@4 866 * One of 'id', 'name', 'bundle' or 'revision'.
danielebarchiesi@4 867 * @return
danielebarchiesi@4 868 * The name of the key used by the entity.
danielebarchiesi@4 869 */
danielebarchiesi@4 870 public function entityKey($name) {
danielebarchiesi@4 871 return isset($this->entityInfo['entity keys'][$name]) ? $this->entityInfo['entity keys'][$name] : FALSE;
danielebarchiesi@4 872 }
danielebarchiesi@4 873
danielebarchiesi@4 874 /**
danielebarchiesi@4 875 * Returns the entity label.
danielebarchiesi@4 876 *
danielebarchiesi@4 877 * @see entity_label()
danielebarchiesi@4 878 */
danielebarchiesi@4 879 public function label() {
danielebarchiesi@4 880 if ($entity = $this->value()) {
danielebarchiesi@4 881 return entity_label($this->type, $entity);
danielebarchiesi@4 882 }
danielebarchiesi@4 883 }
danielebarchiesi@4 884
danielebarchiesi@4 885 /**
danielebarchiesi@4 886 * Prepare for serializiation.
danielebarchiesi@4 887 */
danielebarchiesi@4 888 public function __sleep() {
danielebarchiesi@4 889 $vars = parent::__sleep();
danielebarchiesi@4 890 // Don't serialize the loaded entity and its property info.
danielebarchiesi@4 891 unset($vars['data'], $vars['propertyInfo'], $vars['propertyInfoAltered'], $vars['entityInfo']);
danielebarchiesi@4 892 // In case the entity is not saved yet, serialize the unsaved data.
danielebarchiesi@4 893 if ($this->dataAvailable() && $this->id === FALSE) {
danielebarchiesi@4 894 $vars['data'] = 'data';
danielebarchiesi@4 895 }
danielebarchiesi@4 896 return $vars;
danielebarchiesi@4 897 }
danielebarchiesi@4 898
danielebarchiesi@4 899 public function __wakeup() {
danielebarchiesi@4 900 $this->setUp();
danielebarchiesi@4 901 if ($this->id !== FALSE) {
danielebarchiesi@4 902 // Make sure data is set, so the entity will be loaded when needed.
danielebarchiesi@4 903 $this->data = FALSE;
danielebarchiesi@4 904 }
danielebarchiesi@4 905 }
danielebarchiesi@4 906 }
danielebarchiesi@4 907
danielebarchiesi@4 908 /**
danielebarchiesi@4 909 * Wraps a list of values.
danielebarchiesi@4 910 *
danielebarchiesi@4 911 * If the wrapped data is a list of data, its numerical indexes may be used to
danielebarchiesi@4 912 * retrieve wrappers for the list items. For that this wrapper implements
danielebarchiesi@4 913 * ArrayAccess so it may be used like a usual numerically indexed array.
danielebarchiesi@4 914 */
danielebarchiesi@4 915 class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggregate, ArrayAccess, Countable {
danielebarchiesi@4 916
danielebarchiesi@4 917 /**
danielebarchiesi@4 918 * The type of contained items.
danielebarchiesi@4 919 */
danielebarchiesi@4 920 protected $itemType;
danielebarchiesi@4 921
danielebarchiesi@4 922 /**
danielebarchiesi@4 923 * Whether this is a list of entities with a known entity type, i.e. for
danielebarchiesi@4 924 * generic list of entities (list<entity>) this is FALSE.
danielebarchiesi@4 925 */
danielebarchiesi@4 926 protected $isEntityList;
danielebarchiesi@4 927
danielebarchiesi@4 928
danielebarchiesi@4 929 public function __construct($type, $data = NULL, $info = array()) {
danielebarchiesi@4 930 parent::__construct($type, NULL, $info);
danielebarchiesi@4 931
danielebarchiesi@4 932 $this->itemType = entity_property_list_extract_type($this->type);
danielebarchiesi@4 933 if (!$this->itemType) {
danielebarchiesi@4 934 $this->itemType = 'unknown';
danielebarchiesi@4 935 }
danielebarchiesi@4 936 $this->isEntityList = (bool) entity_get_info($this->itemType);
danielebarchiesi@4 937
danielebarchiesi@4 938 if (isset($data)) {
danielebarchiesi@4 939 $this->set($data);
danielebarchiesi@4 940 }
danielebarchiesi@4 941 }
danielebarchiesi@4 942
danielebarchiesi@4 943 /**
danielebarchiesi@4 944 * Get the wrapper for a single item.
danielebarchiesi@4 945 *
danielebarchiesi@4 946 * @return
danielebarchiesi@4 947 * An instance of EntityMetadataWrapper.
danielebarchiesi@4 948 */
danielebarchiesi@4 949 public function get($delta) {
danielebarchiesi@4 950 // Look it up in the cache if possible.
danielebarchiesi@4 951 if (!array_key_exists($delta, $this->cache)) {
danielebarchiesi@4 952 if (!isset($delta)) {
danielebarchiesi@4 953 // The [] operator has been used so point at a new entry.
danielebarchiesi@4 954 $values = parent::value();
danielebarchiesi@4 955 $delta = $values ? max(array_keys($values)) + 1 : 0;
danielebarchiesi@4 956 }
danielebarchiesi@4 957 if (is_numeric($delta)) {
danielebarchiesi@4 958 $info = array('parent' => $this, 'name' => $delta) + $this->info;
danielebarchiesi@4 959 $this->cache[$delta] = entity_metadata_wrapper($this->itemType, NULL, $info);
danielebarchiesi@4 960 }
danielebarchiesi@4 961 else {
danielebarchiesi@4 962 throw new EntityMetadataWrapperException('There can be only numerical keyed items in a list.');
danielebarchiesi@4 963 }
danielebarchiesi@4 964 }
danielebarchiesi@4 965 return $this->cache[$delta];
danielebarchiesi@4 966 }
danielebarchiesi@4 967
danielebarchiesi@4 968 protected function getPropertyValue($delta) {
danielebarchiesi@4 969 // Make use parent::value() to easily by-pass any entity-loading.
danielebarchiesi@4 970 $data = parent::value();
danielebarchiesi@4 971 if (isset($data[$delta])) {
danielebarchiesi@4 972 return $data[$delta];
danielebarchiesi@4 973 }
danielebarchiesi@4 974 }
danielebarchiesi@4 975
danielebarchiesi@4 976 protected function getPropertyRaw($delta) {
danielebarchiesi@4 977 return $this->getPropertyValue($delta);
danielebarchiesi@4 978 }
danielebarchiesi@4 979
danielebarchiesi@4 980 protected function setProperty($delta, $value) {
danielebarchiesi@4 981 $data = parent::value();
danielebarchiesi@4 982 if (is_numeric($delta)) {
danielebarchiesi@4 983 $data[$delta] = $value;
danielebarchiesi@4 984 $this->set($data);
danielebarchiesi@4 985 }
danielebarchiesi@4 986 }
danielebarchiesi@4 987
danielebarchiesi@4 988 protected function propertyAccess($delta, $op, $account = NULL) {
danielebarchiesi@4 989 return $this->access($op, $account);
danielebarchiesi@4 990 }
danielebarchiesi@4 991
danielebarchiesi@4 992 /**
danielebarchiesi@4 993 * Returns the list as numerically indexed array.
danielebarchiesi@4 994 *
danielebarchiesi@4 995 * Note that a list of entities might contain stale entity references. In
danielebarchiesi@4 996 * that case the wrapper and the identifier of a stale reference would be
danielebarchiesi@4 997 * still accessible, however the entity object value would be NULL. That way,
danielebarchiesi@4 998 * there may be NULL values in lists of entity objects due to stale entity
danielebarchiesi@4 999 * references.
danielebarchiesi@4 1000 *
danielebarchiesi@4 1001 * @param $options
danielebarchiesi@4 1002 * An array of options. Known keys:
danielebarchiesi@4 1003 * - identifier: If set to TRUE for a list of entities, it won't be returned
danielebarchiesi@4 1004 * as list of fully loaded entity objects, but as a list of entity ids.
danielebarchiesi@4 1005 * Note that this list may contain ids of stale entity references.
danielebarchiesi@4 1006 */
danielebarchiesi@4 1007 public function value(array $options = array()) {
danielebarchiesi@4 1008 // For lists of entities fetch full entity objects before returning.
danielebarchiesi@4 1009 // Generic entity-wrappers need to be handled separately though.
danielebarchiesi@4 1010 if ($this->isEntityList && empty($options['identifier']) && $this->dataAvailable()) {
danielebarchiesi@4 1011 $list = parent::value();
danielebarchiesi@4 1012 $entities = $list ? entity_load($this->get(0)->type, $list) : array();
danielebarchiesi@4 1013 // Make sure to keep the array keys as present in the list.
danielebarchiesi@4 1014 foreach ($list as $key => $id) {
danielebarchiesi@4 1015 // In case the entity cannot be loaded, we return NULL just as for empty
danielebarchiesi@4 1016 // properties.
danielebarchiesi@4 1017 $list[$key] = isset($entities[$id]) ? $entities[$id] : NULL;
danielebarchiesi@4 1018 }
danielebarchiesi@4 1019 return $list;
danielebarchiesi@4 1020 }
danielebarchiesi@4 1021 return parent::value();
danielebarchiesi@4 1022 }
danielebarchiesi@4 1023
danielebarchiesi@4 1024 public function set($values) {
danielebarchiesi@4 1025 // Support setting lists of fully loaded entities.
danielebarchiesi@4 1026 if ($this->isEntityList && $values && is_object(reset($values))) {
danielebarchiesi@4 1027 foreach ($values as $key => $value) {
danielebarchiesi@4 1028 list($id, $vid, $bundle) = entity_extract_ids($this->itemType, $value);
danielebarchiesi@4 1029 $values[$key] = $id;
danielebarchiesi@4 1030 }
danielebarchiesi@4 1031 }
danielebarchiesi@4 1032 return parent::set($values);
danielebarchiesi@4 1033 }
danielebarchiesi@4 1034
danielebarchiesi@4 1035 /**
danielebarchiesi@4 1036 * If we wrap a list, we return an iterator over the data list.
danielebarchiesi@4 1037 */
danielebarchiesi@4 1038 public function getIterator() {
danielebarchiesi@4 1039 // In case there is no data available, just iterate over the first item.
danielebarchiesi@4 1040 return new EntityMetadataWrapperIterator($this, $this->dataAvailable() ? array_keys(parent::value()) : array(0));
danielebarchiesi@4 1041 }
danielebarchiesi@4 1042
danielebarchiesi@4 1043 /**
danielebarchiesi@4 1044 * Implements the ArrayAccess interface.
danielebarchiesi@4 1045 */
danielebarchiesi@4 1046 public function offsetGet($delta) {
danielebarchiesi@4 1047 return $this->get($delta);
danielebarchiesi@4 1048 }
danielebarchiesi@4 1049
danielebarchiesi@4 1050 public function offsetExists($delta) {
danielebarchiesi@4 1051 return $this->dataAvailable() && ($data = $this->value()) && array_key_exists($delta, $data);
danielebarchiesi@4 1052 }
danielebarchiesi@4 1053
danielebarchiesi@4 1054 public function offsetSet($delta, $value) {
danielebarchiesi@4 1055 $this->get($delta)->set($value);
danielebarchiesi@4 1056 }
danielebarchiesi@4 1057
danielebarchiesi@4 1058 public function offsetUnset($delta) {
danielebarchiesi@4 1059 if ($this->offsetExists($delta)) {
danielebarchiesi@4 1060 unset($this->data[$delta]);
danielebarchiesi@4 1061 $this->set($this->data);
danielebarchiesi@4 1062 }
danielebarchiesi@4 1063 }
danielebarchiesi@4 1064
danielebarchiesi@4 1065 public function count() {
danielebarchiesi@4 1066 return $this->dataAvailable() ? count($this->value()) : 0;
danielebarchiesi@4 1067 }
danielebarchiesi@4 1068
danielebarchiesi@4 1069 /**
danielebarchiesi@4 1070 * Overridden.
danielebarchiesi@4 1071 */
danielebarchiesi@4 1072 public function validate($value) {
danielebarchiesi@4 1073 // Required lists may not be empty or unset.
danielebarchiesi@4 1074 if (!empty($this->info['required']) && empty($value)) {
danielebarchiesi@4 1075 return FALSE;
danielebarchiesi@4 1076 }
danielebarchiesi@4 1077 return parent::validate($value);
danielebarchiesi@4 1078 }
danielebarchiesi@4 1079
danielebarchiesi@4 1080 /**
danielebarchiesi@4 1081 * Returns the label for the list of set values if available.
danielebarchiesi@4 1082 */
danielebarchiesi@4 1083 public function label() {
danielebarchiesi@4 1084 if ($options = $this->optionsList('view')) {
danielebarchiesi@4 1085 $options = entity_property_options_flatten($options);
danielebarchiesi@4 1086 $labels = array_intersect_key($options, array_flip((array) parent::value()));
danielebarchiesi@4 1087 }
danielebarchiesi@4 1088 else {
danielebarchiesi@4 1089 // Get each label on its own, e.g. to support getting labels of a list
danielebarchiesi@4 1090 // of entities.
danielebarchiesi@4 1091 $labels = array();
danielebarchiesi@4 1092 foreach ($this as $key => $property) {
danielebarchiesi@4 1093 $label = $property->label();
danielebarchiesi@4 1094 if (!$label) {
danielebarchiesi@4 1095 return NULL;
danielebarchiesi@4 1096 }
danielebarchiesi@4 1097 $labels[] = $label;
danielebarchiesi@4 1098 }
danielebarchiesi@4 1099 }
danielebarchiesi@4 1100 return isset($labels) ? implode(', ', $labels) : NULL;
danielebarchiesi@4 1101 }
danielebarchiesi@4 1102 }
danielebarchiesi@4 1103
danielebarchiesi@4 1104 /**
danielebarchiesi@4 1105 * Provide a separate Exception so it can be caught separately.
danielebarchiesi@4 1106 */
danielebarchiesi@4 1107 class EntityMetadataWrapperException extends Exception { }
danielebarchiesi@4 1108
danielebarchiesi@4 1109
danielebarchiesi@4 1110 /**
danielebarchiesi@4 1111 * Allows to easily iterate over existing child wrappers.
danielebarchiesi@4 1112 */
danielebarchiesi@4 1113 class EntityMetadataWrapperIterator implements RecursiveIterator {
danielebarchiesi@4 1114
danielebarchiesi@4 1115 protected $position = 0;
danielebarchiesi@4 1116 protected $wrapper, $keys;
danielebarchiesi@4 1117
danielebarchiesi@4 1118 public function __construct(EntityMetadataWrapper $wrapper, array $keys) {
danielebarchiesi@4 1119 $this->wrapper = $wrapper;
danielebarchiesi@4 1120 $this->keys = $keys;
danielebarchiesi@4 1121 }
danielebarchiesi@4 1122
danielebarchiesi@4 1123 function rewind() {
danielebarchiesi@4 1124 $this->position = 0;
danielebarchiesi@4 1125 }
danielebarchiesi@4 1126
danielebarchiesi@4 1127 function current() {
danielebarchiesi@4 1128 return $this->wrapper->get($this->keys[$this->position]);
danielebarchiesi@4 1129 }
danielebarchiesi@4 1130
danielebarchiesi@4 1131 function key() {
danielebarchiesi@4 1132 return $this->keys[$this->position];
danielebarchiesi@4 1133 }
danielebarchiesi@4 1134
danielebarchiesi@4 1135 function next() {
danielebarchiesi@4 1136 $this->position++;
danielebarchiesi@4 1137 }
danielebarchiesi@4 1138
danielebarchiesi@4 1139 function valid() {
danielebarchiesi@4 1140 return isset($this->keys[$this->position]);
danielebarchiesi@4 1141 }
danielebarchiesi@4 1142
danielebarchiesi@4 1143 public function hasChildren() {
danielebarchiesi@4 1144 return $this->current() instanceof IteratorAggregate;
danielebarchiesi@4 1145 }
danielebarchiesi@4 1146
danielebarchiesi@4 1147 public function getChildren() {
danielebarchiesi@4 1148 return $this->current()->getIterator();
danielebarchiesi@4 1149 }
danielebarchiesi@4 1150 }
danielebarchiesi@4 1151
danielebarchiesi@4 1152 /**
danielebarchiesi@4 1153 * An array object implementation keeping the reference on the given array so
danielebarchiesi@4 1154 * changes to the object are reflected in the passed array.
danielebarchiesi@4 1155 */
danielebarchiesi@4 1156 class EntityMetadataArrayObject implements ArrayAccess, Countable, IteratorAggregate {
danielebarchiesi@4 1157
danielebarchiesi@4 1158 protected $data;
danielebarchiesi@4 1159
danielebarchiesi@4 1160 public function __construct(&$array) {
danielebarchiesi@4 1161 $this->data =& $array;
danielebarchiesi@4 1162 }
danielebarchiesi@4 1163
danielebarchiesi@4 1164 public function &getArray() {
danielebarchiesi@4 1165 return $this->data;
danielebarchiesi@4 1166 }
danielebarchiesi@4 1167
danielebarchiesi@4 1168 /**
danielebarchiesi@4 1169 * Implements the ArrayAccess interface.
danielebarchiesi@4 1170 */
danielebarchiesi@4 1171 public function offsetGet($delta) {
danielebarchiesi@4 1172 return $this->data[$delta];
danielebarchiesi@4 1173 }
danielebarchiesi@4 1174
danielebarchiesi@4 1175 public function offsetExists($delta) {
danielebarchiesi@4 1176 return array_key_exists($delta, $this->data);
danielebarchiesi@4 1177 }
danielebarchiesi@4 1178
danielebarchiesi@4 1179 public function offsetSet($delta, $value) {
danielebarchiesi@4 1180 $this->data[$delta] = $value;
danielebarchiesi@4 1181 }
danielebarchiesi@4 1182
danielebarchiesi@4 1183 public function offsetUnset($delta) {
danielebarchiesi@4 1184 unset($this->data[$delta]);
danielebarchiesi@4 1185 }
danielebarchiesi@4 1186
danielebarchiesi@4 1187 public function count() {
danielebarchiesi@4 1188 return count($this->data);
danielebarchiesi@4 1189 }
danielebarchiesi@4 1190
danielebarchiesi@4 1191 public function getIterator() {
danielebarchiesi@4 1192 return new ArrayIterator($this->data);
danielebarchiesi@4 1193 }
danielebarchiesi@4 1194 }