From: <> --- includes/form.inc | 8 + modules/field/field.module | 2 modules/node/node.module | 4 - modules/node/node.tpl.php | 9 + modules/rdf/rdf.api.php | 57 +++++++ modules/rdf/rdf.entity.inc | 71 +++++++++ modules/rdf/rdf.info | 8 + modules/rdf/rdf.module | 220 ++++++++++++++++++++++++++++ modules/rdf/rdf.test | 161 ++++++++++++++++++++ modules/simpletest/tests/field_test.module | 1 modules/system/page.tpl.php | 13 +- modules/system/system.module | 2 themes/garland/node.tpl.php | 2 themes/garland/page.tpl.php | 4 - themes/seven/page.tpl.php | 4 - 15 files changed, 551 insertions(+), 15 deletions(-) create mode 100644 modules/rdf/rdf.api.php create mode 100644 modules/rdf/rdf.entity.inc create mode 100644 modules/rdf/rdf.info create mode 100644 modules/rdf/rdf.module create mode 100644 modules/rdf/rdf.test diff --git includes/form.inc includes/form.inc index d788007..16ff092 100644 --- includes/form.inc +++ includes/form.inc @@ -2500,7 +2500,7 @@ function theme_textarea($element) { * * @param $element * An associative array containing the properties of the element. - * Properties used: #markup, #children. + * Properties used: #markup, #attributes, #children. * @return * A themed HTML string representing the HTML markup. * @@ -2508,7 +2508,11 @@ function theme_textarea($element) { */ function theme_markup($element) { - return (!empty($element['#markup']) ? $element['#markup'] : '') . drupal_render_children($element); + $output = (!empty($element['#markup']) ? $element['#markup'] : '') . drupal_render_children($element); + if (!empty($element['#attributes'])) { + $output = '' . $output . ''; + } + return $output; } /** diff --git modules/field/field.module modules/field/field.module index 7f30f2a..a3433f7 100644 --- modules/field/field.module +++ modules/field/field.module @@ -727,6 +727,8 @@ function template_preprocess_field(&$variables) { $additions = array( 'object' => $element['#object'], + 'object_type' => $element['#object_type'], + 'bundle' => $bundle, 'field' => $field, 'instance' => $instance, 'build_mode' => $element['#build_mode'], diff --git modules/node/node.module modules/node/node.module index 4bcecb3..6864171 100644 --- modules/node/node.module +++ modules/node/node.module @@ -1112,8 +1112,8 @@ function template_preprocess_node(&$variables) { $variables['node'] = $variables['elements']['#node']; $node = $variables['node']; - $variables['date'] = format_date($node->created); - $variables['name'] = theme('username', $node); + $variables['date'] = array('#markup' => format_date($node->created)); + $variables['name'] = array('#markup' => theme('username', $node)); $variables['node_url'] = url('node/' . $node->nid); $variables['title'] = check_plain($node->title); $variables['page'] = (bool)menu_get_object(); diff --git modules/node/node.tpl.php modules/node/node.tpl.php index af2b0c5..8a7ea3e 100644 --- modules/node/node.tpl.php +++ modules/node/node.tpl.php @@ -12,9 +12,10 @@ * hide($content['field_example']) to temporarily suppress the printing of a * given element. * - $user_picture: The node author's picture from user-picture.tpl.php. - * - $date: Formatted creation date (use $created to reformat with - * format_date()). - * - $name: Themed username of node author output from theme_username(). + * - $date: An array containing the formatted creation date (use $created to + * reformat with format_date()). Use render($date) to print it. + * - $name: An array containing themed username of node author output from + * theme_username(). Use render($name) to print it. * - $node_url: Direct url of the current node. * - $terms: the themed list of taxonomy term links output from theme_links(). * - $display_submitted: whether submission information should be displayed. @@ -84,7 +85,7 @@ $name, '@datetime' => $date)); + array('!username' => render($name), '@datetime' => render($date))); ?> diff --git modules/rdf/rdf.api.php modules/rdf/rdf.api.php new file mode 100644 index 0000000..b9cd938 --- /dev/null +++ modules/rdf/rdf.api.php @@ -0,0 +1,57 @@ + array( + 'rdf-type' => array('sioc:Post'), + 'properties' => array( + 'created' => array( + 'label' => t('Date created.'), + // Specify multiple properties using an array. + 'rdf-property' => 'dc:created', + + 'rdf-datatype' => 'xsd:dateTime', + 'rdf-value' => 'iso8601', + ), + ), + ), + ); +} + +/** + * @} End of "addtogroup hooks". + */ diff --git modules/rdf/rdf.entity.inc modules/rdf/rdf.entity.inc new file mode 100644 index 0000000..43db8ad --- /dev/null +++ modules/rdf/rdf.entity.inc @@ -0,0 +1,71 @@ + t("Current page"), + 'rdf-type' => array('foaf:Document'), + ); + + $properties = &$types['page']['properties']; + + $properties['title'] = array( + 'rdf-property' => array('dc:title'), + 'getter callback' => 'drupal_get_title', + ); + $properties['rdf-uri'] = array( + 'label' => t("RDF URI"), + 'description' => t("The RDF URI of the current page."), + 'getter callback' => '_rdf_get_page_uri', + ); + return $types; +} + +/** + * Implement hook_entity_info_alter(). + */ +function rdf_entity_info_alter(&$entity_info) { + + $entity_info['user']['properties']['rdf-uri'] = array( + 'label' => t("RDF URI"), + 'description' => t("The RDF URI of the user."), + 'getter callback' => '_rdf_generate_user_uri', + ); + $entity_info['user']['rdf-type'][] = 'sioc:User'; + + $properties = &$entity_info['user']['properties']; + $properties['name']['rdf-property'] = 'foaf:name'; + + + // Add in node related rdf mappings. + $entity_info['node']['rdf-type'] = array('sioc:Item', 'foaf:Document'); + $properties = &$entity_info['node']['properties']; + + $properties['rdf-uri'] = array( + 'label' => t("RDF URI"), + 'description' => t("The RDF URI of the node."), + 'getter callback' => '_rdf_generate_node_uri', + ); + + $properties['title']['rdf-property'] = 'dc:title'; + $properties['created']['rdf-property'] = 'dc:created'; + $properties['changed']['rdf-property'] = 'dc:modified'; + $properties['author']['rdf-property'] = 'sioc:has_creator'; + $properties['author-name']['rdf-property'] = 'foaf:name'; + + foreach ($entity_info['node']['bundles'] as $bundle => &$info) { + if (!empty($info['properties']['body'])) { + $info['properties']['body']['rdf-property'] = 'content:encoded'; + } + } +} diff --git modules/rdf/rdf.info modules/rdf/rdf.info new file mode 100644 index 0000000..04a3582 --- /dev/null +++ modules/rdf/rdf.info @@ -0,0 +1,8 @@ +; $Id$ +name = RDF +description = Supports RDF mappings. +package = Core +core = 7.x +files[] = rdf.module +files[] = rdf.entity.inc +files[] = rdf.test diff --git modules/rdf/rdf.module modules/rdf/rdf.module new file mode 100644 index 0000000..f7ae6f3 --- /dev/null +++ modules/rdf/rdf.module @@ -0,0 +1,220 @@ + $info) { + $page['properties'][$name] = $info + $page['properties'][$name]; + } + // Add in the rdf-type if given. + if (isset($rdf_type)) { + $page['rdf-type'] = $rdf_type; + } +} + + +/** + * Returns RDFa attributes to render. + * + * @param $wrapper + * A DrupalPropertyEntityWrapper of the rendered entity. + * @param $property + * The name of the property to get attributes for. If set to NULL, the 'about' + * option defaults to TRUE, so RDFa about the entity is added in. + * @param $options + * (optional) An array of options, supported are: + * - 'about': If set to TRUE, add in RDFa about the entity. + * - 'resource': When refering to another entity, set it to TRUE to also add + * the resource attribute. + * @return array + * An array containing RDFa attributes ready for rendering. + */ +function rdf_attributes(DrupalPropertyEntityWrapper $wrapper, $property = NULL, array $options = array()) { + $attributes = array(); + $options += array('about' => !isset($property), 'resource' => FALSE); + + if ($options['about'] && isset($wrapper->{'rdf-uri'})) { + // Generate attributes for the whole entity. + $attributes['about'] = $wrapper->{'rdf-uri'}; + $info = $wrapper->getInfo() + array('rdf-type' => array()); + $attributes['typeof'] = implode(' ', (array)$info['rdf-type']); + $attributes = array_filter($attributes); + } + + // Add in property specific attributes. + if (isset($property) && ($info = $wrapper->getPropertyInfo($property)) && !empty($info['rdf-property'])) { + $entity_info = entity_get_info(); + // When referring to an entity, we have to use the 'rel' attribute. + $name = isset($info['type']) && isset($entity_info[$info['type']]) ? 'rel' : 'property'; + $attributes[$name] = implode(' ', (array)$info['rdf-property']); + + if ($name == 'rel' && $options['resource'] && isset($wrapper->$property->{'rdf-uri'})) { + $attributes['resource'] = $wrapper->$property->{'rdf-uri'}; + } + + if (!empty($info['rdf-value']) && isset($wrapper->$property->{$info['rdf-value']})) { + $attributes['content'] = $wrapper->$property->{$info['rdf-value']}; + } + if (isset($info['rdf-datatype'])) { + $attributes['datatype'] = $info['rdf-datatype']; + } + } + return $attributes; +} + + +/** + * Helper function to create a URI for the user object. + */ +function _rdf_generate_user_uri($user) { + return base_path() . 'user/' . $user->uid . '#this'; +} + +/** + * Helper function to create a URI for the node object. + */ +function _rdf_generate_node_uri($node) { + return url('node/' . $node->nid) . '#this'; +} + +/** + * Implement hook_property_info(). + */ +function rdf_property_info() { + // Add the iso8601 date format and use it for RDF. + $types['date'] = array(); + $types['date']['rdf-value'] = 'iso8601'; + $types['date']['rdf-datatype'] = 'xsd:dateTime'; + $types['date']['formats']['iso8601'] = array( + 'label' => t("ISO8601 formatted"), + 'description' => t("A date in ISO8601 format. (%date)", array('%date' => rdf_date_iso8601(REQUEST_TIME))), + 'callback' => 'rdf_date_iso8601', + ); + return $types; +} + +/** + * Returns an ISO8601 formatted date based on the given date. + * + * Can be used as a callback for mappings. + * + * @param $date + * A date in a format parseable by strtotime. + * @return string + * An ISO8601 formatted date. + */ +function rdf_date_iso8601($date) { + return date(DATE_ISO8601, strtotime($date)); +} + +/** + * Implement hook_node_view(). + */ +function rdf_node_view($node, $build_mode) { + if ($build_mode == 'full' && menu_get_object() == $node) { + // Full page view, so use some node-properties for the page. + $node_info = entity_get_info('node'); + rdf_alter_page_mapping(array('title' => $node_info['properties']['title']), $node_info['rdf-type']); + } +} + +/** + * Implement MODULE_preprocess_HOOK(). + */ +function rdf_preprocess_node(&$variables) { + $wrapper = drupal_get_property_wrapper('node', $variables['node']); + $variables['node_uri'] = $wrapper->{'rdf-uri'}; + + if (!$variables['page']) { + // Add about and typeof attributes when a node is displayed in teaser mode. + $variables['attributes_array'] += rdf_attributes($wrapper); + } + + $variables['title_attributes_array'] += rdf_attributes($wrapper, 'title'); + $variables['date'] += array('#attributes' => array()); + $variables['date']['#attributes'] += rdf_attributes($wrapper, 'created'); + $variables['name'] += array('#attributes' => array()); + $variables['name']['#attributes'] += rdf_attributes($wrapper, 'author'); +} + +/** + * Implement hook_page_build(). + */ +function rdf_page_build(&$page) { + // Set the default rdf-uri for the current page. + $page['#uri'] = '#this'; +} + +/** + * Helper function to retrieve the RDF URI of a page. + */ +function _rdf_get_page_uri($page) { + return $page['#uri']; +} + +/** + * Implement MODULE_preprocess_HOOK(). + */ +function rdf_preprocess_page(&$variables) { + $wrapper = drupal_get_property_wrapper('page', $variables['page']); + + // HTML attributes which define what resource a page is about and optionally + // its RDF type. + $variables['attributes_array'] += rdf_attributes($wrapper); + // Set the property for the title of the page. + $variables['title_attributes_array'] += rdf_attributes($wrapper, 'title'); +} + +/** + * Adds RDFa attributes to the template variables. + * + * @param $variables + */ +function rdf_preprocess_field(&$variables) { + $wrapper = drupal_get_property_wrapper($variables['object_type'], $variables['object']); + $variables['attributes_array'] += rdf_attributes($wrapper, $variables['field_name']); +} + +/** + * Implement hook_user_view(). + */ +function rdf_user_view($account) { + if (menu_get_object('user_uid_optional') == $account) { + // We are viewing the account, so use the account-properties for the page. + $user_info = entity_get_info('user'); + rdf_alter_page_mapping(array('title' => $user_info['properties']['name']), $user_info['rdf-type']); + } +} + +/** + * Implements MODULE_preprocess_HOOK(). + */ +function rdf_preprocess_username(&$variables) { + $wrapper = drupal_get_property_wrapper('user', $variables['object']->account); + $variables['object']->attributes += rdf_attributes($wrapper, 'name', array('about' => TRUE)); +} diff --git modules/rdf/rdf.test modules/rdf/rdf.test new file mode 100644 index 0000000..1eb2b35 --- /dev/null +++ modules/rdf/rdf.test @@ -0,0 +1,161 @@ + t('RDFa markup'), + 'description' => t('Test RDF markup generation.'), + 'group' => t('RDF'), + ); + } + + function setUp() { + // Enable RDF in the test environment. + parent::setUp('rdf'); + } + + /** + * Test rdf_attributes(). + */ + function testDrupalRdfaAtributes() { + + $node = $this->drupalCreateNode(); + $wrapper = drupal_get_property_wrapper('node', $node); + $attributes = rdf_attributes($wrapper, 'created'); + + // Test getting attributes for the node created date. + $this->assertEqual('xsd:dateTime', $attributes['datatype'], 'Datatype returned.'); + $this->assertEqual('dc:created', $attributes['property'], 'Property returned.'); + $this->assertEqual(rdf_date_iso8601($node->created), $attributes['content'], 'Content returned.'); + + // Test getting attributes for the node title. + $attributes = rdf_attributes($wrapper, 'title'); + $this->assertEqual(array('property' => 'dc:title'), $attributes, 'Property returned.'); + } +} + +class RDFaTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => t('Test RDFa output'), + 'description' => t('Ensure that RDFa is output on pages.'), + 'group' => t('RDF'), + ); + } + + function setUp() { + parent::setUp('rdf'); + } + + function testUserRdfa() { + // Create a user with access to view user profiles. + $user = $this->drupalCreateUser(array('access user profiles')); + // Create a user profile to view. + $account = $this->drupalCreateUser(); + + $this->drupalLogin($user); + $this->drupalGet('user/' . $account->uid); + + $result = $this->xpath('//*[contains(@about, "#this") and contains(@typeof, "sioc:User")]'); + $this->assertFalse(empty($result), t('Found a typeof attribute including sioc:User')); + + $result = $this->xpath('//h2[contains(@property, "foaf:name")]'); + $this->assertFalse(empty($result), t('Found a h2 tag with property foaf:name')); + } + + function testNodeRdfa() { + $user = $this->drupalCreateUser(array('access content')); + // Create a node to view. + $node = $this->drupalCreateNode(); + + $this->drupalLogin($user); + $this->drupalGet('node/' . $node->nid); + + $result = $this->xpath('//*[contains(@about, "#this") and contains(@typeof, "foaf:Document")]'); + $this->assertFalse(empty($result), t('Found a typeof attribute including foaf:Document')); + + $result = $this->xpath('//h2[contains(@property, "dc:title")]'); + $this->assertFalse(empty($result), t('Found a h2 tag with property dc:title')); + } + + +} + + + +class RdfaFieldTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => t('RDFa field markup'), + 'description' => t('Test RDFa markup in fields.'), + 'group' => t('RDF'), + ); + } + + function setUp() { + parent::setUp('field_test', 'rdf'); + + $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); + $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4); + $this->field = field_create_field($this->field); + $this->field_id = $this->field['id']; + $this->instance = array( + 'field_name' => $this->field_name, + 'bundle' => 'test_bundle', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array( + 'test_instance_setting' => $this->randomName(), + ), + 'widget' => array( + 'type' => 'test_field_widget', + 'label' => 'Test Field', + 'settings' => array( + 'test_widget_setting' => $this->randomName(), + ) + ), + 'property info' => array( + 'rdf-property' => array('dc:created'), + ), + ); + field_create_instance($this->instance); + } + + function testAttributesInMarkup() { + $entity_type = 'test_entity'; + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $langcode = FIELD_LANGUAGE_NONE; + + // Populate values to be displayed. + $date = '2009-09-05'; + $values = array(0 => array('value' => $date)); + $entity->{$this->field_name}[$langcode] = $values; + + // Simple formatter, label displayed. + $formatter_setting = $this->randomName(); + $this->field['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'field_test_default', + 'settings' => array( + 'test_formatter_setting' => $formatter_setting, + ) + ), + ); + + field_update_instance($this->instance); + $this->content = drupal_render(field_attach_view($entity_type, $entity)); + $this->assertPattern('/property="dc:created"/'); + $this->assertPattern('/datatype="xsd:dateTime"/'); + $date_iso8601 = preg_quote(date(DATE_ISO8601, strtotime($date))); + $this->assertPattern("/content=\"$date_iso8601\"/"); + } + +} diff --git modules/simpletest/tests/field_test.module modules/simpletest/tests/field_test.module index f41c74c..ac937a7 100644 --- modules/simpletest/tests/field_test.module +++ modules/simpletest/tests/field_test.module @@ -358,6 +358,7 @@ function field_test_field_info() { ), 'default_widget' => 'test_field_widget', 'default_formatter' => 'field_test_default', + 'property_type' => 'date', ), ); } diff --git modules/system/page.tpl.php modules/system/page.tpl.php index ec14756..a855b9a 100644 --- modules/system/page.tpl.php +++ modules/system/page.tpl.php @@ -15,6 +15,12 @@ * or themes/garland. * - $classes_array: Array of html class attribute values. It is flattened * into a string within the variable $classes. + * - $attributes_array: Array of additional attributes. It is flattended into a + * string within the variable $attributes. + * - $title_attributes_array: Array of additional attributes for the title of + * the page. It is flattended into a string within the variable + * $title_attributes. + * string within the variables $attributes. * - $is_front: TRUE if the current page is the front page. Used to toggle the mission statement. * - $logged_in: TRUE if the user is registered and signed in. * - $is_admin: TRUE if the user has permission to access administration pages. @@ -51,6 +57,9 @@ * - no-sidebars: When no sidebar content exists. * - one-sidebar and sidebar-first or sidebar-second: A combination of the two classes * when only one of the two sidebars have content. + * - $attributes: String of additional attributes referring to the whole page. + * - $title_attributes: String of additional attributes referring to the page + * title. * * Site identity: * - $front_page: The URL of the front page. Use this instead of $base_path, @@ -168,9 +177,9 @@
-
+
>
-

+

>

diff --git modules/system/system.module modules/system/system.module index a0cc0ac..6e1ebac 100644 --- modules/system/system.module +++ modules/system/system.module @@ -257,7 +257,9 @@ function system_rdf_namespaces() { 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#', 'rss' => 'http://purl.org/rss/1.0/', + 'tags' => 'http://www.holygoat.co.uk/owl/redwood/0.1/tags/', 'sioc' => 'http://rdfs.org/sioc/ns#', + 'skos' => 'http://www.w3.org/2004/02/skos/core#', 'xsd' => 'http://www.w3.org/2001/XMLSchema', ); } diff --git themes/garland/node.tpl.php themes/garland/node.tpl.php index 1939a66..2fc39b9 100644 --- themes/garland/node.tpl.php +++ themes/garland/node.tpl.php @@ -10,7 +10,7 @@ - +
diff --git themes/garland/page.tpl.php themes/garland/page.tpl.php index 6d2803d..07852ce 100644 --- themes/garland/page.tpl.php +++ themes/garland/page.tpl.php @@ -20,7 +20,7 @@
-
+
>