entity properties From: fago --- includes/properties.inc | 127 ++++++++++++++++++++++++++++++++++++++ modules/node/node.info | 1 modules/node/node.properties.inc | 121 ++++++++++++++++++++++++++++++++++++ modules/system/system.test | 42 +++++++++++++ modules/user/user.info | 1 modules/user/user.properties.inc | 67 ++++++++++++++++++++ 6 files changed, 359 insertions(+), 0 deletions(-) create mode 100644 includes/properties.inc create mode 100644 modules/node/node.properties.inc create mode 100644 modules/user/user.properties.inc diff --git a/includes/properties.inc b/includes/properties.inc new file mode 100644 index 0000000..41f8d71 --- /dev/null +++ b/includes/properties.inc @@ -0,0 +1,127 @@ +entityType = $entityType; + $this->entity = $entity; + $this->info = entity_get_info($entityType) + array('properties' => array()); + $this->options = $options + array('sanitize' => FALSE, 'language' => NULL); + } + + /** + * Gets the info about the given property. + * + * @param $name + * The name of the property. + * @throws DrupalEntityPropertyWrapperException + * If there is no such property. + * @return + * An array of info about the property. + */ + public function getPropertyInfo($name) { + if (!isset($this->info['properties'][$name])) { + throw new DrupalEntityPropertyWrapperException('Unknown entity property '. check_plain($name). '.'); + } + return $this->info['properties'][$name] + array( + 'type' => 'string', + 'sanitize' => 'check_plain', + ); + } + + /** + * Magic method: Get a property. + */ + public function __get($name) { + $info = $this->getPropertyInfo($name); + if (!empty($info['getter callback']) && drupal_function_exists($info['getter callback'])) { + $value = $info['getter callback']($this->entity, $this->options, $name, $this->entityType); + unset($info['sanitize']); + } + elseif (is_array($this->entity)) { + $value = isset($this->entity[$name]) ? $this->entity[$name] : NULL; + } + elseif (is_object($this->entity)) { + $value = isset($this->entity->$name) ? $this->entity->$name : NULL; + } + else { + return NULL; + } + // Return another wrapper if the property is another entity. + if (in_array($info['type'], array_keys(entity_get_info()))) { + return new DrupalEntityPropertyWrapper($info['type'], $value, $this->options); + } + elseif (!empty($this->options['sanitize']) && !empty($info['sanitize']) && function_exists($info['sanitize'])) { + return $info['sanitize']($value); + } + return $value; + } + + /** + * Magic method: Set a property. + */ + public function __set($name, $value) { + $info = $this->getPropertyInfo($name); + if (!empty($info['setter callback']) && drupal_function_exists($info['setter callback'])) { + $info['setter callback']($this->entity, $name, $value, $this->entityType); + } + elseif (!isset($info['setter callback']) && is_array($this->entity)) { + $this->entity[$name] = $value; + } + elseif (!isset($info['setter callback']) && is_object($this->entity)) { + $this->entity->$name = $value; + } + throw new DrupalEntityPropertyWrapperException('Entity property '. check_plain($name). " doesn't support writing."); + } + + /** + * Magic method: isset() can be used to check if a property is known. + */ + public function __isset($name) { + return isset($this->info['properties'][$name]); + } + + /** + * Get the entity wrapped by this object. + */ + public function getEntity() { + return $this->entity; + } + +} + +/** + * Provide a separate Exception so it can be caught separately. + */ +class DrupalEntityPropertyWrapperException extends Exception { + +} diff --git a/modules/node/node.info b/modules/node/node.info index 1a6f91c..7675125 100644 --- a/modules/node/node.info +++ b/modules/node/node.info @@ -10,4 +10,5 @@ files[] = node.admin.inc files[] = node.pages.inc files[] = node.install files[] = node.test +files[] = node.properties.inc required = TRUE diff --git a/modules/node/node.properties.inc b/modules/node/node.properties.inc new file mode 100644 index 0000000..f04c933 --- /dev/null +++ b/modules/node/node.properties.inc @@ -0,0 +1,121 @@ + t("Node ID"), + 'type' => 'integer', + 'description' => t("The unique ID of the node."), + ); + $node['vid'] = array( + 'name' => t("Revision ID"), + 'type' => 'integer', + 'description' => t("The unique ID of the node's latest revision."), + ); + $node['tnid'] = array( + 'name' => t("Translation set ID"), + 'type' => 'integer', + 'description' => t("The unique ID of the original-language version of this node, if one exists."), + ); + $node['uid'] = array( + 'name' => t("User ID"), + 'type' => 'integer', + 'description' => t("The unique ID of the user who posted the node."), + ); + $node['type'] = array( + 'name' => t("Content type"), + 'description' => t("The type of the node."), + ); + $node['typeName'] = array( + 'name' => t("Content type name"), + 'description' => t("The human-readable name of the node type."), + 'getter callback' => 'node_get_properties', + ); + $node['title'] = array( + 'name' => t("Title"), + 'description' => t("The title of the node."), + ); + $node['body'] = array( + 'name' => t("Body"), + 'description' => t("The main body text of the node."), + 'getter callback' => 'field_property_get', + ); + $node['summary'] = array( + 'name' => t("Summary"), + 'description' => t("The summary of the node's main body text."), + 'getter callback' => 'field_property_get', + ); + $node['language'] = array( + 'name' => t("Language"), + 'description' => t("The language the node is written in."), + ); + $node['url'] = array( + 'name' => t("URL"), + 'description' => t("The URL of the node."), + 'getter callback' => 'node_get_properties', + ); + $node['editUrl'] = array( + 'name' => t("Edit URL"), + 'description' => t("The URL of the node's edit page."), + 'getter callback' => 'node_get_properties', + ); + $node['created'] = array( + 'name' => t("Date created"), + 'type' => 'date', + 'description' => t("The date the node was posted."), + ); + $node['changed'] = array( + 'name' => t("Date changed"), + 'type' => 'date', + 'description' => t("The date the node was most recently updated."), + ); + $node['authorName'] = array( + 'name' => t("Author name"), + 'description' => t("The node author's name."), + 'getter callback' => 'node_get_properties', + ); + $node['author'] = array( + 'name' => t("Author"), + 'type' => 'user', + 'description' => t("The author of the node."), + 'getter callback' => 'node_get_properties', + ); +} + +function field_property_get($object, array $options, $name, $obj_type) { + return $options['sanitize'] ? $object->$name[0]['safe'] : $object->$name[0]['value']; +} + +function node_get_properties($node, array $options, $name, $entity_type) { + + switch ($name) { + case 'typeName': + $type_name = node_type_get_name($node->type); + return $options['sanitize'] ? check_plain($type_name) : $type_name; + + case 'url': + return url('node/' . $node->nid, $options + array('absolute' => TRUE)); + + case 'editUrl': + return url('node/' . $node->nid . '/edit', $options + array('absolute' => TRUE)); + + case 'authorName': + $name = ($node->uid == 0) ? variable_get('anonymous', t('Anonymous')) : $node->name; + return $options['sanitize'] ? filter_xss($name) : $name; + + case 'author': + return user_load($node->uid); + } +} + + diff --git a/modules/system/system.test b/modules/system/system.test index 1aa1e86..ff48eb1 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -1058,3 +1058,45 @@ class QueueTestCase extends DrupalWebTestCase { return $score; } } + +/** + * Test token replacement in strings. + */ +class DrupalEntityPropertyWrapperTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Entity properties', + 'description' => 'Tests using the drupal entity property wrappers.', + 'group' => 'System', + ); + } + + /** + * Creates a user and a node, then tests the tokens generated from them. + */ + function testEntityPropertyWrapper() { + $account = $this->drupalCreateUser(); + $node = $this->drupalCreateNode(array('uid' => $account->uid, 'title' => 'Is it bold?')); + // For testing sanitizing give the user a malicious user name + $account = user_save($account, array('name' => 'BadName')); + + // Fetch the object so every properties are set as usual. + $node = node_load($node->nid); + + // First test without sanitizing. + $wrapper = new DrupalEntityPropertyWrapper('node', $node); + + $this->assertEqual($node->title, $wrapper->title, 'Getting property.'); + $this->assertEqual($node->name, $wrapper->authorName, 'Getting property with getter callback.'); + + // Test sanitized output. + $wrapper = new DrupalEntityPropertyWrapper('node', $node, array('sanitize' => TRUE)); + + $this->assertEqual(check_plain($node->title), $wrapper->title, 'Getting sanitized property.'); + $this->assertEqual(filter_xss($node->name), $wrapper->authorName, 'Getting sanitized property with getter callback.'); + + // Test chaining + $this->assertEqual(check_plain($account->mail), $wrapper->author->mail, 'Testing chained usage.'); + $this->assertEqual(filter_xss($account->name), $wrapper->author->name, 'Testing chained usage with callback and sanitizing.'); + } +} diff --git a/modules/user/user.info b/modules/user/user.info index e0066a7..04f049e 100644 --- a/modules/user/user.info +++ b/modules/user/user.info @@ -9,4 +9,5 @@ files[] = user.admin.inc files[] = user.pages.inc files[] = user.install files[] = user.test +files[] = user.properties.inc required = TRUE diff --git a/modules/user/user.properties.inc b/modules/user/user.properties.inc new file mode 100644 index 0000000..a3ccbc7 --- /dev/null +++ b/modules/user/user.properties.inc @@ -0,0 +1,67 @@ + t('User ID'), + 'type' => 'integer', + 'description' => t("The unique ID of the user account."), + ); + $user['name'] = array( + 'name' => t("Name"), + 'description' => t("The login name of the user account."), + 'getter callback' => 'user_get_properties', + ); + $user['mail'] = array( + 'name' => t("Email"), + 'description' => t("The email address of the user account."), + ); + $user['url'] = array( + 'name' => t("URL"), + 'description' => t("The URL of the account profile page."), + 'getter callback' => 'user_get_properties', + ); + $user['editUrl'] = array( + 'name' => t("Edit URL"), + 'description' => t("The url of the account edit page."), + 'getter callback' => 'user_get_properties', + ); + $user['login'] = array( + 'name' => t("Last login"), + 'description' => t("The date the user last logged in to the site."), + 'type' => 'date', + ); + $user['created'] = array( + 'name' => t("Created"), + 'description' => t("The date the user account was created."), + 'type' => 'date', + ); +} + +/** + * Implement hook_tokens(). + */ +function user_get_properties($account, array $options, $name, $entity_type) { + + switch ($name) { + case 'name': + $name = ($account->uid == 0) ? variable_get('anonymous', t('Anonymous')) : $account->name; + return $options['sanitize'] ? filter_xss($name) : $name; + + case 'url': + return url("user/$account->uid", $options + array('absolute' => TRUE)); + + case 'editUrl': + return url("user/$account->uid/edit", $options + array('absolute' => TRUE)); + } +}