security update for contrib modules

This commit is contained in:
2019-04-23 16:27:46 +02:00
parent 3c1b4f164e
commit 868629cbb2
729 changed files with 41254 additions and 20866 deletions
View File
@@ -19,9 +19,13 @@ function _entityreference_devel_generate($object, $field, $instance, $bundle) {
// Get all the entity that are referencable here.
$referencable_entity = entityreference_get_selection_handler($field, $instance)->getReferencableEntities();
if (is_array($referencable_entity) && !empty($referencable_entity)) {
// Get a random key.
foreach ($referencable_entity as $type => $eids) {
$object_field['target_id'] = array_rand($eids);
// $referencable_entity is keyed by bundle type.
$random_bundle = array_rand($referencable_entity);
if (!empty($random_bundle)) {
$target_id = array_rand($referencable_entity[$random_bundle]);
if (!empty($referencable_entity[$random_bundle][$target_id])) {
$object_field['target_id'] = $target_id;
}
}
}
return $object_field;
@@ -125,10 +125,15 @@ function entityreference_feeds_set_target($source, $entity, $target, $value) {
break;
case 'label':
$options = $handler->getReferencableEntities($value, '=');
$options = reset($options);
$etids = array_keys($options);
// Use the first matching entity.
$entity_id = reset($etids);
if ($options) {
$options = reset($options);
$etids = array_keys($options);
// Use the first matching entity.
$entity_id = reset($etids);
}
else {
$entity_id = NULL;
}
break;
}
/*
@@ -1,18 +1,23 @@
name = Entity Reference
description = Provides a field that can reference other entities.
core = 7.x
package = Fields
core = 7.x
dependencies[] = entity
dependencies[] = ctools
test_dependencies[] = feeds
test_dependencies[] = views
; Migrate handler.
files[] = entityreference.migrate.inc
; Our plugins interfaces and abstract implementations.
; Plugins interfaces and abstract implementations.
files[] = plugins/selection/abstract.inc
files[] = plugins/selection/views.inc
files[] = plugins/behavior/abstract.inc
; Views integration.
files[] = views/entityreference_plugin_display.inc
files[] = views/entityreference_plugin_style.inc
files[] = views/entityreference_plugin_row_fields.inc
@@ -22,10 +27,11 @@ files[] = tests/entityreference.handlers.test
files[] = tests/entityreference.taxonomy.test
files[] = tests/entityreference.admin.test
files[] = tests/entityreference.feeds.test
files[] = tests/entityreference.entity_translation.test
; Information added by packaging script on 2013-11-20
version = "7.x-1.1"
; Information added by Drupal.org packaging script on 2017-08-16
version = "7.x-1.5"
core = "7.x"
project = "entityreference"
datestamp = "1384973110"
datestamp = "1502895850"
@@ -41,6 +41,7 @@ function entityreference_field_schema($field) {
}
// Invoke the behaviors to allow them to change the schema.
module_load_include('module', 'entityreference');
foreach (entityreference_get_behavior_handlers($field) as $handler) {
$handler->schema_alter($schema, $field);
}
@@ -161,4 +162,29 @@ function entityreference_update_7002() {
'not null' => TRUE,
));
}
}
}
/**
* Implements hook_update_N().
*
* Remove duplicate rows in the taxonomy_index table.
*/
function entityreference_update_7100() {
if (db_table_exists('taxonomy_index')) {
if (db_table_exists('taxonomy_index_tmp')) {
db_drop_table('taxonomy_index_tmp');
}
$tx_schema = drupal_get_schema('taxonomy_index');
db_create_table('taxonomy_index_tmp', $tx_schema);
$select = db_select('taxonomy_index', 'tx');
$select->fields('tx', array('nid', 'tid'));
$select->groupBy('tx.nid');
$select->groupBy('tx.tid');
$select->addExpression('MAX(sticky)', 'sticky');
$select->addExpression('MAX(created)', 'created');
db_insert('taxonomy_index_tmp')->from($select)->execute();
db_drop_table('taxonomy_index');
db_rename_table('taxonomy_index_tmp', 'taxonomy_index');
}
}
@@ -1,12 +1,11 @@
<?php
/**
* @file
* Support for processing entity reference fields in Migrate.
*/
/**
* Implement hook_migrate_api().
* Implements hook_migrate_api().
*/
function entityreference_migrate_api() {
return array(
@@ -15,7 +14,14 @@ function entityreference_migrate_api() {
);
}
/**
* Extended class for handling entityreference fields.
*/
class MigrateEntityReferenceFieldHandler extends MigrateSimpleFieldHandler {
/**
* Constructor.
*/
public function __construct() {
parent::__construct(array(
'value_key' => 'target_id',
@@ -1,5 +1,12 @@
<?php
define('ENTITYREFERENCE_DENIED', '- Restricted access -');
/**
* @file
* Entityreference primary module file.
*/
/**
* Implements hook_ctools_plugin_directory().
*/
@@ -87,6 +94,20 @@ function entityreference_flush_caches() {
variable_set('entityreference:base-tables', $base_tables);
}
/**
* Implements hook_theme().
*/
function entityreference_theme($existing, $type, $theme, $path) {
return array(
'entityreference_label' => array(
'variables' => array('label' => NULL, 'item' => NULL, 'settings' => NULL, 'uri' => NULL),
),
'entityreference_entity_id' => array(
'variables' => array('item' => NULL, 'settings' => NULL),
),
);
}
/**
* Implements hook_menu().
*/
@@ -163,7 +184,7 @@ function entityreference_get_behavior_handlers($field, $instance = NULL) {
/**
* Get the behavior handler for a given entityreference field and instance.
*
* @param $handler
* @param $behavior
* The behavior handler name.
*/
function _entityreference_get_behavior_handler($behavior) {
@@ -220,13 +241,15 @@ function entityreference_field_validate($entity_type, $entity, $field, $instance
if ($ids) {
$valid_ids = entityreference_get_selection_handler($field, $instance, $entity_type, $entity)->validateReferencableEntities(array_keys($ids));
$invalid_entities = array_diff_key($ids, array_flip($valid_ids));
if ($invalid_entities) {
foreach ($invalid_entities as $id => $delta) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'entityreference_invalid_entity',
'message' => t('The referenced entity (@type: @id) is invalid.', array('@type' => $field['settings']['target_type'], '@id' => $id)),
);
if (!empty($valid_ids)) {
$invalid_entities = array_diff_key($ids, array_flip($valid_ids));
if ($invalid_entities) {
foreach ($invalid_entities as $id => $delta) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'entityreference_invalid_entity',
'message' => t('The referenced entity (@type: @id) is invalid.', array('@type' => $field['settings']['target_type'], '@id' => $id)),
);
}
}
}
}
@@ -398,6 +421,9 @@ function entityreference_field_settings_form($field, $instance, $has_data) {
return $form;
}
/**
* Callback for custom element processing.
*/
function _entityreference_field_settings_process($form, $form_state) {
$field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field'];
$instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance'];
@@ -479,11 +505,17 @@ function _entityreference_field_settings_process($form, $form_state) {
return $form;
}
/**
* Custom callback for ajax processing.
*/
function _entityreference_field_settings_ajax_process($form, $form_state) {
_entityreference_field_settings_ajax_process_element($form, $form);
return $form;
}
/**
* Helper function for custom ajax processing.
*/
function _entityreference_field_settings_ajax_process_element(&$element, $main_form) {
if (isset($element['#ajax']) && $element['#ajax'] === TRUE) {
$element['#ajax'] = array(
@@ -498,6 +530,9 @@ function _entityreference_field_settings_ajax_process_element(&$element, $main_f
}
}
/**
* Custom callback for element processing.
*/
function _entityreference_form_process_merge_parent($element) {
$parents = $element['#parents'];
array_pop($parents);
@@ -505,11 +540,17 @@ function _entityreference_form_process_merge_parent($element) {
return $element;
}
/**
* Helper function to remove blank elements.
*/
function _entityreference_element_validate_filter(&$element, &$form_state) {
$element['#value'] = array_filter($element['#value']);
form_set_value($element, $element['#value'], $form_state);
}
/**
* Implements hook_validate().
*/
function _entityreference_field_settings_validate($form, &$form_state) {
// Store the new values in the form state.
$field = $form['#field'];
@@ -545,6 +586,9 @@ function entityreference_field_instance_settings_form($field, $instance) {
return $form;
}
/**
* Implements hook_field_settings_form().
*/
function _entityreference_field_instance_settings_form($form, $form_state) {
$field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field'];
$instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance'];
@@ -562,6 +606,9 @@ function _entityreference_field_instance_settings_form($form, $form_state) {
return $form;
}
/**
* Implements hook_validate().
*/
function _entityreference_field_instance_settings_validate($form, &$form_state) {
// Store the new values in the form state.
$instance = $form['#instance'];
@@ -793,7 +840,7 @@ function entityreference_query_entityreference_alter(QueryAlterableInterface $qu
function entityreference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
// Ensure that the entity target type exists before displaying the widget.
$entity_info = entity_get_info($field['settings']['target_type']);
if (empty($entity_info)){
if (empty($entity_info)) {
return;
}
$entity_type = $instance['entity_type'];
@@ -818,7 +865,9 @@ function entityreference_field_widget_form(&$form, &$form_state, $field, $instan
// Build an array of entities ID.
foreach ($items as $item) {
$entity_ids[] = $item['target_id'];
if (isset($item['target_id'])) {
$entity_ids[] = $item['target_id'];
}
}
// Load those entities and loop through them to extract their labels.
@@ -879,6 +928,9 @@ function entityreference_field_widget_form(&$form, &$form_state, $field, $instan
}
}
/**
* Implements hook_validate().
*/
function _entityreference_autocomplete_validate($element, &$form_state, $form) {
// If a value was entered into the autocomplete...
$value = '';
@@ -903,6 +955,9 @@ function _entityreference_autocomplete_validate($element, &$form_state, $form) {
form_set_value($element, $value, $form_state);
}
/**
* Implements hook_validate().
*/
function _entityreference_autocomplete_tags_validate($element, &$form_state, $form) {
$value = array();
// If a value was entered into the autocomplete...
@@ -949,7 +1004,8 @@ function entityreference_field_widget_error($element, $error) {
* The entity type.
* @param $bundle_name
* The bundle name.
* @return
*
* @return bool
* True if user can access this menu item.
*/
function entityreference_autocomplete_access_callback($type, $field_name, $entity_type, $bundle_name) {
@@ -981,10 +1037,11 @@ function entityreference_autocomplete_access_callback($type, $field_name, $entit
*/
function entityreference_autocomplete_callback($type, $field_name, $entity_type, $bundle_name, $entity_id = '', $string = '') {
// If the request has a '/' in the search text, then the menu system will have
// split it into multiple arguments and $string will only be a partial. We want
// to make sure we recover the intended $string.
// split it into multiple arguments and $string will only be a partial.
// We want to make sure we recover the intended $string.
$args = func_get_args();
// Shift off the $type, $field_name, $entity_type, $bundle_name, and $entity_id args.
// Shift off the $type, $field_name, $entity_type,
// $bundle_name, and $entity_id args.
array_shift($args);
array_shift($args);
array_shift($args);
@@ -1020,6 +1077,7 @@ function entityreference_autocomplete_callback($type, $field_name, $entity_type,
*/
function entityreference_autocomplete_callback_get_matches($type, $field, $instance, $entity_type, $entity_id = '', $string = '') {
$matches = array();
$prefix = '';
$entity = NULL;
if ($entity_id !== 'NULL') {
@@ -1034,7 +1092,8 @@ function entityreference_autocomplete_callback_get_matches($type, $field, $insta
$handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity);
if ($type == 'tags') {
// The user enters a comma-separated list of tags. We only autocomplete the last tag.
// The user enters a comma-separated list of tags.
// We only autocomplete the last tag.
$tags_typed = drupal_explode_tags($string);
$tag_last = drupal_strtolower(array_pop($tags_typed));
if (!empty($tag_last)) {
@@ -1043,19 +1102,22 @@ function entityreference_autocomplete_callback_get_matches($type, $field, $insta
}
else {
// The user enters a single tag.
$prefix = '';
$tag_last = $string;
}
if (isset($tag_last)) {
// Get an array of matching entities.
$entity_labels = $handler->getReferencableEntities($tag_last, $instance['widget']['settings']['match_operator'], 10);
$denied_label = t(ENTITYREFERENCE_DENIED);
// Loop through the products and convert them into autocomplete output.
foreach ($entity_labels as $values) {
foreach ($values as $entity_id => $label) {
// Never autocomplete entities that aren't accessible.
if ($label == $denied_label) {
continue;
}
$key = "$label ($entity_id)";
// Strip things like starting/trailing white spaces, line breaks and tags.
// Strip starting/trailing white spaces, line breaks and tags.
$key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($key)))));
// Names containing commas or quotes must be wrapped in quotes.
if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
@@ -1069,6 +1131,32 @@ function entityreference_autocomplete_callback_get_matches($type, $field, $insta
drupal_json_output($matches);
}
/**
* Introspects field and instance settings, and determines the correct settings
* for the functioning of the formatter.
*
* Settings:
* - entity_type - The entity_type being loaded.
* - column - The name of the ref. field column that stores the entity id.
*/
function entityreference_field_type_settings($field) {
$settings = array(
'entity_type' => NULL,
'column' => NULL,
);
if ($field['type'] == 'entityreference') {
$settings['entity_type'] = $field['settings']['target_type'];
$settings['column'] = 'target_id';
}
elseif ($field['type'] == 'taxonomy_term_reference') {
$settings['entity_type'] = 'taxonomy_term';
$settings['column'] = 'tid';
}
return $settings;
}
/**
* Implements hook_field_formatter_info().
*/
@@ -1080,6 +1168,7 @@ function entityreference_field_formatter_info() {
'field types' => array('entityreference'),
'settings' => array(
'link' => FALSE,
'bypass_access' => FALSE,
),
),
'entityreference_entity_id' => array(
@@ -1090,10 +1179,11 @@ function entityreference_field_formatter_info() {
'entityreference_entity_view' => array(
'label' => t('Rendered entity'),
'description' => t('Display the referenced entities rendered by entity_view().'),
'field types' => array('entityreference'),
'field types' => array('entityreference', 'taxonomy_term_reference'),
'settings' => array(
'view_mode' => 'default',
'links' => TRUE,
'use_content_language' => TRUE,
),
),
);
@@ -1105,8 +1195,17 @@ function entityreference_field_formatter_info() {
function entityreference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$field_type_settings = entityreference_field_type_settings($field);
$element = array();
if ($display['type'] == 'entityreference_label') {
$element['bypass_access'] = array(
'#title' => t('Show entity labels regardless of user access'),
'#description' => t("All entities in the field will be shown, without checking them for access. If the 'Link' setting is also enabled, an entity which the user does not have access to view will show without a link."),
'#type' => 'checkbox',
'#default_value' => $settings['bypass_access'],
);
$element['link'] = array(
'#title' => t('Link label to the referenced entity'),
'#type' => 'checkbox',
@@ -1115,7 +1214,7 @@ function entityreference_field_formatter_settings_form($field, $instance, $view_
}
if ($display['type'] == 'entityreference_entity_view') {
$entity_info = entity_get_info($field['settings']['target_type']);
$entity_info = entity_get_info($field_type_settings['entity_type']);
$options = array('default' => t('Default'));
if (!empty($entity_info['view modes'])) {
foreach ($entity_info['view modes'] as $view_mode => $view_mode_settings) {
@@ -1136,6 +1235,12 @@ function entityreference_field_formatter_settings_form($field, $instance, $view_
'#title' => t('Show links'),
'#default_value' => $settings['links'],
);
$element['use_content_language'] = array(
'#type' => 'checkbox',
'#title' => t('Use current content language'),
'#default_value' => $settings['use_content_language'],
);
}
return $element;
@@ -1147,21 +1252,24 @@ function entityreference_field_formatter_settings_form($field, $instance, $view_
function entityreference_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$field_type_settings = entityreference_field_type_settings($field);
$summary = array();
if ($display['type'] == 'entityreference_label') {
$summary[] = $settings['link'] ? t('Link to the referenced entity') : t('No link');
$summary[] = $settings['bypass_access'] ? t('Show labels regardless of access') : t('Respect entity access for label visibility');
}
if ($display['type'] == 'entityreference_entity_view') {
$entity_info = entity_get_info($field['settings']['target_type']);
$entity_info = entity_get_info($field_type_settings['entity_type']);
$view_mode_label = $settings['view_mode'] == 'default' ? t('Default') : $settings['view_mode'];
if (isset($entity_info['view modes'][$settings['view_mode']]['label'])) {
$view_mode_label = $entity_info['view modes'][$settings['view_mode']]['label'];
}
$summary[] = t('Rendered as @mode', array('@mode' => $view_mode_label));
$summary[] = !empty($settings['links']) ? t('Display links') : t('Do not display links');
$summary[] = !empty($settings['use_content_language']) ? t('Use current content language') : t('Use field language');
}
return implode('<br />', $summary);
@@ -1171,19 +1279,22 @@ function entityreference_field_formatter_settings_summary($field, $instance, $vi
* Implements hook_field_formatter_prepare_view().
*/
function entityreference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
$field_type_settings = entityreference_field_type_settings($field);
$target_type = $field_type_settings['entity_type'];
$column = $field_type_settings['column'];
$target_ids = array();
// Collect every possible entity attached to any of the entities.
foreach ($entities as $id => $entity) {
foreach ($items[$id] as $delta => $item) {
if (isset($item['target_id'])) {
$target_ids[] = $item['target_id'];
if (isset($item[$column])) {
$target_ids[] = $item[$column];
}
}
}
if ($target_ids) {
$target_entities = entity_load($field['settings']['target_type'], $target_ids);
$target_entities = entity_load($target_type, $target_ids);
}
else {
$target_entities = array();
@@ -1195,12 +1306,12 @@ function entityreference_field_formatter_prepare_view($entity_type, $entities, $
foreach ($items[$id] as $delta => $item) {
// Check whether the referenced entity could be loaded.
if (isset($target_entities[$item['target_id']])) {
if (isset($target_entities[$item[$column]]) && isset($target_entities[$item[$column]])) {
// Replace the instance value with the term data.
$items[$id][$delta]['entity'] = $target_entities[$item['target_id']];
$items[$id][$delta]['entity'] = $target_entities[$item[$column]];
// Check whether the user has access to the referenced entity.
$has_view_access = (entity_access('view', $field['settings']['target_type'], $target_entities[$item['target_id']]) !== FALSE);
$has_update_access = (entity_access('update', $field['settings']['target_type'], $target_entities[$item['target_id']]) !== FALSE);
$has_view_access = (entity_access('view', $target_type, $target_entities[$item[$column]]) !== FALSE);
$has_update_access = (entity_access('update', $target_type, $target_entities[$item[$column]]) !== FALSE);
$items[$id][$delta]['access'] = ($has_view_access || $has_update_access);
}
// Otherwise, unset the instance value, since the entity does not exist.
@@ -1223,52 +1334,91 @@ function entityreference_field_formatter_prepare_view($entity_type, $entities, $
function entityreference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$result = array();
$settings = $display['settings'];
// Rebuild the items list to contain only those with access.
foreach ($items as $key => $item) {
if (empty($item['access'])) {
unset($items[$key]);
}
}
$field_type_settings = entityreference_field_type_settings($field);
$target_type = $field_type_settings['entity_type'];
$column = $field_type_settings['column'];
switch ($display['type']) {
case 'entityreference_label':
$handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity);
foreach ($items as $delta => $item) {
$label = $handler->getLabel($item['entity']);
// If the link is to be displayed and the entity has a uri, display a link.
// Note the assignment ($url = ) here is intended to be an assignment.
if ($display['settings']['link'] && ($uri = entity_uri($field['settings']['target_type'], $item['entity']))) {
$result[$delta] = array('#markup' => l($label, $uri['path'], $uri['options']));
// Skip an item that is not accessible, unless we're allowing output of
// entity labels without considering access.
if (empty($item['access']) && !$display['settings']['bypass_access']) {
continue;
}
else {
$result[$delta] = array('#markup' => check_plain($label));
// Calling EntityReferenceHandler::getLabel() would make a repeated,
// wasteful call to entity_access().
$label = entity_label($field['settings']['target_type'], $item['entity']);
// Check if the settings and access allow a link to be displayed.
$display_link = $display['settings']['link'] && $item['access'];
$uri = NULL;
// If the link is allowed and the entity has a uri, display a link.
if ($display_link) {
$uri = entity_uri($target_type, $item['entity']);
}
$result[$delta] = array(
'#theme' => 'entityreference_label',
'#label' => $label,
'#item' => $item,
'#uri' => $uri,
'#settings' => array(
'display' => $display['settings'],
'field' => $field['settings'],
),
);
}
break;
case 'entityreference_entity_id':
foreach ($items as $delta => $item) {
$result[$delta] = array('#markup' => check_plain($item['target_id']));
// Skip an item that is not accessible.
if (empty($item['access'])) {
continue;
}
$result[$delta] = array(
'#theme' => 'entityreference_entity_id',
'#item' => $item,
'#settings' => array(
'display' => $display['settings'],
'field' => $field['settings'],
),
);
}
break;
case 'entityreference_entity_view':
$target_langcode = $langcode;
if (!empty($settings['use_content_language']) && !empty($GLOBALS['language_content']->language)) {
$target_langcode = $GLOBALS['language_content']->language;
}
foreach ($items as $delta => $item) {
// Skip an item that is not accessible.
if (empty($item['access'])) {
continue;
}
// Protect ourselves from recursive rendering.
static $depth = 0;
$depth++;
if ($depth > 20) {
throw new EntityReferenceRecursiveRenderingException(t('Recursive rendering detected when rendering entity @entity_type(@entity_id). Aborting rendering.', array('@entity_type' => $entity_type, '@entity_id' => $item['target_id'])));
throw new EntityReferenceRecursiveRenderingException(t('Recursive rendering detected when rendering entity @entity_type(@entity_id). Aborting rendering.', array('@entity_type' => $target_type, '@entity_id' => $item[$column])));
}
$entity = clone $item['entity'];
unset($entity->content);
$result[$delta] = entity_view($field['settings']['target_type'], array($item['target_id'] => $entity), $settings['view_mode'], $langcode, FALSE);
$target_entity = clone $item['entity'];
unset($target_entity->content);
$result[$delta] = entity_view($target_type, array($item[$column] => $target_entity), $settings['view_mode'], $target_langcode, FALSE);
if (empty($settings['links']) && isset($result[$delta][$field['settings']['target_type']][$item['target_id']]['links'])) {
$result[$delta][$field['settings']['target_type']][$item['target_id']]['links']['#access'] = FALSE;
if (empty($settings['links']) && isset($result[$delta][$target_type][$column]['links'])) {
$result[$delta][$target_type][$item[$column]]['links']['#access'] = FALSE;
}
$depth = 0;
}
@@ -1292,3 +1442,44 @@ function entityreference_views_api() {
'path' => drupal_get_path('module', 'entityreference') . '/views',
);
}
/**
* Theme label.
*
* @ingroup themeable.
*/
function theme_entityreference_label($vars) {
$label = $vars['label'];
$settings = $vars['settings'];
$item = $vars['item'];
$uri = $vars['uri'];
$output = '';
// If the link is to be displayed and the entity has a uri, display a link.
// Note the assignment ($url = ) here is intended to be an assignment.
if ($settings['display']['link'] && isset($uri['path'])) {
$output .= l($label, $uri['path'], $uri['options']);
}
else {
$output .= check_plain($label);
}
return $output;
}
/**
* Theme entity_id
*
* @ingroup themeable.
*/
function theme_entityreference_entity_id($vars) {
$settings = $vars['settings'];
$item = $vars['item'];
$output = '';
$output = check_plain($item['target_id']);
return $output;
}
@@ -4,9 +4,9 @@ core = 7.x
package = Fields
dependencies[] = entityreference
; Information added by packaging script on 2013-11-20
version = "7.x-1.1"
; Information added by Drupal.org packaging script on 2017-08-16
version = "7.x-1.5"
core = "7.x"
project = "entityreference"
datestamp = "1384973110"
datestamp = "1502895850"
@@ -144,18 +144,20 @@ class EntityReferenceBehavior_TaxonomyIndex extends EntityReference_BehaviorHand
// already inserted in taxonomy_build_node_index().
$tid_all = array_diff($tid_all, $original_tid_all);
// Insert index entries for all the node's terms.
// Insert index entries for all the node's terms, preventing duplicates.
if (!empty($tid_all)) {
$query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created'));
foreach ($tid_all as $tid) {
$query->values(array(
$row = array(
'nid' => $node->nid,
'tid' => $tid,
'sticky' => $sticky,
'created' => $node->created,
));
);
$query = db_merge('taxonomy_index')
->key($row)
->fields($row);
$query->execute();
}
$query->execute();
}
}
}
@@ -208,7 +208,11 @@ class EntityReference_SelectionHandler_Generic implements EntityReference_Select
* Implements EntityReferenceHandler::validateAutocompleteInput().
*/
public function validateAutocompleteInput($input, &$element, &$form_state, $form) {
$entities = $this->getReferencableEntities($input, '=', 6);
$bundled_entities = $this->getReferencableEntities($input, '=', 6);
$entities = array();
foreach($bundled_entities as $entities_list) {
$entities += $entities_list;
}
if (empty($entities)) {
// Error if there are no entities available for a required field.
form_error($element, t('There are no entities matching "%value"', array('%value' => $input)));
@@ -305,7 +309,7 @@ class EntityReference_SelectionHandler_Generic implements EntityReference_Select
*/
public function getLabel($entity) {
$target_type = $this->field['settings']['target_type'];
return entity_access('view', $target_type, $entity) ? entity_label($target_type, $entity) : t('- Restricted access -');
return entity_access('view', $target_type, $entity) ? entity_label($target_type, $entity) : t(ENTITYREFERENCE_DENIED);
}
/**
@@ -339,9 +343,11 @@ class EntityReference_SelectionHandler_Generic implements EntityReference_Select
// Join the known base-table.
$target_type = $this->field['settings']['target_type'];
$entity_info = entity_get_info($target_type);
$target_type_base_table = $entity_info['base table'];
$id = $entity_info['entity keys']['id'];
// Return the alias of the table.
return $query->innerJoin($target_type, NULL, "%alias.$id = $alias.entity_id");
return $query->innerJoin($target_type_base_table, NULL, "%alias.$id = $alias.entity_id");
}
}
@@ -543,7 +549,7 @@ class EntityReference_SelectionHandler_Generic_taxonomy_term extends EntityRefer
if ($vocabulary = taxonomy_vocabulary_machine_name_load($bundle)) {
if ($terms = taxonomy_get_tree($vocabulary->vid, 0, NULL, TRUE)) {
foreach ($terms as $term) {
$options[$vocabulary->machine_name][$term->tid] = str_repeat('-', $term->depth) . check_plain($term->name);
$options[$vocabulary->machine_name][$term->tid] = str_repeat('-', $term->depth) . check_plain(entity_label('taxonomy_term', $term));
}
}
}
@@ -9,12 +9,13 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio
* Implements EntityReferenceHandler::getInstance().
*/
public static function getInstance($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
return new EntityReference_SelectionHandler_Views($field, $instance);
return new EntityReference_SelectionHandler_Views($field, $instance, $entity);
}
protected function __construct($field, $instance) {
protected function __construct($field, $instance, $entity) {
$this->field = $field;
$this->instance = $instance;
$this->entity = $entity;
}
/**
@@ -52,13 +53,32 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio
);
$default = !empty($view_settings['args']) ? implode(', ', $view_settings['args']) : '';
$description = t('Provide a comma separated list of arguments to pass to the view.') . '<br />' . t('This field supports tokens.');
if (!module_exists('token')) {
$description .= '<br>' . t('Install the <a href="@url">token module</a> to get more tokens and display available once.', array('@url' => 'http://drupal.org/project/token'));
}
$form['view']['args'] = array(
'#type' => 'textfield',
'#title' => t('View arguments'),
'#default_value' => $default,
'#required' => FALSE,
'#description' => t('Provide a comma separated list of arguments to pass to the view.'),
'#description' => $description,
'#maxlength' => '512',
);
if (module_exists('token')) {
// Get the token type for the entity type our field is in (a type 'taxonomy_term' has a 'term' type token).
$info = entity_get_info($instance['entity_type']);
$form['view']['tokens'] = array(
'#theme' => 'token_tree',
'#token_types' => array($info['token type']),
'#global_types' => TRUE,
'#click_insert' => TRUE,
'#dialog' => TRUE,
);
}
}
else {
$form['view']['no_view_help'] = array(
@@ -84,6 +104,7 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio
return FALSE;
}
$this->view->set_display($display_name);
$this->view->pre_execute();
// Make sure the query is not cached.
$this->view->is_cacheable = FALSE;
@@ -104,7 +125,7 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio
*/
public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
$display_name = $this->field['settings']['handler_settings']['view']['display_name'];
$args = $this->field['settings']['handler_settings']['view']['args'];
$args = $this->handleArgs($this->field['settings']['handler_settings']['view']['args']);
$result = array();
if ($this->initializeView($match, $match_operator, $limit)) {
// Get the results.
@@ -133,12 +154,14 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio
function validateReferencableEntities(array $ids) {
$display_name = $this->field['settings']['handler_settings']['view']['display_name'];
$args = $this->field['settings']['handler_settings']['view']['args'];
$args = $this->handleArgs($this->field['settings']['handler_settings']['view']['args']);
$result = array();
if ($this->initializeView(NULL, 'CONTAINS', 0, $ids)) {
// Get the results.
$entities = $this->view->execute_display($display_name, $args);
$result = array_keys($entities);
if (!empty($entities)) {
$result = array_keys($entities);
}
}
return $result;
}
@@ -164,6 +187,49 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio
}
/**
* Handles arguments for views.
*
* Replaces tokens using token_replace().
*
* @param array $args
* Usually $this->field['settings']['handler_settings']['view']['args'].
*
* @return array
* The arguments to be send to the View.
*/
protected function handleArgs($args) {
if (!module_exists('token')) {
return $args;
}
// Parameters for token_replace().
$data = array();
$options = array('clear' => TRUE);
if ($entity = $this->entity) {
// D7 HACK: For new entities, entity and revision id are not set. This leads to
// * token replacement emitting PHP warnings
// * views choking on empty arguments
// We workaround this by filling in '0' for these IDs
// and use a clone to leave no traces of our unholy doings.
$info = entity_get_info($this->instance['entity_type']);
if (!isset($entity->{$info['entity keys']['id']})) {
$entity = clone $entity;
$entity->{$info['entity keys']['id']} = '0';
if (!empty($info['entity keys']['revision'])) {
$entity->{$info['entity keys']['revision']} = '0';
}
}
$data[$info['token type']] = $entity;
}
// Replace tokens for each argument.
foreach ($args as $key => $arg) {
$args[$key] = token_replace($arg, $data, $options);
}
return $args;
}
}
function entityreference_view_settings_validate($element, &$form_state, $form) {
@@ -21,8 +21,9 @@ interface EntityReference_SelectionHandler {
* Return a list of referencable entities.
*
* @return
* An array of referencable entities, which keys are entity ids and
* values (safe HTML) labels to be displayed to the user.
* A nested array of entities, the first level is keyed by the
* entity bundle, which contains an array of entity labels (safe HTML),
* keyed by the entity ID.
*/
public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0);
@@ -21,7 +21,7 @@ class EntityReferenceAdminTestCase extends DrupalWebTestCase {
parent::setUp(array('field_ui', 'entity', 'ctools', 'entityreference'));
// Create test user.
$this->admin_user = $this->drupalCreateUser(array('access content', 'administer content types'));
$this->admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer fields'));
$this->drupalLogin($this->admin_user);
// Create content type, with underscores.
@@ -17,6 +17,7 @@ class FeedsMapperFieldTestCase extends DrupalWebTestCase{
'name' => 'Feeds integration (field mapper)',
'description' => 'Test Feeds Mapper support for fields.',
'group' => 'Entity Reference',
'dependencies' => array('feeds'),
);
}
@@ -28,10 +29,6 @@ class FeedsMapperFieldTestCase extends DrupalWebTestCase{
module_enable(array('entityreference_feeds_test'), TRUE);
$this->resetAll();
if (!module_exists('feeds')) {
return;
}
$permissions[] = 'access content';
$permissions[] = 'administer site configuration';
$permissions[] = 'administer content types';
@@ -157,10 +154,6 @@ class FeedsMapperFieldTestCase extends DrupalWebTestCase{
* Basic test loading a double entry CSV file.
*/
public function test() {
if (!module_exists('feeds')) {
return;
}
$this->drupalLogin($this->admin_user);
$this->drupalGet('admin/structure/types/manage/article/fields');
$this->assertText('Ref - entity ID', t('Found Entity reference field %field.', array('%field' => 'field_er_id')));
@@ -194,6 +194,21 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase {
),
);
$this->assertReferencable($field, $referencable_tests, 'Node handler (admin)');
// Verify autocomplete input validation.
$handler = entityreference_get_selection_handler($field);
$element = array(
'#parents' => array('element_name'),
);
$form_state = array();
$form = array();
$value = $handler->validateAutocompleteInput($nodes['published1']->title, $element, $form_state, $form);
$this->assertEqual($value, $nodes['published1']->nid);
$invalid_input = $this->randomName();
$value = $handler->validateAutocompleteInput($invalid_input, $element, $form_state, $form);
$this->assertNull($value);
$this->assertEqual(form_get_error($element), t('There are no entities matching "%value"', array('%value' => $invalid_input)));
}
/**
@@ -256,7 +271,7 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase {
),
'result' => array(
'user' => array(
$users['admin']->uid => '- Restricted access -',
$users['admin']->uid => ENTITYREFERENCE_DENIED,
$users['non_admin']->uid => $user_labels['non_admin'],
),
),
@@ -112,4 +112,52 @@ class EntityReferenceTaxonomyTestCase extends DrupalWebTestCase {
$this->assertFalse(taxonomy_select_nodes(1));
}
/**
* Add a second ER field from node/article to taxonomy.
*
* This should not cause {taxonomy_index} to receive duplicate entries.
*/
protected function setupForIndexDuplicates() {
// Create an entity reference field.
$field = array(
'entity_types' => array('node'),
'settings' => array(
'handler' => 'base',
'target_type' => 'taxonomy_term',
'handler_settings' => array(
'target_bundles' => array(),
),
),
'field_name' => 'field_entityreference_term2',
'type' => 'entityreference',
);
$field = field_create_field($field);
$instance = array(
'field_name' => 'field_entityreference_term2',
'bundle' => 'article',
'entity_type' => 'node',
);
// Enable the taxonomy-index behavior.
$instance['settings']['behaviors']['taxonomy-index']['status'] = TRUE;
field_create_instance($instance);
}
/**
* Make sure the index only contains one entry for a given node->term
* reference, even when multiple ER fields link from the node bundle to terms.
*/
public function testIndexDuplicates() {
// Extra setup for this test: add another ER field on this content type.
$this->setupForIndexDuplicates();
// Assert node insert with reference to term in first field.
$tid = 1;
$settings = array();
$settings['type'] = 'article';
$settings['field_entityreference_term'][LANGUAGE_NONE][0]['target_id'] = $tid;
$node = $this->drupalCreateNode($settings);
$this->assertEqual(taxonomy_select_nodes($tid), array($node->nid));
}
}
@@ -8,9 +8,9 @@ dependencies[] = feeds
dependencies[] = feeds_ui
dependencies[] = entityreference
; Information added by packaging script on 2013-11-20
version = "7.x-1.1"
; Information added by Drupal.org packaging script on 2017-08-16
version = "7.x-1.5"
core = "7.x"
project = "entityreference"
datestamp = "1384973110"
datestamp = "1502895850"
@@ -81,8 +81,9 @@ class entityreference_plugin_display extends views_plugin_display {
$field = $this->view->query->fields[$this->view->field[$field_alias]->field_alias];
}
else {
$this->view->query->add_field($this->view->field[$field_alias]->options['table'], $this->view->field[$field_alias]->real_field, $this->view->field[$field_alias]->options['field'], array());
$field = $this->view->query->fields[$this->view->field[$field_alias]->options['field']];
$field_table = $this->view->query->ensure_table($this->view->field[$field_alias]->table, $this->view->field[$field_alias]->relationship);
$this->view->query->add_field($field_table, $this->view->field[$field_alias]->real_field, $this->view->field[$field_alias]->field, array());
$field = $this->view->query->fields[$this->view->field[$field_alias]->field];
}
// Add an OR condition for the field
$conditions->condition($field['table'] . '.' . $field['field'], $value, 'LIKE');
@@ -26,7 +26,7 @@ class entityreference_plugin_style extends views_plugin_style {
'#title' => t('Search fields'),
'#options' => $options,
'#required' => TRUE,
'#default_value' => $this->options['search_fields'],
'#default_value' => isset($this->options['search_fields']) ? $this->options['search_fields'] : array(),
'#description' => t('Select the field(s) that will be searched when using the autocomplete widget.'),
'#weight' => -3,
);
@@ -1,6 +1,58 @@
/* $Id*/
CHANGELOG for field_group for Drupal 7
Field Group origin/7.x-1.x, 2017-11-04
--------------------------------------
- Tests fail after new 'administer fields' permission.
- Update CHANGELOG.txt to 7.x-1.5 Release.
- Revert "Issue #2646106 by jribeiro: Update CHANGELOG.txt to 7.x-1.5 Release".
- Update CHANGELOG.txt to 7.x-1.5 Release.
- Fix whitespace between "&" and parameters for some functions.
- Avoid bad code pattern (that is picked up by security scanners) when getting hash.
- Should #array_parents be updated in field_group_fields_nest()?.
- "Show" appears in horizontal tab title if node form submitted through ajax.
- CSS breaks i18n string translation interface.
- "uncaught exception: Syntax error, unrecognized expression: #" after 7.x-1.4 update.
- PHP7 - Uniform Variable Syntax updates are causing exported field_groups to not have names.
Field_group 7.x-1.5
o Added extra formatting on html element attributes.
o Fixed bug on automatic ID generation in javascript.
o Issue #2511960 by rrfegade: spelling errors in D7.
o Issue #2194279 by Georgique: Export translatables (label and descriptions) in Features.
o Issue #2386335 by Dubs: Integer Group Array Keys can create empty node forms.
o Issue #2269133 by sammuell: Accordion is always closed when using jQuery update module.
o Issue #2258939 by Snipon: Fixed Change to preventDefault on multipage controls.
o Issue #2173937 by jantoine, omerida | Bernieman: Fixed Accordion Element has fixed height in HTML.
o Issue #2311789 by eelkeblok: Fixed Group is incorrectly determined to be empty with nested form elements.
o Issue #2272003 by karolrybak | Kwb: Fixed Fieldgroup no longer renders after using more than one 'HTML Element' on the same level.
o Issue #2309219 by czigor: Fixed Proper CTools exportable loading.
o Issue #2283245 by stefan.r | jrb: Fixed New id functionality breaks tests and CSS on existing field groups.
o Issue #2295133 by boyan.borisov, interX: Fixed field_group_update_7007 error.
o Remove field_group.css, the css is not needed anymore.
o Fix horiziontal tabs when used as standalone element.
Field_group 7.x-1.4
o Issue #2129805 by RaF: Incorrect markup when open div & custom classes provided.
o Issue #2037731 by maximpodorov, Zach Harkey: Remove id attribute from HTML elements.
o Issue #2099505 by borisson_ | Maks: Warning: Invalid argument supplied for foreach() in field_group_build_entity_groups() (line 1894 ..sites/all/modules/field_group.
o Issue #2102397 by thebruce: Field Group 7006 update fails with "Cannot use string offset as an array in /field_group/field_group.install on line 318.
o Issue #2122733 by Alan D.: Remove test module from admin listing.
o Issue #2212431 by tim.plunkett: Field groups that contain only elements that use #markup are hidden.
o Issue #2175731 by tobiasb, daggerhart: HTML produced by module is broken.
o Issue #2190425 by tobiasb: Fix attach behavior.
o Issue #1911530 by tobiasb, Simon Georges: Remove useless files[] directive from .info files.
o Issue #1358430 by oushen, tobiasb | FriedJam: JS "Error: uncaught exception: Syntax error, unrecognized expression:".
o Issue #2104391 by mike_san: Package descriptions.
o Issue #2189777 by gmercer: Exporting a feature with field groups sometimes leads to $field_group->data being set to an empty string.
o Issue #2168689 by Paul B: Beïng should be being.
o Issue #2037731 by rreiss, maximpodorov, Zach Harkey: Make id attribute optional. Current installations will default get the old ids
o Issue #2173351 by axe312: Add a label to html-element wrappers.
o Issue #1954056 by klonos: Proper capitalization in the project name.
o Issue #2224547 by sdrycroft: Fixed field_group_info_groups() clears all CTools caches when it should only clear the field_group caches.
o Issue #2099505 by joelpittet, borisson_ | Maks: Fixed Warning: Invalid argument supplied for foreach() in field_group_build_entity_groups() (line 1894 ..sites/all/modules/field_group.
o Issue #2223269 by barraponto: Fixed Don't filter and translate empty strings.
Field_group 7.x-1.3
o Issue #2077695 by FreekVR | ChoY: Fixed field group entity display bug after update.
o Issue #2078201 by DeFr, eneko1907 | cmriley: Fixed Started getting a ton of notices.
View File
@@ -220,7 +220,7 @@ function hook_field_group_format_settings($group) {
* @param Array $elements by address.
* @param Object $group The Field group info.
*/
function hook_field_group_pre_render(& $element, $group, & $form) {
function hook_field_group_pre_render(&$element, $group, &$form) {
// You can prepare some variables to use in the logic.
$view_mode = isset($form['#view_mode']) ? $form['#view_mode'] : 'form';
@@ -268,7 +268,7 @@ function hook_field_group_pre_render(& $element, $group, & $form) {
*
* Function that fungates as last resort to alter the pre_render build.
*/
function hook_field_group_pre_render_alter(&$element, $group, & $form) {
function hook_field_group_pre_render_alter(&$element, $group, &$form) {
if ($group->format_type == 'htab') {
$element['#theme_wrappers'] = array('my_horizontal_tab');
@@ -285,7 +285,7 @@ function hook_field_group_pre_render_alter(&$element, $group, & $form) {
*
* @param Array $elements by address.
*/
function hook_field_group_build_pre_render_alter(& $element) {
function hook_field_group_build_pre_render_alter(&$element) {
// Prepare variables.
$display = isset($element['#view_mode']);
@@ -1,23 +0,0 @@
/* $Id: field_group.css,v 1.1.2.12 2010/12/22 22:22:35 stalski Exp $ */
/**
* Fix for fieldsets in vertical tabs.
* Note that this can only be hardcoded to the Seven theme
* where people who override this, are in trouble.
* This can be removed in next d7 release.
*/
.vertical-tabs fieldset.default-fallback,
div.field-group-tabs-wrapper div.field-type-image fieldset,
div.field-group-tabs-wrapper div.field-type-file fieldset,
div.field-group-tabs-wrapper div.field-type-datetime fieldset {
border: 1px solid #CCCCCC;
margin: 1em 0;
padding: 2.5em 0 0;
position: relative;
}
div.field-group-tabs-wrapper div.field-type-image legend,
div.field-group-tabs-wrapper div.field-type-file legend,
div.field-group-tabs-wrapper div.field-type-datetime legend {
display: block;
}
@@ -99,10 +99,10 @@ function field_group_field_ui_overview_form_alter(&$form, &$form_state, $display
// Play around with form_state so we only need to hold things
// between requests, until the save button was hit.
if (isset($form_state['field_group'][$name])) {
$group = & $form_state['field_group'][$name];
$group = &$form_state['field_group'][$name];
}
else {
$group = & $params->groups[$name];
$group = &$params->groups[$name];
}
// Check the currently selected formatter, and merge persisted values for
@@ -222,7 +222,7 @@ function field_group_field_ui_overview_form_alter(&$form, &$form_state, $display
$table[$name]['format']['type']['#attributes']['class'] = array('element-invisible');
}
else {
// After saving, the settings are updated here aswell. First we create
// After saving, the settings are updated here as well. First we create
// the element for the table cell.
$table[$name]['settings_summary'] = array('#markup' => '');
if (!empty($group->format_settings)) {
@@ -456,7 +456,7 @@ function field_group_format_settings_label_validate($element, &$form_state) {
* @param Object $group
* @param array $settings
*/
function field_group_formatter_row_update(& $group, $settings) {
function field_group_formatter_row_update(&$group, $settings) {
// if the row has changed formatter type, update the group object
if (!empty($settings['format']['type']) && $settings['format']['type'] != $group->format_type) {
$group->format_type = $settings['format']['type'];
@@ -469,7 +469,7 @@ function field_group_formatter_row_update(& $group, $settings) {
* @param Object $group The group object
* @param Array $settings Configuration settings
*/
function field_group_formatter_settings_update(& $group, $settings) {
function field_group_formatter_settings_update(&$group, $settings) {
// for format changes we load the defaults.
if (empty($settings['format_settings']['settings'])) {
@@ -6,9 +6,9 @@ dependencies[] = ctools
core = 7.x
files[] = tests/field_group.ui.test
files[] = tests/field_group.display.test
; Information added by Drupal.org packaging script on 2014-06-04
version = "7.x-1.4"
; Information added by Drupal.org packaging script on 2017-11-03
version = "7.x-1.6"
core = "7.x"
project = "field_group"
datestamp = "1401918529"
datestamp = "1509751991"
@@ -17,6 +17,7 @@ function field_group_schema() {
'key' => 'identifier',
'identifier' => 'field_group',
'default hook' => 'field_group_info',
'load callback' => 'field_group_group_load_by_identifier',
'save callback' => 'field_group_group_save',
'delete callback' => 'field_group_group_export_delete',
'can disable' => TRUE,
@@ -229,6 +230,8 @@ function field_group_update_7001() {
->condition('name', 'field_group')
->execute();
// Clear drupal and static cache.
field_group_info_groups(NULL, NULL, NULL, TRUE);
}
/**
@@ -246,6 +249,8 @@ function field_group_update_7002() {
// See http://drupal.org/node/1018550.
_field_group_recreate_identifiers();
// Clear drupal and static cache.
field_group_info_groups(NULL, NULL, NULL, TRUE);
}
/**
@@ -257,6 +262,8 @@ function field_group_update_7003() {
module_load_include('module', 'field_group');
_field_group_recreate_identifiers();
// Clear drupal and static cache.
field_group_info_groups(NULL, NULL, NULL, TRUE);
}
/**
@@ -295,6 +302,8 @@ function field_group_update_7005() {
drupal_write_record('field_group', $row, array('id'));
}
// Clear drupal and static cache.
field_group_info_groups(NULL, NULL, NULL, TRUE);
}
/**
@@ -330,6 +339,8 @@ function field_group_update_7006() {
}
}
// Clear drupal and static cache.
field_group_info_groups(NULL, NULL, NULL, TRUE);
}
/**
@@ -344,8 +355,18 @@ function field_group_update_7007() {
// Migrate the field groups so they have a unique identifier.
$field_groups = ctools_export_load_object("field_group");
foreach ($field_groups as $row) {
if ($row->data['format_type'] == 'div' || $row->data['format_type'] == 'html5' || $row->data['format_type'] == 'html-element') {
// These field group types have an ID setting to populate.
$populate_id_setting = array(
'html-element',
'div',
'html5',
'fieldset',
'tabs',
'htabs',
'accordion',
);
if (in_array($row->data['format_type'], $populate_id_setting)) {
// If mode is default, we don't know what view mode it was. Take full then.
$view_mode = $row->mode == 'default' ? 'full' : $row->mode;
$id = $row->entity_type . '_' . $row->bundle . '_' . $view_mode . '_' . $row->group_name;
@@ -360,4 +381,14 @@ function field_group_update_7007() {
drupal_write_record('field_group', $row, array('id'));
}
}
// Clear drupal and static cache.
field_group_info_groups(NULL, NULL, NULL, TRUE);
}
/**
* Clear cache to notice the CTools load callback.
*/
function field_group_update_7008() {
drupal_flush_all_caches();
}
@@ -41,9 +41,17 @@ Drupal.FieldGroup.Effects.processAccordion = {
$('div.field-group-accordion-wrapper', context).once('fieldgroup-effects', function () {
var wrapper = $(this);
// Get the index to set active.
var active_index = false;
wrapper.find('.accordion-item').each(function(i) {
if ($(this).hasClass('field-group-accordion-active')) {
active_index = i;
}
});
wrapper.accordion({
autoHeight: false,
active: '.field-group-accordion-active',
heightStyle: "content",
active: active_index,
collapsible: true,
changestart: function(event, ui) {
if ($(this).hasClass('effect-none')) {
@@ -111,6 +119,9 @@ Drupal.FieldGroup.Effects.processHtabs = {
Drupal.FieldGroup.Effects.processTabs = {
execute: function (context, settings, type) {
if (type == 'form') {
var errorFocussed = false;
// Add required fields mark to any fieldsets containing required fields
$('fieldset.vertical-tabs-pane', context).once('fieldgroup-effects', function(i) {
if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) {
@@ -118,8 +129,12 @@ Drupal.FieldGroup.Effects.processTabs = {
}
if ($('.error', $(this)).length) {
$(this).data('verticalTab').link.parent().addClass('error');
Drupal.FieldGroup.setGroupWithfocus($(this));
$(this).data('verticalTab').focus();
// Focus the first tab with error.
if (!errorFocussed) {
Drupal.FieldGroup.setGroupWithfocus($(this));
$(this).data('verticalTab').focus();
errorFocussed = true;
}
}
});
}
@@ -202,15 +217,14 @@ Drupal.behaviors.fieldGroup = {
$('.fieldset-wrapper .fieldset > legend').css({display: 'block'});
$('.vertical-tabs fieldset.fieldset').addClass('default-fallback');
// Add a new ID to each fieldset.
$('.group-wrapper fieldset').each(function() {
$('.group-wrapper .horizontal-tabs-panes > fieldset', context).once('group-wrapper-panes-processed', function() {
// Tats bad, but we have to keep the actual id to prevent layouts to break.
var fieldgorupID = 'field_group-' + $(this).attr('id') + ' ' + $(this).attr('id');
$(this).attr('id', fieldgorupID);
})
var fieldgroupID = 'field_group-' + $(this).attr('id');
$(this).attr('id', fieldgroupID);
});
// Set the hash in url to remember last userselection.
$('.group-wrapper ul li').each(function() {
$('.group-wrapper ul li').once('group-wrapper-ul-processed', function() {
var fieldGroupNavigationListIndex = $(this).index();
$(this).children('a').click(function() {
var fieldset = $('.group-wrapper fieldset').get(fieldGroupNavigationListIndex);
@@ -219,7 +233,8 @@ Drupal.behaviors.fieldGroup = {
window.location.hash = hashUrl;
});
});
}
};
})(jQuery);
})(jQuery);
@@ -122,6 +122,20 @@ function field_group_menu_load($group_name, $entity_type, $bundle_name, $bundle_
return empty($group) ? FALSE : $group;
}
/**
* Ctools load callback to load fieldgroup by identifier.
*/
function field_group_load_field_group_by_identifier($identifier) {
$parts = explode('|', $identifier);
if (count($parts) != 4) {
return;
}
return field_group_load_field_group($parts[0], $parts[1], $parts[2], $parts[3]);
}
/**
* Loads a group definition.
*
@@ -244,8 +258,6 @@ function field_group_field_attach_delete_bundle($entity_type, $bundle) {
* Implements hook_field_attach_form().
*/
function field_group_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
$form['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.css';
field_group_attach_groups($form, 'form', $form_state);
$form['#pre_render'][] = 'field_group_form_pre_render';
}
@@ -292,7 +304,7 @@ function field_group_field_group_formatter_info() {
'html-element' => array(
'label' => t('HTML element'),
'description' => t('This fieldgroup renders the inner content in a HTML element with classes and attributes.'),
'instance_settings' => array('element' => 'div', 'show_label' => 0, 'label_element' => 'div', 'classes' => '', 'attributes' => '', 'required_fields' => 1),
'instance_settings' => array('element' => 'div', 'show_label' => 0, 'label_element' => 'div', 'classes' => '', 'attributes' => '', 'required_fields' => 1, 'id' => ''),
),
'div' => array(
'label' => t('Div'),
@@ -310,13 +322,13 @@ function field_group_field_group_formatter_info() {
'label' => t('Fieldset'),
'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'),
'format_types' => array('open', 'collapsible', 'collapsed'),
'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1),
'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1, 'id' => ''),
'default_formatter' => 'collapsible',
),
'tabs' => array(
'label' => t('Vertical tabs group'),
'description' => t('This fieldgroup renders child groups in its own vertical tabs wrapper.'),
'instance_settings' => array('classes' => ''),
'instance_settings' => array('classes' => '', 'id' => ''),
),
'tab' => array(
'label' => t('Vertical tab'),
@@ -328,14 +340,14 @@ function field_group_field_group_formatter_info() {
'htabs' => array(
'label' => t('Horizontal tabs group'),
'description' => t('This fieldgroup renders child groups in its own horizontal tabs wrapper.'),
'instance_settings' => array('classes' => ''),
'instance_settings' => array('classes' => '', 'id' => ''),
),
'htab' => array(
'label' => t('Horizontal tab'),
'format_types' => array('open', 'closed'),
'description' => t('This fieldgroup renders the content in a fieldset, part of horizontal tabs group.'),
'default_formatter' => 'closed',
'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1, 'id' => ''),
'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1),
),
'multipage-group' => array(
'label' => t('Multipage group'),
@@ -352,7 +364,7 @@ function field_group_field_group_formatter_info() {
'accordion' => array(
'label' => t('Accordion group'),
'description' => t('This fieldgroup renders child groups as jQuery accordion.'),
'instance_settings' => array('effect' => 'none', 'classes' => ''),
'instance_settings' => array('effect' => 'none', 'classes' => '', 'id' => ''),
),
'accordion-item' => array(
'label' => t('Accordion item'),
@@ -366,7 +378,7 @@ function field_group_field_group_formatter_info() {
'html-element' => array(
'label' => t('HTML element'),
'description' => t('This fieldgroup renders the inner content in a HTML element with classes and attributes.'),
'instance_settings' => array('element' => 'div', 'show_label' => 0, 'label_element' => 'div', 'classes' => '', 'attributes' => '', 'required_fields' => 1),
'instance_settings' => array('element' => 'div', 'show_label' => 0, 'label_element' => 'div', 'classes' => '', 'attributes' => '', 'required_fields' => 1, 'id' => ''),
),
'div' => array(
'label' => t('Div'),
@@ -384,13 +396,13 @@ function field_group_field_group_formatter_info() {
'label' => t('Fieldset'),
'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'),
'format_types' => array('open', 'collapsible', 'collapsed'),
'instance_settings' => array('description' => '', 'classes' => ''),
'instance_settings' => array('description' => '', 'classes' => '', 'id' => ''),
'default_formatter' => 'collapsible',
),
'tabs' => array(
'label' => t('Vertical tabs group'),
'description' => t('This fieldgroup renders child groups in its own vertical tabs wrapper.'),
'instance_settings' => array('classes' => ''),
'instance_settings' => array('classes' => '', 'id' => ''),
),
'tab' => array(
'label' => t('Vertical tab'),
@@ -402,7 +414,7 @@ function field_group_field_group_formatter_info() {
'htabs' => array(
'label' => t('Horizontal tabs group'),
'description' => t('This fieldgroup renders child groups in its own horizontal tabs wrapper.'),
'instance_settings' => array('classes' => ''),
'instance_settings' => array('classes' => '', 'id' => ''),
),
'htab' => array(
'label' => t('Horizontal tab item'),
@@ -414,7 +426,7 @@ function field_group_field_group_formatter_info() {
'accordion' => array(
'label' => t('Accordion group'),
'description' => t('This fieldgroup renders child groups as jQuery accordion.'),
'instance_settings' => array('description' => '', 'classes' => '', 'effect' => 'bounceslide'),
'instance_settings' => array('description' => '', 'classes' => '', 'effect' => 'bounceslide', 'id' => ''),
),
'accordion-item' => array(
'label' => t('Accordion item'),
@@ -706,12 +718,12 @@ function field_group_pre_render_html_element(&$element, $group, &$form) {
$html_element = isset($group->format_settings['instance_settings']['element']) ? $group->format_settings['instance_settings']['element'] : 'div';
$show_label = isset($group->format_settings['instance_settings']['show_label']) ? $group->format_settings['instance_settings']['show_label'] : 0;
$label_element = isset($group->format_settings['instance_settings']['label_element']) ? $group->format_settings['instance_settings']['label_element'] : 'div';
$attributes = isset($group->format_settings['instance_settings']['attributes']) ? ' ' . $group->format_settings['instance_settings']['attributes'] : '';
$configured_attributes = isset($group->format_settings['instance_settings']['attributes']) ? ' ' . $group->format_settings['instance_settings']['attributes'] : '';
$group->classes = trim($group->classes);
// This regex split the attributes string so that we can pass that
// later to drupal_attributes().
preg_match_all('/([^\s=]+)="([^"]+)"/', $attributes, $matches);
preg_match_all('/([^\s=]+)="([^"]+)"/', $configured_attributes, $matches);
$element_attributes = array();
// Put the attribute and the value together.
@@ -727,7 +739,13 @@ function field_group_pre_render_html_element(&$element, $group, &$form) {
$element_attributes['class'] .= ' ' . $group->classes;
}
$attributes = drupal_attributes($element_attributes);
if (isset($element['#id'])) {
$element_attributes['id'] = $element['#id'];
}
// Sanitize the attributes.
$element_attributes = _filter_xss_attributes(drupal_attributes($element_attributes));
$attributes = $element_attributes ? ' ' . implode(' ', $element_attributes) : '';
$element['#prefix'] = '<' . $html_element . $attributes . '>';
if ($show_label) {
@@ -810,9 +828,11 @@ function field_group_pre_render_accordion(&$element, $group, &$form) {
// Add the jQuery UI accordion.
$element['#attached']['library'][] = array('system', 'ui.accordion');
$id = !empty($element['#id']) ? ' id="' . $element['#id'] . '"' : '';
$element += array(
'#type' => 'markup',
'#prefix' => '<div class="' . $group->classes . '">',
'#prefix' => '<div class="' . $group->classes . '"' . $id .'>',
'#suffix' => '</div>',
);
}
@@ -850,11 +870,18 @@ function field_group_pre_render_accordion_item(&$element, $group, &$form) {
*/
function field_group_pre_render_htabs(&$element, $group, &$form) {
$classes = 'field-group-' . $group->format_type . '-wrapper';
if (!empty($group->classes)) {
$classes .= ' ' . $group->classes;
}
$id = !empty($element['#id']) ? ' id="' . $element['#id'] . '"' : '';
$element += array(
'#type' => 'horizontal_tabs',
'#title' => check_plain(t($group->label)),
'#theme_wrappers' => array('horizontal_tabs'),
'#prefix' => '<div class="field-group-' . $group->format_type . '-wrapper ' . $group->classes . '">',
'#prefix' => '<div class="' . $classes . '"' . $id . '>',
'#suffix' => '</div>',
);
@@ -964,10 +991,17 @@ function field_group_pre_render_multipage(&$element, $group, &$form) {
*/
function field_group_pre_render_tabs(&$element, $group, &$form) {
$classes = 'field-group-' . $group->format_type . '-wrapper';
if (!empty($group->classes)) {
$classes .= ' ' . $group->classes;
}
$id = !empty($element['#id']) ? ' id="' . $element['#id'] . '"' : '';
$element += array(
'#type' => 'vertical_tabs',
'#theme_wrappers' => array('vertical_tabs'),
'#prefix' => '<div class="field-group-' . $group->format_type . '-wrapper ' . $group->classes . '">',
'#prefix' => '<div class="' . $classes . '"' . $id . '>',
'#suffix' => '</div>',
);
@@ -1074,7 +1108,7 @@ function field_group_pre_render_tab(&$element, $group, &$form) {
* Implements hook_field_group_build_pre_render_alter().
* @param Array $elements by address.
*/
function field_group_field_group_build_pre_render_alter(& $element) {
function field_group_field_group_build_pre_render_alter(&$element) {
// Someone is doing a node view, in a node view. Reset content.
// TODO Check if this breaks something else.
@@ -1096,7 +1130,6 @@ function field_group_field_group_build_pre_render_alter(& $element) {
// Add the default field_group javascript and stylesheet.
$element['#attached']['js'][] = drupal_get_path('module', 'field_group') . '/field_group.js';
$element['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.css';
// Move additional settings to the last multipage pane if configured that way.
// Note that multipages MUST be in the root of the form.
@@ -1127,19 +1160,16 @@ function field_group_field_group_build_pre_render_alter(& $element) {
*/
function field_group_remove_empty_form_groups($name, & $element, $groups, &$form_groups, $entity) {
$exceptions = array('user__account', 'comment__author');
$children = element_children($element);
$hasChildren = FALSE;
if (count($children)) {
foreach ($children as $childname) {
if (in_array($childname, $groups)) {
if (in_array($childname, $groups, TRUE)) {
field_group_remove_empty_form_groups($childname, $element[$childname], $groups, $form_groups, $entity);
}
$exception = $entity . '__' . $childname;
$hasChildren = $hasChildren ? TRUE : (isset($element[$childname]['#type']) || isset($element[$childname]['#markup']) || in_array($exception, $exceptions));
$hasChildren = $hasChildren ? TRUE : _field_group_is_empty_element($element, $entity, $childname, $groups);
}
}
@@ -1164,6 +1194,43 @@ function field_group_remove_empty_form_groups($name, & $element, $groups, &$form
}
/**
* Determine if an element has non-empty children.
*/
function _field_group_is_empty_element($element, $entity, $childname, $groups) {
$exceptions = array('user__account', 'comment__author');
$exception = $entity . '__' . $childname;
if (in_array($exception, $exceptions)) {
return TRUE;
}
if (isset($element[$childname]['#type'])
|| isset($element[$childname]['#markup'])
|| isset($element[$childname]['#prefix'])
|| isset($element[$childname]['#suffix'])
) {
return TRUE;
}
// Prevent a double recursive loop (groups are already recursive looped in field_group_remove_empty_form_groups.
if (in_array($childname, $groups)) {
return FALSE;
}
$children = element_children($element[$childname]);
foreach ($children as $child) {
if (_field_group_is_empty_element($element[$childname], $entity, $child, $groups)) {
return TRUE;
}
}
return FALSE;
}
/**
* Remove empty groups on entity display.
* @param array $element
@@ -1423,7 +1490,7 @@ function form_process_horizontal_tabs($element, &$form_state) {
function theme_horizontal_tabs($variables) {
$element = $variables['element'];
// Add required JavaScript and Stylesheet.
$element['#attached']['library'][] = array('field_group', 'horizontal-tabs');
drupal_add_library('field_group', 'horizontal-tabs');
$output = '<h2 class="element-invisible">' . (!empty($element['#title']) ? $element['#title'] : t('Horizontal Tabs')) . '</h2>';
$output .= '<div class="horizontal-tabs-panes">' . $element['#children'] . '</div>';
@@ -1861,8 +1928,10 @@ function field_group_attach_groups(&$element, $view_mode, $form_state = array())
// Create a lookup array.
$group_children = array();
foreach ($element['#groups'] as $group_name => $group) {
foreach ($group->children as $child) {
$group_children[$child] = $group_name;
if (!empty($group->children)) {
foreach ($group->children as $child) {
$group_children[$child] = $group_name;
}
}
}
$element['#group_children'] = $group_children;
@@ -1951,9 +2020,25 @@ function field_group_fields_nest(&$element, &$vars = NULL) {
// Create all groups and keep a flat list of references to these groups.
$group_references = array();
foreach ($element['#fieldgroups'] as $group_name => $group) {
// Construct own weight, as some fields (for example preprocess fields) don't have weight set.
$element[$group_name] = array();
$group_references[$group_name] = &$element[$group_name];
// check for any erroneous groups from other modules
if (is_string($group_name)) {
// Construct own weight, as some fields (for example preprocess fields) don't have weight set.
$element[$group_name] = array();
$group_references[$group_name] = &$element[$group_name];
// Get group parents
$parents = array();
$current_group = $group;
while (!empty($current_group)) {
array_unshift($parents, $current_group->group_name);
$current_group = isset($element['#fieldgroups'][$current_group->parent_name]) ?
$element['#fieldgroups'][$current_group->parent_name] : NULL;
}
$group_references[$group_name]['#array_parents'] = $parents;
$element['#fieldgroups'][$group_name]->array_parents = $parents;
// Remove self from parents and set #field_parents
array_pop($parents);
$group_references[$group_name]['#field_parents'] = $parents;
}
}
// Loop through all form children looking for those that are supposed to be
@@ -2003,6 +2088,19 @@ function field_group_fields_nest(&$element, &$vars = NULL) {
// list intact (but if it is a field we don't mind).
$group_references[$parent_name][$child_name] = &$element[$child_name];
$group_references[$parent_name]['#weight'] = $element['#fieldgroups'][$parent_name]->weight;
// Prepend #array_parents & #field_parents of group child element & its element_children
// if those keys are set, and don't already include the group parents
$group_child = &$group_references[$parent_name][$child_name];
$group_parents = $group_references[$parent_name]['#array_parents'];
$process_elements = array_merge(array(&$group_child), _field_group_element_children_recursive_ref($group_child));
foreach ($process_elements as $key => $current_element) {
if (isset($current_element['#array_parents']) && !in_array($group_parents[0], $current_element['#array_parents'])) {
$process_elements[$key]['#array_parents'] = array_merge($group_parents, $current_element['#array_parents']);
}
if (isset($current_element['#field_parents']) && !in_array($group_parents[0], $current_element['#field_parents'])) {
$process_elements[$key]['#field_parents'] = array_merge($group_parents, $current_element['#field_parents']);
}
}
}
// The child has been copied to its parent: remove it from the root element.
@@ -2019,6 +2117,23 @@ function field_group_fields_nest(&$element, &$vars = NULL) {
}
/**
* Recursive element_children, returns children by reference
*/
function _field_group_element_children_recursive_ref(&$element) {
$results = array();
$children = element_children($element);
foreach ($children as $key) {
$child = &$element[$key];
if (is_array($child)) {
$results[] = &$child;
$results = array_merge($results, _field_group_element_children_recursive_ref($child));
}
unset($child);
}
return $results;
}
/**
* Function to pre render the field group element.
*
@@ -2167,3 +2282,37 @@ function _field_group_get_default_formatter_settings($format_type, $mode) {
);
}
/**
* Callback to bulk export field groups.
*/
function field_group_field_group_to_hook_code($data, $module) {
ctools_include('export');
$schema = ctools_export_get_schema('field_group');
$export = $schema['export'];
$translatables = array();
$objects = ctools_export_load_object('field_group', 'names', array_values($data));
$code = "/**\n";
$code .= " * Implements hook_{$export['default hook']}()\n";
$code .= " */\n";
$code .= "function " . $module . "_{$export['default hook']}() {\n";
$code .= " \${$export['identifier']}s = array();\n\n";
foreach ($objects as $object) {
$code .= ctools_export_object('field_group', $object, ' ');
$code .= " \${$export['identifier']}s['" . check_plain($object->{$export['key']}) . "'] = \${$export['identifier']};\n\n";
if (!empty($object->data['label'])) {
$translatables[] = $object->data['label'];
}
if (!empty($object->data['description'])) {
$translatables[] = $object->data['description'];
}
}
if (!empty($translatables)) {
$code .= features_translatables_export($translatables, ' ') . "\n";
}
$code .= " return \${$export['identifier']}s;";
$code .= "}\n";
return $code;
}
@@ -29,8 +29,10 @@ Drupal.behaviors.horizontalTabs = {
// Transform each fieldset into a tab.
$fieldsets.each(function (i) {
var $legend = $('> legend', this);
$('.element-invisible', $legend).remove();
var horizontal_tab = new Drupal.horizontalTab({
title: $('> legend', this).text(),
title: $legend.text(),
fieldset: $(this)
});
horizontal_tab.item.addClass('horizontal-tab-button-' + i);
@@ -52,7 +54,7 @@ Drupal.behaviors.horizontalTabs = {
// element that matches the URL fragment, activate that tab.
var hash = window.location.hash.replace(/[=%;,\/]/g, "");
if (hash !== '#' && $(hash, this).length) {
tab_focus = $(window.location.hash, this).closest('.horizontal-tabs-pane');
tab_focus = $(hash, this).closest('.horizontal-tabs-pane');
}
else {
tab_focus = $('> .horizontal-tabs-pane:first', this);
@@ -82,14 +82,14 @@ Drupal.multipageControl = function (settings) {
var controls = Drupal.theme('multipage', settings);
$.extend(self, settings, controls);
this.nextLink.click(function () {
this.nextLink.click(function (e) {
e.preventDefault();
self.nextPage();
return false;
});
this.previousLink.click(function () {
this.previousLink.click(function (e) {
e.preventDefault();
self.previousPage();
return false;
});
/*
@@ -16,7 +16,7 @@ class GroupDisplayTestCase extends DrupalWebTestCase {
return array(
'name' => 'Display tests',
'description' => 'Test the field group display.',
'group' => 'Field group',
'group' => 'Field Group',
);
}
@@ -209,7 +209,6 @@ class GroupDisplayTestCase extends DrupalWebTestCase {
),
);
$first_tab = $this->createGroup('default', $data);
$first_tab_id = 'node_article_full_' . $first_tab->group_name;
$data = array(
'label' => 'Tab 2',
@@ -228,7 +227,6 @@ class GroupDisplayTestCase extends DrupalWebTestCase {
),
);
$second_tab = $this->createGroup('default', $data);
$second_tab_id = 'node_article_full_' . $first_tab->group_name;
$data = array(
'label' => 'Tabs',
@@ -258,8 +256,8 @@ class GroupDisplayTestCase extends DrupalWebTestCase {
$this->assertRaw('class="collapsible collapsed test-class-2', t('Second tab is default collapsed'));
// Test if correctly nested
$this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@id, '$first_tab_id')]", NULL, 'First tab is displayed as child of the wrapper.');
$this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@id, '$second_tab_id')]", NULL, 'Second tab is displayed as child of the wrapper.');
$this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@class, 'test-class')]", NULL, 'First tab is displayed as child of the wrapper.');
$this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@class, 'test-class-2')]", NULL, 'Second tab is displayed as child of the wrapper.');
}
@@ -285,7 +283,7 @@ class GroupDisplayTestCase extends DrupalWebTestCase {
),
);
$first_tab = $this->createGroup('default', $data);
$first_tab_id = 'node_article_full_' . $first_tab->group_name;
$first_tab_id = 'edit-' . $first_tab->group_name;
$data = array(
'label' => 'Tab 2',
@@ -304,7 +302,7 @@ class GroupDisplayTestCase extends DrupalWebTestCase {
),
);
$second_tab = $this->createGroup('default', $data);
$second_tab_id = 'node_article_full_' . $first_tab->group_name;
$second_tab_id = 'edit-' . $second_tab->group_name;
$data = array(
'label' => 'Tabs',
@@ -22,7 +22,7 @@ class GroupUITestCase extends DrupalWebTestCase {
parent::setUp('field_test', 'field_group', 'field_group_test');
// Create test user.
$admin_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'access administration pages', 'bypass node access'));
$admin_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'access administration pages', 'bypass node access', 'administer fields'));
$this->drupalLogin($admin_user);
}
@@ -5,9 +5,9 @@ package = Fields
hidden = TRUE
; Information added by Drupal.org packaging script on 2014-06-04
version = "7.x-1.4"
; Information added by Drupal.org packaging script on 2017-11-03
version = "7.x-1.6"
core = "7.x"
project = "field_group"
datestamp = "1401918529"
datestamp = "1509751991"
@@ -0,0 +1,101 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Installation
* Configuration
* Example
* Theming and Output
* Maintainers
INTRODUCTION
------------
The link can be count to the top 50 projects in Drupal installations and
provides a standard custom content field for links. With this module links can
be added easily to any content types and profiles and include advanced
validating and different ways of storing internal or external links and URLs. It
also supports additional link text title, site wide tokens for titles and title
attributes, target attributes, css class attribution, static repeating values,
input conversion, and many more.
REQUIREMENTS
------------
Project in Drupal 7 requires the following modules:
* Fields API (Fields API is provided already by core)
* Panels (https://drupal.org/project/panels)
Drupal 8:
* Link is in core now. No installation needed. Yay! Don't forget to activate
it. It's deactivated by default.
INSTALLATION
------------
Install as you would normally install a contributed Drupal module. See:
https://drupal.org/documentation/install/modules-themes/modules-7 for further
information.
CONFIGURATION
-------------
* Configuration is only slightly more complicated than a text field. Link text
titles for URLs can be made required, set as instead of URL, optional
(default), or left out entirely. If no link text title is provided, the
trimmed version of the complete URL will be displayed. The target attribute
should be set to "_blank", "top", or left out completely (checkboxes provide
info). The rel=nofollow attribute prevents the link from being followed by
certain search engines. More info at Wikipedia
(http://en.wikipedia.org/wiki/Spam_in_blogs#rel.3D.22nofollow.22).
EXAMPLE
-------
If you were to create a field named 'My New Link', the default display of the
link would be:
<em><div class="field_my_new_link" target="[target_value]"><a href="[URL]">
[Title]</a></div></em> where items between [] characters would be customized
based on the user input.
The link project supports both, internal and external URLs. URLs are validated
on input. Here are some examples of data input and the default view of a link:
http://drupal.org results in http://drupal.org, but drupal.org results in
http://drupal.org, while <front> will convert into http://drupal.org and
node/74971 into http://drupal.org/project/link
Anchors and query strings may also be used in any of these cases, including:
node/74971/edit?destination=node/74972<front>#pager
THEMING AND OUTPUT
------------------
Since link module is mainly a data storage field in a modular framework, the
theming and output is up to the site builder and other additional modules. There
are many modules in the Drupal repository, which control the output of fields
perfectly and can handle rules, user actions, markup dependencies, and can vary
the output under many different conditions, with much more efficience and
flexibility for different scenarios. Please check out modules like views,
display suite, panels, etc for such needs
MAINTAINERS
-----------
Current maintainers:
* John C Fiala (jcfiala) - https://www.drupal.org/user/163643
* Renato Gonçalves (RenatoG) - https://www.drupal.org/user/3326031
* Clemens Tolboom (clemens.tolboom) - https://www.drupal.org/user/125814
* diqidoq - https://www.drupal.org/user/1001934
* dropcube - https://www.drupal.org/user/37031
* Tom Kirkpatrick (mrfelton) - https://www.drupal.org/user/305669
* Sumit Madan (sumitmadan) - https://www.drupal.org/user/1538790
* Daniel Kudwien (sun) - https://www.drupal.org/user/54136
@@ -1,7 +1,6 @@
.link-field-column {
float: right;
}
.link-field-column.link-field-url .form-text {
direction: ltr;
text-align: left;
@@ -2,7 +2,6 @@
float: left;
width: 48%;
}
.link-field-column .form-text {
width: 95%;
}
@@ -11,11 +11,11 @@
function link_field_diff_view($items, $context) {
$diff_items = array();
foreach ($items as $delta => $item) {
if ($item['url'] && $item['title']) {
if ($item['url'] && isset($item['title'])) {
$diff_items[$delta] = $item['title'] . ' (' . $item['url'] . ')';
}
else {
$diff_items[$delta] = $item['title'] . $item['url'];
$diff_items[$delta] = $item['url'];
}
}
return $diff_items;
@@ -3,7 +3,6 @@ description = Defines simple link field types.
core = 7.x
package = Fields
files[] = link.module
files[] = link.migrate.inc
; Tests
@@ -12,15 +11,15 @@ files[] = tests/link.attribute.test
files[] = tests/link.crud.test
files[] = tests/link.crud_browser.test
files[] = tests/link.token.test
files[] = tests/link.entity_token.test
files[] = tests/link.validate.test
; Views Handlers
files[] = views/link_views_handler_argument_target.inc
files[] = views/link_views_handler_filter_protocol.inc
; Information added by Drupal.org packaging script on 2014-10-21
version = "7.x-1.3"
; Information added by Drupal.org packaging script on 2019-02-20
version = "7.x-1.6"
core = "7.x"
project = "link"
datestamp = "1413924830"
datestamp = "1550680687"
@@ -6,10 +6,38 @@
*/
/**
* Upgrade notes:
* Things we need to make sure work when upgrading from Drupal 6 to Drupal 7:
* Upgrade notes.
*
* Things we need to make sure work when upgrading from Drupal 6 to Drupal 7:.
*/
/**
* Implements hook_uninstall().
*/
function link_install() {
// Notify the user they may want to install token.
if (!module_exists('token')) {
$t = get_t();
drupal_set_message($t('If you install the <a href="!url" target="blank">Token</a>, static title can use any other entity field as its value.', array(
'!url' => 'http://drupal.org/project/token',
)));
}
}
/**
* Removes unused link_extra_domains variable.
*/
function link_update_7002() {
variable_del('link_extra_domains');
}
/**
* Implements hook_uninstall().
*/
function link_uninstall() {
variable_del('link_allowed_domains');
}
/**
* Implements hook_field_schema().
*/
@@ -46,19 +74,21 @@ function link_update_last_removed() {
}
/**
* Handles moving settings data from field_config.data to field_config_instance.data.
* Implements hook_update_N().
*
* Handles moving settings data from field_config.data to
* field_config_instance.data.
*/
function link_update_7000() {
// For each field that is a link field, we need to copy the settings from the general field level down to the instance.
//$field_data = array();
// For each field that is a link field, we need to copy the settings from the
// general field level down to the instance.
$result = db_query("SELECT id, field_name, data FROM {field_config} WHERE module = 'link' AND type = 'link_field'");
foreach ($result as $field) {
$field_id = $field->id;
$name = $field->field_name;
$field_data = unserialize($field->data);
$instances = db_query("SELECT id, data FROM {field_config_instance} WHERE field_id = :field_id", array(':field_id' => $field_id));
$instances = db_query("SELECT id, data FROM {field_config_instance} WHERE field_id = :field_id", array(':field_id' => $field->id));
foreach ($instances as $instance) {
// If this field has been updated already, we want to skip it.
$instance_data = unserialize($instance->data);
@@ -71,8 +101,8 @@ function link_update_7000() {
}
}
if ($update_instance) {
// update the database.
$num_updated = db_update('field_config_instance')
// Update the database.
db_update('field_config_instance')
->fields(array('data' => serialize($instance_data)))
->condition('id', $instance->id)
->execute();
@@ -80,22 +110,19 @@ function link_update_7000() {
}
}
}
return t("Instance settings have been set with the data from the field settings.");
}
/**
* Renames all displays from foobar to link_foobar
* Renames all displays from foobar to link_foobar.
*/
function link_update_7001() {
// Update the display type for each link field type.
$result = db_query("SELECT id, field_name, data FROM {field_config} WHERE module = 'link' AND type = 'link_field'");
foreach ($result as $field) {
$field_id = $field->id;
$name = $field->field_name;
$field_data = unserialize($field->data);
$instances = db_query("SELECT id, data FROM {field_config_instance} WHERE field_id = :field_id", array(':field_id' => $field_id));
$instances = db_query("SELECT id, data FROM {field_config_instance} WHERE field_id = :field_id", array(':field_id' => $field->id));
foreach ($instances as $instance) {
// If this field has been updated already, we want to skip it.
$instance_data = unserialize($instance->data);
@@ -11,11 +11,9 @@
* $this->addFieldMapping('field_my_link', 'source_url');
* $this->addFieldMapping('field_my_link:title', 'source_title');
* $this->addFieldMapping('field_my_link:attributes', 'source_attributes');
* @endcode
*
* With earlier versions of Migrate, you must pass an arguments array:
* # With earlier versions of Migrate, you must pass an arguments array:
*
* @code
* $link_args = array(
* 'title' => array('source_field' => 'source_title'),
* 'attributes' => array('source_field' => 'source_attributes'),
@@ -25,6 +23,10 @@
* @endcode
*/
if (!class_exists('MigrateFieldHandler')) {
return;
}
/**
* Implements hook_migrate_api().
*/
@@ -35,12 +37,20 @@ function link_migrate_api() {
);
}
// @codingStandardsIgnoreLine
class MigrateLinkFieldHandler extends MigrateFieldHandler {
/**
* Construct.
*/
public function __construct() {
$this->registerTypes(array('link_field'));
}
static function arguments($title = NULL, $attributes = NULL, $language = NULL) {
/**
* Arguments.
*/
public static function arguments($title = NULL, $attributes = NULL, $language = NULL) {
$arguments = array();
if (!is_null($title)) {
$arguments['title'] = $title;
@@ -57,16 +67,21 @@ class MigrateLinkFieldHandler extends MigrateFieldHandler {
/**
* Implementation of MigrateFieldHandler::fields().
*
* @param $type
* The field type.
* @param $instance
* Instance info for the field.
* @param array $type
* The field type.
* @param array $instance
* Instance info for the field.
* @param Migration $migration
* The migration context for the parent field. We can look at the mappings
* and determine which subfields are relevant.
* The migration context for the parent field. We can look at the mappings
* and determine which subfields are relevant.
*
* @return array
* Array with values.
*
* @codingStandardsIgnoreStart
*/
public function fields($type, $instance, $migration = NULL) {
// @codingStandardsIgnoreEnd
return array(
'title' => t('Subfield: The link title attribute'),
'attributes' => t('Subfield: The attributes for this link'),
@@ -74,6 +89,9 @@ class MigrateLinkFieldHandler extends MigrateFieldHandler {
);
}
/**
* Prepare.
*/
public function prepare($entity, array $field_info, array $instance, array $values) {
if (isset($values['arguments'])) {
$arguments = $values['arguments'];
@@ -105,9 +123,17 @@ class MigrateLinkFieldHandler extends MigrateFieldHandler {
}
}
$item['url'] = $value;
$return[$language][$delta] = $item;
if (is_array($language)) {
$current_language = $language[$delta];
}
else {
$current_language = $language;
}
$return[$current_language][$delta] = $item;
}
return isset($return) ? $return : NULL;
}
}
+457 -146
View File
@@ -10,8 +10,6 @@ define('LINK_INTERNAL', 'internal');
define('LINK_FRONT', 'front');
define('LINK_EMAIL', 'email');
define('LINK_NEWS', 'news');
define('LINK_DOMAINS', 'aero|arpa|asia|biz|build|com|cat|ceo|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|post|pro|tel|travel|mobi|local|xxx');
define('LINK_TARGET_DEFAULT', 'default');
define('LINK_TARGET_NEW_WINDOW', '_blank');
define('LINK_TARGET_TOP', '_top');
@@ -22,6 +20,22 @@ define('LINK_TARGET_USER', 'user');
*/
define('LINK_URL_MAX_LENGTH', 2048);
/**
* Implements hook_help().
*/
function link_help($path, $arg) {
switch ($path) {
case 'admin/help#link':
$output = '<p><strong>About</strong></p>';
$output .= '<p>' . 'The link provides a standard custom content field for links. Links can be easily added to any content types and profiles and include advanced validating and different ways of storing internal or external links and URLs. It also supports additional link text title, site wide tokens for titles and title attributes, target attributes, css class attribution, static repeating values, input conversion, and many more.' . '</p>';
$output .= '<p>' . '<strong>Requirements / Dependencies</strong>' . '</p>';
$output .= '<p>' . 'Fields API is provided already by core [no dependencies].' . '</p>';
$output .= '<p><strong>Configuration</strong></p>';
$output .= '<p>' . 'Configuration is only slightly more complicated than a text field. Link text titles for URLs can be made required, set as instead of URL, optional (default), or left out entirely. If no link text title is provided, the trimmed version of the complete URL will be displayed. The target attribute should be set to "_blank", "top", or left out completely (checkboxes provide info). The rel=nofollow attribute prevents the link from being followed by certain search engines.' . '</p>';
return $output;
}
}
/**
* Implements hook_field_info().
*/
@@ -98,6 +112,7 @@ function link_field_instance_settings_form($field, $instance) {
'optional' => t('Optional Title'),
'required' => t('Required Title'),
'value' => t('Static Title'),
'select' => t('Selected Title'),
'none' => t('No Title'),
);
@@ -111,9 +126,26 @@ function link_field_instance_settings_form($field, $instance) {
$form['title_value'] = array(
'#type' => 'textfield',
'#title' => t('Static title'),
'#title' => t('Static or default title'),
'#default_value' => isset($instance['settings']['title_value']) ? $instance['settings']['title_value'] : '',
'#description' => t('This title will always be used if &ldquo;Static Title&rdquo; is selected above.'),
'#description' => t('This title will 1) always be used if "Static Title" is selected above, or 2) used if "Optional title" is selected above and no title is entered when creating content.'),
'#states' => array(
'visible' => array(
':input[name="instance[settings][title]"]' => array('value' => 'value'),
),
),
);
$form['title_allowed_values'] = array(
'#type' => 'textarea',
'#title' => t('Title allowed values'),
'#default_value' => isset($instance['settings']['title_allowed_values']) ? $instance['settings']['title_allowed_values'] : '',
'#description' => t('When using "Selected Title", you can allow users to select the title from a limited set of values (eg. Home, Office, Other). Enter here all possible values that title can take, one value per line.'),
'#states' => array(
'visible' => array(
':input[name="instance[settings][title]"]' => array('value' => 'select'),
),
),
);
$form['title_label_use_field_label'] = array(
@@ -181,7 +213,7 @@ function link_field_instance_settings_form($field, $instance) {
$form['attributes']['rel'] = array(
'#type' => 'textfield',
'#title' => t('Rel Attribute'),
'#description' => t('When output, this link will have this rel attribute. The most common usage is <a href="http://en.wikipedia.org/wiki/Nofollow">rel=&quot;nofollow&quot;</a> which prevents some search engines from spidering entered links.'),
'#description' => t('When output, this link will have this rel attribute. The most common usage is <a href="http://en.wikipedia.org/wiki/Nofollow" target="blank">rel=&quot;nofollow&quot;</a> which prevents some search engines from spidering entered links.'),
'#default_value' => empty($instance['settings']['attributes']['rel']) ? '' : $instance['settings']['attributes']['rel'],
'#field_prefix' => 'rel = "',
'#field_suffix' => '"',
@@ -207,7 +239,7 @@ function link_field_instance_settings_form($field, $instance) {
$form['attributes']['class'] = array(
'#type' => 'textfield',
'#title' => t('Additional CSS Class'),
'#description' => t('When output, this link will have this class attribute. Multiple classes should be separated by spaces.'),
'#description' => t('When output, this link will have this class attribute. Multiple classes should be separated by spaces. Only alphanumeric characters and hyphens are allowed'),
'#default_value' => empty($instance['settings']['attributes']['class']) ? '' : $instance['settings']['attributes']['class'],
);
$form['attributes']['configurable_title'] = array(
@@ -218,7 +250,7 @@ function link_field_instance_settings_form($field, $instance) {
$form['attributes']['title'] = array(
'#title' => t("Default link 'title' Attribute"),
'#type' => 'textfield',
'#description' => t('When output, links will use this "title" attribute if the user does not provide one and when different from the link text. Read <a href="http://www.w3.org/TR/WCAG10-HTML-TECHS/#links">WCAG 1.0 Guidelines</a> for links comformances. Tokens values will be evaluated.'),
'#description' => t('When output, links will use this "title" attribute if the user does not provide one and when different from the link text. Read <a href="http://www.w3.org/TR/WCAG10-HTML-TECHS/#links" target="blank">WCAG 1.0 Guidelines</a> for links comformances. Tokens values will be evaluated.'),
'#default_value' => empty($instance['settings']['attributes']['title']) ? '' : $instance['settings']['attributes']['title'],
'#field_prefix' => 'title = "',
'#field_suffix' => '"',
@@ -228,11 +260,17 @@ function link_field_instance_settings_form($field, $instance) {
}
/**
* Form validate.
*
* #element_validate handler for link_field_instance_settings_form().
*/
function link_field_settings_form_validate($element, &$form_state, $complete_form) {
if ($form_state['values']['instance']['settings']['title'] === 'value' && empty($form_state['values']['instance']['settings']['title_value'])) {
form_set_error('title_value', t('A default title must be provided if the title is a static value.'));
form_set_error('instance][settings][title_value', t('A default title must be provided if the title is a static value.'));
}
if ($form_state['values']['instance']['settings']['title'] === 'select'
&& empty($form_state['values']['instance']['settings']['title_allowed_values'])) {
form_set_error('instance][settings][title_allowed_values', t('You must enter one or more allowed values for link Title, the title is a selected value.'));
}
if (!empty($form_state['values']['instance']['settings']['display']['url_cutoff']) && !is_numeric($form_state['values']['instance']['settings']['display']['url_cutoff'])) {
form_set_error('display', t('URL Display Cutoff value must be numeric.'));
@@ -277,6 +315,16 @@ function link_field_validate($entity_type, $entity, $field, $instance, $langcode
}
}
foreach ($items as $delta => $value) {
if (isset($value['attributes']) && is_string($value['attributes'])) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'link_required',
'message' => t('String values are not acceptable for attributes.'),
'error_element' => array('url' => TRUE, 'title' => FALSE),
);
}
}
if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) {
$errors[$field['field_name']][$langcode][0][] = array(
'error' => 'link_required',
@@ -291,7 +339,7 @@ function link_field_validate($entity_type, $entity, $field, $instance, $langcode
*/
function link_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
foreach ($items as $delta => $value) {
_link_process($items[$delta], $delta, $field, $entity);
_link_process($items[$delta], $delta, $field, $entity, $instance);
}
}
@@ -300,7 +348,7 @@ function link_field_insert($entity_type, $entity, $field, $instance, $langcode,
*/
function link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
foreach ($items as $delta => $value) {
_link_process($items[$delta], $delta, $field, $entity);
_link_process($items[$delta], $delta, $field, $entity, $instance);
}
}
@@ -343,10 +391,10 @@ function link_field_widget_form(&$form, &$form_state, $field, $instance, $langco
* Implements hook_field_widget_error().
*/
function link_field_widget_error($element, $error, $form, &$form_state) {
if ($error['error_element']['title']) {
if (!empty($error['error_element']['title'])) {
form_error($element['title'], $error['message']);
}
elseif ($error['error_element']['url']) {
elseif (!empty($error['error_element']['url'])) {
form_error($element['url'], $error['message']);
}
}
@@ -371,8 +419,22 @@ function _link_load($field, $item, $instance) {
/**
* Prepares the item attributes and url for storage.
*
* @param array $item
* Link field values.
* @param array $delta
* The sequence number for current values.
* @param array $field
* The field structure array.
* @param object $entity
* Entity object.
* @param array $instance
* The instance structure for $field on $entity's bundle.
*
* @codingStandardsIgnoreStart
*/
function _link_process(&$item, $delta, $field, $entity) {
function _link_process(&$item, $delta, $field, $entity, $instance) {
// @codingStandardsIgnoreEnd
// Trim whitespace from URL.
if (!empty($item['url'])) {
$item['url'] = trim($item['url']);
@@ -391,7 +453,8 @@ function _link_process(&$item, $delta, $field, $entity) {
// Don't save an invalid default value (e.g. 'http://').
if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($entity)) {
if (!link_validate_url($item['url'])) {
$langcode = !empty($entity) ? field_language($instance['entity_type'], $entity, $instance['field_name']) : LANGUAGE_NONE;
if (!link_validate_url($item['url'], $langcode)) {
unset($item['url']);
}
}
@@ -403,7 +466,7 @@ function _link_process(&$item, $delta, $field, $entity) {
function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found, &$errors) {
if ($item['url'] && !(isset($instance['default_value'][$delta]['url']) && $item['url'] === $instance['default_value'][$delta]['url'] && !$instance['required'])) {
// Validate the link.
if (link_validate_url(trim($item['url'])) == FALSE) {
if (!link_validate_url(trim($item['url']), $langcode)) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'link_required',
'message' => t('The value %value provided for %field is not a valid URL.', array(
@@ -430,9 +493,10 @@ function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &
'error_element' => array('url' => TRUE, 'title' => FALSE),
);
}
// In a totally bizzaro case, where URLs and titles are optional but the field is required, ensure there is at least one link.
// In a totally bizzaro case, where URLs and titles are optional but the field
// is required, ensure there is at least one link.
if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional'
&& (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) {
&& (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) {
$optional_field_found = TRUE;
}
// Require entire field.
@@ -448,16 +512,19 @@ function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &
/**
* Clean up user-entered values for a link field according to field settings.
*
* @param array $item
* @param array $item
* A single link item, usually containing url, title, and attributes.
* @param int $delta
* @param int $delta
* The delta value if this field is one of multiple fields.
* @param array $field
* @param array $field
* The CCK field definition.
* @param object $entity
* @param object $entity
* The entity containing this link.
*
* @codingStandardsIgnoreStart
*/
function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
// @codingStandardsIgnoreEnd
// Don't try to process empty links.
if (empty($item['url']) && empty($item['title'])) {
return;
@@ -471,22 +538,24 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
$entity_info = entity_get_info($entity_type);
$property_id = $entity_info['entity keys']['id'];
$entity_token_type = isset($entity_info['token type']) ? $entity_info['token type'] : (
$entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary' ? str_replace('taxonomy_', '', $entity_type) : $entity_type
$entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary' ? str_replace('taxonomy_', '', $entity_type) : $entity_type
);
if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) {
global $user;
// Load the entity if necessary for entities in views.
if (isset($entity->{$property_id})) {
$entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
$entity_loaded = array_pop($entity_loaded);
$text_tokens = token_scan($item['url']);
if (!empty($text_tokens)) {
// Load the entity if necessary for entities in views.
if (isset($entity->{$property_id})) {
$entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
$entity_loaded = array_pop($entity_loaded);
}
else {
$entity_loaded = $entity;
}
$item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded));
}
else {
$entity_loaded = $entity;
}
$item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded));
}
$type = link_validate_url($item['url']);
$type = link_url_type($item['url']);
// If the type of the URL cannot be determined and URL validation is disabled,
// then assume LINK_EXTERNAL for later processing.
if ($type == FALSE && $instance['settings']['validate_url'] === 0) {
@@ -496,12 +565,13 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
$url_parts = _link_parse_url($url);
if (!empty($url_parts['url'])) {
$item['url'] = $url_parts['url'];
$item += array(
'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
'absolute' => !empty($instance['settings']['absolute_url']),
'html' => TRUE,
$item['url'] = url($url_parts['url'],
array(
'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
'absolute' => !empty($instance['settings']['absolute_url']),
'html' => TRUE,
)
);
}
@@ -532,28 +602,51 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
}
}
// Use the title defined by the user at the widget level.
elseif (isset($item['title'])) {
elseif (drupal_strlen(trim($item['title']))) {
$title = $item['title'];
}
// Use the static title if a user-defined title is optional and a static title
// has been defined.
elseif ($instance['settings']['title'] == 'optional' && drupal_strlen(trim($instance['settings']['title_value']))) {
$title = $instance['settings']['title_value'];
}
else {
$title = '';
}
// Replace title tokens.
if ($title && $instance['settings']['enable_tokens']) {
$text_tokens = token_scan($title);
if (!empty($text_tokens)) {
// Load the entity if necessary for entities in views.
if (isset($entity->{$property_id})) {
$entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
$entity_loaded = array_pop($entity_loaded);
}
else {
$entity_loaded = $entity;
}
$title = token_replace($title, array($entity_token_type => $entity_loaded));
}
}
if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) {
// Load the entity if necessary for entities in views.
if (isset($entity->{$property_id})) {
$entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
$entity_loaded = array_pop($entity_loaded);
}
else {
$entity_loaded = $entity;
}
$title = token_replace($title, array($entity_token_type => $entity_loaded));
$title = filter_xss($title, array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
$title = filter_xss($title, array(
'b',
'br',
'code',
'em',
'i',
'img',
'span',
'strong',
'sub',
'sup',
'tt',
'u',
));
$item['html'] = TRUE;
}
$item['title'] = empty($title) ? $item['display_url'] : $title;
$item['title'] = empty($title) && $title !== '0' ? $item['display_url'] : $title;
if (!isset($item['attributes'])) {
$item['attributes'] = array();
@@ -599,22 +692,38 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
// Handle "title" link attribute.
if (!empty($item['attributes']['title']) && module_exists('token')) {
// Load the entity (necessary for entities in views).
if (isset($entity->{$property_id})) {
$entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
$entity_loaded = array_pop($entity_loaded);
$text_tokens = token_scan($item['attributes']['title']);
if (!empty($text_tokens)) {
// Load the entity (necessary for entities in views).
if (isset($entity->{$property_id})) {
$entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
$entity_loaded = array_pop($entity_loaded);
}
else {
$entity_loaded = $entity;
}
$item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded));
}
else {
$entity_loaded = $entity;
}
$item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded));
$item['attributes']['title'] = filter_xss($item['attributes']['title'], array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
$item['attributes']['title'] = filter_xss($item['attributes']['title'], array(
'b',
'br',
'code',
'em',
'i',
'img',
'span',
'strong',
'sub',
'sup',
'tt',
'u',
));
}
// Handle attribute classes.
if (!empty($item['attributes']['class'])) {
$classes = explode(' ', $item['attributes']['class']);
foreach ($classes as &$class) {
$class = drupal_html_class($class);
$class = drupal_clean_css_identifier($class);
}
$item['attributes']['class'] = implode(' ', $classes);
}
@@ -660,7 +769,8 @@ function _link_parse_url($url) {
* Replaces the PHP parse_str() function.
*
* Because parse_str replaces the following characters in query parameters name
* in order to maintain compability with deprecated register_globals directive:
* in order to maintain compatibility with deprecated register_globals
* directive:
*
* - chr(32) ( ) (space)
* - chr(46) (.) (dot)
@@ -700,11 +810,21 @@ function link_theme() {
'link_formatter_link_plain' => array(
'variables' => array('element' => NULL, 'field' => NULL),
),
'link_formatter_link_host' => array(
'variables' => array('element' => NULL),
),
'link_formatter_link_absolute' => array(
'variables' => array('element' => NULL, 'field' => NULL),
),
'link_formatter_link_domain' => array(
'variables' => array('element' => NULL, 'display' => NULL, 'field' => NULL),
'variables' => array(
'element' => NULL,
'display' => NULL,
'field' => NULL,
),
),
'link_formatter_link_no_protocol' => array(
'variables' => array('element' => NULL, 'field' => NULL),
),
'link_formatter_link_title_plain' => array(
'variables' => array('element' => NULL, 'field' => NULL),
@@ -791,7 +911,8 @@ function _link_default_attributes() {
* Build the form element. When creating a form using FAPI #process,
* note that $element['#value'] is already set.
*
* The $fields array is in $complete_form['#field_info'][$element['#field_name']].
* The $fields array is in
* $complete_form['#field_info'][$element['#field_name']].
*/
function link_field_process($element, $form_state, $complete_form) {
$instance = field_widget_instance($element, $form_state);
@@ -803,7 +924,7 @@ function link_field_process($element, $form_state, $complete_form) {
'#required' => ($element['#delta'] == 0 && $settings['url'] !== 'optional') ? $element['#required'] : FALSE,
'#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL,
);
if ($settings['title'] !== 'none' && $settings['title'] !== 'value') {
if (in_array($settings['title'], array('optional', 'required'))) {
// Figure out the label of the title field.
if (!empty($settings['title_label_use_field_label'])) {
// Use the element label as the title field label.
@@ -815,15 +936,31 @@ function link_field_process($element, $form_state, $complete_form) {
$title_label = t('Title');
}
// Default value.
$title_maxlength = 128;
if (!empty($settings['title_maxlength'])) {
$title_maxlength = $settings['title_maxlength'];
}
$element['title'] = array(
'#type' => 'textfield',
'#maxlength' => $settings['title_maxlength'],
'#maxlength' => $title_maxlength,
'#title' => $title_label,
'#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $settings['title_maxlength'])),
'#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $title_maxlength)),
'#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE,
'#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
);
}
elseif ($settings['title'] == 'select') {
$options = drupal_map_assoc(array_filter(explode("\n", str_replace("\r", "\n", trim($settings['title_allowed_values'])))));
$element['title'] = array(
'#type' => 'select',
'#title' => t('Title'),
'#description' => t('Select the a title for this link.'),
'#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
'#options' => $options,
);
}
// Initialize field attributes as an array if it is not an array yet.
if (!is_array($settings['attributes'])) {
@@ -859,8 +996,9 @@ function link_field_process($element, $form_state, $complete_form) {
);
}
// If the title field is avaliable or there are field accepts multiple values
// then allow the individual field items display the required asterisk if needed.
// If the title field is available or there are field accepts multiple values
// then allow the individual field items display the required asterisk if
// needed.
if (isset($element['title']) || isset($element['_weight'])) {
// To prevent an extra required indicator, disable the required flag on the
// base element since all the sub-fields are already required if desired.
@@ -885,6 +1023,11 @@ function link_field_formatter_info() {
'field types' => array('link_field'),
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
),
'link_host' => array(
'label' => t('Host, as plain text'),
'field types' => array('link_field'),
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
),
'link_url' => array(
'label' => t('URL, as link'),
'field types' => array('link_field'),
@@ -908,6 +1051,11 @@ function link_field_formatter_info() {
'strip_www' => FALSE,
),
),
'link_no_protocol' => array(
'label' => t('URL with the protocol removed'),
'field types' => array('link_field'),
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
),
'link_short' => array(
'label' => t('Short, as link with title "Link"'),
'field types' => array('link_field'),
@@ -947,8 +1095,9 @@ function link_field_formatter_settings_form($field, $instance, $view_mode, $form
* Implements hook_field_formatter_settings_summary().
*/
function link_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
if ($display['type'] == 'link_domain') {
if ($display['settings']['strip_www']) {
return t('Strip www. from domain');
@@ -993,7 +1142,7 @@ function theme_link_formatter_link_default($vars) {
}
// If only a title, display the title.
elseif (!empty($vars['element']['title'])) {
return $link_options['html'] ? $vars['element']['title'] : check_plain($vars['element']['title']);
return !empty($link_options['html']) ? $vars['element']['title'] : check_plain($vars['element']['title']);
}
elseif (!empty($vars['element']['url'])) {
return l($vars['element']['title'], $vars['element']['url'], $link_options);
@@ -1015,6 +1164,14 @@ function theme_link_formatter_link_plain($vars) {
return empty($vars['element']['url']) ? check_plain($vars['element']['title']) : url($vars['element']['url'], $link_options);
}
/**
* Theme function for 'host' text field formatter.
*/
function theme_link_formatter_link_host($vars) {
$host = @parse_url($vars['element']['url']);
return isset($host['host']) ? check_plain($host['host']) : '';
}
/**
* Formats a link as an absolute URL.
*/
@@ -1037,11 +1194,27 @@ function theme_link_formatter_link_domain($vars) {
return $vars['element']['url'] ? l($domain, $vars['element']['url'], $link_options) : '';
}
/**
* Formats a link without the http:// or https://.
*/
function theme_link_formatter_link_no_protocol($vars) {
$link_options = $vars['element'];
unset($link_options['title']);
unset($link_options['url']);
// We drop any scheme of the url.
$scheme = parse_url($vars['element']['url']);
$search = '/' . preg_quote($scheme['scheme'] . '://', '/') . '/';
$replace = '';
$display_url = preg_replace($search, $replace, $vars['element']['url'], 1);
return $vars['element']['url'] ? l($display_url, $vars['element']['url'], $link_options) : '';
}
/**
* Formats a link's title as plain text.
*/
function theme_link_formatter_link_title_plain($vars) {
return empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
return empty($vars['element']['title']) ? '' : check_plain(decode_entities($vars['element']['title']));
}
/**
@@ -1101,7 +1274,8 @@ function theme_link_formatter_link_separate($vars) {
/**
* Implements hook_token_list().
*
* @TODO: hook_token_list no longer exists - this should change to hook_token_info().
* @TODO: hook_token_list no longer exists - this should change to
* hook_token_info().
*/
function link_token_list($type = 'all') {
if ($type === 'field' || $type === 'all') {
@@ -1116,7 +1290,8 @@ function link_token_list($type = 'all') {
/**
* Implements hook_token_values().
*
* @TODO: hook_token_values no longer exists - this should change to hook_tokens().
* @TODO: hook_token_values no longer exists - this should change to
* hook_tokens().
*/
function link_token_values($type, $object = NULL) {
if ($type === 'field') {
@@ -1142,26 +1317,27 @@ function link_views_api() {
/**
* Forms a valid URL if possible from an entered address.
*
*
* Trims whitespace and automatically adds an http:// to addresses without a
* protocol specified
* protocol specified.
*
* @param string $url
* The url entered by the user.
* @param string $protocol
* The protocol to be prepended to the url if one is not specified
* The protocol to be prepended to the url if one is not specified.
*/
function link_cleanup_url($url, $protocol = 'http') {
$url = trim($url);
$type = link_validate_url($url);
$type = link_url_type($url);
if ($type === LINK_EXTERNAL) {
// Check if there is no protocol specified.
$protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i", $url);
if (empty($protocol_match)) {
// But should there be? Add an automatic http:// if it starts with a domain name.
$LINK_DOMAINS = _link_domains();
$domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(' . $LINK_DOMAINS . '|[a-z]{2}))/i', $url);
// But should there be? Add an automatic http:// if it starts with a
// domain name.
$link_domains = _link_domains();
$domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(' . $link_domains . '|[a-z]{2}))/i', $url);
if (!empty($domain_match)) {
$url = $protocol . "://" . $url;
}
@@ -1173,109 +1349,207 @@ function link_cleanup_url($url, $protocol = 'http') {
/**
* Validates a URL.
*
*
* @param string $text
* Url to be validated.
* @param string $langcode
* An optional language code to look up the path in.
*
* @return bool
* True if a valid link, FALSE otherwise.
*/
function link_validate_url($text, $langcode = NULL) {
$text = _link_clean_relative($text);
$text = link_cleanup_url($text);
$type = link_url_type($text);
if ($type && ($type == LINK_INTERNAL || $type == LINK_EXTERNAL)) {
$flag = valid_url($text, TRUE);
if (!$flag) {
$normal_path = drupal_get_normal_path($text, $langcode);
$parsed_link = parse_url($normal_path, PHP_URL_PATH);
if ($normal_path != $parsed_link) {
$normal_path = $parsed_link;
}
$flag = drupal_valid_path($normal_path);
}
if (!$flag) {
$flag = file_exists($normal_path);
}
if (!$flag) {
$uri = file_build_uri($normal_path);
$flag = file_exists($uri);
}
}
else {
$flag = (bool) $type;
}
return $flag;
}
/**
* Cleaner of relatives urls.
*
* @param string $url
* The url to clean up the relative protocol.
*/
function _link_clean_relative($url) {
$check = substr($url, 0, 2);
if (isset($_SERVER['HTTPS']) &&
($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) ||
isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
$_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
$protocol = 'https://';
}
else {
$protocol = 'http://';
}
if ($check == '//') {
$url = str_replace('//', $protocol, $url);
}
return $url;
}
/**
* Type check a URL.
*
* Accepts all URLs following RFC 1738 standard for URL formation and all e-mail
* addresses following the RFC 2368 standard for mailto address formation.
*
* @param string $text
* Url to be validated.
*
* Url to be checked.
*
* @return mixed
* Returns boolean FALSE if the URL is not valid. On success, returns one of
* the LINK_(linktype) constants.
*/
function link_validate_url($text) {
function link_url_type($text) {
// @TODO Complete letters.
$LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array(
"&#x00E6;", // æ
"&#x00C6;", // Æ
// @codingStandardsIgnoreStart
$link_ichars_domain = (string) html_entity_decode(implode("", array(
"&#x00BF;", // ¿
"&#x00C0;", // À
"&#x00E0;", // à
"&#x00C1;", // Á
"&#x00E1;", // á
"&#x00C2;", // Â
"&#x00E2;", // â
"&#x00E5;", // å
"&#x00C5;", // Å
"&#x00E4;", // ä
"&#x00C3;", // Ã
"&#x00C4;", // Ä
"&#x00C5;", // Å
"&#x00C6;", // Æ
"&#x00C7;", // Ç
"&#x00E7;", // ç
"&#x00D0;", // Ð
"&#x00F0;", // ð
"&#x00C8;", // È
"&#x00E8;", // è
"&#x00C9;", // É
"&#x00E9;", // é
"&#x00CA;", // Ê
"&#x00EA;", // ê
"&#x00CB;", // Ë
"&#x00EB;", // ë
"&#x00CC;", // Ì
"&#x00CD;", // Í
"&#x00CE;", // Î
"&#x00EE;", // î
"&#x00CF;", // Ï
"&#x00EF;", // ï
"&#x00F8;", // ø
"&#x00D8;", // Ø
"&#x00F6;", // ö
"&#x00D6;", // Ö
"&#x00D0;", // Ð
"&#x00D1;", // Ñ
"&#x00D2;", // Ò
"&#x00D3;", // Ó
"&#x00D4;", // Ô
"&#x00F4;", // ô
"&#x00D5;", // Õ
"&#x00D6;", // Ö
// ×
"&#x00D8;", // Ø
"&#x00D9;", // Ù
"&#x00DA;", // Ú
"&#x00DB;", // Û
"&#x00DC;", // Ü
"&#x00DD;", // Ý
"&#x00DE;", // Þ
// ß (see LINK_ICHARS)
"&#x00E0;", // à
"&#x00E1;", // á
"&#x00E2;", // â
"&#x00E3;", // ã
"&#x00E4;", // ä
"&#x00E5;", // å
"&#x00E6;", // æ
"&#x00E7;", // ç
"&#x00E8;", // è
"&#x00E9;", // é
"&#x00EA;", // ê
"&#x00EB;", // ë
"&#x00EC;", // ì
"&#x00ED;", // í
"&#x00EE;", // î
"&#x00EF;", // ï
"&#x00F0;", // ð
"&#x00F1;", // ñ
"&#x00F2;", // ò
"&#x00F3;", // ó
"&#x00F4;", // ô
"&#x00F5;", // õ
"&#x00F6;", // ö
// ÷
"&#x00F8;", // ø
"&#x00F9;", // ù
"&#x00FA;", // ú
"&#x00FB;", // û
"&#x00FC;", // ü
"&#x00FD;", // ý
"&#x00FE;", // þ
"&#x00FF;", // ÿ
"&#x0152;", // Œ
"&#x0153;", // œ
"&#x00FC;", // ü
"&#x00DC;", // Ü
"&#x00D9;", // Ù
"&#x00F9;", // ù
"&#x00DB;", // Û
"&#x00FB;", // û
"&#x0178;", // Ÿ
"&#x00FF;", // ÿ
"&#x00D1;", // Ñ
"&#x00F1;", // ñ
"&#x00FE;", // þ
"&#x00DE;", // Þ
"&#x00FD;", // ý
"&#x00DD;", // Ý
"&#x00BF;", // ¿
)), ENT_QUOTES, 'UTF-8');
// @codingStandardsIgnoreEnd
$LINK_ICHARS = $LINK_ICHARS_DOMAIN . (string) html_entity_decode(implode("", array(
"&#x00DF;", // ß
)), ENT_QUOTES, 'UTF-8');
$allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'));
$LINK_DOMAINS = _link_domains();
$link_ichars = $link_ichars_domain . (string) html_entity_decode(implode("", array(
// ß.
"&#x00DF;",
)), ENT_QUOTES, 'UTF-8');
$allowed_protocols = variable_get('filter_allowed_protocols', array(
'http',
'https',
'ftp',
'news',
'nntp',
'telnet',
'mailto',
'irc',
'ssh',
'sftp',
'webcal',
));
$link_domains = _link_domains();
// Starting a parenthesis group with (?: means that it is grouped, but is not captured.
// Starting a parenthesis group with (?: means that it is grouped, but is not
// captured.
$protocol = '((?:' . implode("|", $allowed_protocols) . '):\/\/)';
$authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $LINK_ICHARS . "]|%[0-9a-f]{2})+(?::(?:[\w" . $LINK_ICHARS . "\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)";
$domain = '(?:(?:[a-z0-9' . $LINK_ICHARS_DOMAIN . ']([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])*)(\.(([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])+\.)*(' . $LINK_DOMAINS . '|[a-z]{2}))?)';
$authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $link_ichars . "]|%[0-9a-f]{2})+(?::(?:[\w" . $link_ichars . "\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)";
$domain = '(?:(?:[a-z0-9' . $link_ichars_domain . ']([a-z0-9' . $link_ichars_domain . '\-_\[\]])*)(\.(([a-z0-9' . $link_ichars_domain . '\-_\[\]])+\.)*(' . $link_domains . '|[a-z]{2}))?)';
$ipv4 = '(?:[0-9]{1,3}(\.[0-9]{1,3}){3})';
$ipv6 = '(?:[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})';
$port = '(?::([0-9]{1,5}))';
// Pattern specific to external links.
$external_pattern = '/^' . $protocol . '?' . $authentication . '?(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?';
// Pattern specific to internal links.
$internal_pattern = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\] ]+)";
$internal_pattern_file = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \/\(\)][a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \(\)][a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \/\(\)]+)$/i";
$internal_pattern = "/^(?:[a-z0-9" . $link_ichars . "_\-+\[\] ]+)";
$internal_pattern_file = "/^(?:[a-z0-9" . $link_ichars . "_\-+\[\]\. \/\(\)][a-z0-9" . $link_ichars . "_\-+\[\]\. \(\)][a-z0-9" . $link_ichars . "_\-+\[\]\. \/\(\)]+)$/i";
$directories = "(?:\/[a-z0-9" . $LINK_ICHARS . "_\-\.~+%=&,$'#!():;*@\[\]]*)*";
$directories = "(?:\/[a-z0-9" . $link_ichars . "_\-\.~+%=&,$'#!():;*@\[\]]*)*";
// Yes, four backslashes == a single backslash.
$query = "(?:\/?\?([?a-z0-9" . $LINK_ICHARS . "+_|\-\.~\/\\\\%=&,$'!():;*@\[\]{} ]*))";
$anchor = "(?:#[a-z0-9" . $LINK_ICHARS . "_\-\.~+%=&,$'():;*@\[\]\/\?]*)";
$query = "(?:\/?\?([?a-z0-9" . $link_ichars . "+_|\-\.~\/\\\\%=&,$'!():;*@\[\]{} ]*))";
$anchor = "(?:#[a-z0-9" . $link_ichars . "_\-\.~+%=&,$'():;*@\[\]\/\?]*)";
// The rest of the path for a standard URL.
// @codingStandardsIgnoreLine
$end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i';
$message_id = '[^@].*@' . $domain;
$newsgroup_name = '(?:[0-9a-z+-]*\.)*[0-9a-z+-]*';
$news_pattern = '/^news:(' . $newsgroup_name . '|' . $message_id . ')$/i';
$user = '[a-zA-Z0-9' . $LINK_ICHARS . '_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+';
$user = '[a-zA-Z0-9' . $link_ichars . '_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+';
$email_pattern = '/^mailto:' . $user . '@' . '(?:' . $domain . '|' . $ipv4 . '|' . $ipv6 . '|localhost)' . $query . '?$/';
if (strpos($text, '<front>') === 0) {
@@ -1290,6 +1564,9 @@ function link_validate_url($text) {
if (preg_match($internal_pattern . $end, $text)) {
return LINK_INTERNAL;
}
if (drupal_valid_path($text) && url_is_external($text) == FALSE) {
return LINK_INTERNAL;
}
if (preg_match($external_pattern . $end, $text)) {
return LINK_EXTERNAL;
}
@@ -1301,11 +1578,16 @@ function link_validate_url($text) {
}
/**
* Returns the list of allowed domains, including domains added by admins via variable_set/$config.
* Returns the list of allowed domains.
*
* If the variable link_allowed_domains is set, restrict allowed domains to the
* strings in that array. If the variable link_allowed_domains is not set, allow
* all domains between 2 and 63 characters in length.
* See https://tools.ietf.org/html/rfc1034.
*/
function _link_domains() {
$link_extra_domains = variable_get('link_extra_domains', array());
return empty($link_extra_domains) ? LINK_DOMAINS : LINK_DOMAINS . '|' . implode('|', $link_extra_domains);
$link_allowed_domains = variable_get('link_allowed_domains', array());
return empty($link_allowed_domains) ? '[a-z][a-z0-9-]{1,62}' : implode('|', $link_allowed_domains);
}
/**
@@ -1316,7 +1598,15 @@ function link_content_migrate_field_alter(&$field_value, $instance_value) {
// Adjust the field type.
$field_value['type'] = 'link_field';
// Remove settings that are now on the instance.
foreach (array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens', 'validate_url') as $setting) {
foreach (array(
'attributes',
'display',
'url',
'title',
'title_value',
'enable_tokens',
'validate_url',
) as $setting) {
unset($field_value['settings'][$setting]);
}
}
@@ -1330,7 +1620,15 @@ function link_content_migrate_field_alter(&$field_value, $instance_value) {
function link_content_migrate_instance_alter(&$instance_value, $field_value) {
if ($field_value['type'] == 'link') {
// Grab settings that were previously on the field.
foreach (array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens', 'validate_url') as $setting) {
foreach (array(
'attributes',
'display',
'url',
'title',
'title_value',
'enable_tokens',
'validate_url',
) as $setting) {
if (isset($field_value['settings'][$setting])) {
$instance_value['settings'][$setting] = $field_value['settings'][$setting];
}
@@ -1341,7 +1639,15 @@ function link_content_migrate_instance_alter(&$instance_value, $field_value) {
}
// Adjust formatter types.
foreach ($instance_value['display'] as $context => $settings) {
if (in_array($settings['type'], array('default', 'title_plain', 'url', 'plain', 'short', 'label', 'separate'))) {
if (in_array($settings['type'], array(
'default',
'title_plain',
'url',
'plain',
'short',
'label',
'separate',
))) {
$instance_value['display'][$context]['type'] = 'link_' . $settings['type'];
}
}
@@ -1357,7 +1663,7 @@ function link_field_settings_form() {
/**
* Additional callback to adapt the property info of link fields.
*
*
* @see entity_metadata_field_entity_property_info()
*/
function link_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
@@ -1385,7 +1691,7 @@ function link_field_property_info_callback(&$info, $entity_type, $field, $instan
* @see link_field_property_info_callback()
*/
function link_field_item_create() {
return array('title' => NULL, 'url' => NULL);
return array('title' => NULL, 'url' => NULL, 'display_url' => NULL);
}
/**
@@ -1398,7 +1704,7 @@ function link_field_item_property_info() {
'setter callback' => 'entity_property_verbatim_set',
);
$properties['url'] = array(
'type' => 'uri',
'type' => 'text',
'label' => t('The URL of the link.'),
'setter callback' => 'entity_property_verbatim_set',
);
@@ -1408,6 +1714,11 @@ function link_field_item_property_info() {
'setter callback' => 'entity_property_verbatim_set',
'getter callback' => 'link_attribute_property_get',
);
$properties['display_url'] = array(
'type' => 'uri',
'label' => t('The full URL of the link.'),
'setter callback' => 'entity_property_verbatim_set',
);
return $properties;
}
@@ -1422,7 +1733,7 @@ function link_attribute_property_get($data, array $options, $name, $type, $info)
* Implements hook_field_update_instance().
*/
function link_field_update_instance($instance, $prior_instance) {
if (function_exists('i18n_string_update') && $instance['widget']['type'] == 'link_field' && $prior_instance['settings']['title_value'] != $instance['settings']['title_value']) {
if (function_exists('i18n_string_update') && isset($instance['widget']) && $instance['widget']['type'] == 'link_field' && $prior_instance['settings']['title_value'] != $instance['settings']['title_value']) {
$i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
i18n_string_update($i18n_string_name, $instance['settings']['title_value']);
}
@@ -5,7 +5,11 @@
* Basic simpletests to test options on link module.
*/
/**
* Attribute Crud Test.
*/
class LinkAttributeCrudTest extends DrupalWebTestCase {
private $zebra;
protected $permissions = array(
@@ -19,6 +23,9 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
'access administration pages',
);
/**
* Get Info.
*/
public static function getInfo() {
return array(
'name' => 'Link Attribute Tests',
@@ -27,14 +34,23 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
);
}
function setup() {
/**
* Setup.
*/
public function setup() {
parent::setup('field_ui', 'link');
$this->zebra = 0;
// Create and login user.
$this->web_user = $this->drupalCreateUser(array('administer content types'));
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
));
$this->drupalLogin($this->web_user);
}
/**
* Create Link.
*/
protected function createLink($url, $title, $attributes = array()) {
return array(
'url' => $url,
@@ -43,20 +59,26 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
);
}
/**
* Assert Link On Node.
*/
protected function assertLinkOnNode($field_name, $link_value, $message = '', $group = 'Other') {
$this->zebra++;
$zebra_string = ($this->zebra % 2 == 0) ? 'even' : 'odd';
$cssFieldLocator = 'field-' . str_replace('_', '-', $field_name);
$this->assertPattern('@<div class="field field-type-link ' . $cssFieldLocator . '".*<div class="field-item ' . $zebra_string . '">\s*' . $link_value . '\s*</div>@is',
$message,
$group);
$message,
$group);
}
/**
* A simple test that just creates a new node type, adds a link field to it, creates a new node of that type, and makes sure
* that the node is being displayed.
* Test Basic.
*
* A simple test that just creates a new node type, adds a link field to it,
* creates a new node of that type, and makes sure that the node is being
* displayed.
*/
function testBasic() {
public function testBasic() {
$content_type_friendly = $this->randomName(20);
$content_type_machine = strtolower($this->randomName(10));
$title = $this->randomName(20);
@@ -76,7 +98,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
// Now add a singleton field.
$single_field_name_friendly = $this->randomName(20);
$single_field_name_machine = strtolower($this->randomName(10));
$single_field_name = 'field_' . $single_field_name_machine;
$edit = array(
'fields[_add_new_field][label]' => $single_field_name_friendly,
'fields[_add_new_field][field_name]' => $single_field_name_machine,
@@ -101,7 +123,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
$this->assertTrue($type_exists, 'The new content type has been created in the database.');
$permission = 'create ' . $content_type_machine . ' content';
$permission_edit = 'edit ' . $content_type_machine . ' content';
// Reset the permissions cache.
$this->checkPermissions(array($permission), TRUE);
@@ -122,7 +144,10 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
);
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertText(t('@content_type_friendly @title has been created', array('@content_type_friendly' => $content_type_friendly, '@title' => $title)));
$this->assertText(t('@content_type_friendly @title has been created', array(
'@content_type_friendly' => $content_type_friendly,
'@title' => $title,
)));
$this->drupalGet('node/add/' . $content_type_machine);
@@ -135,12 +160,18 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
// Now we can fill in the second item in the multivalue field and save.
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertText(t('@content_type_friendly @title has been created', array('@content_type_friendly' => $content_type_friendly, '@title' => $title)));
$this->assertText(t('@content_type_friendly @title has been created', array(
'@content_type_friendly' => $content_type_friendly,
'@title' => $title,
)));
$this->assertText('Display');
$this->assertLinkByHref('http://www.example.com');
}
/**
* Create Simple Link Field.
*/
protected function createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine) {
$this->drupalGet('admin/structure/types/manage/' . $content_type_machine . '/fields');
$edit = array(
@@ -166,9 +197,11 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
$this->assertTrue($type_exists, 'The new content type has been created in the database.');
}
/**
* Create Node Type User.
*/
protected function createNodeTypeUser($content_type_machine) {
$permission = 'create ' . $content_type_machine . ' content';
$permission_edit = 'edit ' . $content_type_machine . ' content';
// Reset the permissions cache.
$this->checkPermissions(array($permission), TRUE);
@@ -179,6 +212,9 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
$this->drupalLogin($this->web_user);
}
/**
* Create Node For Testing.
*/
protected function createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $title, $url, $node_title = '') {
$this->drupalGet('node/add/' . $content_type_machine);
@@ -196,14 +232,17 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
}
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertText(t('@content_type_friendly @title has been created', array('@content_type_friendly' => $content_type_friendly, '@title' => $node_title)));
$this->assertText(t('@content_type_friendly @title has been created', array(
'@content_type_friendly' => $content_type_friendly,
'@title' => $node_title,
)));
}
/**
* Test the link_plain formatter and it's output.
*/
function testFormatterPlain() {
public function testFormatterPlain() {
$content_type_friendly = $this->randomName(20);
$content_type_machine = strtolower($this->randomName(10));
@@ -215,7 +254,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
// Now add a singleton field.
$single_field_name_friendly = $this->randomName(20);
$single_field_name_machine = strtolower($this->randomName(10));
//$single_field_name = 'field_'. $single_field_name_machine;
// $single_field_name = 'field_'. $single_field_name_machine;.
$this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine);
// Okay, now we want to make sure this display is changed:
@@ -227,7 +266,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
$this->drupalPost(NULL, $edit, t('Save'));
$this->createNodeTypeUser($content_type_machine);
$link_tests = array(
'plain' => array(
'text' => 'Display',
@@ -243,18 +282,21 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
),
);
foreach ($link_tests as $key => $link_test) {
foreach ($link_tests as $link_test) {
$link_text = $link_test['text'];
$link_url = $link_test['url'];
$this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url);
$this->assertText($link_url);
$this->assertNoText($link_text);
$this->assertNoLinkByHref($link_url);
}
}
function testFormatterURL() {
/**
* Formatter Host.
*/
public function testFormatterHost() {
$content_type_friendly = $this->randomName(20);
$content_type_machine = strtolower($this->randomName(10));
@@ -266,7 +308,47 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
// Now add a singleton field.
$single_field_name_friendly = $this->randomName(20);
$single_field_name_machine = strtolower($this->randomName(10));
//$single_field_name = 'field_'. $single_field_name_machine;
// $single_field_name = 'field_'. $single_field_name_machine;.
$this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine);
// Okay, now we want to make sure this display is changed:
$this->drupalGet('admin/structure/types/manage/' . $content_type_machine . '/display');
$edit = array(
'fields[field_' . $single_field_name_machine . '][label]' => 'above',
'fields[field_' . $single_field_name_machine . '][type]' => 'link_host',
);
$this->drupalPost(NULL, $edit, t('Save'));
$this->createNodeTypeUser($content_type_machine);
$link_text = 'Display';
$link_url = 'http://www.example.com/';
$this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url);
$this->assertText('www.example.com');
$this->assertNoText($link_text);
$this->assertNoLinkByHref($link_url);
}
/**
* Formatter URL.
*
* @codingStandardsIgnoreStart
*/
public function testFormatterURL() {
// @codingStandardsIgnoreEnd
$content_type_friendly = $this->randomName(20);
$content_type_machine = strtolower($this->randomName(10));
$this->drupalCreateContentType(array(
'type' => $content_type_machine,
'name' => $content_type_friendly,
));
// Now add a singleton field.
$single_field_name_friendly = $this->randomName(20);
$single_field_name_machine = strtolower($this->randomName(10));
// $single_field_name = 'field_'. $single_field_name_machine;.
$this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine);
// Okay, now we want to make sure this display is changed:
@@ -278,7 +360,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
$this->drupalPost(NULL, $edit, t('Save'));
$this->createNodeTypeUser($content_type_machine);
$link_tests = array(
'plain' => array(
'text' => 'Display',
@@ -294,17 +376,20 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
),
);
foreach ($link_tests as $key => $link_test) {
foreach ($link_tests as $link_test) {
$link_text = $link_test['text'];
$link_url = $link_test['url'];
$this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url);
$this->assertNoText($link_text);
$this->assertLinkByHref($link_url);
}
}
function testFormatterShort() {
/**
* Formatter Short.
*/
public function testFormatterShort() {
$content_type_friendly = $this->randomName(20);
$content_type_machine = strtolower($this->randomName(10));
@@ -316,7 +401,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
// Now add a singleton field.
$single_field_name_friendly = $this->randomName(20);
$single_field_name_machine = strtolower($this->randomName(10));
//$single_field_name = 'field_'. $single_field_name_machine;
// $single_field_name = 'field_'. $single_field_name_machine;.
$this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine);
// Okay, now we want to make sure this display is changed:
@@ -344,18 +429,21 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
),
);
foreach ($link_tests as $key => $link_test) {
foreach ($link_tests as $link_test) {
$link_text = $link_test['text'];
$link_url = $link_test['url'];
$this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url);
$this->assertText('Link');
$this->assertNoText($link_text);
$this->assertLinkByHref($link_url);
}
}
function testFormatterLabel() {
/**
* Formatter Label.
*/
public function testFormatterLabel() {
$content_type_friendly = $this->randomName(20);
$content_type_machine = strtolower($this->randomName(10));
@@ -367,7 +455,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
// Now add a singleton field.
$single_field_name_friendly = $this->randomName(20);
$single_field_name_machine = strtolower($this->randomName(10));
//$single_field_name = 'field_'. $single_field_name_machine;
// $single_field_name = 'field_'. $single_field_name_machine;.
$this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine);
// Okay, now we want to make sure this display is changed:
@@ -395,18 +483,21 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
),
);
foreach ($link_tests as $key => $link_test) {
foreach ($link_tests as $link_test) {
$link_text = $link_test['text'];
$link_url = $link_test['url'];
$link_url = $link_test['url'];
$this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url);
$this->assertNoText($link_text);
$this->assertText($single_field_name_friendly);
$this->assertLinkByHref($link_url);
}
}
function testFormatterSeparate() {
/**
* Formatter Separate.
*/
public function testFormatterSeparate() {
$content_type_friendly = $this->randomName(20);
$content_type_machine = strtolower($this->randomName(10));
@@ -418,7 +509,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
// Now add a singleton field.
$single_field_name_friendly = $this->randomName(20);
$single_field_name_machine = strtolower($this->randomName(10));
//$single_field_name = 'field_'. $single_field_name_machine;
// $single_field_name = 'field_'. $single_field_name_machine;.
$this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine);
// Okay, now we want to make sure this display is changed:
@@ -447,32 +538,35 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
),
);
foreach ($link_tests as $key => $link_test) {
foreach ($link_tests as $link_test) {
$link_text = $link_test['text'];
$link_url = $link_test['url'];
$this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url);
$this->assertText($link_text);
$this->assertLink($plain_url);
$this->assertLinkByHref($link_url);
}
}
function testFormatterPlainTitle() {
/**
* Formatter Plain Title.
*/
public function testFormatterPlainTitle() {
$content_type_friendly = $this->randomName(20);
$content_type_machine = strtolower($this->randomName(10));
$this->drupalCreateContentType(array(
'type' => $content_type_machine,
'name' => $content_type_friendly,
));
// Now add a singleton field.
$single_field_name_friendly = $this->randomName(20);
$single_field_name_machine = strtolower($this->randomName(10));
//$single_field_name = 'field_'. $single_field_name_machine;
// $single_field_name = 'field_'. $single_field_name_machine;.
$this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine);
// Okay, now we want to make sure this display is changed:
$this->drupalGet('admin/structure/types/manage/' . $content_type_machine . '/display');
$edit = array(
@@ -480,15 +574,16 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
'fields[field_' . $single_field_name_machine . '][type]' => 'link_title_plain',
);
$this->drupalPost(NULL, $edit, t('Save'));
$this->createNodeTypeUser($content_type_machine);
$link_text = 'Display';
$link_url = 'http://www.example.com/';
$this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url);
$this->assertText($link_text);
$this->assertNoText($link_url);
$this->assertNoLinkByHref($link_url);
}
}
@@ -2,11 +2,20 @@
/**
* @file
* Basic CRUD simpletests for the link module, based off of content.crud.test in CCK.
* File for Crud Tests.
*
* Basic CRUD simpletests for the link module, based off of content.crud.test in
* CCK.
*/
/**
* Content Crud.
*/
class LinkContentCrudTest extends DrupalWebTestCase {
/**
* Get Info.
*/
public static function getInfo() {
return array(
'name' => 'Link CRUD - Basic API tests',
@@ -15,21 +24,31 @@ class LinkContentCrudTest extends DrupalWebTestCase {
);
}
function setUp() {
/**
* Setup.
*/
public function setUp() {
parent::setUp('field_ui', 'link');
}
/**
* All we're doing here is creating a content type, creating a simple link field
* on that content type.
* Create Field API.
*
* All we're doing here is creating a content type, creating a simple link
* field on that content type.
*
* @codingStandardsIgnoreStart
*/
function testLinkCreateFieldAPI() {
public function testLinkCreateFieldAPI() {
// @codingStandardsIgnoreEnd
$content_type_friendly = $this->randomName(20);
$content_type_machine = strtolower($this->randomName(10));
$title = $this->randomName(20);
// Create and login user.
$this->web_user = $this->drupalCreateUser(array('administer content types'));
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
));
$this->drupalLogin($this->web_user);
$this->drupalGet('admin/structure/types');
@@ -69,4 +88,5 @@ class LinkContentCrudTest extends DrupalWebTestCase {
$type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $content_type_machine))->fetchField();
$this->assertTrue($type_exists, 'The new content type has been created in the database.');
}
}
@@ -6,25 +6,28 @@
*/
/**
* Testing that users can not input bad URLs or labels
* Testing that users can not input bad URLs or labels.
*/
class LinkUITest extends DrupalWebTestcase {
/**
* Link supposed to be good
* Link supposed to be good.
*/
const LINK_INPUT_TYPE_GOOD = 0;
/**
* Link supposed to have a bad title
* Link supposed to have a bad title.
*/
const LINK_INPUT_TYPE_BAD_TITLE = 1;
/**
* Link supposed to have a bad URL
* Link supposed to have a bad URL.
*/
const LINK_INPUT_TYPE_BAD_URL = 2;
/**
* Get Info.
*/
public static function getInfo() {
return array(
'name' => 'Link CRUD - browser test',
@@ -33,26 +36,30 @@ class LinkUITest extends DrupalWebTestcase {
);
}
function setUp() {
/**
* Setup.
*/
public function setUp() {
parent::setUp('field_ui', 'link');
}
/**
* Creates a link field for the "page" type and creates a page with a link.
*/
function testLinkCreate() {
//libxml_use_internal_errors(true);
public function testLinkCreate() {
// libxml_use_internal_errors(true);
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'administer nodes',
'administer filters',
'access content',
'create page content',
'access administration pages'
'access administration pages',
));
$this->drupalLogin($this->web_user);
// create field
// Create field.
$name = strtolower($this->randomName());
$edit = array(
'fields[_add_new_field][label]' => $name,
@@ -72,8 +79,8 @@ class LinkUITest extends DrupalWebTestcase {
$permission = 'create page content';
$this->checkPermissions(array($permission), TRUE);
// create page form
//$this->drupalGet('node/add');
// Create page form
// $this->drupalGet('node/add');.
$this->drupalGet('node/add/page');
$field_name = 'field_' . $name;
$this->assertField('edit-field-' . $name . '-und-0-title', 'Title found');
@@ -84,37 +91,37 @@ class LinkUITest extends DrupalWebTestcase {
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName(),
'msg' => 'Link found',
'type' => self::LINK_INPUT_TYPE_GOOD
'type' => self::LINK_INPUT_TYPE_GOOD,
),
array(
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName() . '<script>alert("hi");</script>',
'msg' => 'js label',
'type' => self::LINK_INPUT_TYPE_BAD_TITLE
'type' => self::LINK_INPUT_TYPE_BAD_TITLE,
),
array(
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName() . '<script src="http://devil.site.com"></script>',
'msg' => 'js label',
'type' => self::LINK_INPUT_TYPE_BAD_TITLE
'type' => self::LINK_INPUT_TYPE_BAD_TITLE,
),
array(
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName() . '" onmouseover="alert(\'hi\')',
'msg' => 'js label',
'type' => self::LINK_INPUT_TYPE_BAD_TITLE
'type' => self::LINK_INPUT_TYPE_BAD_TITLE,
),
array(
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName() . '\' onmouseover="alert(\'hi\')',
'msg' => 'js label',
'type' => self::LINK_INPUT_TYPE_BAD_TITLE
'type' => self::LINK_INPUT_TYPE_BAD_TITLE,
),
array(
'href' => 'javascript:alert("http://example.com/' . $this->randomName() . '")',
'label' => $this->randomName(),
'msg' => 'js url',
'type' => self::LINK_INPUT_TYPE_BAD_URL
'type' => self::LINK_INPUT_TYPE_BAD_URL,
),
array(
'href' => 'http://ecs-es.kelkoo.es/ctl/go/sitesearchGo?.ts=1338833010331&.sig=qP9GXeEFH6syBzwmzYkxmsvp1EI-',
@@ -143,23 +150,26 @@ class LinkUITest extends DrupalWebTestcase {
);
$this->drupalPost(NULL, $edit, t('Save'));
if ($input['type'] == self::LINK_INPUT_TYPE_BAD_URL) {
$this->assertRaw(t('The value %value provided for %field is not a valid URL.', array('%field' => $name, '%value' => trim($input['href']))), 'Not a valid URL: ' . $input['href']);
$this->assertRaw(t('The value %value provided for %field is not a valid URL.', array(
'%field' => $name,
'%value' => trim($input['href']),
)), 'Not a valid URL: ' . $input['href']);
continue;
}
else {
$this->assertRaw(' ' . t('has been created.',
array('@type' => 'Basic Page', '%title' => $edit['title'])),
'Page created: ' . $input['href']);
array('@type' => 'Basic Page', '%title' => $edit['title'])),
'Page created: ' . $input['href']);
}
$url = $this->getUrl();
// change to Anonymous user.
// Change to Anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
//debug($this);
// If simpletest starts using something to override the error system, this will flag
// us and let us know it's broken.
// debug($this);
// If simpletest starts using something to override the error system, this
// will flag us and let us know it's broken.
$this->assertFalse(libxml_use_internal_errors(TRUE));
if (isset($input['expected_href'])) {
$path = '//a[@href="' . $input['expected_href'] . '" and text()="' . $input['label'] . '"]';
@@ -171,18 +181,27 @@ class LinkUITest extends DrupalWebTestcase {
libxml_use_internal_errors(FALSE);
$this->assertIdentical(isset($elements[0]), $input['type'] == self::LINK_INPUT_TYPE_GOOD, $input['msg']);
}
//libxml_use_internal_errors(FALSE);
// libxml_use_internal_errors(FALSE);
}
/**
* Static Link Create.
*
* Testing that if you use <strong> in a static title for your link, that the
* title actually displays <strong>.
*/
function testStaticLinkCreate() {
$this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content'));
public function testStaticLinkCreate() {
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'access content',
'create page content',
));
$this->drupalLogin($this->web_user);
// create field
// Create field.
$name = strtolower($this->randomName());
$field_name = 'field_' . $name;
$edit = array(
@@ -195,17 +214,18 @@ class LinkUITest extends DrupalWebTestcase {
$this->drupalPost(NULL, array(), t('Save field settings'));
$this->drupalPost(NULL, array(
'instance[settings][title]' => 'value',
'instance[settings][title_value]' => '<strong>' . $name . '</strong>'), t('Save settings'));
'instance[settings][title_value]' => '<strong>' . $name . '</strong>',
), t('Save settings'));
// Is field created?
$this->assertRaw(t('Saved %label configuration', array('%label' => $name)), 'Field added');
// create page form
// Create page form.
$this->drupalGet('node/add/page');
$this->assertField($field_name . '[und][0][url]', 'URL found');
$input = array(
'href' => 'http://example.com/' . $this->randomName()
'href' => 'http://example.com/' . $this->randomName(),
);
$edit = array(
@@ -216,21 +236,32 @@ class LinkUITest extends DrupalWebTestcase {
$url = $this->getUrl();
// change to anonymous user
// Change to anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
$this->assertRaw(l('<strong>' . $name . '</strong>', $input['href'], array('html' => TRUE)));
}
/**
* Testing that if you have the title but no url, the title is not sanitized twice.
* CRUD Title Only Title No Link.
*
* Testing that if you have the title but no url, the title is not sanitized
* twice.
*
* @codingStandardsIgnoreStart
*/
function testCRUDTitleOnlyTitleNoLink() {
$this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content'));
public function testCRUDTitleOnlyTitleNoLink() {
// @codingStandardsIgnoreEnd
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'access content',
'create page content',
));
$this->drupalLogin($this->web_user);
// create field
// Create field.
$name = strtolower($this->randomName());
$field_name = 'field_' . $name;
$edit = array(
@@ -247,8 +278,8 @@ class LinkUITest extends DrupalWebTestcase {
// Is field created?
$this->assertRaw(t('Saved %label configuration', array('%label' => $name)), 'Field added');
// create page form
// Create page form.
$this->drupalGet('node/add/page');
$this->assertField($field_name . '[und][0][url]', 'URL found');
@@ -265,8 +296,8 @@ class LinkUITest extends DrupalWebTestcase {
$this->drupalPost(NULL, $edit, t('Save'));
$url = $this->getUrl();
// change to anonymous user
// Change to anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
@@ -274,14 +305,26 @@ class LinkUITest extends DrupalWebTestcase {
}
/**
* If we're creating a new field and just hit 'save' on the default options, we want to make
* sure they are set to the expected results.
* CRUD Create Field Defaults.
*
* If we're creating a new field and just hit 'save' on the default options,
* we want to make sure they are set to the expected results.
*
* @codingStandardsIgnoreStart
*/
function testCRUDCreateFieldDefaults() {
$this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content'));
public function testCRUDCreateFieldDefaults() {
// @codingStandardsIgnoreEnd
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'access content',
'create page content',
));
$this->drupalLogin($this->web_user);
// create field
// Create field.
$name = strtolower($this->randomName());
$edit = array(
'fields[_add_new_field][label]' => $name,
@@ -312,4 +355,169 @@ class LinkUITest extends DrupalWebTestcase {
$this->assertFalse($instance['settings']['attributes']['class'], 'By default, no class should be set.');
$this->assertFalse($instance['settings']['title_value'], 'By default, no title should be set.');
}
/**
* CRUD Create Field With Class.
*
* If we're creating a new field and just hit 'save' on the default options,
* we want to make sure they are set to the expected results.
*
* @codingStandardsIgnoreStart
*/
public function testCRUDCreateFieldWithClass() {
// @codingStandardsIgnoreEnd
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'access content',
'create page content',
));
$this->drupalLogin($this->web_user);
// Create field.
$name = strtolower($this->randomName());
$edit = array(
'fields[_add_new_field][label]' => $name,
'fields[_add_new_field][field_name]' => $name,
'fields[_add_new_field][type]' => 'link_field',
'fields[_add_new_field][widget_type]' => 'link_field',
);
$this->drupalPost('admin/structure/types/manage/page/fields', $edit, t('Save'));
$this->drupalPost(NULL, array(), t('Save field settings'));
$link_class_name = 'basic-link-' . strtolower($this->randomName());
$edit = array(
'instance[settings][attributes][class]' => $link_class_name,
);
$this->drupalPost(NULL, $edit, t('Save settings'));
// Is field created?
$this->assertRaw(t('Saved %label configuration', array('%label' => $name)), 'Field added');
node_types_rebuild();
menu_rebuild();
_field_info_collate_fields(TRUE);
$instances = field_info_instances('node', 'page');
$instance = $instances['field_' . $name];
$this->assertFalse($instance['required'], 'Make sure field is not required.');
$this->assertEqual($instance['settings']['title'], 'optional', 'Title should be optional by default.');
$this->assertTrue($instance['settings']['validate_url'], 'Make sure validation is on.');
$this->assertTrue($instance['settings']['enable_tokens'], 'Enable Tokens should be on by default.');
$this->assertEqual($instance['settings']['display']['url_cutoff'], 80, 'Url cutoff should be at 80 characters.');
$this->assertEqual($instance['settings']['attributes']['target'], 'default', 'Target should be "default"');
$this->assertFalse($instance['settings']['attributes']['rel'], 'Rel should be blank by default.');
$this->assertEqual($instance['settings']['attributes']['class'], $link_class_name, 'One class should be set.');
$this->assertFalse($instance['settings']['title_value'], 'By default, no title should be set.');
// Now, let's create a node with this field and make sure the link shows up:
// create page form.
$field_name = 'field_' . $name;
$this->drupalGet('node/add/page');
$this->assertField($field_name . '[und][0][url]', 'URL found');
$input = array(
'title' => 'This & That',
'href' => 'http://www.example.com/',
);
$edit = array(
'title' => $field_name,
$field_name . '[und][0][title]' => $input['title'],
$field_name . '[und][0][url]' => $input['href'],
);
$this->drupalPost(NULL, $edit, t('Save'));
$url = $this->getUrl();
// Change to anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
$this->assertRaw('This &amp; That');
$this->assertPattern('|class\s?=\s?"' . $link_class_name . '"|', "Class $link_class_name exists on page.");
}
/**
* CRUD Create Field With Two Classes.
*
* If we're creating a new field and just hit 'save' on the default options,
* we want to make sure they are set to the expected results.
*
* @codingStandardsIgnoreStart
*/
public function testCRUDCreateFieldWithTwoClasses() {
// @codingStandardsIgnoreEnd
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'access content',
'create page content',
));
$this->drupalLogin($this->web_user);
// Create field.
$name = strtolower($this->randomName());
$edit = array(
'fields[_add_new_field][label]' => $name,
'fields[_add_new_field][field_name]' => $name,
'fields[_add_new_field][type]' => 'link_field',
'fields[_add_new_field][widget_type]' => 'link_field',
);
$this->drupalPost('admin/structure/types/manage/page/fields', $edit, t('Save'));
$this->drupalPost(NULL, array(), t('Save field settings'));
$link_class_name = 'basic-link ' . strtoupper($this->randomName());
$edit = array(
'instance[settings][attributes][class]' => $link_class_name,
);
$this->drupalPost(NULL, $edit, t('Save settings'));
// Is field created?
$this->assertRaw(t('Saved %label configuration', array('%label' => $name)), 'Field added');
node_types_rebuild();
menu_rebuild();
_field_info_collate_fields(TRUE);
$instances = field_info_instances('node', 'page');
$instance = $instances['field_' . $name];
$this->assertFalse($instance['required'], 'Make sure field is not required.');
$this->assertEqual($instance['settings']['title'], 'optional', 'Title should be optional by default.');
$this->assertTrue($instance['settings']['validate_url'], 'Make sure validation is on.');
$this->assertTrue($instance['settings']['enable_tokens'], 'Enable Tokens should be on by default.');
$this->assertEqual($instance['settings']['display']['url_cutoff'], 80, 'Url cutoff should be at 80 characters.');
$this->assertEqual($instance['settings']['attributes']['target'], 'default', 'Target should be "default"');
$this->assertFalse($instance['settings']['attributes']['rel'], 'Rel should be blank by default.');
$this->assertEqual($instance['settings']['attributes']['class'], $link_class_name, 'Two classes should be set.');
$this->assertFalse($instance['settings']['title_value'], 'By default, no title should be set.');
// Now, let's create a node with this field and make sure the link shows up:
// create page form.
$field_name = 'field_' . $name;
$this->drupalGet('node/add/page');
$this->assertField($field_name . '[und][0][url]', 'URL found');
$input = array(
'title' => 'This & That',
'href' => 'http://www.example.com/',
);
$edit = array(
'title' => $field_name,
$field_name . '[und][0][title]' => $input['title'],
$field_name . '[und][0][url]' => $input['href'],
);
$this->drupalPost(NULL, $edit, t('Save'));
$url = $this->getUrl();
// Change to anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
$this->assertRaw('This &amp; That');
$this->assertPattern('|class\s?=\s?"' . $link_class_name . '"|', "Classes $link_class_name exist on page.");
}
}
@@ -0,0 +1,164 @@
<?php
/**
* @file
* Contains simpletests making sure entity_token integration works.
*/
/**
* Testing that tokens can be used in link titles.
*/
class LinkEntityTokenTest extends LinkBaseTestClass {
/**
* Get Info.
*/
public static function getInfo() {
return array(
'name' => 'Link entity tokens test',
'description' => 'Tests that a link field appears properly in entity tokens',
'group' => 'Link',
'dependencies' => array('token', 'entity', 'entity_token'),
);
}
/**
* Setup.
*/
public function setUp($modules = array()) {
parent::setUp(array('token', 'entity', 'entity_token'));
}
/**
* Creates a link field, fills it, then uses a loaded node to test tokens.
*/
public function testFieldTokenNodeLoaded() {
// Create field.
$settings = array(
'instance[settings][enable_tokens]' => 0,
);
$field_name = $this->createLinkField('page',
$settings);
// Create page form.
$this->drupalGet('node/add/page');
// $field_name = 'field_' . $name;.
$this->assertField($field_name . '[und][0][title]', 'Title found');
$this->assertField($field_name . '[und][0][url]', 'URL found');
$token_url_tests = array(
1 => array(
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName(),
),
2 => array(
'href' => 'http://example.com/' . $this->randomName() . '?property=value',
'label' => $this->randomName(),
),
3 => array(
'href' => 'http://example.com/' . $this->randomName() . '#position',
'label' => $this->randomName(),
),
4 => array(
'href' => 'http://example.com/' . $this->randomName() . '#lower?property=value2',
'label' => $this->randomName(),
),
);
// @codingStandardsIgnoreLine
// $this->assert('pass', '<pre>' . print_r($token_url_tests, TRUE) . '<pre>');.
foreach ($token_url_tests as &$input) {
$this->drupalGet('node/add/page');
$edit = array(
'title' => $input['label'],
$field_name . '[und][0][title]' => $input['label'],
$field_name . '[und][0][url]' => $input['href'],
);
$this->drupalPost(NULL, $edit, t('Save'));
$url = $this->getUrl();
$input['url'] = $url;
}
// Change to anonymous user.
$this->drupalLogout();
foreach ($token_url_tests as $index => $input2) {
$node = node_load($index);
$this->assertNotEqual(NULL, $node, "Do we have a node?");
$this->assertEqual($node->nid, $index, "Test that we have a node.");
$token_name = '[node:' . str_replace('_', '-', $field_name) . ':url]';
$assert_data = token_replace($token_name,
array('node' => $node));
$this->assertEqual($input2['href'], $assert_data, "Test that the url token has been set to " . $input2['href'] . ' - ' . $assert_data);
}
}
/**
* Field Token Node Viewed.
*
* Creates a link field, fills it, then uses a loaded and node_view'd node to
* test tokens.
*/
public function testFieldTokenNodeViewed() {
// Create field.
$settings = array(
'instance[settings][enable_tokens]' => 0,
);
$field_name = $this->createLinkField('page',
$settings);
// Create page form.
$this->drupalGet('node/add/page');
// $field_name = 'field_' . $name;.
$this->assertField($field_name . '[und][0][title]', 'Title found');
$this->assertField($field_name . '[und][0][url]', 'URL found');
$token_url_tests = array(
1 => array(
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName(),
),
2 => array(
'href' => 'http://example.com/' . $this->randomName() . '?property=value',
'label' => $this->randomName(),
),
3 => array(
'href' => 'http://example.com/' . $this->randomName() . '#position',
'label' => $this->randomName(),
),
4 => array(
'href' => 'http://example.com/' . $this->randomName() . '#lower?property=value2',
'label' => $this->randomName(),
),
);
//@codingStandardsIgnoreLine
// $this->assert('pass', '<pre>' . print_r($token_url_tests, TRUE) . '<pre>');.
foreach ($token_url_tests as &$input) {
$this->drupalGet('node/add/page');
$edit = array(
'title' => $input['label'],
$field_name . '[und][0][title]' => $input['label'],
$field_name . '[und][0][url]' => $input['href'],
);
$this->drupalPost(NULL, $edit, t('Save'));
$url = $this->getUrl();
$input['url'] = $url;
}
// Change to anonymous user.
$this->drupalLogout();
foreach ($token_url_tests as $index => $input2) {
$node = node_load($index);
$this->assertNotEqual(NULL, $node, "Do we have a node?");
$this->assertEqual($node->nid, $index, "Test that we have a node.");
$token_name = '[node:' . str_replace('_', '-', $field_name) . ':url]';
$assert_data = token_replace($token_name,
array('node' => $node));
$this->assertEqual($input2['href'], $assert_data, "Test that the url token has been set to " . $input2['href'] . ' - ' . $assert_data);
}
}
}
@@ -5,10 +5,15 @@
* Link base test file - contains common functions for testing links.
*/
/**
* Base Test Class.
*/
class LinkBaseTestClass extends DrupalWebTestCase {
protected $permissions = array(
'access content',
'administer content types',
'administer fields',
'administer nodes',
'administer filters',
'access comments',
@@ -17,17 +22,23 @@ class LinkBaseTestClass extends DrupalWebTestCase {
'create page content',
);
function setUp() {
/**
* Setup.
*/
public function setUp() {
$modules = func_get_args();
$modules = (isset($modules[0]) && is_array($modules[0]) ? $modules[0] : $modules);
$modules[] = 'field_ui';
$modules[] = 'link';
parent::setUp($modules);
$this->web_user = $this->drupalCreateUser($this->permissions);
$this->drupalLogin($this->web_user);
}
/**
* Create Link Field.
*/
protected function createLinkField($node_type = 'page', $settings = array()) {
$name = strtolower($this->randomName());
$edit = array(
@@ -48,4 +59,5 @@ class LinkBaseTestClass extends DrupalWebTestCase {
return $field_name;
}
}
@@ -6,10 +6,13 @@
*/
/**
* Testing that tokens can be used in link titles
* Testing that tokens can be used in link titles.
*/
class LinkTokenTest extends LinkBaseTestClass {
/**
* Get Info.
*/
public static function getInfo() {
return array(
'name' => 'Link tokens - browser test',
@@ -19,34 +22,38 @@ class LinkTokenTest extends LinkBaseTestClass {
);
}
function setUp($modules = array()) {
/**
* Setup.
*/
public function setUp($modules = array()) {
parent::setUp(array('token'));
}
/**
* Creates a link field with a required title enabled for user-entered tokens.
*
* Creates a node with a token in the link title and checks the value.
*/
function testUserTokenLinkCreate() {
// create field
public function testUserTokenLinkCreate() {
// Create field.
$settings = array(
'instance[settings][enable_tokens]' => 1,
);
$field_name = $this->createLinkField('page',
$settings);
$settings);
// create page form
// Create page form.
$this->drupalGet('node/add/page');
//$field_name = 'field_' . $name;
// $field_name = 'field_' . $name;.
$this->assertField($field_name . '[und][0][title]', 'Title found');
$this->assertField($field_name . '[und][0][url]', 'URL found');
$input = array(
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName(),
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName(),
);
//$this->drupalLogin($this->web_user);
// $this->drupalLogin($this->web_user);.
$this->drupalGet('node/add/page');
$edit = array(
@@ -57,36 +64,37 @@ class LinkTokenTest extends LinkBaseTestClass {
$this->drupalPost(NULL, $edit, t('Save'));
$url = $this->getUrl();
// change to anonymous user
// Change to anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
$this->assertRaw(l($input['label'] . ' page', $input['href']));
}
/**
* Creates a link field with a static title and an admin-entered token.
*
* Creates a node with a link and checks the title value.
*/
function testStaticTokenLinkCreate() {
public function testStaticTokenLinkCreate() {
// create field
// Create field.
$name = $this->randomName();
$settings = array(
'instance[settings][title]' => 'value',
'instance[settings][title_value]' => $name . ' [node:content-type:machine-name]');
'instance[settings][title_value]' => $name . ' [node:content-type:machine-name]',
);
$field_name = $this->createLinkField('page', $settings);
// create page form
// Create page form.
$this->drupalGet('node/add/page');
$this->assertField($field_name . '[und][0][url]', 'URL found');
$input = array(
'href' => 'http://example.com/' . $this->randomName()
'href' => 'http://example.com/' . $this->randomName(),
);
//$this->drupalLogin($this->web_user);
// $this->drupalLogin($this->web_user);.
$this->drupalGet('node/add/page');
$edit = array(
@@ -97,7 +105,7 @@ class LinkTokenTest extends LinkBaseTestClass {
$url = $this->getUrl();
// change to anonymous user
// Change to anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
@@ -106,30 +114,32 @@ class LinkTokenTest extends LinkBaseTestClass {
/**
* Creates a link field with a static title and an admin-entered token.
*
* Creates a node with a link and checks the title value.
*
* Basically, I want to make sure the [title-raw] token works, because it's a
* token that changes from node to node, where [type]'s always going to be the
* same.
*/
function testStaticTokenLinkCreate2() {
public function testStaticTokenLinkCreate2() {
// create field
// Create field.
$name = $this->randomName();
$settings = array(
'instance[settings][title]' => 'value',
'instance[settings][title_value]' => $name . ' [node:title]');
'instance[settings][title_value]' => $name . ' [node:title]',
);
$field_name = $this->createLinkField('page', $settings);
// create page form
// Create page form.
$this->drupalGet('node/add/page');
$this->assertField($field_name . '[und][0][url]', 'URL found');
$input = array(
'href' => 'http://example.com/' . $this->randomName()
'href' => 'http://example.com/' . $this->randomName(),
);
//$this->drupalLogin($this->web_user);
// $this->drupalLogin($this->web_user);.
$this->drupalGet('node/add/page');
$edit = array(
@@ -140,27 +150,32 @@ class LinkTokenTest extends LinkBaseTestClass {
$url = $this->getUrl();
// change to anonymous user
// Change to anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
$this->assertRaw(l($name . ' ' . $name, $input['href']));
}
// This test doesn't seem to actually work, due to lack of 'title' in url.
function _test_Link_With_Title_Attribute_token_url_form() {
/* $this->loginWithPermissions($this->permissions);
/**
* This test doesn't seem to actually work, due to lack of 'title' in url.
*
* @codingStandardsIgnoreStart
*/
public function _test_Link_With_Title_Attribute_token_url_form() {
// @codingStandardsIgnoreEnd
/* $this->loginWithPermissions($this->permissions);
$this->acquireContentTypes(1);
$field_settings = array(
'type' => 'link',
'widget_type' => 'link',
'type_name' => $this->content_types[0]->name,
'attributes' => array(
'class' => '',
'target' => 'default',
'rel' => 'nofollow',
'title' => '',
),
'type' => 'link',
'widget_type' => 'link',
'type_name' => $this->content_types[0]->name,
'attributes' => array(
'class' => '',
'target' => 'default',
'rel' => 'nofollow',
'title' => '',
),
);
$field = $this->createField($field_settings, 0);
@@ -170,10 +185,10 @@ class LinkTokenTest extends LinkBaseTestClass {
$url_type = str_replace('_', '-', $this->content_types[0]->type);
$edit = array('attributes[title]' => '['. $field_name .'-url]',
'enable_tokens' => TRUE);
'enable_tokens' => TRUE);
// @codingStandardsIgnoreLine
$this->drupalPost('admin/content/node-type/'. $url_type .'/fields/'. $field['field_name'],
$edit, t('Save field settings'));
$edit, t('Save field settings'));
$this->assertText(t('Saved field @field_name', array('@field_name' => $field['field_name'])));*/
$name = $this->randomName();
$settings = array(
@@ -183,12 +198,9 @@ class LinkTokenTest extends LinkBaseTestClass {
$field_name = $this->createLinkField('page', $settings);
// So, having saved this field_name, let's see if it works...
//$this->acquireNodes(1);
//$node = node_load($this->nodes[0]->nid);
//$this->drupalGet('node/'. $this->nodes[0]->nid);
// $this->acquireNodes(1);
// $node = node_load($this->nodes[0]->nid);
// $this->drupalGet('node/'. $this->nodes[0]->nid);.
$edit = array();
$test_link_url = 'http://www.example.com/test';
$edit[$field_name . '[und][0][url]'] = $test_link_url;
@@ -200,23 +212,28 @@ class LinkTokenTest extends LinkBaseTestClass {
$this->drupalPost(NULL, $edit, t('Save'));
// Make sure we get a new version!
//$node = node_load($this->nodes[0]->nid, NULL, TRUE);
// $node = node_load($this->nodes[0]->nid, NULL, TRUE);.
$this->assertText(t('Basic page @title has been updated.',
array('@title' => $name)));
array('@title' => $name)));
//$this->drupalGet('node/'. $node->nid);
// $this->drupalGet('node/'. $node->nid);.
$this->assertText($title, 'Make sure the link title/text shows');
$this->assertRaw(' title="' . $test_link_url . '"', "Do we show the link url as the title attribute?");
$this->assertNoRaw(' title="[' . $field_name . '-url]"');
$this->assertTrue(module_exists('token'), t('Assure that Token Module is enabled.'));
//$this->fail($this->content);
// $this->fail($this->content);.
}
/**
* Link With Title Attribute token title form.
*
* If the title of the link is set to the title attribute, then the title
* attribute isn't supposed to show.
*
* @codingStandardsIgnoreStart
*/
function _test_Link_With_Title_Attribute_token_title_form() {
public function _test_Link_With_Title_Attribute_token_title_form() {
// @codingStandardsIgnoreEnd
$this->loginWithPermissions($this->permissions);
$this->acquireContentTypes(1);
$field_settings = array(
@@ -233,21 +250,20 @@ class LinkTokenTest extends LinkBaseTestClass {
$field = $this->createField($field_settings, 0);
$field_name = $field['field_name'];
$field_db_info = content_database_info($field);
$url_type = str_replace('_', '-', $this->content_types[0]->type);
$edit = array('attributes[title]' => '[' . $field_name . '-title]',
'enable_tokens' => TRUE);
$edit = array(
'attributes[title]' => '[' . $field_name . '-title]',
'enable_tokens' => TRUE,
);
$this->drupalPost('admin/content/node-type/' . $url_type . '/fields/' . $field['field_name'],
$edit, t('Save field settings'));
$edit, t('Save field settings'));
$this->assertText(t('Saved field @field_name', array('@field_name' => $field['field_name'])));
// So, having saved this field_name, let's see if it works...
$this->acquireNodes(1);
$node = node_load($this->nodes[0]->nid);
$this->drupalGet('node/' . $this->nodes[0]->nid);
$edit = array();
@@ -260,24 +276,35 @@ class LinkTokenTest extends LinkBaseTestClass {
// Make sure we get a new version!
$node = node_load($this->nodes[0]->nid, NULL, TRUE);
$this->assertText(t('@type @title has been updated.',
array('@title' => $node->title,
'@type' => $this->content_types[0]->name)));
array(
'@title' => $node->title,
'@type' => $this->content_types[0]->name,
)));
$this->drupalGet('node/' . $node->nid);
$this->assertText($title, 'Make sure the link title/text shows');
$this->assertNoRaw(' title="' . $title . '"', "We should not show the link title as the title attribute?");
$this->assertNoRaw(' title="[' . $field_name . '-title]"');
//$this->fail($this->content);
// $this->fail($this->content);.
}
/**
* Trying to set the url to contain a token.
* Trying to set the url to contain a token.
*
* @codingStandardsIgnoreStart
*/
function _testUserTokenLinkCreateInURL() {
$this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content'));
public function _testUserTokenLinkCreateInURL() {
//@codingStandardsIgnoreEnd
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'access content',
'create page content',
));
$this->drupalLogin($this->web_user);
// create field
// Create field.
$name = strtolower($this->randomName());
$edit = array(
'_add_new_field[label]' => $name,
@@ -288,20 +315,21 @@ class LinkTokenTest extends LinkBaseTestClass {
$this->drupalPost('admin/content/node-type/page/fields', $edit, t('Save'));
$this->drupalPost(NULL, array(
'title' => 'required',
'enable_tokens' => 1), t('Save field settings'));
'enable_tokens' => 1,
), t('Save field settings'));
// Is field created?
$this->assertRaw(t('Added field %label.', array('%label' => $name)), 'Field added');
// create page form
// Create page form.
$this->drupalGet('node/add/page');
$field_name = 'field_' . $name;
$this->assertField($field_name . '[0][title]', 'Title found');
$this->assertField($field_name . '[0][url]', 'URL found');
$input = array(
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName(),
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName(),
);
$this->drupalLogin($this->web_user);
@@ -315,22 +343,31 @@ class LinkTokenTest extends LinkBaseTestClass {
$this->drupalPost(NULL, $edit, t('Save'));
$url = $this->getUrl();
// change to anonymous user
// Change to anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
$this->assertRaw(l($input['label'], $input['href'] . '/page'));
//$this->fail($this->content);
// $this->fail($this->content);.
}
/**
* Trying to set the url to contain a token.
* Trying to set the url to contain a token.
*
* @codingStandardsIgnoreStart
*/
function _testUserTokenLinkCreateInURL2() {
$this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content'));
public function _testUserTokenLinkCreateInURL2() {
// @codingStandardsIgnoreEnd
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'access content',
'create page content',
));
$this->drupalLogin($this->web_user);
// create field
// Create field.
$name = strtolower($this->randomName());
$edit = array(
'_add_new_field[label]' => $name,
@@ -341,20 +378,21 @@ class LinkTokenTest extends LinkBaseTestClass {
$this->drupalPost('admin/content/node-type/page/fields', $edit, t('Save'));
$this->drupalPost(NULL, array(
'title' => 'required',
'enable_tokens' => 1), t('Save field settings'));
'enable_tokens' => 1,
), t('Save field settings'));
// Is field created?
$this->assertRaw(t('Added field %label.', array('%label' => $name)), 'Field added');
// create page form
// Create page form.
$this->drupalGet('node/add/page');
$field_name = 'field_' . $name;
$this->assertField($field_name . '[0][title]', 'Title found');
$this->assertField($field_name . '[0][url]', 'URL found');
$input = array(
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName(),
'href' => 'http://example.com/' . $this->randomName(),
'label' => $this->randomName(),
);
$this->drupalLogin($this->web_user);
@@ -368,22 +406,34 @@ class LinkTokenTest extends LinkBaseTestClass {
$this->drupalPost(NULL, $edit, t('Save'));
$url = $this->getUrl();
// change to anonymous user
// Change to anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
$this->assertRaw(l($input['label'], $input['href'] . '/' . $this->web_user->uid));
}
/**
* Test that if you have a title and no url on a field which does not have tokens enabled,
* that the title is sanitized once.
* CRUD Title Only Title No Link.
*
* Test that if you have a title and no url on a field which does not have
* tokens enabled, that the title is sanitized once.
*
* @codingStandardsIgnoreStart
*/
function testCRUDTitleOnlyTitleNoLink2() {
$this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content'));
public function testCRUDTitleOnlyTitleNoLink2() {
//@codingStandardsIgnoreEnd
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'access content',
'create page content',
));
$this->drupalLogin($this->web_user);
// create field
// Create field.
$name = strtolower($this->randomName());
$field_name = 'field_' . $name;
$edit = array(
@@ -401,8 +451,8 @@ class LinkTokenTest extends LinkBaseTestClass {
// Is field created?
$this->assertRaw(t('Saved %label configuration', array('%label' => $name)), 'Field added');
// create page form
// Create page form.
$this->drupalGet('node/add/page');
$this->assertField($field_name . '[und][0][url]', 'URL found');
@@ -419,11 +469,12 @@ class LinkTokenTest extends LinkBaseTestClass {
$this->drupalPost(NULL, $edit, t('Save'));
$url = $this->getUrl();
// change to anonymous user
// Change to anonymous user.
$this->drupalLogout();
$this->drupalGet($url);
$this->assertRaw('This &amp; That');
}
}
@@ -5,8 +5,14 @@
* Tests that exercise the validation functions in the link module.
*/
/**
* Validate Test Case.
*/
class LinkValidateTestCase extends LinkBaseTestClass {
/**
* Create Link.
*/
protected function createLink($url, $title, $attributes = array()) {
return array(
'url' => $url,
@@ -17,35 +23,44 @@ class LinkValidateTestCase extends LinkBaseTestClass {
/**
* Takes a url, and sees if it can validate that the url is valid.
*
* @codingStandardsIgnoreStart
*/
protected function link_test_validate_url($url) {
// @codingStandardsIgnoreEnd
$field_name = $this->createLinkField();
$permission = 'create page content';
$this->checkPermissions(array($permission), TRUE);
$this->drupalGet('node/add/page');
$label = $this->randomName();
$edit = array(
$settings = array(
'title' => $label,
$field_name . '[und][0][title]' => $label,
$field_name . '[und][0][url]' => $url,
$field_name => array(
LANGUAGE_NONE => array(
array(
'title' => $label,
'url' => $url,
),
),
),
);
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertRaw(' has been created.', 'Node created');
$nid = 1; //$matches[1];
$node = $this->drupalCreateNode($settings);
$node = node_load($nid);
$this->assertNotNull($node, ' has been created.', 'Node created');
$this->assertEqual($url, $node->{$field_name}['und'][0]['url']);
$this->assertEqual($url, $node->{$field_name}[LANGUAGE_NONE][0]['url']);
}
}
/**
* Class for Validate Test.
*/
class LinkValidateTest extends LinkValidateTestCase {
/**
* Get Info.
*/
public static function getInfo() {
return array(
'name' => 'Link Validation Tests',
@@ -54,23 +69,35 @@ class LinkValidateTest extends LinkValidateTestCase {
);
}
function test_link_validate_basic_url() {
/**
* Validate basic URL.
*
* @codingStandardsIgnoreStart
*/
public function test_link_validate_basic_url() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('http://www.example.com');
}
/**
* Test if we're stopped from posting a bad url on default validation.
*
* @codingStandardsIgnoreStart
*/
function test_link_validate_bad_url_validate_default() {
$this->web_user = $this->drupalCreateUser(array('administer content types',
'administer nodes',
'administer filters',
'access content',
'create page content',
'access administration pages'));
public function test_link_validate_bad_url_validate_default() {
// @codingStandardsIgnoreEnd
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'administer nodes',
'administer filters',
'access content',
'create page content',
'access administration pages',
));
$this->drupalLogin($this->web_user);
// create field
// Create field.
$name = strtolower($this->randomName());
$edit = array(
'fields[_add_new_field][label]' => $name,
@@ -87,35 +114,43 @@ class LinkValidateTest extends LinkValidateTestCase {
node_types_rebuild();
menu_rebuild();
// create page form
// Create page form.
$this->drupalGet('node/add/page');
$field_name = 'field_' . $name;
$this->assertField('edit-field-' . $name . '-und-0-title', 'Title found');
$this->assertField('edit-field-' . $name . '-und-0-url', 'URL found');
$edit = array(
'title' => 'Simple Title',
$field_name . '[und][0][url]' => 'edik:naw',
);
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertText(t('The value @value provided for @field is not a valid URL.', array('@value' => 'edik:naw', '@field' => $name)));
$this->assertText(t('The value @value provided for @field is not a valid URL.', array(
'@value' => 'edik:naw',
'@field' => $name,
)));
}
/**
* Test if we're stopped from posting a bad url with validation on.
*
* @codingStandardsIgnoreStart
*/
function test_link_validate_bad_url_validate_on() {
$this->web_user = $this->drupalCreateUser(array('administer content types',
'administer nodes',
'administer filters',
'access content',
'create page content',
'access administration pages'));
public function test_link_validate_bad_url_validate_on() {
// @codingStandardsIgnoreEnd
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'administer nodes',
'administer filters',
'access content',
'create page content',
'access administration pages',
));
$this->drupalLogin($this->web_user);
// create field
// Create field.
$name = strtolower($this->randomName());
$edit = array(
'fields[_add_new_field][label]' => $name,
@@ -132,36 +167,44 @@ class LinkValidateTest extends LinkValidateTestCase {
node_types_rebuild();
menu_rebuild();
// create page form
// Create page form.
$this->drupalGet('node/add/page');
$field_name = 'field_' . $name;
$this->assertField('edit-field-' . $name . '-und-0-title', 'Title found');
$this->assertField('edit-field-' . $name . '-und-0-url', 'URL found');
$edit = array(
'title' => 'Simple Title',
$field_name . '[und][0][url]' => 'edik:naw',
);
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertText(t('The value @value provided for @field is not a valid URL.', array('@field' => $name, '@value' => 'edik:naw')));
$this->assertText(t('The value @value provided for @field is not a valid URL.', array(
'@field' => $name,
'@value' => 'edik:naw',
)));
}
/**
* Test if we can post a bad url if the validation is expressly turned off.
*
* @codingStandardsIgnoreStart
*/
function test_link_validate_bad_url_validate_off() {
$this->web_user = $this->drupalCreateUser(array('administer content types',
'administer nodes',
'administer filters',
'access content',
'create page content',
'access administration pages'));
public function test_link_validate_bad_url_validate_off() {
// @codingStandardsIgnoreEnd
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'administer nodes',
'administer filters',
'access content',
'create page content',
'access administration pages',
));
$this->drupalLogin($this->web_user);
// create field
// Create field.
$name = strtolower($this->randomName());
$edit = array(
'fields[_add_new_field][label]' => $name,
@@ -173,6 +216,7 @@ class LinkValidateTest extends LinkValidateTestCase {
$this->drupalPost(NULL, array(), t('Save field settings'));
$this->drupalPost(NULL, array('instance[settings][validate_url]' => FALSE), t('Save settings'));
// @codingStandardsIgnoreLine
/*$instance_details = db_query("SELECT * FROM {field_config_instance} WHERE field_name = :field_name AND bundle = 'page'", array(':field_name' => 'field_'. $name))->fetchObject();
$this->fail('<pre>'. print_r($instance_details, TRUE) .'</pre>');
$this->fail('<pre>'. print_r(unserialize($instance_details->data), TRUE) .'</pre>');*/
@@ -182,51 +226,62 @@ class LinkValidateTest extends LinkValidateTestCase {
node_types_rebuild();
menu_rebuild();
// create page form
// Create page form.
$this->drupalGet('node/add/page');
$field_name = 'field_' . $name;
$this->assertField('edit-field-' . $name . '-und-0-title', 'Title found');
$this->assertField('edit-field-' . $name . '-und-0-url', 'URL found');
$edit = array(
'title' => 'Simple Title',
$field_name . '[und][0][url]' => 'edik:naw',
);
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertNoText(t('The value %value provided for %field is not a valid URL.', array('%field' => $name, '%value' => 'edik:naw')));
$this->assertNoText(t('The value %value provided for %field is not a valid URL.', array(
'%field' => $name,
'%value' => 'edik:naw',
)));
}
/**
* Test if a bad url can sneak through un-filtered if we play with the validation...
* Validate switching between validation status.
*
* Test if a bad url can sneak through un-filtered if we play with the
* validation...
*
* @codingStandardsIgnoreStart
*/
function x_test_link_validate_switching_between_validation_status() {
public function x_test_link_validate_switching_between_validation_status() {
// @codingStandardsIgnoreEnd
$this->acquireContentTypes(1);
$this->web_user = $this->drupalCreateUser(array('administer content types',
'administer nodes',
'access administration pages',
'access content',
'create ' . $this->content_types[0]->type . ' content',
'edit any ' . $this->content_types[0]->type . ' content'));
$this->web_user = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
'administer nodes',
'access administration pages',
'access content',
'create ' . $this->content_types[0]->type . ' content',
'edit any ' . $this->content_types[0]->type . ' content',
));
$this->drupalLogin($this->web_user);
variable_set('node_options_' . $this->content_types[0]->name, array('status', 'promote'));
variable_set('node_options_' . $this->content_types[0]->name, array(
'status',
'promote',
));
$field_settings = array(
'type' => 'link',
'widget_type' => 'link',
'type_name' => $this->content_types[0]->name,
'attributes' => array(), // <-- This is needed or we have an error
// <-- This is needed or we have an error.
'attributes' => array(),
'validate_url' => 0,
);
$field = $this->createField($field_settings, 0);
//$this->fail('<pre>'. print_r($field, TRUE) .'</pre>');
$field_db_info = content_database_info($field);
$this->acquireNodes(2);
$node = node_load($this->nodes[0]->nid);
$this->drupalGet('node/' . $this->nodes[0]->nid);
$edit = array();
@@ -236,8 +291,12 @@ class LinkValidateTest extends LinkValidateTestCase {
$edit[$field['field_name'] . '[0][title]'] = $title;
$this->drupalPost('node/' . $this->nodes[0]->nid . '/edit', $edit, t('Save'));
//$this->pass($this->content);
$this->assertNoText(t('The value %value provided for %field is not a valid URL.', array('%field' => $name, '%value' => trim($url))));
// $this->pass($this->content);.
// @codingStandardsIgnoreLine
$this->assertNoText(t('The value %value provided for %field is not a valid URL.', array(
'%field' => $name,
'%value' => trim($url),
)));
// Make sure we get a new version!
$node = node_load($this->nodes[0]->nid, NULL, TRUE);
@@ -249,8 +308,9 @@ class LinkValidateTest extends LinkValidateTestCase {
// Turn the array validation back _on_.
$edit = array('validate_url' => TRUE);
$node_type_link = str_replace('_', '-', $node->type);
//$this->drupalGet('admin/content/node-type/'. $node_type_link .'/fields'); ///'. $field['field_name']);
//$this->fail($this->content);
// @codingStandardsIgnoreLine
// $this->drupalGet('admin/content/node-type/'. $node_type_link .'/fields'); ///'. $field['field_name']);
// $this->fail($this->content);.
$this->drupalPost('admin/content/node-type/' . $node_type_link . '/fields/' . $field['field_name'], $edit, t('Save field settings'));
$this->drupalGet('node/' . $node->nid);
@@ -258,36 +318,75 @@ class LinkValidateTest extends LinkValidateTestCase {
// url() function. But we should have a test that makes sure it continues
// to work.
$this->assertNoRaw($url, 'Make sure Javascript does not display.');
//$this->fail($this->content);
// $this->fail($this->content);.
}
// Validate that '<front>' is a valid url.
function test_link_front_url() {
/**
* Validate that '<front>' is a valid url.
*
* @codingStandardsIgnoreStart
*/
public function test_link_front_url() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('<front>');
}
// Validate that an internal url would be accepted.
function test_link_internal_url() {
$this->link_test_validate_url('node/32');
/**
* Validate that an internal url would be accepted.
*
* @codingStandardsIgnoreStart
*/
public function test_link_internal_url() {
// @codingStandardsIgnoreEnd
// Create the content first.
$node = $this->drupalCreateNode();
$link = 'node/' . $node->nid;
$this->link_test_validate_url($link);
$type = link_url_type($link);
$this->assertEqual(LINK_INTERNAL, $type, 'Test ' . $link . ' is an internal link.');
}
// Validate a simple mailto.
function test_link_mailto() {
/**
* Validate a simple mailto.
*
* @codingStandardsIgnoreStart
*/
public function test_link_mailto() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('mailto:jcfiala@gmail.com');
}
function test_link_external_https() {
/**
* Check link external https.
*
* @codingStandardsIgnoreStart
*/
public function test_link_external_https() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('https://www.example.com/');
}
function test_link_ftp() {
/**
* Check link FTP.
*
* @codingStandardsIgnoreStart
*/
public function test_link_ftp() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('ftp://www.example.com/');
}
}
/**
* Validate Test News.
*/
class LinkValidateTestNews extends LinkValidateTestCase {
/**
* Get Info.
*/
public static function getInfo() {
return array(
'name' => 'Link News Validation Tests',
@@ -296,18 +395,36 @@ class LinkValidateTestNews extends LinkValidateTestCase {
);
}
// Validate a news link to a message group
function test_link_news() {
/**
* Validate a news link to a message group.
*
* @codingStandardsIgnoreStart
*/
public function test_link_news() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('news:comp.infosystems.www.misc');
}
// Validate a news link to a message id. Said ID copied off of google groups.
function test_link_news_message() {
/**
* Validate a news link to a message id. Said ID copied off of google groups.
*
* @codingStandardsIgnoreStart
*/
public function test_link_news_message() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('news:hj0db8$vrm$1@news.eternal-september.org');
}
}
/**
* Validate Specific URL.
*/
class LinkValidateSpecificURL extends LinkValidateTestCase {
/**
* Get Info.
*/
public static function getInfo() {
return array(
'name' => 'Link Specific URL Validation Tests',
@@ -316,33 +433,65 @@ class LinkValidateSpecificURL extends LinkValidateTestCase {
);
}
// Lets throw in a lot of umlouts for testing!
function test_umlout_url() {
/**
* Lets throw in a lot of umlouts for testing!
*
* @codingStandardsIgnoreStart
*/
public function test_umlout_url() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('http://üÜü.exämple.com/nöde');
}
function test_umlout_mailto() {
/**
* Check umlout mailto.
*
* @codingStandardsIgnoreStart
*/
public function test_umlout_mailto() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('mailto:Üser@exÅmple.com');
}
function test_german_b_url() {
/**
* Check german b in url.
*
* @codingStandardsIgnoreStart
*/
public function test_german_b_url() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('http://www.test.com/ßstuff');
}
function test_special_n_url() {
/**
* Check Special in url.
*
* @codingStandardsIgnoreStart
*/
public function test_special_n_url() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('http://www.testÑñ.com/');
}
function test_curly_brackets_in_query() {
/**
* Curly Brackets in query.
*
* @codingStandardsIgnoreStart
*/
public function test_curly_brackets_in_query() {
// @codingStandardsIgnoreEnd
$this->link_test_validate_url('http://www.healthyteennetwork.org/index.asp?Type=B_PR&SEC={2AE1D600-4FC6-4B4D-8822-F1D5F072ED7B}&DE={235FD1E7-208D-4363-9854-4E6775EB8A4C}');
}
/**
* Here, we're testing that a very long url is stored properly in the db.
*
* Basicly, trying to test http://drupal.org/node/376818
* Basically, trying to test http://drupal.org/node/376818
*
* @codingStandardsIgnoreStart
*/
function testLinkURLFieldIsBig() {
public function testLinkURLFieldIsBig() {
// @codingStandardsIgnoreEnd
$long_url = 'http://th.wikipedia.org/wiki/%E0%B9%82%E0%B8%A3%E0%B8%87%E0%B9%80%E0%B8%A3%E0%B8%B5%E0%B8%A2%E0%B8%99%E0%B9%80%E0%B8%9A%E0%B8%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%B8%B2%E0%B8%8A%E0%B8%B9%E0%B8%97%E0%B8%B4%E0%B8%A8_%E0%B8%99%E0%B8%84%E0%B8%A3%E0%B8%A8%E0%B8%A3%E0%B8%B5%E0%B8%98%E0%B8%A3%E0%B8%A3%E0%B8%A1%E0%B8%A3%E0%B8%B2%E0%B8%8A';
$this->link_test_validate_url($long_url);
}
@@ -350,12 +499,18 @@ class LinkValidateSpecificURL extends LinkValidateTestCase {
}
/**
* A series of tests of links, only going against the link_validate_url function in link.module.
* Validate Url Light.
*
* A series of tests of links, only going against the link_validate_url function
* in link.module.
*
* Validation is guided by the rules in http://tools.ietf.org/html/rfc1738 !
*/
class LinkValidateUrlLight extends DrupalWebTestCase {
/**
* Get Info.
*/
public static function getInfo() {
return array(
'name' => 'Link Light Validation Tests',
@@ -365,73 +520,117 @@ class LinkValidateUrlLight extends DrupalWebTestCase {
}
/**
* Translates the LINK type constants to english for display and debugging of tests
* Setup.
*/
function name_Link_Type($type) {
public function setUp() {
parent::setUp('link');
}
/**
* Name Link Type.
*
* Translates the LINK type constants to english for display and debugging of
* tests.
*
* @codingStandardsIgnoreStart
*/
public function name_Link_Type($type) {
// @codingStandardsIgnoreEnd
switch ($type) {
case LINK_FRONT:
return "Front";
case LINK_EMAIL:
return "Email";
case LINK_NEWS:
return "Newsgroup";
case LINK_INTERNAL:
return "Internal Link";
case LINK_EXTERNAL:
return "External Link";
case FALSE:
return "Invalid Link";
default:
return "Bad Value:" . $type;
}
}
// Make sure that a link labelled <front> works.
function testValidateFrontLink() {
/**
* Make sure that a link labeled <front> works.
*/
public function testValidateFrontLink() {
$valid = link_validate_url('<front>');
$this->assertEqual(LINK_FRONT, $valid, 'Make sure that front link is verfied and identified');
$this->assertEqual(LINK_FRONT, $valid, 'Make sure that front link is verified and identified');
}
function testValidateEmailLink() {
/**
* Validate Email Link.
*/
public function testValidateEmailLink() {
$valid = link_validate_url('mailto:bob@example.com');
$this->assertEqual(LINK_EMAIL, $valid, "Make sure a basic mailto is verified and identified");
}
function testValidateEmailLinkBad() {
/**
* Validate Email Link Bad.
*/
public function testValidateEmailLinkBad() {
$valid = link_validate_url(':bob@example.com');
$this->assertEqual(FALSE, $valid, 'Make sure just a bad address is correctly failed');
}
function testValidateNewsgroupLink() {
/**
* Validate Newsgroup Link.
*/
public function testValidateNewsgroupLink() {
$valid = link_validate_url('news:comp.infosystems.www.misc');
$this->assertEqual(LINK_NEWS, $valid, 'Make sure link to newsgroup validates as news.');
}
function testValidateNewsArticleLink() {
/**
* Validate News Article Link.
*/
public function testValidateNewsArticleLink() {
$valid = link_validate_url('news:hj0db8$vrm$1@news.eternal-september.org');
$this->assertEqual(LINK_NEWS, $valid, 'Make sure link to specific article valiates as news.');
$this->assertEqual(LINK_NEWS, $valid, 'Make sure link to specific article validates as news.');
}
function testValidateBadNewsgroupLink() {
/**
* Validate Bad Newsgroup Link.
*/
public function testValidateBadNewsgroupLink() {
$valid = link_validate_url('news:comp.bad_name.misc');
$this->assertEqual(FALSE, $valid, 'newsgroup names can\'t contain underscores, so it should come back as invalid.');
}
function testValidateInternalLinks() {
/**
* Validate Internal Links.
*/
public function testValidateInternalLinks() {
$tempfile = drupal_tempnam('public://files', 'test');
$links = array(
'node/5',
'rss.xml',
'files/test.jpg',
'/var/www/test',
file_uri_target($tempfile),
drupal_realpath($tempfile),
);
foreach ($links as $link) {
$type = link_url_type($link);
$this->assertEqual(LINK_INTERNAL, $type, 'Test ' . $link . ' is an internal link.');
$valid = link_validate_url($link);
$this->assertEqual(LINK_INTERNAL, $valid, 'Test ' . $link . ' internal link.');
$this->assertTrue($valid, 'Test ' . $link . ' is valid internal link.');
}
}
function testValidateExternalLinks() {
/**
* Validate External Links.
*/
public function testValidateExternalLinks() {
$links = array(
'http://localhost:8080/',
'www.example.com',
@@ -446,9 +645,8 @@ class LinkValidateUrlLight extends DrupalWebTestCase {
'http://255.255.255.255:4823/',
'www.test-site.com',
'http://example.com/index.php?q=node/123',
'http://example.com/index.php?page=this\that',
'http://example.com/?first_name=Joe Bob&last_name=Smith',
// Anchors
// Anchors.
'http://www.example.com/index.php#test',
'http://www.example.com/index.php#this@that.',
'http://www.example.com/index.php#',
@@ -456,39 +654,61 @@ class LinkValidateUrlLight extends DrupalWebTestCase {
'http://www.archive.org/stream/aesopsfables00aesorich#page/n7/mode/2up',
'http://www.example.com/blah/#this@that?',
);
// Test all of the protocols.
$allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'));
$allowed_protocols = variable_get('filter_allowed_protocols', array(
'http',
'https',
'ftp',
'news',
'nntp',
'telnet',
'mailto',
'irc',
'ssh',
'sftp',
'webcal',
));
foreach ($allowed_protocols as $protocol) {
if ($protocol !== 'news' && $protocol !== 'mailto') {
$links[] = $protocol . '://www.example.com';
}
}
foreach ($links as $link) {
$type = link_url_type($link);
$this->assertEqual(LINK_EXTERNAL, $type, 'Testing that ' . $link . ' is an external link.');
$valid = link_validate_url($link);
$this->assertEqual(LINK_EXTERNAL, $valid, 'Testing that ' . $link . ' is a valid external link.');
// The following two lines are commented out and only used for comparisons.
//$valid2 = valid_url($link, TRUE);
//$this->assertEqual(TRUE, $valid2, "Using valid_url() on $link.");
$this->assertTrue($valid, 'Test ' . $link . ' is valid external link.');
// The following two lines are commented out and only used for
// comparisons.
// $valid2 = valid_url($link, TRUE);
// $this->assertEqual(TRUE, $valid2, "Using valid_url() on $link.");.
}
// Test if we can make a tld valid:
variable_set('link_extra_domains', array('frog'));
$valid = link_validate_url('http://www.example.frog');
$this->assertEqual(LINK_EXTERNAL, $valid, "Testing that http://www.example.frog is a valid external link if we've added 'frog' to the list of valid domains.");
}
function testInvalidExternalLinks() {
/**
* Check Invalid External Links.
*/
public function testInvalidExternalLinks() {
$links = array(
'http://www.ex ample.com/',
'http://25.0.0/', // bad ip!
// Bad ip!
'http://25.0.0/',
'http://4827.0.0.2/',
'//www.example.com/',
'http://www.testß.com/', // ß not allowed in domain names!
'http://www.example.frog/', // Bad TLD
//'http://www.-fudge.com/', // domains can't have sections starting with a dash.
// ß not allowed in domain names!
'http://www.testß.com/',
// Bad TLD.
'http://.www.foo.bar./',
// Domains can't have sections starting with a dash.
// 'http://www.-fudge.com/',
'http://example.com/index.php?page=this\that',
'example@example.com',
);
foreach ($links as $link) {
$valid = link_validate_url($link);
$this->assertEqual(FALSE, $valid, 'Testing that ' . $link . ' is not a valid link.');
}
}
}
@@ -1,4 +1,7 @@
<?php
// @codingStandardsIgnoreFile
/**
* @file
* Contains functions handling views integration.
@@ -7,20 +7,26 @@
/**
* Argument handler to filter results by target.
*
* @codingStandardsIgnoreStart
*/
class link_views_handler_argument_target extends views_handler_argument {
/**
* Provide defaults for the argument when a new one is created.
*/
function options(&$options) {
parent::options($options);
}
function option_definition() {
$options = parent::option_definition();
return $options;
}
/**
* Provide a default options form for the argument.
*
* @codingStandardsIgnoreStart
*/
function options_form(&$form, &$form_state) {
public function options_form(&$form, &$form_state) {
// @codingStandardsIgnoreEnd
$defaults = $this->default_actions();
$form['title'] = array(
@@ -52,7 +58,7 @@ class link_views_handler_argument_target extends views_handler_argument {
$form['wildcard'] = array(
'#prefix' => '<div class="views-right-50">',
// prefix and no suffix means these two items will be grouped together.
// Prefix and no suffix means these two items will be grouped together.
'#type' => 'textfield',
'#title' => t('Wildcard'),
'#size' => 20,
@@ -125,8 +131,8 @@ class link_views_handler_argument_target extends views_handler_argument {
asort($validate_types);
$form['validate_type']['#options'] = $validate_types;
// Show this gadget if *anything* but 'none' is selected
// Show this gadget if *anything* but 'none' is selected.
$form['validate_fail'] = array(
'#type' => 'select',
'#title' => t('Action to take if argument does not validate'),
@@ -140,10 +146,11 @@ class link_views_handler_argument_target extends views_handler_argument {
*
* The argument sent may be found at $this->argument.
*/
function query($group_by = FALSE) {
public function query($group_by = FALSE) {
$this->ensure_my_table();
// Because attributes are stored serialized, our only option is to also
// serialize the data we're searching for and use LIKE to find similar data.
$this->query->add_where(0, $this->table_alias . ' . ' . $this->real_field . " LIKE '%%%s%'", serialize(array('target' => $this->argument)));
}
}
@@ -7,22 +7,30 @@
/**
* Filter handler for limiting a view to URLs of a certain protocol.
*
* @codingStandardsIgnoreStart
*/
class link_views_handler_filter_protocol extends views_handler_filter_string {
/**
* Set defaults for the filter options.
*
* @codingStandardsIgnoreEnd
*/
function options(&$options) {
parent::options($options);
function option_definition() {
$options = parent::option_definition();
$options['operator'] = 'OR';
$options['value'] = 'http';
$options['case'] = 0;
return $options;
}
/**
* Define the operators supported for protocols.
*/
function operators() {
public function operators() {
$operators = array(
'OR' => array(
'title' => t('Is one of'),
@@ -35,7 +43,13 @@ class link_views_handler_filter_protocol extends views_handler_filter_string {
return $operators;
}
function options_form(&$form, &$form_state) {
/**
* Options form.
*
* @codingStandardsIgnoreStart
*/
public function options_form(&$form, &$form_state) {
//@codingStandardsIgnoreEnd
parent::options_form($form, $form_state);
$form['case'] = array(
'#type' => 'value',
@@ -45,8 +59,11 @@ class link_views_handler_filter_protocol extends views_handler_filter_string {
/**
* Provide a select list to choose the desired protocols.
*
* @codingStandardsIgnoreStart
*/
function value_form(&$form, &$form_state) {
public function value_form(&$form, &$form_state) {
// @codingStandardsIgnoreEnd
// We have to make some choices when creating this as an exposed
// filter form. For example, if the operator is locked and thus
// not rendered, we can't render dependencies; instead we only
@@ -61,7 +78,19 @@ class link_views_handler_filter_protocol extends views_handler_filter_string {
'#type' => 'select',
'#title' => t('Protocol'),
'#default_value' => $this->value,
'#options' => drupal_map_assoc(variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'))),
'#options' => drupal_map_assoc(variable_get('filter_allowed_protocols', array(
'http',
'https',
'ftp',
'news',
'nntp',
'telnet',
'mailto',
'irc',
'ssh',
'sftp',
'webcal',
))),
'#multiple' => 1,
'#size' => 4,
'#description' => t('The protocols displayed here are those globally available. You may add more protocols by modifying the <em>filter_allowed_protocols</em> variable in your installation.'),
@@ -71,8 +100,11 @@ class link_views_handler_filter_protocol extends views_handler_filter_string {
/**
* Filter down the query to include only the selected protocols.
*
* @codingStandardsIgnoreStart
*/
function op_protocol($field, $upper) {
public function op_protocol($field, $upper) {
// @codingStandardsIgnoreEnd
$db_type = db_driver();
$protocols = $this->value;
@@ -82,20 +114,25 @@ class link_views_handler_filter_protocol extends views_handler_filter_string {
// Simple case, the URL begins with the specified protocol.
$condition = $field . ' LIKE \'' . $protocol . '%\'';
// More complex case, no protocol specified but is automatically cleaned up
// by link_cleanup_url(). RegEx is required for this search operation.
// More complex case, no protocol specified but is automatically cleaned
// up by link_cleanup_url(). RegEx is required for this search operation.
if ($protocol == 'http') {
$LINK_DOMAINS = _link_domains();
$link_domains = _link_domains();
if ($db_type == 'pgsql') {
// PostGreSQL code has NOT been tested. Please report any problems to the link issue queue.
// pgSQL requires all slashes to be double escaped in regular expressions.
// PostGreSQL code has NOT been tested. Please report any problems to
// the link issue queue.
// pgSQL requires all slashes to be double escaped in regular
// expressions.
// @codingStandardsIgnoreLine
// See http://www.postgresql.org/docs/8.1/static/functions-matching.html#FUNCTIONS-POSIX-REGEXP
$condition .= ' OR ' . $field . ' ~* \'' . '^(([a-z0-9]([a-z0-9\\-_]*\\.)+)(' . $LINK_DOMAINS . '|[a-z][a-z]))' . '\'';
$condition .= ' OR ' . $field . ' ~* \'' . '^(([a-z0-9]([a-z0-9\\-_]*\\.)+)(' . $link_domains . '|[a-z][a-z]))' . '\'';
}
else {
// mySQL requires backslashes to be double (triple?) escaped within character classes.
// mySQL requires backslashes to be double (triple?) escaped within
// character classes.
// @codingStandardsIgnoreLine
// See http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html#operator_regexp
$condition .= ' OR ' . $field . ' REGEXP \'' . '^(([a-z0-9]([a-z0-9\\\-_]*\.)+)(' . $LINK_DOMAINS . '|[a-z][a-z]))' . '\'';
$condition .= ' OR ' . $field . ' REGEXP \'' . '^(([a-z0-9]([a-z0-9\\\-_]*\.)+)(' . $link_domains . '|[a-z][a-z]))' . '\'';
}
}
@@ -104,4 +141,5 @@ class link_views_handler_filter_protocol extends views_handler_filter_string {
$this->query->add_where($this->options['group'], implode(' ' . $this->operator . ' ', $where_conditions));
}
}
@@ -3,6 +3,46 @@ Title 7.x-1.x, xxxx-xx-xx
-------------------------
Title 7.x-1.0-alpha9, 2017-01-13
--------------------------------
#2757739 by dewalt, Pol: Token value is not sanitized, when replaced from title field.
#2813673 by plach, czigor, Stevel: Tests broken since new permission in drupal core.
Title 7.x-1.0-alpha8, 2016-03-28
--------------------------------
#2465141 by DuaelFr, Matthijs, jfrederick: Used entity_uri() options if given.
#2602568 by sdstyles: Fixed title_entity_label() should be documented as
callback implementation.
#2605040 by ShaxA, joelpittet, sylus: Removed recursive call to
entity_get_info().
#2040055 by flux423, cs_shadow, visabhishek, plach, OlyN: Fixed Notice:
Undefined index: safe_value in title_field_formatter_view().
#2426105 by Peacog, jcisio: Fixed Views "Link this field to the original entity"
doesn't work when using relationship.
#2286147 by plach: Language fallback does not work when an entity translation is
unpublished.
#2286145 by plach: Prevent empty translations from being synced into the legacy
field.
#1772116 by duellj, GaëlG | f4o: Fixed Menu link title is not getting node title
by default.
#1779268 by ndobromirov | brycesenz: Undefined index: field_name in
title_field_views_data_alter().
#1441224 by MiroslavBanov | OPIN: Fieldgroup and Title break Manage Fields UI.
#1980520 by StoraH, pbz1912: Fixed Empty wrapper tags.
#1920096 by Johnny vd Laar | GiorgosK: Fixed Title incompatibility with the Term
reference widget
#1991712 by milesw: Fixed Title displays wrong revision using Revisioning
module.
#1907078 by sylus: Fixed Undefined index: field_name() in
title_field_replacement_enabled().
#1961810 by peximo | fcortez: Fixed Wrap tag classes are not being inserted.
#1850866 by B-Prod: Fixed Undefined index 'format' in
title_field_text_with_summary_sync_set().
#1269076 by plach, das-peter, danielnolde | renat: Fixed Translated
title_field replaces node->title with translated value.
Title 7.x-1.0-alpha7, 2013-03-18
--------------------------------
#1919640 by peximo: Fixed Empty tag shown when the title field is displayed.
@@ -0,0 +1,13 @@
Title is built and maintained by the Drupal project community.
Everyone is encouraged to submit issues and changes (patches) to improve it, and
to contribute in other ways -- see http://drupal.org/contribute to find out how.
Project maintainers
-------------------
The Title maintainers oversee the development of the project as a whole. The project
maintainers for Title are:
- Francesco Placella 'plach' <http://drupal.org/u/plach>, branch 7.x-1.x
- Sam Becker 'Sam152' <https://www.drupal.org/u/sam152>, branch 8.x-2.x
@@ -9,6 +9,7 @@
* Tests for legacy field replacement.
*/
class TitleFieldReplacementTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Field replacement',
@@ -17,14 +18,17 @@ class TitleFieldReplacementTestCase extends DrupalWebTestCase {
);
}
function setUp() {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp('entity', 'field_test', 'title', 'title_test');
}
/**
* Test field replacement API and workflow.
*/
function testFieldReplacementWorkflow() {
public function testFieldReplacementWorkflow() {
$info = entity_get_info('test_entity');
$label_key = $info['entity keys']['label'];
$field_name = $label_key . '_field';
@@ -72,12 +76,13 @@ class TitleFieldReplacementTestCase extends DrupalWebTestCase {
// Clear field cache so synchronization can be performed on field attach
// load.
cache_clear_all('*', 'cache_field');
drupal_static_reset();
// Check that the replacing field value is correctly synchronized on load
// and view.
$entity = title_test_entity_test_load($entity);
title_test_phase_check('after_load', $entity);
$build = entity_view('test_entity', array($entity->ftid => $entity));
entity_view('test_entity', array($entity->ftid => $entity));
foreach (title_test_phase_store() as $phase => $value) {
$this->assertTrue($value, t('Field synchronization is correctly performed on %phase.', array('%phase' => $phase)));
@@ -94,8 +99,16 @@ class TitleFieldReplacementTestCase extends DrupalWebTestCase {
/**
* Test field replacement UI.
*/
function testFieldReplacementUI() {
$admin_user = $this->drupalCreateUser(array('access administration pages', 'view the administration theme', 'administer content types', 'administer taxonomy', 'administer comments'));
public function testFieldReplacementUI() {
$permissions = array(
'access administration pages',
'view the administration theme',
'administer content types',
'administer taxonomy',
'administer comments',
'administer fields',
);
$admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($admin_user);
foreach (entity_get_info() as $entity_type => $entity_info) {
@@ -146,12 +159,14 @@ class TitleFieldReplacementTestCase extends DrupalWebTestCase {
}
}
}
}
/**
* Tests for legacy field replacement.
*/
class TitleAdminSettingsTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Admin settings',
@@ -160,7 +175,10 @@ class TitleAdminSettingsTestCase extends DrupalWebTestCase {
);
}
function setUp() {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp('field_test', 'title', 'title_test');
$admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer taxonomy'));
$this->drupalLogin($admin_user);
@@ -169,15 +187,18 @@ class TitleAdminSettingsTestCase extends DrupalWebTestCase {
/**
* Check for automated title_field attachment.
*/
function testAutomatedFieldAttachement() {
$this->assertAutomatedFieldAttachement(TRUE);
$this->assertAutomatedFieldAttachement(FALSE);
public function testAutomatedFieldAttachment() {
$this->doTestAutomatedFieldAttachment(TRUE);
$this->doTestAutomatedFieldAttachment(FALSE);
}
/**
* Check that the fields are replaced or skipped depdening on the given value.
* Check that the fields are replaced or skipped depending on the given value.
*
* @param bool $enabled
* Whether replacement is enabled or not.
*/
function assertAutomatedFieldAttachement($enabled) {
public function doTestAutomatedFieldAttachment($enabled) {
$edit = array(
'title_taxonomy_term[auto_attach][name]' => $enabled,
'title_taxonomy_term[auto_attach][description]' => $enabled,
@@ -197,12 +218,14 @@ class TitleAdminSettingsTestCase extends DrupalWebTestCase {
$this->assertTrue(title_field_replacement_enabled($entity_type, $bundle, 'name') == $enabled, 'Name field correctly processed.');
$this->assertTrue(title_field_replacement_enabled($entity_type, $bundle, 'description') == $enabled, 'Description field correctly processed.');
}
}
/**
* Tests for legacy field replacement.
*/
class TitleTranslationTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Replaced fields translation',
@@ -211,11 +234,22 @@ class TitleTranslationTestCase extends DrupalWebTestCase {
);
}
function setUp() {
parent::setUp('locale', 'entity_translation', 'title');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp('locale', 'entity_translation', 'title', 'field_test', 'title_test');
// Create a power user.
$admin_user = $this->drupalCreateUser(array('administer modules', 'view the administration theme', 'administer languages', 'administer taxonomy', 'administer entity translation', 'translate any entity'));
$permissions = array(
'administer modules',
'view the administration theme',
'administer languages',
'administer taxonomy',
'administer entity translation',
'translate any entity',
);
$admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($admin_user);
// Enable a translation language.
@@ -240,6 +274,7 @@ class TitleTranslationTestCase extends DrupalWebTestCase {
$edit = array(
'name' => $this->randomString(),
'machine_name' => $name,
'entity_translation_taxonomy' => 1,
);
$this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
$this->vocabulary = taxonomy_vocabulary_machine_name_load($name);
@@ -252,19 +287,118 @@ class TitleTranslationTestCase extends DrupalWebTestCase {
$t_args = array('%legacy_field' => $legacy_field);
$this->assertTrue(field_info_instance($entity_type, $info['field']['field_name'], $name), t('The %legacy_field field has been correctly replaced.', $t_args));
}
// Ensure static caches do not interfere with API calls.
drupal_static_reset();
}
/**
* Test taxonomy translation workflow.
* Tests taxonomy programmatic translation workflow.
*/
function testTranslationWorkflow() {
public function testProgrammaticTranslationWorkflow() {
// Create a taxonomy term and assign it an original language different from
// the default language.
$langcode = 'it';
$original_values = array(
'name' => $langcode . '_' . $this->randomName(),
'description' => $langcode . '_' . $this->randomName(),
);
$term = (object) ($original_values + array(
'format' => 'filtered_html',
'vocabulary_machine_name' => $this->vocabulary->machine_name,
'vid' => $this->vocabulary->vid,
));
entity_translation_get_handler('taxonomy_term', $term)->setOriginalLanguage($langcode);
taxonomy_term_save($term);
$this->assertTrue($this->checkLegacyValues($term, $original_values), 'Legacy field values correctly stored.');
$term = $this->termLoad($term->tid);
$this->assertTrue($this->checkFieldValues($term, $original_values, $langcode), 'Replacing field values correctly created from the legacy field values.');
// Pollute synchronization cache to ensure the expected values are stored
// anyway.
title_entity_sync('taxonomy_term', $term, $langcode);
// Create a translation using the default language.
$translation_langcode = language_default()->language;
$translated_values = array(
'name' => $translation_langcode . '_' . $this->randomName(),
'description' => $translation_langcode . '_' . $this->randomName(),
);
foreach ($translated_values as $name => $value) {
$term->{$name} = $value;
}
$translation = array(
'language' => $translation_langcode,
'source' => $langcode,
'uid' => $this->loggedInUser->uid,
'status' => 1,
'translate' => 0,
'created' => REQUEST_TIME,
'changed' => REQUEST_TIME,
);
entity_translation_get_handler('taxonomy_term', $term)->setTranslation($translation);
taxonomy_term_save($term);
$this->assertTrue($this->checkLegacyValues($term, $original_values), 'Legacy field values correctly stored.');
$term = $this->termLoad($term->tid, $translation_langcode);
$this->assertTrue($this->checkFieldValues($term, $translated_values, $translation_langcode), 'Replacing field translations correctly created.');
$this->assertTrue($this->checkFieldValues($term, $original_values, $langcode, FALSE), 'Replacing field original values correctly preserved.');
// Delete the translation.
entity_translation_get_handler('taxonomy_term', $term)->removeTranslation($translation_langcode);
taxonomy_term_save($term);
$this->assertTrue($this->checkLegacyValues($term, $original_values), 'Legacy field values correctly stored.');
$term = $this->termLoad($term->tid, $langcode);
$this->assertTrue($this->checkFieldValues($term, $original_values, $langcode), 'Replacing field translations correctly deleted.');
// Make the term language neutral.
entity_translation_get_handler('taxonomy_term', $term)->setOriginalLanguage(LANGUAGE_NONE);
foreach ($original_values as $name => $value) {
$field_name = $name . '_field';
$term->{$field_name}[LANGUAGE_NONE] = $term->{$field_name}[$langcode];
$term->{$field_name}[$langcode] = array();
}
taxonomy_term_save($term);
$this->assertTrue($this->checkLegacyValues($term, $original_values), 'Legacy field values correctly stored.');
$term = $this->termLoad($term->tid);
$this->assertTrue($this->checkFieldValues($term, $original_values, LANGUAGE_NONE), 'Term original language correctly changed to the former translation language.');
// Change the term language to the former translation language.
entity_translation_get_handler('taxonomy_term', $term)->setOriginalLanguage($translation_langcode);
foreach ($original_values as $name => $value) {
$field_name = $name . '_field';
$term->{$field_name}[$translation_langcode] = $term->{$field_name}[LANGUAGE_NONE];
$term->{$field_name}[LANGUAGE_NONE] = array();
}
taxonomy_term_save($term);
$this->assertTrue($this->checkLegacyValues($term, $original_values), 'Legacy field values correctly stored.');
$term = $this->termLoad($term->tid, $translation_langcode);
$this->assertTrue($this->checkFieldValues($term, $original_values, $translation_langcode), 'Term original language correctly changed to language neutral.');
// Make a replacing field untranslatable and change its value.
$field_name = 'name_field';
$field = field_info_field($field_name);
$field['translatable'] = FALSE;
field_update_field($field);
$original_values['name'] = LANGUAGE_NONE . '_' . $this->randomName();
$term->name = $original_values['name'];
taxonomy_term_save($term);
$this->assertTrue($this->checkLegacyValues($term, $original_values), 'Legacy field values correctly stored.');
$term = $this->termLoad($term->tid);
$this->assertEqual($term->{$field_name}[LANGUAGE_NONE][0]['value'], $original_values['name'], 'Untranslatable replacing field on translatable entity correctly handled.');
}
/**
* Tests taxonomy form translation workflow.
*/
public function testFormTranslationWorkflow() {
// Create a taxonomy term and check that legacy fields are properly
// populated.
$original_values = array(
'name' => $this->randomName(),
'description' => $this->randomName(),
);
$edit = $this->editValues($original_values, 'en');
$langcode = 'en';
$edit = $this->editValues($original_values, $langcode);
$this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', $edit, t('Save'));
$term = current(entity_load('taxonomy_term', FALSE, array('name' => $original_values['name']), TRUE));
$this->assertEqual($term->description, $original_values['description'], t('Taxonomy term created.'));
@@ -275,11 +409,12 @@ class TitleTranslationTestCase extends DrupalWebTestCase {
'name' => $this->randomName(),
'description' => $this->randomName(),
);
$translation_langcode = 'it';
$edit = $this->editValues($translated_values, 'it');
$this->drupalPost('it/taxonomy/term/' . $term->tid . '/edit/add/en/it', $edit, t('Save'));
$term = current(entity_load('taxonomy_term', array($term->tid), array(), TRUE));
$this->assertTrue($this->checkFieldValues($term, $translated_values, 'it'), t('Taxonomy term translation created.'));
$this->assertTrue($this->checkFieldValues($term, $original_values, 'en'), t('Taxonomy term original values preserved.'));
$this->drupalPost($translation_langcode . '/taxonomy/term/' . $term->tid . '/edit/add/' . $langcode . '/' . $translation_langcode, $edit, t('Save'));
$term = $this->termLoad($term->tid);
$this->assertTrue($this->checkFieldValues($term, $translated_values, $translation_langcode, FALSE), t('Taxonomy term translation created.'));
$this->assertTrue($this->checkFieldValues($term, $original_values, $langcode), t('Taxonomy term original values preserved.'));
// Check that legacy fields have the correct values.
$this->assertEqual($term->name, $original_values['name'], t('Taxonomy term name correctly stored.'));
@@ -291,31 +426,69 @@ class TitleTranslationTestCase extends DrupalWebTestCase {
'name' => $this->randomName(),
'description' => $this->randomName(),
);
$edit = $this->editValues($translated_values, 'it');
$this->drupalPost('it/taxonomy/term/' . $term->tid . '/edit/it', $edit, t('Save'));
$term = current(entity_load('taxonomy_term', array($term->tid), array(), TRUE));
$this->assertTrue($this->checkFieldValues($term, $translated_values, 'it'), t('Taxonomy term translation updated.'));
$this->assertTrue($this->checkFieldValues($term, $original_values, 'en'), t('Taxonomy term original values preserved.'));
$edit = $this->editValues($translated_values, $translation_langcode);
$this->drupalPost($translation_langcode . '/taxonomy/term/' . $term->tid . '/edit/' . $translation_langcode, $edit, t('Save'));
$term = $this->termLoad($term->tid);
$this->assertTrue($this->checkFieldValues($term, $translated_values, $translation_langcode, FALSE), t('Taxonomy term translation updated.'));
$this->assertTrue($this->checkFieldValues($term, $original_values, $langcode), t('Taxonomy term original values preserved.'));
// Check that legacy fields have the correct values.
$this->assertEqual($term->name, $original_values['name'], t('Taxonomy term name correctly stored.'));
$this->assertEqual($term->description, $original_values['description'], t('Taxonomy term description correctly stored.'));
}
/**
* Loads a term using the given language as active language.
*
* @param int $tid
* The term identifier.
* @param string|null $langcode
* (optional) The active language to be set. Defaults to none.
*
* @return object|bool
* A term object.
*/
protected function termLoad($tid, $langcode = NULL) {
drupal_static_reset();
title_active_language($langcode);
return current(entity_load('taxonomy_term', array($tid), array(), TRUE));
}
/**
* Returns the drupalPost() $edit array corresponding to the given values.
*/
protected function editValues($values, $langcode) {
$edit = array();
foreach ($values as $key => $value) {
$edit["{$key}_field[{$langcode}][0][value]"] = $value;
foreach ($values as $name => $value) {
$edit["{$name}_field[{$langcode}][0][value]"] = $value;
}
return $edit;
}
protected function checkFieldValues($term, $values, $langcode) {
foreach ($values as $key => $value) {
if ($term->{$key . '_field'}[$langcode][0]['value'] != $value) {
/**
* Checks that the field values and optionally the legacy ones match the given values.
*/
protected function checkFieldValues($term, $values, $langcode, $legacy_match = TRUE) {
foreach ($values as $name => $value) {
$field_value = $term->{$name . '_field'}[$langcode][0]['value'];
if ($field_value != $value || ($legacy_match !== ($field_value == $term->{$name}))) {
return FALSE;
}
}
return TRUE;
}
/**
* Checks that the legacy field values stored in the database match the given values.
*/
protected function checkLegacyValues($term, $values) {
$record = db_query('SELECT * FROM {taxonomy_term_data} t WHERE t.tid = :tid', array(':tid' => $term->tid))->fetchAssoc();
foreach ($values as $name => $value) {
if ($record[$name] != $value) {
return FALSE;
}
}
return TRUE;
}
}
@@ -7,9 +7,9 @@ dependencies[] = title
dependencies[] = entity
dependencies[] = entity_translation
; Information added by drupal.org packaging script on 2013-03-18
version = "7.x-1.0-alpha7"
; Information added by Drupal.org packaging script on 2017-01-13
version = "7.x-1.0-alpha9"
core = "7.x"
project = "title"
datestamp = "1363626024"
datestamp = "1484302985"
@@ -14,20 +14,16 @@ function title_form_field_ui_overview(&$form, &$form_state) {
if (!empty($entity_info['field replacement'])) {
$field_replacement_info = $entity_info['field replacement'];
$admin_path = _field_ui_bundle_admin_path($form['#entity_type'], $form['#bundle']);
$form['fields']['#header'][6]['colspan'] += 1;
foreach (element_children($form['fields']) as $field_name) {
if (isset($field_replacement_info[$field_name])) {
$form['fields'][$field_name]['field_replacement'] = array(
$form['fields'][$field_name]['delete'] = array(
'#type' => 'link',
'#title' => t('replace'),
'#href' => $admin_path . '/fields/replace/' . $field_name,
'#options' => array('attributes' => array('title' => t('Replace %field with a customizable field instance that can be translated.', array('%field' => $field_name)))),
);
}
else {
$form['fields'][$field_name]['field_replacement'] = array();
}
}
}
}
@@ -127,7 +127,7 @@ function title_field_text_sync_get($entity_type, $entity, $legacy_field, $info,
$items = $entity->{$field_name}[$langcode];
$value = !empty($items[0]['value']) ? $items[0]['value'] : NULL;
}
return $value;
return array($legacy_field => $value);
}
/**
@@ -142,25 +142,31 @@ function title_field_text_sync_set($entity_type, $entity, $legacy_field, $info,
*/
function title_field_text_with_summary_sync_get($entity_type, $entity, $legacy_field, $info, $langcode) {
$value = NULL;
$format = NULL;
$format_key = $info['additional keys']['format'];
$field_name = $info['field']['field_name'];
// Return values only if there is any available to process for the current
// language.
if (!empty($entity->{$field_name}[$langcode]) && is_array($entity->{$field_name}[$langcode])) {
$entity->{$format_key} = $entity->{$field_name}[$langcode][0]['format'];
$items = $entity->{$field_name}[$langcode];
$value = !empty($items[0]['value']) ? $items[0]['value'] : NULL;
$format = $entity->{$field_name}[$langcode][0]['format'];
}
return $value;
return array(
$legacy_field => $value,
$format_key => $format,
);
}
/**
* Sync back callback for the text with summary field type.
*/
function title_field_text_with_summary_sync_set($entity_type, $entity, $legacy_field, $info, $langcode) {
$format_key = $info['additional keys']['format'];
$entity->{$info['field']['field_name']}[$langcode][0]['value'] = $entity->{$legacy_field};
$entity->{$info['field']['field_name']}[$langcode][0]['format'] = $entity->{$format_key};
foreach (array('value' => $legacy_field, 'format' => $info['additional keys']['format']) as $column => $name) {
if (isset($entity->{$name})) {
$entity->{$info['field']['field_name']}[$langcode][0][$column] = $entity->{$name};
}
}
}
/**
@@ -97,36 +97,48 @@ function title_field_formatter_settings_summary($field, $instance, $view_mode) {
*/
function title_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$settings = $display['settings'];
$output = isset($items[0]) ? $items[0]['safe_value'] : '';
if (!empty($output) && $settings['title_link'] == 'content') {
$uri = entity_uri($entity_type, $entity);
$output = l($output, $uri['path'], array('html' => TRUE));
$output = '';
if (isset($items[0]['safe_value'])) {
$output = $items[0]['safe_value'];
}
elseif (isset($items[0]['value'])) {
$output = _text_sanitize($instance, $langcode, $items[0], 'value');
}
$element = array();
$wrap_tag = empty($settings['title_style']) ? '_none' : $settings['title_style'];
if ($wrap_tag != '_none') {
$element = array(
'element' => array(
'#tag' => $wrap_tag,
'#value' => $output,
),
);
if (!empty($settings['title_class'])) {
$element['#attributes'] = array('class' => $settings['title_class']);
if (!empty($output)) {
if ($settings['title_link'] == 'content') {
$uri = entity_uri($entity_type, $entity);
$options = array('html' => TRUE);
if (!empty($uri['options'])) {
$options = array_merge($options, $uri['options']);
}
$output = l($output, $uri['path'], $options);
}
$output = theme('html_tag', $element);
$wrap_tag = empty($settings['title_style']) ? '_none' : $settings['title_style'];
if ($wrap_tag != '_none') {
$variables = array(
'element' => array(
'#tag' => $wrap_tag,
'#value' => $output,
),
);
if (!empty($settings['title_class'])) {
$variables['element']['#attributes'] = array('class' => $settings['title_class']);
}
$output = theme('html_tag', $variables);
}
$element = array(
array(
'#markup' => $output,
),
);
}
$element = array(
array(
'#markup' => $output,
),
);
return $element;
}
@@ -9,9 +9,9 @@ files[] = title.module
files[] = views/views_handler_title_field.inc
files[] = tests/title.test
; Information added by drupal.org packaging script on 2013-03-18
version = "7.x-1.0-alpha7"
; Information added by Drupal.org packaging script on 2017-01-13
version = "7.x-1.0-alpha9"
core = "7.x"
project = "title"
datestamp = "1363626024"
datestamp = "1484302985"
@@ -33,6 +33,7 @@ function title_module_implements_alter(&$implementations, $hook) {
// The following hook implementations should be executed as last ones.
case 'entity_info_alter':
case 'entity_presave':
case 'field_attach_presave':
$implementations['title'] = $group;
break;
@@ -104,17 +105,7 @@ function title_field_replacement_info($entity_type, $legacy_field = NULL) {
}
/**
* Return an entity label value.
*
* @param $entity
* The entity whose label has to be displayed.
* @param $type
* The name of the entity type.
* @param $langcode
* (Optional) The language the entity label has to be displayed in.
*
* @return
* The entity label as a string value.
* Implements callback_entity_info_label().
*/
function title_entity_label($entity, $type, $langcode = NULL) {
$entity_info = entity_get_info($type);
@@ -125,7 +116,8 @@ function title_entity_label($entity, $type, $langcode = NULL) {
// If field replacement is enabled we use the replacing field value.
if (title_field_replacement_enabled($type, $bundle, $legacy_field)) {
$langcode = field_language($type, $entity, $info['field']['field_name'], $langcode);
return $info['callbacks']['sync_get']($type, $entity, $legacy_field, $info, $langcode);
$values = $info['callbacks']['sync_get']($type, $entity, $legacy_field, $info, $langcode);
return $values[$legacy_field];
}
// Otherwise if we have a fallback defined we use the original label callback.
elseif (isset($entity_info['label fallback']['title']) && function_exists($entity_info['label fallback']['title'])) {
@@ -139,8 +131,57 @@ function title_entity_label($entity, $type, $langcode = NULL) {
/**
* Implements hook_entity_presave().
*/
function title_entity_presave($entity, $type) {
title_entity_sync($type, $entity, NULL, TRUE);
function title_entity_presave($entity, $entity_type) {
$entity_langcode = title_entity_language($entity_type, $entity);
$langcode = $entity_langcode;
// If Entity Translation is enabled and the entity type is transltable,we need
// to check if we have a translation for the current active language. If so we
// need to synchronize the legacy field values into the replacing field
// translations in the active language.
if (module_invoke('entity_translation', 'enabled', $entity_type)) {
$langcode = title_active_language();
$translations = entity_translation_get_handler($entity_type, $entity)->getTranslations();
// If we are removing a translation for the active language we need to skip
// reverse synchronization, as we would store empty values in the original
// replacing fields immediately afterwards.
if (!isset($translations->data[$langcode])) {
$langcode = isset($translations->hook[$langcode]['hook']) && $translations->hook[$langcode]['hook'] == 'delete' ? FALSE : $entity_langcode;
}
}
// Perform reverse synchronization to retain any change in the legacy field
// values. We must avoid doing this twice as we might overwrite the already
// synchronized values, if we are updating an existing entity.
if ($langcode) {
title_entity_sync($entity_type, $entity, $langcode, TRUE);
}
// If we are not dealing with the entity language, we need to synchronize the
// original values into the legacy fields to ensure they are always stored in
// the entity table.
if ($entity_langcode != $langcode) {
list($id, , ) = entity_extract_ids($entity_type, $entity);
$sync = &drupal_static('title_entity_sync', array());
unset($sync[$entity_type][$id]);
title_entity_sync($entity_type, $entity, $entity_langcode);
}
}
/**
* Implements hook_field_attach_update().
*/
function title_field_attach_update($entity_type, $entity) {
// Reset the field_attach_presave static cache so that subsequent saves work
// correctly.
$sync = &drupal_static('title_field_attach_presave', array());
list($id, , ) = entity_extract_ids($entity_type, $entity);
unset($sync[$entity_type][$id]);
// Immediately after saving the entity we need to ensure that the legacy field
// holds a value corresponding to the current active language, as it were
// just loaded.
title_entity_sync($entity_type, $entity);
}
/**
@@ -152,7 +193,10 @@ function title_entity_presave($entity, $type) {
* @see title_entity_load()
*/
function title_field_attach_load($entity_type, $entities, $age, $options) {
// @todo: Do we need to handle revisions here?
// Allow values to re-sync when field_attach_load_revision() is called.
if ($age == FIELD_LOAD_REVISION) {
title_entity_sync_static_reset($entity_type, array_keys($entities));
}
title_entity_load($entities, $entity_type);
}
@@ -164,6 +208,9 @@ function title_field_attach_load($entity_type, $entities, $age, $options) {
* replaced fields.
*/
function title_entity_load($entities, $type) {
// Load entity translations otherwise field language will not be computed
// correctly.
module_invoke('entity_translation', 'entity_load', $entities, $type);
foreach ($entities as &$entity) {
// Synchronize values from the regular field unless we are intializing it.
title_entity_sync($type, $entity, NULL, !empty($GLOBALS['title_field_replacement_init']));
@@ -183,20 +230,10 @@ function title_entitycache_load($entities, $type) {
/**
* Implements hook_entitycache_reset().
*
* If the entity cache is reseted the field sync has to be done again.
* When the entity cache is reset the field sync has to be done again.
*/
function title_entitycache_reset($ids, $entity_type) {
$sync = &drupal_static('title_entity_sync', array());
if (!empty($ids)) {
// Clear specific ids.
foreach ($ids as $id) {
unset($sync[$entity_type][$id]);
}
}
elseif (!empty($sync[$entity_type])) {
// Reset cache for an entity_type.
$sync[$entity_type] = array();
}
title_entity_sync_static_reset($entity_type, $ids);
}
/**
@@ -227,7 +264,9 @@ function title_entity_prepare_view($entities, $type, $langcode) {
*/
function title_field_replacement_enabled($entity_type, $bundle, $legacy_field) {
$info = title_field_replacement_info($entity_type, $legacy_field);
$instance = field_info_instance($entity_type, $info['field']['field_name'], $bundle);
if (!empty($info['field']['field_name'])) {
$instance = field_info_instance($entity_type, $info['field']['field_name'], $bundle);
}
return !empty($instance);
}
@@ -374,10 +413,13 @@ function title_field_replacement_init($entity_type, $bundle, $legacy_field, $ids
function title_entity_sync($entity_type, &$entity, $langcode = NULL, $set = FALSE) {
$sync = &drupal_static(__FUNCTION__, array());
list($id, , $bundle) = entity_extract_ids($entity_type, $entity);
$langcode = field_valid_language($langcode, FALSE);
// We do not need to perform this more than once.
if (!empty($id) && !empty($sync[$entity_type][$id][$langcode][$set])) {
if (!isset($langcode)) {
$langcode = $set ? title_entity_language($entity_type, $entity) : title_active_language();
}
// We do not need to perform synchronization more than once.
if (!$set && !empty($id) && !empty($sync[$entity_type][$id][$langcode][$set])) {
return;
}
@@ -394,6 +436,26 @@ function title_entity_sync($entity_type, &$entity, $langcode = NULL, $set = FALS
}
}
/**
* Reset the list of entities whose fields have already been synchronized.
*
* @param $entity_type
* The name of the entity type.
* @param $entity_ids
* Either an array of entity IDs to reset or NULL to reset all.
*/
function title_entity_sync_static_reset($entity_type, $entity_ids = NULL) {
$sync = &drupal_static('title_entity_sync', array());
if (is_array($entity_ids)) {
foreach ($entity_ids as $id) {
unset($sync[$entity_type][$id]);
}
}
else {
unset($sync[$entity_type]);
}
}
/**
* Synchronize a single legacy field with its regular field value.
*
@@ -405,8 +467,8 @@ function title_entity_sync($entity_type, &$entity, $langcode = NULL, $set = FALS
* The name of the legacy field to be replaced.
* @param $field_name
* The regular field to use as source value.
* @param $display
* Specifies if synchronization is being performed on display or on save.
* @param $info
* Field replacement information for the given entity.
* @param $langcode
* The field language to use for the source value.
*/
@@ -416,7 +478,16 @@ function title_field_sync_get($entity_type, $entity, $legacy_field, $info, $lang
$entity->{$legacy_field . '_original'} = $entity->{$legacy_field};
// Find out the actual language to use (field might be untranslatable).
$langcode = field_language($entity_type, $entity, $info['field']['field_name'], $langcode);
$entity->{$legacy_field} = $info['callbacks']['sync_get']($entity_type, $entity, $legacy_field, $info, $langcode);
$values = $info['callbacks']['sync_get']($entity_type, $entity, $legacy_field, $info, $langcode);
foreach ($values as $name => $value) {
if ($value !== NULL) {
$entity->{$name} = $value;
}
}
// Ensure we do not pollute field language static cache.
$cache = &drupal_static('field_language');
list($id, ,) = entity_extract_ids($entity_type, $entity);
unset($cache[$entity_type][$id]);
}
}
@@ -431,18 +502,45 @@ function title_field_sync_get($entity_type, $entity, $legacy_field, $info, $lang
* The name of the legacy field to be replaced.
* @param $field_name
* The regular field to use as source value.
* @param $display
* Specifies if synchronization is being performed on display or on save.
* @param $info
* Field replacement information for the given entity.
* @param $langcode
* The field language to use for the source value.
* The field language to use for the target value.
*/
function title_field_sync_set($entity_type, $entity, $legacy_field, $info) {
function title_field_sync_set($entity_type, $entity, $legacy_field, $info, $langcode) {
if (property_exists($entity, $legacy_field)) {
$langcode = title_entity_language($entity_type, $entity);
// Find out the actual language to use (field might be untranslatable).
$field = field_info_field($info['field']['field_name']);
$langcode = field_is_translatable($entity_type, $field) ? $langcode : LANGUAGE_NONE;
$info['callbacks']['sync_set']($entity_type, $entity, $legacy_field, $info, $langcode);
}
}
/**
* Returns and optionally stores the active language.
*
* @param string $langcode
* (optional) The active language to be set. If none is provided the active
* language is just returned.
*
* @return string
* The active language code. Defaults to the current content language.
*/
function title_active_language($langcode = NULL) {
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['active_language'] = &drupal_static(__FUNCTION__);
}
$active_langcode = &$drupal_static_fast['active_language'];
if (isset($langcode)) {
$active_langcode = $langcode;
}
if (empty($active_langcode)) {
$active_langcode = $GLOBALS['language_content']->language;
}
return $active_langcode;
}
/**
* Provide the original entity language.
*
@@ -485,6 +583,11 @@ function title_field_attach_form($entity_type, $entity, &$form, &$form_state, $l
if (isset($form[$legacy_field]['#access'])) {
$form[$info['field']['field_name']]['#access'] = $form[$legacy_field]['#access'];
}
// Add class from legacy field so behaviors can still be applied on
// title widget.
$form[$info['field']['field_name']]['#attributes']['class'] = array('form-item-' . $legacy_field);
// Restrict access to the legacy field form element and mark it as
// replaced.
$form[$legacy_field]['#access'] = FALSE;
@@ -510,7 +613,7 @@ function title_field_attach_submit($entity_type, $entity, $form, &$form_state) {
// drupal_array_set_nested_value().
$values = $form_state['values'];
$values = drupal_array_get_nested_value($values, $form['#parents']);
$langcode = title_entity_language($entity_type, $entity);
$langcode = entity_language($entity_type, $entity);
foreach ($fr_info as $legacy_field => $info) {
if (!empty($form[$legacy_field]['#field_replacement'])) {
@@ -641,23 +744,26 @@ function title_tokens_alter(array &$replacements, array $context) {
$entity = $context['data'][$context['type']];
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
$options = $context['options'];
$sanitize = !empty($options['sanitize']);
$langcode = NULL;
if (isset($options['language'])) {
$langcode = $options['language']->language;
}
// Since Title tokens are mostly used in storage contexts we default to
// the current working language, that is the entity language. Modules
// using Title tokens in display contexts need to specify the current
// display language.
$langcode = isset($options['language']) ? $options['language']->language : entity_language($entity_type, $entity);
if ($fr_info) {
foreach ($fr_info as $legacy_field => $info) {
if (title_field_replacement_enabled($entity_type, $bundle, $legacy_field)) {
if (isset($context['tokens'][$legacy_field])) {
title_field_sync_get($entity_type, $entity, $legacy_field, $info, $langcode);
$item = $entity->{$legacy_field};
$langcode = field_language($entity_type, $entity, $info['field']['field_name'], $langcode);
$values = $info['callbacks']['sync_get']($entity_type, $entity, $legacy_field, $info, $langcode);
$item = $values[$legacy_field];
if (!empty($item)) {
if (is_array($item)) {
$item = reset($item);
}
$replacements[$context['tokens'][$legacy_field]] = $item;
$replacements[$context['tokens'][$legacy_field]] = $sanitize ? check_plain($item) : $item;
}
}
}
@@ -851,3 +957,38 @@ function title_field_attach_create_bundle($entity_type, $bundle) {
}
}
}
/**
* Implements hook_field_info_alter().
*/
function title_field_info_alter(&$info) {
$supported_types = array('taxonomy_term_reference' => TRUE);
foreach ($info as $field_type => &$field_type_info) {
if (isset($supported_types[$field_type])) {
if (!isset($field_type_info['settings'])) {
$field_type_info['settings'] = array();
}
$field_type_info['settings'] += array('options_list_callback' => 'title_taxonomy_allowed_values');
}
}
}
/**
* Return taxonomy term values for taxonomy reference fields.
*/
function title_taxonomy_allowed_values($field) {
$bundle = !empty($field['settings']['allowed_values'][0]['vocabulary']) ? $field['settings']['allowed_values'][0]['vocabulary'] : NULL;
if ($bundle && ($label = title_field_replacement_get_label_field('taxonomy_term', $bundle))) {
$options = array();
foreach ($field['settings']['allowed_values'] as $tree) {
$vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary']);
if ($vocabulary && ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'], NULL, TRUE))) {
foreach ($terms as $term) {
$options[$term->tid] = str_repeat('-', $term->depth) . entity_label('taxonomy_term', $term);
}
}
}
return $options;
}
return taxonomy_allowed_values($field);
}
@@ -6,13 +6,14 @@
*/
function title_field_views_data_alter(&$data) {
foreach (entity_get_info() as $entity_type => $entity_info) {
$replacements = title_field_replacement_info($entity_type);
if ($replacements) {
foreach ($replacements as $replacement) {
$field = field_info_field($replacement['field']['field_name']);
$table = _field_sql_storage_tablename($field);
if (isset($data[$table][$field['field_name']])) {
$data[$table][$field['field_name']]['field']['handler'] = 'views_handler_title_field';
if (!empty($entity_info['field replacement'])) {
foreach ($entity_info['field replacement'] as $replacement) {
if (isset($replacement['field']['field_name'])) {
$field = field_info_field($replacement['field']['field_name']);
$table = _field_sql_storage_tablename($field);
if (isset($data[$table][$field['field_name']])) {
$data[$table][$field['field_name']]['field']['handler'] = 'views_handler_title_field';
}
}
}
}
@@ -36,13 +36,13 @@ class views_handler_title_field extends views_handler_field_field {
if (!empty($this->options['link_to_entity'])) {
$values = $this->original_values;
$entity_type = $this->definition['entity_tables'][$this->base_table];
$entity_info = entity_get_info($entity_type);
$key = $entity_info['entity keys']['id'];
$key = $this->field_alias;
if (!empty($values->_field_data[$key]['entity'])) {
$entity = $values->_field_data[$key]['entity'];
$uri = entity_uri($entity_type, $entity);
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['path'] = $uri['path'];
$this->options['alter']['options'] = !empty($uri['options']) ? $uri['options'] : array();
}
}
return parent::render_item($count, $item);
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

@@ -1,16 +1,21 @@
<?php
/**
* Define this Export UI plugin.
* @file
* Defines the CTools Export UI plugin.
*/
/**
* Defines this Export UI plugin.
*/
$plugin = array(
'schema' => 'vef_video_styles', // As defined in hook_schema().
'access' => 'administer video styles', // Define a permission users must have to access these pages.
'schema' => 'vef_video_styles',
'access' => 'administer video styles',
// Define the menu item.
'menu' => array(
'menu prefix' => 'admin/config/media',
'menu prefix' => 'admin/config/media/vef',
'menu item' => 'vef_video_styles',
'menu title' => 'Configure Video Embed Styles',
'menu title' => 'Video Embed Styles',
'menu description' => 'Administer Video Embed Field\'s video styles.',
),
// Define user interface texts.
@@ -23,4 +28,7 @@ $plugin = array(
'settings' => 'video_embed_field_video_style_form',
// 'submit' and 'validate' are also valid callbacks.
),
'export' => array(
'admin_title' => 'title',
),
);
@@ -19,4 +19,4 @@
<div class="player">
<?php print $embed_code; ?>
</div>
</div>
</div>
@@ -0,0 +1,5 @@
Brightcove Video Embed Field
----------------------------
This module parses a Brightcove URL via the Video Embed Field
Example URL: http://link.brightcove.com/services/player/bcpid3303744435001?bckey=AQ~~,AAAAAGL7jok~,vslbwQw3pdWM_Ya9IsUVNvJJIhV9YG1S&bctid=3118695475001
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

@@ -0,0 +1,14 @@
name = "Video Embed Brightcove"
description = "Provides Brightcove handler for Video Embed Fields."
core = 7.x
package = Media
configure = admin/config/media/vef_video_styles
dependencies[] = "video_embed_field"
; Information added by Drupal.org packaging script on 2015-09-07
version = "7.x-2.0-beta11"
core = "7.x"
project = "video_embed_field"
datestamp = "1441639440"
@@ -0,0 +1,168 @@
<?php
/**
* @file
* Add a handler for brightcove videos to Video Embed Field.
* @see video_embed_field.api.php for more documentation.
*/
/**
* Implements hook_video_embed_handler_info().
*/
function video_embed_brightcove_video_embed_handler_info() {
$handlers = array();
$handlers['brightcove'] = array(
'title' => 'Brightcove Video',
'function' => 'video_embed_brightcove_handle_video',
'thumbnail_default' => drupal_get_path('module', 'video_embed_brightcove') . '/img/brightcove.jpg',
'form' => 'video_embed_brightcove_form',
'form_validate' => 'video_embed_field_handler_brightcove_form_validate',
'domains' => array(
'brightcove.com',
'link.brightcove.com',
),
'defaults' => array(
'width' => 640,
'height' => 360,
'class' => '',
),
);
return $handlers;
}
/**
* Form to configure out video settings.
*
* @param array $defaults
* Values for your provider.
*
* @return array
* A form as defined by form API.
*/
function video_embed_brightcove_form($defaults) {
$form = array();
// Element for the width of the player.
$form['width'] = array(
'#type' => 'textfield',
'#title' => t('Player Width'),
'#description' => t('The width of the player.'),
'#default_value' => $defaults['width'],
);
// Element for the height of the player.
$form['height'] = array(
'#type' => 'textfield',
'#title' => t('Player Height'),
'#description' => t('The height of the player.'),
'#default_value' => $defaults['height'],
);
$form['class'] = array(
'#type' => 'textfield',
'#title' => t('Player CSS class'),
'#description' => t('CSS class to add to the player'),
'#default_value' => $defaults['class'],
);
return $form;
}
/**
* Validates the form elements for the Brightcove configuration form.
*/
function video_embed_field_handler_brightcove_form_validate($element, &$form_state, $form) {
video_embed_field_validate_dimensions($element);
}
/**
* The video handler.
*
* @param string $url
* The full video url.
* @param array $settings
* Handlers settings from the settings form.
*
* @return array|string
* The embed code for the video.
*/
function video_embed_brightcove_handle_video($url, $settings) {
$parameters = _video_embed_brightcove_get_video_properties($url);
if (isset($parameters['id']) && isset($parameters['key'])) {
// Embed code.
$embed = '<object class="@class" id="myExperience" class="BrightcoveExperience">
<param name="bgcolor" value="#FFFFFF" />
<param name="width" value="@width" />
<param name="height" value="@height" />
<param name="playerID" value="!id" />
<param name="playerKey" value="!key" />
<param name="isVid" value="true" />
<param name="isUI" value="true" />
<param name="dynamicStreaming" value="true" />
<param name="@videoPlayer" value="!videoplayer" />
</object>';
// Replace our placeholders with the values from the settings.
$embed = format_string($embed, array(
'!id' => $parameters['id'],
'!key' => $parameters['key'],
'@width' => $settings['width'],
'@height' => $settings['height'],
'@class' => $settings['class'],
'!videoplayer' => $parameters['player'],
));
$video = array(
'#markup' => $embed,
'#suffix' => '<script type="text/javascript">brightcove.createExperiences();</script>',
'#attached' => array(
'js' => array(
'//admin.brightcove.com/js/BrightcoveExperiences.js' => array(
'type' => 'external',
),
),
),
);
return $video;
}
return '';
}
/**
* Helper function to take a brightcove video url and return its id.
*
* @param string $url
* The full brightcove video url.
*
* @return array
* The video properties.
*/
function _video_embed_brightcove_get_video_properties($url) {
// Easy way to break a url into its components.
$components = array(
'id' => array(
'start' => 'bcpid',
'finish' => '\?bckey',
),
'key' => array(
'start' => 'bckey=',
'finish' => '&bctid',
),
'player' => array(
'start' => 'bctid=',
'finish' => '',
),
);
$matches = array();
$return = array();
foreach ($components as $key => $component) {
$string = "/(.*){$component['start']}(.*){$component['finish']}/";
preg_match($string, $url, $matches);
if ($matches && !empty($matches[2])) {
$return[$key] = check_plain($matches[2]);
}
}
return $return;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

@@ -5,9 +5,9 @@ package = Media
configure = admin/config/media/vef_video_styles
dependencies[] = "video_embed_field"
; Information added by drupal.org packaging script on 2012-10-17
version = "7.x-2.0-beta5+11-dev"
; Information added by Drupal.org packaging script on 2015-09-07
version = "7.x-2.0-beta11"
core = "7.x"
project = "video_embed_field"
datestamp = "1350438417"
datestamp = "1441639440"
@@ -1,41 +1,31 @@
<?php
/**
* @file Add a handler for facebook videos to Video Embed Field.
* @file
* Adds a handler for Facebook videos to Video Embed Field.
*
* @see video_embed_field.api.php for more documentation
*/
/**
* Implements hook_video_embed_handler_info().
*
* This function is used to tell video_embed_field which functions will be used to handle
* different operations, along with a bit of metadata.
* @return an associative array with the data
* @see video_embed_field.api.php for specific details on the data to return.
*/
function video_embed_facebook_video_embed_handler_info() {
$handlers = array();
//the key here should be unique to our handler, normally the name of the service will suffice
$handlers['facebook'] = array(
'title' => 'Facebook Video', //The title is the name to show to users
//function is a function to take the url and return the embed code
'title' => 'Facebook Video',
'function' => 'video_embed_facebook_handle_video',
//thumbnail_function is optional and takes the url and returns the thumbnail url
'thumbnail_function' => 'video_embed_facebook_handle_thumbnail',
//data_function is optional and returns an array of extra data for the given video url
//because facebook requires oath to get this data, we'll leave it out for now
//'data_function' => 'video_embed_facebook_handle_data',
//form is the configure form to embed into video_embed styles - this is where all settings should go
'thumbnail_default' => drupal_get_path('module', 'video_embed_facebook') . '/img/facebook.jpg',
'form' => 'video_embed_facebook_form',
//domains is how video embed determines which handler to use, its an array of domains to match
//urls against. Don't include the scheme (like http://) or www.
'form_validate' => 'video_embed_field_handler_youtube_form_validate',
'domains' => array(
'facebook.com',
),
//defaults are the defaults to provide to your form (as defined in your form callback)
'defaults' => array(
'width' => 640,
'height' => 360,
'allowfullscreen' => TRUE,
'class' => '',
),
);
@@ -43,107 +33,124 @@ function video_embed_facebook_video_embed_handler_info() {
}
/**
* Our configuration form (the callback for the form key in info)
* Provide a form to configure out video settings
* @param $defaults - default/current values for your provider, the currently saved settings
* with empty values filled with the defaults provided in info hook
* @return a form as defined by forms api
* Defines the form elements for the Facebook videos configuration form.
*
* @param array $defaults
* The form default values.
*
* @return array
* The provider settings form array.
*/
function video_embed_facebook_form($defaults) {
$form = array();
//form element for the width of the player - note we're using the default from defaults
$form['height'] = array(
'#type' => 'textfield',
'#title' => t('Player Width'),
'#description' => t('The width of the player.'),
'#default_value' => $defaults['height'],
);
//element for width
$form['width'] = array(
'#type' => 'textfield',
'#title' => t('Player Width'),
'#description' => t('The width of the player.'),
'#default_value' => $defaults['width'],
);
//allow configuration of fullscreen
$form['allowfullscreen'] = array(
'#type' => 'checkbox',
'#title' => t('Allow Fullscreen'),
'#desecription' => t('This will allow the video to be fullscreened.'),
'#default_value' => $defaults['allowfullscreen'],
$form['height'] = array(
'#type' => 'textfield',
'#title' => t('Player Height'),
'#description' => t('The height of the player.'),
'#default_value' => $defaults['height'],
);
$form['class'] = array(
'#type' => 'textfield',
'#title' => t('Player CSS class'),
'#description' => t('CSS class to add to the player'),
'#default_value' => $defaults['class'],
);
return $form;
}
/**
* Validates the form elements for the Facebook video configuration form.
*
* @param array $element
* The form element to validate.
* @param array $form_state
* The form to validate state.
* @param array $form
* The form to validate structure.
*/
function video_embed_field_handler_facebook_form_validate($element, &$form_state, $form) {
video_embed_field_validate_dimensions($element);
}
/**
* This is the video handler (the 'function' key from handler_info)
* @param $url - the full video url
* @param $settings - an associative array of this handlers settings, from the settings form
* @return - the embed code for the video
* Handler for Facebook videos.
*
* @param string $url
* The video URL.
* @param array $settings
* The settings array.
*
* @return string|bool
* The video iframe, or FALSE in case the ID can't be retrieved from the URL.
*/
function video_embed_facebook_handle_video($url, $settings) {
$id = _video_embed_facebook_get_video_id($url);
if ($id) {
//our embed code
$embed = '<object>
<param name="allowfullscreen" value="!fullscreen" />
<param name="allowscriptaccess" value="always" />
<param name="movie" value="https://www.facebook.com/v/!id" />
<param name="wmode" value="opaque" />
<embed src="https://www.facebook.com/v/!id" type="application/x-shockwave-flash" wmode="opaque"
allowscriptaccess="always" allowfullscreen="!fullscreen" width="!width" height="!height">
</embed>
</object>';
//use format_string to replace our placeholders with the values from the settings
// Our embed code.
$embed='<iframe class="@class" src="//www.facebook.com/video/embed?video_id=!id" width="@width" height="@height"></iframe> ';
// Use format_string to replace our placeholders with the settings values.
$embed = format_string($embed, array(
'!id' => $id,
'!fullscreen' => $settings['allowfullscreen'] ? 'true' : 'false',
'!width' => $settings['width'],
'!height' => $settings['height'],
'@width' => $settings['width'],
'@height' => $settings['height'],
'@class' => $settings['class'],
));
//we want to return a render array
$video = array(
'#markup' => $embed,
);
return $video;
}
// just return an empty string if there is no id, so we don't have broken embeds showing up
return '';
return FALSE;
}
/**
* Retreive the thumbnail for the facebook video - note that based on a users permissions, this may be
* the facebook unknown thumbnail: https://s-static.ak.facebook.com/rsrc.php/v1/y0/r/XsEg9L6Ie5_.jpg
* @param $url - the url of the video as entered by the user
* @return an array with the keys:
* 'id' => an id for the video which is unique to your provider, used for naming the cached thumbnail file
* 'url' => the url to retrieve the thumbnail from
* Gets the thumbnail url for Facebook videos.
*
* @param string $url
* The video URL.
*
* @return array
* The video thumbnail information.
*/
function video_embed_facebook_handle_thumbnail($url) {
$id = _video_embed_facebook_get_video_id($url);
// We're using a part of the graphs api to return the thumbnail. This only works for some videos
return array(
'id' => $id, //generally the id that the provider uses for the video
'url' => 'https://graph.facebook.com/' . $id . '/picture', //the url of the thumbnail
'id' => $id,
'url' => 'https://graph.facebook.com/' . $id . '/picture',
);
}
/**
* Helper function to take a facebook video url and return its id
* @param $url - the full facebook video url
* @return the id for the video
* Helper function to get the Facebook video's id.
*
* @param string $url
* The video URL.
*
* @return string|bool
* The video ID, or FALSE in case the ID can't be retrieved from the URL.
*/
function _video_embed_facebook_get_video_id($url) {
//parse_url is an easy way to break a url into its components
// Parse_url is an easy way to break a url into its components.
$matches = array();
preg_match('/(.*)?v=([^&#]*)/', $url, $matches);
//if the v get parameter is set, return it
if ($matches && !empty($matches[2])) {
return $matches[2];
preg_match('/(?:.*)(?:v=|video_id=|videos\/|videos\/v.\.\d+\/)(\d+).*/', $url, $matches);
// If the v or video_id get parameters are set, return it.
if ($matches && !empty($matches[1])) {
return check_plain($matches[1]);
}
//otherwise return false.
// Otherwise return false.
return FALSE;
}
@@ -1,11 +1,14 @@
<?php
/**
* @file
* Form builder; Form for editing a video style.
* Used by CTools export ui
*
* @ingroup forms
* @see video_embed_field_video_style_form_submit()
* Used by CTools export ui.
*/
/**
* Video embed style form handler.
*/
function video_embed_field_video_style_form(&$form, &$form_state) {
if (isset($form_state['item'])) {
@@ -16,16 +19,28 @@ function video_embed_field_video_style_form(&$form, &$form_state) {
}
$form_state['video_style'] = $style;
//Grab the settings off the parser form
// Grab the settings off the parser form.
$values = isset($style['data']) ? $style['data'] : array();
$parser_form = video_embed_field_get_form($values);
//General settings for playback - formerly in the configuration section
// General settings for playback - formerly in the configuration section.
$form['data'] = array(
'#type' => 'vertical_tabs',
'#title' => t('Playback settings'),
'#tree' => TRUE,
) + $parser_form; //add in our extra settings
) + $parser_form;
return $form;
}
/**
* VEF settings page form callback.
*/
function video_embed_field_settings_form($form, &$form_state) {
$form['video_embed_field_youtube_v3_api_key'] = array(
'#type' => 'textfield',
'#title' => t('Youtube v3 API key'),
'#default_value' => variable_get('video_embed_field_youtube_v3_api_key', ''),
);
return system_settings_form($form);
}
+38 -9
View File
@@ -7,15 +7,26 @@
/**
* @function hook_video_embed_handler_info
* Can be used to add more handlers for video_embed_field
* @return an array of handlers, each handler is an array with the following keys
* 'title' : required, the untranslated title of the provider, to show to the admin user
* 'function' : required, the function used to generate the embed code
* 'thumbnail_function' : optional, the function used to provide the thumbnail for a video
* @return an array of handlers, each handler is an array with the following
* keys:
* 'title' : required, the untranslated title of the provider, to show to the
* admin user.
* 'function' : required, the function used to generate the embed code.
* 'thumbnail_function' : optional, the function used to provide the thumbnail
* for a video.
* 'thumbnail_default : optional, the default thumbnail image to display in case
* thumbnail_function does not exist or has no results.
* 'data_function' : optional, the function to return an array of video data.
* 'form' : required, the function that returns the settings form for your provider
* 'domains' : required, an array of domains to match against, this is used to know which provider to use
* 'defaults' : default values for each setting made configurable in your form function
* 'form' : required, the function that returns the settings form for your
* provider.
* 'form_validate: optional the function that validates the settings form for
* your provider.
* 'domains' : required, an array of domains to match against, this is used to
* know which provider to use.
* 'defaults' : default values for each setting made configurable in your form
* function.
*
* @see hook_video_embed_handler_info_alter()
* @see below for function definitions
*/
function hook_video_embed_handler_info() {
@@ -25,8 +36,10 @@ function hook_video_embed_handler_info() {
'title' => 'UStream',
'function' => 'your_module_handle_ustream',
'thumbnail_function' => 'your_module_handle_ustream_thumbnail',
'thumbnail_default' => drupal_get_path('module', 'your_module') . '/img/ustream.jpg',
'data_function' => 'your_module_handler_ustream_data',
'form' => 'your_module_handler_ustream_form',
'form_validate' => 'your_module_handler_ustream_form_validate',
'domains' => array(
'ustream.com',
),
@@ -39,8 +52,24 @@ function hook_video_embed_handler_info() {
return $handlers;
}
// Example callbacks for a provider (in this case for ustream) - obviously, these functions are only for example
// purposes
/**
* Performs alterations on video_embed_field handlers.
*
* @param $info
* Array of information on video handlers exposed by
* hook_video_embed_handler_info() implementations.
*/
function hook_video_embed_handler_info_alter(&$info) {
// Change the thumbnail function for 'ustream' provider.
if (isset($info['ustream'])) {
$info['ustream']['thumbnail_function'] = 'your_module_handle_ustream_thumbnail_alter';
}
}
/**
* Example callbacks for a provider (in this case for ustream).
* Obviously, these functions are only for example purposes.
*/
/**
* Generate the embed code for a video
@@ -0,0 +1,94 @@
<?php
/**
* @file
* Devel generate support for video_embed_field module.
*/
// The Youtubes API url.
define('YT_API_URL', 'http://gdata.youtube.com/feeds/api/videos?q=');
/**
* Devel generate plugin definition.
*/
function video_embed_field_devel_generate($object, $field, $instance, $bundle) {
if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_CUSTOM) {
return devel_generate_multiple('_video_embed_field_devel_generate', $object, $field, $instance, $bundle);
}
else {
return _video_embed_field_devel_generate($object, $field, $instance, $bundle);
}
}
/**
* Generates a random video_embed_field item.
*
* @param object $object
* The devel_generate object.
* @param array $field
* The field definition.
* @param array $instance
* The field instance definition.
* @param array $bundle
* The bundle definition.
*
* @return array
* The video_embed_field item.
*/
function _video_embed_field_devel_generate($object, $field, $instance, $bundle) {
$video = video_embed_field_retrieve_video();
$object_field = array();
$object_field['video_url'] = $video['video_url'];
if ($instance['settings']['description_field']) {
$object_field['description'] = $video['description'];
}
return $object_field;
}
/**
* Retrieves a random youtube video info from the bunch.
*
* @return array
* The video definition.
*/
function video_embed_field_retrieve_video() {
$videos = video_embed_field_generate_videos();
return $videos[array_rand($videos)];
}
/**
* Generates a pseudo random bunch of youtube videos.
*
* @return array
* A bunch of youtube videos.
*/
function video_embed_field_generate_videos() {
$videos = &drupal_static(__FUNCTION__);
if (!isset($videos)) {
$videos = array();
// Create random video seed.
$video_id = user_password(2);
// Using cURL php extension to make the request to youtube API.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, YT_API_URL . $video_id);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// $feed holds a rss feed xml returned by youtube API.
$feed = curl_exec($ch);
curl_close($ch);
// Using SimpleXML to parse youtubes feed.
$xml = simplexml_load_string($feed);
foreach ($xml->entry as $entry) {
$videos[] = array(
'video_url' => $entry->children('media', TRUE)->group->player->attributes()->url,
'description' => $entry->title,
);
}
if (empty($videos)) {
video_embed_field_generate_videos();
}
}
return $videos;
}
@@ -7,16 +7,23 @@
/**
* Implements hook_feeds_processor_targets_alter().
*
* @see FeedsNodeProcessor::getMappingTargets().
* @see FeedsNodeProcessor::getMappingTargets()
*/
function video_embed_field_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
$info = field_info_field($name);
if ($info['type'] == 'video_embed_field') {
$targets[$name] = array(
'name' => $instance['label'],
$targets[$name . ":video_url"] = array(
'name' => t('@name: Embed URL', array('@name' => $instance['label'])),
'callback' => 'video_embed_field_set_target',
'description' => t('The @label field of the node.', array('@label' => $instance['label'])),
'description' => t('The URL for the @label field of the @entity_type.', array('@entity_type' => $entity_type, '@label' => $instance['label'])),
'real_target' => $name,
);
$targets[$name . ':description'] = array(
'name' => t('@name: Embed description', array('@name' => $instance['label'])),
'callback' => 'video_embed_field_set_target',
'description' => t('The description for the @label field of the @entity_type.', array('@entity_type' => $entity_type, '@label' => $instance['label'])),
'real_target' => $name,
);
}
}
@@ -34,10 +41,33 @@ function video_embed_field_set_target($source, $entity, $target, $value) {
return;
}
$field = isset($entity->$target) ? $entity->$target : array();
if (!is_array($value) && !is_object($value)) {
$field['und'][0]['video_url'] = $value;
if (!is_array($value)) {
$value = array($value);
}
$entity->{$target} = $field;
list($field_name, $sub_field) = explode(':', $target, 2);
$info = field_info_field($field_name);
// Iterate over all values.
$field = isset($entity->$field_name) ? $entity->$field_name : array(LANGUAGE_NONE => array());
// Allow for multiple mappings to the same target.
$count = call_user_func_array('array_merge_recursive', $field[LANGUAGE_NONE]);
$delta = count($count[$sub_field]);
foreach ($value as $v) {
if ($info['cardinality'] != FIELD_CARDINALITY_UNLIMITED && $info['cardinality'] <= $delta) {
break;
}
if (is_scalar($v)) {
$field[LANGUAGE_NONE][$delta][$sub_field] = $v;
$delta++;
}
}
$entity->{$field_name} = $field;
}
+182 -65
View File
@@ -17,25 +17,98 @@ function video_embed_field_field_info() {
'instance_settings' => array(
'description_field' => 0,
'description_length' => 128,
'allowed_providers' => array_keys(video_embed_get_handlers()),
),
'default_widget' => 'video_embed_field_video',
'default_formatter' => 'video_embed_field',
'property_type' => 'video_embed_field',
'property_callbacks' => array('video_embed_field_property_info_callback'),
),
);
}
/**
* Property callback for the Entity Metadata framework.
*/
function video_embed_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
// Apply the default.
entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type);
// Finally add in instance specific property info.
$name = $field['field_name'];
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
$property['type'] = ($field['cardinality'] != 1) ? 'list<video_embed_field>' : 'video_embed_field';
$property['property info'] = video_embed_field_data_property_info('Video embed');
$property['getter callback'] = 'entity_metadata_field_verbatim_get';
$property['setter callback'] = 'entity_metadata_field_verbatim_set';
}
/**
* Defines info for the properties of the video_embed_field data structure.
*/
function video_embed_field_data_property_info($name = NULL) {
// Build an array of basic property information for video_embed_field.
$properties = array(
'video_url' => array(
'label' => 'Video URL',
'type' => 'uri',
'setter callback' => 'entity_property_verbatim_set',
),
'thumbnail_path' => array(
'label' => 'Thumbnail path',
'type' => 'uri',
'getter callback' => 'entity_property_verbatim_get_url',
),
'description' => array(
'label' => 'Description',
'type' => 'text',
'setter callback' => 'entity_property_verbatim_set',
),
);
// Add the default values for each of the video_embed_field properties.
foreach ($properties as $key => &$value) {
$value += array(
'description' => !empty($name) ? t('!label of field %name', array('!label' => $value['label'], '%name' => $name)) : '',
);
}
return $properties;
}
/**
* Gets the property just as it is set in the data and converts to absolute url.
*/
function entity_property_verbatim_get_url($data, array $options, $name, $type, $info) {
$property = entity_property_verbatim_get($data, $options, $name, $type, $info);
return file_create_url($property);
}
/**
* Implements hook_field_instance_settings_form().
*/
function video_embed_field_field_instance_settings_form($field, $instance) {
$settings = $instance['settings'];
$providers = video_embed_get_handlers();
$allowed_providers = array();
foreach ($providers as $provider_id => $definition) {
$allowed_providers[$provider_id] = $definition['title'];
}
$form['allowed_providers'] = array(
'#type' => 'checkboxes',
'#title' => t('Select the allowed video providers'),
'#options' => $allowed_providers,
'#default_value' => isset($settings['allowed_providers']) ? $settings['allowed_providers'] : array(),
'#weight' => 10,
);
$form['description_field'] = array(
'#type' => 'checkbox',
'#title' => t('Enable <em>Description</em> field'),
'#default_value' => isset($settings['description_field']) ? $settings['description_field'] : '',
'#description' => t('The description field allows users to enter a description about the video.'),
'#parents' => array('instance', 'settings', 'description_field'),
'#weight' => 11,
);
@@ -43,10 +116,14 @@ function video_embed_field_field_instance_settings_form($field, $instance) {
'#type' => 'textfield',
'#title' => t('Max description length'),
'#default_value' => isset($settings['description_length']) ? $settings['description_length'] : 128,
'#parents' => array('instance', 'settings', 'description_length'),
'#weight' => 12,
'#size' => 5,
'#maxlength' => 5,
'#states' => array(
'visible' => array(
':input[id="edit-instance-settings-description-field"]' => array('checked' => TRUE),
),
),
);
return $form;
@@ -74,7 +151,7 @@ function video_embed_field_field_widget_info() {
* Implements hook_field_widget_form().
*/
function video_embed_field_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
// Don't need to check the type right now because we're only defining one
// Don't need to check the type right now because we're only defining one.
$element += array(
'#type' => 'fieldset',
);
@@ -90,6 +167,7 @@ function video_embed_field_field_widget_form(&$form, &$form_state, $field, $inst
),
'#default_value' => isset($items[$delta]['video_url']) ? $items[$delta]['video_url'] : '',
'#required' => $element['#required'],
'#maxlength' => 255,
);
// Add the description field if enabled.
@@ -129,7 +207,7 @@ function video_embed_field_field_validate($entity_type, $entity, $field, $instan
if (stripos($host, 'www.') > -1) {
$host = substr($host, 4);
}
$domains = _video_embed_field_get_provider_domains();
$domains = _video_embed_field_get_instance_provider_domains($instance);
if (!array_key_exists($host, $domains)) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => t('Unsupported Video Provider'),
@@ -142,41 +220,44 @@ function video_embed_field_field_validate($entity_type, $entity, $field, $instan
}
/**
* Implementation of hook_field_presave().
* Implements hook_field_presave().
*
* Download and save the thumbnail if it hasn't already been stored.
* Get video data.
*/
function video_embed_field_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
foreach ($items as $delta => $item) {
//Trim whitespace from the video URL.
// Trim whitespace from the video URL.
$items[$delta]['video_url'] = trim($item['video_url']);
// Try to load thumbnail URL
// Try to load thumbnail URL.
$info = video_embed_field_thumbnail_url($item['video_url']);
if (isset($info['url']) && $info['url']) {
$thumb_url = $info['url'];
$local_path = 'public://video_embed_field_thumbnails/' . $info['handler'] . '/' . $info['id'] . '.jpg';
$thumb_extension = pathinfo($thumb_url, PATHINFO_EXTENSION);
$local_path = "public://video_embed_field_thumbnails/{$info['handler']}/{$info['id']}.$thumb_extension";
$dirname = drupal_dirname($local_path);
file_prepare_directory($dirname, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
$response = drupal_http_request($thumb_url);
if (!isset($response->error)) {
file_save_data($response->data, $local_path, TRUE);
file_save_data($response->data, $local_path, FILE_EXISTS_REPLACE);
}
else {
@copy($thumb_url, $local_path);
}
$items[$delta]['thumbnail_path'] = $local_path;
} //couldn't get the thumbnail for whatever reason
// Delete any image derivatives at the original image path.
image_path_flush($local_path);
}
// Couldn't get the thumbnail for whatever reason.
else {
$items[$delta]['thumbnail_path'] = '';
}
// Try to load video data
// Try to load video data.
$data = video_embed_field_get_video_data($item['video_url']);
if (is_array($data) && !empty($data)) {
$items[$delta]['video_data'] = serialize($data);
@@ -206,31 +287,43 @@ function video_embed_field_field_formatter_info() {
'settings' => array(
'video_style' => 'normal',
'description' => 1,
'description_position' => 'bottom'
'description_position' => 'bottom',
),
),
'video_embed_field_url' => array(
'label' => t('URL to Video'),
'field types' => array('video_embed_field'),
'settings' => array(),
),
'video_embed_field_thumbnail' => array(
'label' => t('Thumbnail Preview'),
'field types' => array('video_embed_field'),
'settings' => array(
'image_style' => 'none',
'image_style' => '',
'description' => 1,
'description_position' => 'bottom',
'image_link' => 'none',
),
),
);
if ( module_exists('colorbox') ) {
if (module_exists('colorbox')) {
$info['video_embed_field_thumbnail_colorbox'] = array(
'label' => t('Thumbnail Preview w/Colorbox'),
'field types' => array('video_embed_field'),
'settings' => array(
'video_style' => 'normal',
'image_style' => 'none',
'image_style' => '',
'description' => 1,
'description_position' => 'bottom',
),
);
$info['video_embed_field_url_colorbox'] = array(
'label' => t('URL to Video w/Colorbox'),
'field types' => array('video_embed_field'),
'settings' => array(
'video_style' => 'normal',
),
);
}
return $info;
}
@@ -243,7 +336,7 @@ function video_embed_field_field_formatter_settings_form($field, $instance, $vie
$settings = $display['settings'];
$element = array();
if ($display['type'] == 'video_embed_field' || $display['type'] == 'video_embed_field_thumbnail_colorbox') {
if ($display['type'] == 'video_embed_field' || $display['type'] == 'video_embed_field_thumbnail_colorbox' || $display['type'] == 'video_embed_field_url_colorbox') {
$video_styles = video_embed_field_video_style_options(FALSE);
$element['video_style'] = array(
'#title' => t('Video style'),
@@ -264,7 +357,7 @@ function video_embed_field_field_formatter_settings_form($field, $instance, $vie
if ($display['type'] == 'video_embed_field_thumbnail') {
$link_types = array(
'node' => t('Node'),
'content' => t('Content'),
'source' => t('Video Source'),
);
$element['image_link'] = array(
@@ -276,7 +369,7 @@ function video_embed_field_field_formatter_settings_form($field, $instance, $vie
);
}
if ($instance['settings']['description_field']) {
if ($instance['settings']['description_field'] && $display['type'] != 'video_embed_field_url' && $display['type'] != 'video_embed_field_url_colorbox') {
$element['description'] = array(
'#title' => t('Show description'),
'#type' => 'checkbox',
@@ -288,9 +381,14 @@ function video_embed_field_field_formatter_settings_form($field, $instance, $vie
'#type' => 'select',
'#options' => array(
'top' => t('Top'),
'bottom' => t('Bottom')
'bottom' => t('Bottom'),
),
'#default_value' => $settings['description_position'],
'#states' => array(
'visible' => array(
':input[name="fields[' . $field['field_name'] . '][settings_edit_form][settings][description]"]' => array('checked' => TRUE),
),
),
);
}
return $element;
@@ -303,7 +401,7 @@ function video_embed_field_field_formatter_settings_summary($field, $instance, $
$settings = $display['settings'];
$summary = array();
if ($display['type'] == 'video_embed_field' || $display['type'] == 'video_embed_field_thumbnail_colorbox') {
if ($display['type'] == 'video_embed_field' || $display['type'] == 'video_embed_field_thumbnail_colorbox' || $display['type'] == 'video_embed_field_url_colorbox') {
$video_styles = video_embed_field_video_style_options(FALSE);
// Styles could be lost because of enabled/disabled modules that defines
// their styles in code.
@@ -315,23 +413,26 @@ function video_embed_field_field_formatter_settings_summary($field, $instance, $
$image_styles = image_style_options(FALSE);
if (isset($image_styles[$settings['image_style']])) {
$summary[] = t('Image style: @style', array('@style' => $image_styles[$settings['image_style']]));
} //No Image style (original image)
}
// No Image style (original image).
else {
$summary[] = t('Original Image.');
}
if (isset($settings['image_link'])) {
$summary[] = t('Image link: ' . $settings['image_link']);
$summary[] = t('Image link: @image_link', array('@image_link' => $settings['image_link']));
}
else {
$summary[] = t('Image link: none');
}
}
if ($settings['description'] && $instance['settings']['description_field']) {
$summary[] = t('Show description');
}
elseif ($instance['settings']['description_field']) {
$summary[] = t('Hide description');
if (isset($settings['description'])) {
if ($settings['description'] && $instance['settings']['description_field']) {
$summary[] = t('Show description');
}
elseif ($instance['settings']['description_field']) {
$summary[] = t('Hide description');
}
}
return implode('<br />', $summary);
@@ -344,20 +445,26 @@ function video_embed_field_field_formatter_view($entity_type, $entity, $field, $
$element = array();
$settings = $display['settings'];
if ($display['type'] == 'video_embed_field_thumbnail' && $display['settings']['image_link'] == 'content') {
$path = entity_uri($entity_type, $entity);
}
foreach ($items as $delta => $item) {
//create the render array for the description
// Create the render array for the description.
if (isset($item['description']) && $item['description'] && $settings['description'] && $instance['settings']['description_field']) {
$description = array(
'#prefix' => '<div class="video-embed-description">',
'#markup' => $item['description'],
'#markup' => check_plain($item['description']),
'#suffix' => '</div>',
);
$alt = $item['description'];
}
else {
$description = array();
$alt = '';
}
//Render the field
// Render the field.
if ($display['type'] == 'video_embed_field') {
$element[$delta] = array(
array(
@@ -368,63 +475,74 @@ function video_embed_field_field_formatter_view($entity_type, $entity, $field, $
),
);
}
elseif ($display['type'] == 'video_embed_field_url') {
$element[$delta] = array(
array(
'#markup' => url($item['video_url']),
),
);
}
elseif ($display['type'] == 'video_embed_field_thumbnail') {
if (isset($item['thumbnail_path'])) {
if (empty($settings['image_style']) || $settings['image_style'] == 'none') {
$element[$delta] = array(
array(
'#theme' => 'image',
'#path' => $item['thumbnail_path'],
),
if ($display['settings']['image_link'] == 'source') {
if ($ret = parse_url($item['video_url'])) {
if (!isset($ret["scheme"])) {
$item['video_url'] = "http://{$item['video_url']}";
}
}
$path = array(
'path' => $item['video_url'],
'options' => array(),
);
}
else {
$element[$delta] = array(
array(
'#theme' => 'image_style',
'#path' => $item['thumbnail_path'],
'#style_name' => $settings['image_style'],
),
);
}
if ($settings['image_link'] == 'source') {
$link = explode('|||', l('|||', $item['video_url']));
$element[$delta]['#prefix'] = $link[0];
$element[$delta]['#suffix'] = $link[1];
}
elseif ($settings['image_link'] == 'node') {
$nid = $entity->nid;
$link = explode('|||', l('|||', 'node/' . $nid));
$element[$delta]['#prefix'] = $link[0];
$element[$delta]['#suffix'] = $link[1];
}
} //incase no thumbnail was downloaded / provider doesn't support thumbnails
else {
$element[$delta] = array(
'#theme' => 'image_formatter',
'#item' => array('uri' => $item['thumbnail_path'], 'alt' => $alt),
'#image_style' => $display['settings']['image_style'],
'#path' => isset($path) ? $path : '',
);
}
// Incase no thumbnail was downloaded/provider doesn't support thumbnails.
else {
$element[$delta] = array();
}
}
elseif ($display['type'] == 'video_embed_field_thumbnail_colorbox') {
if (isset($item['thumbnail_path'])) {
if ($ret = parse_url($item['video_url'])) {
if (!isset($ret["scheme"])) {
$item['video_url'] = "http://{$item['video_url']}";
}
}
$element[$delta] = array(
array(
'#theme' => 'video_embed_field_colorbox_code',
'#image_url' => $item['thumbnail_path'],
'#image_style' => $display['settings']['image_style'],
'#image_alt' => $alt,
'#video_url' => $item['video_url'],
'#video_style' => $display['settings']['video_style'],
'#video_data' => unserialize($item['video_data']),
),
);
}
//incase no thumbnail was downloaded / provider doesn't support thumbnails
// Incase no thumbnail was downloaded/provider doesn't support thumbnails.
else {
$element[$delta] = array(
);
$element[$delta] = array();
}
}
//Get the HTML instead of the array, because we append it to the suffix.
//This way, the thumbnail link wrapper doesn't make the description a link as well.
elseif ($display['type'] == 'video_embed_field_url_colorbox') {
$path = video_embed_field_get_ajax_url($item['video_url'], $display['settings']['video_style']);
$element[$delta] = array(
array(
'#markup' => url($path['path'], $path['options']),
),
);
}
// Get the HTML instead of the array, because we append it to the suffix.
// This way, the thumbnail link doesn't make the description a link as well.
$description_html = drupal_render($description);
$pos = isset($settings['description_position']) ? $settings['description_position'] : 'bottom';
@@ -438,6 +556,5 @@ function video_embed_field_field_formatter_view($entity_type, $entity, $field, $
}
}
return $element;
}
+282 -107
View File
@@ -1,7 +1,10 @@
<?php
/**
* @file
* Provide some handlers for video embed field
* Other modules can implement the hook_video_embed_handler_info to provide more handlers
* Other modules can implement the hook_video_embed_handler_info to provide more
* handlers.
*/
@@ -15,23 +18,27 @@ function video_embed_field_video_embed_handler_info() {
'title' => 'Youtube',
'function' => 'video_embed_field_handle_youtube',
'thumbnail_function' => 'video_embed_field_handle_youtube_thumbnail',
'thumbnail_default' => drupal_get_path('module', 'video_embed_field') . '/img/youtube.jpg',
'data_function' => 'video_embed_field_handle_youtube_data',
'form' => 'video_embed_field_handler_youtube_form',
'form_validate' => 'video_embed_field_handler_youtube_form_validate',
'domains' => array(
'youtube.com',
'youtu.be',
),
'defaults' => array(
'width' => '640px',
'height' => '360px',
'width' => 640,
'height' => 360,
'autoplay' => 0,
'hd' => 1,
'vq' => 'large',
'rel' => 0,
'controls' => 1,
'autohide' => 2,
'showinfo' => 1,
'modestbranding' => 0,
'theme' => 'dark',
'iv_load_policy' => 1,
'class' => '',
),
);
@@ -39,20 +46,24 @@ function video_embed_field_video_embed_handler_info() {
'title' => 'Vimeo',
'function' => 'video_embed_field_handle_vimeo',
'thumbnail_function' => 'video_embed_field_handle_vimeo_thumbnail',
'data_function' => 'video_embed_field_handle_vimeo_data',
'thumbnail_default' => drupal_get_path('module', 'video_embed_field') . '/img/vimeo.jpg',
'data_function' => '_video_embed_field_get_vimeo_data',
'form' => 'video_embed_field_handler_vimeo_form',
'form_validate' => 'video_embed_field_handler_vimeo_form_validate',
'domains' => array(
'vimeo.com',
),
'defaults' => array(
'width' => '640px',
'height' => '360px',
'width' => 640,
'height' => 360,
'color' => '00adef',
'portrait' => 1,
'title' => 1,
'byline' => 1,
'autoplay' => 0,
'loop' => 0,
'froogaloop' => 0,
'class' => ''
),
);
@@ -60,11 +71,16 @@ function video_embed_field_video_embed_handler_info() {
}
/**
* Helper function to get the youtube video's id
* Returns false if it doesn't parse for wahtever reason
* Helper function to get the youtube video's id.
*
* @param string $url
* The video URL.
*
* @return string|bool
* The video ID, or FALSE in case the ID can't be retrieved from the URL.
*/
function _video_embed_field_get_youtube_id($url) {
// Find the ID of the video they want to play from the url
// Find the ID of the video they want to play from the url.
if (stristr($url, 'http://')) {
$url = substr($url, 7);
}
@@ -73,7 +89,7 @@ function _video_embed_field_get_youtube_id($url) {
}
if (stristr($url, 'playlist')) {
//Playlists need the appended ampersand to take the options properly.
// Playlists need the appended ampersand to take the options properly.
$url = $url . '&';
$pos = strripos($url, '?list=');
if ($pos !== FALSE) {
@@ -84,14 +100,14 @@ function _video_embed_field_get_youtube_id($url) {
return FALSE;
}
}
//Alternate playlist link
// Alternate playlist link.
elseif (stristr($url, 'view_play_list')) {
$url = $url . '&';
//All playlist ID's are prepended with PL. view_play_list links allow you to not have that, though.
// All playlist ID's are prepended with PL.
if (!stristr($url, '?p=PL')) {
$url = substr_replace($url, 'PL', strpos($url, '?p=') + 3, 0);
}
//Replace the links format with the embed format
// Replace the links format with the embed format.
$url = str_ireplace('play_list?p=', 'videoseries?list=', $url);
$pos = strripos($url, 'videoseries?list=');
if ($pos !== FALSE) {
@@ -133,57 +149,81 @@ function _video_embed_field_get_youtube_id($url) {
$id = substr($url, $pos);
}
}
return $id;
return check_plain($id);
}
/**
* Handler for Youtube videos.
*
* @param string $url
* The video URL.
* @param array $settings
* The settings array.
*
* @return array
* The video iframe render array.
*/
function video_embed_field_handle_youtube($url, $settings) {
$output = array();
//Grab the minutes and seconds, and just convert it down to seconds
preg_match('/#t=((?P<min>\d+)m)?((?P<sec>\d+)s)?/', $url, $matches);
//Give it some default data in case there is no #t=...
$matches += array(
"min" => 0,
"sec" => 0,
);
$time = ($matches["min"] * 60) + $matches["sec"];
$settings['start'] = $time;
if(preg_match('/#t=((?P<min>\d+)m)?((?P<sec>\d+)s)?((?P<tinsec>\d+))?/', $url, $matches)){
if(isset($matches['tinsec'])){
$settings['start'] = $matches['tinsec']; // url already in form #t=125 for 2 minutes and 5 seconds
} else {
// url in form #t=2m5s or with other useless data, this is why we still keep adding the default data..
// give it some default data in case there is no #t=...
$matches += array(
"min" => 0,
"sec" => 0,
);
if ($time = ($matches["min"] * 60) + $matches["sec"]) {
$settings['start'] = $time;
}
}
}
$id = _video_embed_field_get_youtube_id($url);
if (!$id) {
// We can't decode the URL - just return the URL as a link
// We can't decode the URL - just return the URL as a link.
$output['#markup'] = l($url, $url);
return $output;
}
// Construct the embed code
$settings['wmode'] = 'opaque';
$settings_str = _video_embed_code_get_settings_str($settings);
$output['#markup'] = '<iframe width="' . $settings['width'] . '" height="' . $settings['height'] . '" src="//www.youtube.com/embed/' . $id . '?' . $settings_str . '" frameborder="0" allowfullscreen></iframe>';
// Add class to variable to avoid adding it to URL param string.
$class = $settings['class'];
unset($settings['class']);
// Construct the embed code.
$settings['wmode'] = 'opaque';
$settings_str = urlencode(_video_embed_code_get_settings_str($settings));
$output['#markup'] = '<iframe class="' . check_plain($class) . '" width="' . check_plain($settings['width']) . '" height="' . check_plain($settings['height']) . '" src="//www.youtube.com/embed/' . $id . '?' . $settings_str . '" frameborder="0" allowfullscreen></iframe>';
return $output;
}
/**
* Get the thumbnail url for youtube videos
* Gets the thumbnail url for youtube videos.
*
* @param string $url
* The video URL.
*
* @return array
* The video thumbnail information.
*/
function video_embed_field_handle_youtube_thumbnail($video_url) {
function video_embed_field_handle_youtube_thumbnail($url) {
$info = array();
$id = _video_embed_field_get_youtube_id($video_url);
$id = _video_embed_field_get_youtube_id($url);
//Playlist
// Playlist.
if (stristr($id, '?list=')) {
//Strip out all but the ID, including the PL behind the ID.
// Strip out all but the ID, including the PL behind the ID.
$start = strpos($id, '?list=PL') + 8;
$length = strpos($id, '&') - $start;
$id = substr($id, $start, $length);
$info['id'] = $id;
//Playlist info is stored in XML. The thumbnail is in there.
// Playlist info is stored in XML. The thumbnail is in there.
$xml = drupal_http_request('http://gdata.youtube.com/feeds/api/playlists/' . $id);
if (!isset($xml->error)) {
$xml = new SimpleXMLElement($xml->data);
@@ -194,7 +234,7 @@ function video_embed_field_handle_youtube_thumbnail($video_url) {
}
}
}
//Regular video
// Regular video.
elseif ($id) {
$info['id'] = $id;
$info['url'] = 'http://img.youtube.com/vi/' . $id . '/0.jpg';
@@ -203,26 +243,30 @@ function video_embed_field_handle_youtube_thumbnail($video_url) {
}
/**
* Get video data for a YouTube video URL
* Gets video data for a YouTube video URL.
*
* @param string $url
* A YouTube video URL to get data for
*
* @return array|false $data
* @return array|bool
* An array of video data, or FALSE if unable to fetch data
*/
function video_embed_field_handle_youtube_data($url) {
$data = array();
// Get YouTube video ID from URL
// Get YouTube video ID from URL.
$id = _video_embed_field_get_youtube_id($url);
if ($id) {
$response = drupal_http_request('http://gdata.youtube.com/feeds/api/videos/' . $id . '?v=2&alt=json');
$options['v'] = 3;
$options['key'] = variable_get('video_embed_field_youtube_v3_api_key', '');
$options['part'] = 'snippet';
$options['id'] = $id;
$response = drupal_http_request(url('https://www.googleapis.com/youtube/v3/videos', array('query' => $options)));
if (!isset($response->error)) {
$data = json_decode($response->data);
$data = isset($data->entry) ? (array) $data->entry : (array) $data->feed;
return _video_embed_field_clean_up_youtube_data($data);
return _video_embed_field_clean_up_youtube_data($data->items);
}
}
@@ -230,10 +274,16 @@ function video_embed_field_handle_youtube_data($url) {
}
/**
* Flatten out some unnecessary nesting in the youtube data
* Flattens out some unnecessary nesting in the youtube data.
*
* @param array $data
* The unflattened data.
*
* @return array
* The flattened data.
*/
function _video_embed_field_clean_up_youtube_data($data) {
//make things a bit nicer for people trying to use the data
// Make things a bit nicer for people trying to use the data.
foreach ($data as $key => $value) {
if (is_object($value)) {
$temp = (array) $value;
@@ -267,6 +317,12 @@ function _video_embed_field_clean_up_youtube_data($data) {
/**
* Defines the form elements for the Youtube configuration form.
*
* @param array $defaults
* The form default values.
*
* @return array
* The provider settings form array.
*/
function video_embed_field_handler_youtube_form($defaults) {
$form = array();
@@ -300,11 +356,18 @@ function video_embed_field_handler_youtube_form($defaults) {
'#description' => t('Play the video immediately.'),
'#default_value' => $defaults['autoplay'],
);
$form['hd'] = array(
'#type' => 'checkbox',
'#title' => t('Use HD'),
'#description' => t('Attempt to play the video in HD if available.'),
'#default_value' => $defaults['hd'],
$form['vq'] = array(
'#type' => 'select',
'#title' => t('Video quality'),
'#options' => array(
'small' => t('Small (240p)'),
'medium' => t('Medium (360p)'),
'large' => t('Large (480p)'),
'hd720' => t('HD 720p'),
'hd1080' => t('HD 10800p'),
),
'#default_value' => $defaults['vq'],
'#description' => t('Attempt to play the video in certain quality if available.'),
);
$form['rel'] = array(
'#type' => 'checkbox',
@@ -334,6 +397,17 @@ function video_embed_field_handler_youtube_form($defaults) {
'#description' => t('Controls the display of annotations over the video content. Only works when using the flash player.'),
'#default_value' => $defaults['iv_load_policy'],
);
$form['controls'] = array(
'#type' => 'radios',
'#options' => array(
0 => t('Hide video controls.'),
1 => t('Show video controls. Youtube default.'),
2 => t('Show video controls with performance improvement for iframe embeds.'),
),
'#title' => t('Display Youtube player controls'),
'#description' => t('This parameter indicates whether the video player controls will display.'),
'#default_value' => $defaults['controls'],
);
$form['autohide'] = array(
'#type' => 'radios',
'#options' => array(
@@ -346,93 +420,149 @@ function video_embed_field_handler_youtube_form($defaults) {
'#default_value' => $defaults['autohide'],
);
$form['class'] = array(
'#type' => 'textfield',
'#title' => t('Player CSS class'),
'#description' => t('CSS class to add to the player'),
'#default_value' => $defaults['class'],
);
return $form;
}
/**
* Helper function to get the Vimeo video's ID
* Validates the form elements for the Youtube configuration form.
*
* @param array $element
* The form element to validate.
* @param array $form_state
* The form to validate state.
* @param array $form
* The form to validate structure.
*/
function video_embed_field_handler_youtube_form_validate($element, &$form_state, $form) {
video_embed_field_validate_dimensions($element);
}
/**
* Helper function to get the Vimeo video's data attributes.
*
* @param string $url
* A Vimeo video URL to get the ID of
* A Vimeo video URL to get the data from.
*
* @return integer|false $id
* The video ID, or FALSE if unable to get the video ID
* @return integer|false
* The video's data attributes, or FALSE if unable to get the video ID.
*/
function _video_embed_field_get_vimeo_id($url) {
$pos = strripos($url, '/');
if ($pos != FALSE) {
$pos += 1;
return (int) substr($url, $pos);
function _video_embed_field_get_vimeo_data($url) {
// Set oembed endpoint
$oembed_endpoint = 'http://vimeo.com/api/oembed';
// Fetch vimeo data
$response = drupal_http_request($oembed_endpoint . '.json?url=' . rawurlencode($url));
try {
return json_decode($response->data, TRUE);
} catch (Exception $e) {
return FALSE;
}
return FALSE;
}
/**
* Helper function to get the Vimeo video's data attributes.
*
* @param string $url
* A Vimeo video URL to get the ID of.
*
* @return integer|false
* The video ID, or FALSE if unable to get the video ID.
*/
function _video_embed_field_get_vimeo_id($vimeo_data) {
try {
$video_id = $vimeo_data['video_id'];
} catch (Exception $e) {
$video_id = FALSE;
}
return $video_id;
}
/**
* Handler for Vimeo videos.
*
* @param string $url
* The video URL.
* @param array $settings
* The settings array.
*
* @return string
* The video iframe.
*/
function video_embed_field_handle_vimeo($url, $settings) {
// Get ID of video from URL
$id = _video_embed_field_get_vimeo_id($url);
if (!$id) {
$vimeo_data = _video_embed_field_get_vimeo_data($url);
// Get ID of video from URL.
$id = _video_embed_field_get_vimeo_id($vimeo_data);
if (empty($id)) {
return array(
'#markup' => l($url, $url),
);
}
// Construct the embed code
$settings['portrait'] = 0;
// Construct the embed code.
$settings['player_id'] = drupal_html_id('vimeo-' . $id);
if (!empty($settings['froogaloop'])) {
$settings['api'] = 1;
}
unset($settings['froogaloop']);
// Add class to variable to avoid adding it to URL param string.
$class = $settings['class'];
unset($settings['class']);
$settings_str = _video_embed_code_get_settings_str($settings);
return array(
'#markup' => '<iframe width="' . $settings['width'] . '" height="' . $settings['height'] . '" src="//player.vimeo.com/video/' . $id .
'?' . $settings_str . '" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowfullscreen></iframe>',
'#markup' => '<iframe class="' . check_plain($class) . '" id="' . $settings['player_id'] . '" width="' . check_plain($settings['width']) . '" height="' . check_plain($settings['height']) . '" src="//player.vimeo.com/video/' . $id .
'?' . $settings_str . '" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowfullscreen></iframe>',
);
}
/**
* Get the thumbnail url for youtube videos
* Gets the thumbnail url for vimeo videos.
*
* @param string $url
* The video URL.
*
* @return array
* The video thumbnail information.
*/
function video_embed_field_handle_vimeo_thumbnail($url) {
// Get ID of video from URL
$id = _video_embed_field_get_vimeo_id($url);
$vimeo_data = _video_embed_field_get_vimeo_data($url);
// Get ID of video from URL.
$id = _video_embed_field_get_vimeo_id($vimeo_data);
$info = array(
'id' => $id,
);
$response = drupal_http_request('http://vimeo.com/api/v2/video/' . $id . '.php');
if (!isset($response->error)) {
$response = unserialize($response->data);
$video = current($response);
$image_url = $video['thumbnail_large'];
$info['url'] = $image_url;
try {
$info['url'] = $vimeo_data['thumbnail_url'];
} catch (Exception $e) {
}
return $info;
}
/**
* Get video data for a Vimeo video URL
*
* @param string $url
* A Vimeo video URL to get data for
*
* @return array|false $data
* An array of video data, or FALSE if unable to fetch data
*/
function video_embed_field_handle_vimeo_data($url) {
// Get ID of video from URL
$id = _video_embed_field_get_vimeo_id($url);
if ($id) {
$response = drupal_http_request('http://vimeo.com/api/v2/video/' . $id . '.php');
if (!isset($response->error)) {
$response = unserialize($response->data);
return (array) current($response);
}
}
return FALSE;
}
/**
* Defines the form elements for the Vimeo configuration form.
*
* @param array $defaults
* The form default values.
*
* @return array
* The provider settings form array.
*/
function video_embed_field_handler_vimeo_form($defaults) {
$form = array();
@@ -444,6 +574,7 @@ function video_embed_field_handler_vimeo_form($defaults) {
'#description' => t('The width of the vimeo player.'),
'#default_value' => $defaults['width'],
);
$form['height'] = array(
'#type' => 'textfield',
'#size' => '5',
@@ -451,6 +582,7 @@ function video_embed_field_handler_vimeo_form($defaults) {
'#description' => t('The height of the vimeo player.'),
'#default_value' => $defaults['height'],
);
$form['color'] = array(
'#type' => 'select',
'#options' => array(
@@ -464,47 +596,90 @@ function video_embed_field_handler_vimeo_form($defaults) {
'#description' => t('The color to use on the vimeo player.'),
'#default_value' => $defaults['color'],
);
$form['portrait'] = array(
'#type' => 'checkbox',
'#title' => t('Overlay Author Thumbnail'),
'#description' => t('Overlay the author\'s thumbnail before the video is played.'),
'#description' => t("Overlay the author's thumbnail before the video is played."),
'#default_value' => $defaults['portrait'],
);
$form['title'] = array(
'#type' => 'checkbox',
'#title' => t('Overlay Video\'s Title'),
'#description' => t('Overlay the video\'s title before the video is played.'),
'#title' => t("Overlay Video's Title"),
'#description' => t("Overlay the video's title before the video is played."),
'#default_value' => $defaults['title'],
);
$form['byline'] = array(
'#type' => 'checkbox',
'#title' => t('Overlay Video\'s Byline'),
'#description' => t('Overlay the video\'s description before the video is played.'),
'#title' => t("Overlay Video's Byline"),
'#description' => t("Overlay the video's description before the video is played."),
'#default_value' => $defaults['byline'],
);
$form['overridable'] = array(
'#prefix' => '<p class="note"><strong>' . t('Note') . ': </strong><em>',
'#markup' => t('Color, portrait, title and byline can be restricted by Vimeo Plus videos.
Such videos will ignore these settings.'),
'#suffix' => '</em></p>',
);
$form['autoplay'] = array(
'#type' => 'checkbox',
'#title' => t('Autoplay'),
'#description' => t('Play the video immediately.'),
'#default_value' => $defaults['autoplay'],
);
$form['loop'] = array(
'#type' => 'checkbox',
'#title' => t('Loop'),
'#description' => t('Loop the video\'s playback'),
'#description' => t("Loop the video's playback"),
'#default_value' => $defaults['loop'],
);
$form['froogaloop'] = array(
'#type' => 'checkbox',
'#title' => t('Enable froogaloop support'),
'#description' => t("Enables Froogallop Vimeo's library support"),
'#default_value' => $defaults['loop'],
);
$form['class'] = array(
'#type' => 'textfield',
'#title' => t('Player CSS class'),
'#description' => t('CSS class to add to the player'),
'#default_value' => $defaults['class'],
);
return $form;
}
/**
* Calculate the min index for use in finding the id of a youtube video
* Validates the form elements for the Vimeo configuration form.
*
* @param array $element
* The form element to validate.
* @param array $form_state
* The form to validate state.
* @param array $form
* The form to validate structure.
*/
function video_embed_field_handler_vimeo_form_validate($element, &$form_state, $form) {
video_embed_field_validate_dimensions($element);
}
/**
* Calculates the min index for use in finding the id of a youtube video.
*
* @param string $pos1
* The first index.
* @param string $pos2
* The second index.
*
* @return string
* The min index.
*/
function _video_embed_get_min($pos1, $pos2) {
if (!$pos1) {
@@ -2,13 +2,17 @@ name = "Video Embed Field"
description = "Expose a field type for embedding videos from youtube or vimeo."
core = 7.x
package = Media
configure = admin/config/media/vef_video_styles
configure = admin/config/media/vef
files[] = video_embed_field.migrate.inc
files[] = views/handlers/views_embed_field_views_handler_field_thumbnail_path.inc
dependencies[] = ctools
dependencies[] = image
; Information added by drupal.org packaging script on 2012-10-17
version = "7.x-2.0-beta5+11-dev"
; Information added by Drupal.org packaging script on 2015-09-07
version = "7.x-2.0-beta11"
core = "7.x"
project = "video_embed_field"
datestamp = "1350438417"
datestamp = "1441639440"
+181 -33
View File
@@ -1,8 +1,8 @@
<?php
/**
* @file
* Install, update and uninstall functions for the video_embed_field module.
*
*/
/**
@@ -61,28 +61,26 @@ function video_embed_field_schema() {
'description' => 'Stores video embed styles.',
'export' => array(
'key' => 'name',
'primary key' => 'vsid',
'identifier' => 'video_embed_style', // Exports will be as $video_style
'default hook' => 'default_video_embed_styles', // Function hook name.
'identifier' => 'video_embed_style',
'default hook' => 'default_video_embed_styles',
'api' => array(
'owner' => 'video_embed_field',
'api' => 'default_video_embed_styles', // Base name for api include files.
'api' => 'default_video_embed_styles',
'minimum_version' => 1,
'current_version' => 1,
),
),
'fields' => array(
'vsid' => array(
'description' => 'The primary identifier for a video style.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
'no export' => TRUE,
),
'name' => array(
'description' => 'The style name.',
'description' => 'The machine-readable option set name.',
'type' => 'varchar',
'length' => '255',
'length' => 255,
'not null' => TRUE,
),
'title' => array(
'description' => 'The human-readable title for this option set.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'data' => array(
@@ -93,10 +91,7 @@ function video_embed_field_schema() {
'serialize' => TRUE,
),
),
'primary key' => array('vsid'),
'unique keys' => array(
'name' => array('name'),
),
'primary key' => array('name'),
);
return $schema;
}
@@ -105,12 +100,11 @@ function video_embed_field_schema() {
* Implements hook_uninstall().
*/
function video_embed_field_uninstall() {
//do nothing right now - should eventually remove all the variables
variable_del('video_embed_field_youtube_api_key');
}
/**
* Update 7000
* Add an optional description form
* Adds an optional description form.
*/
function video_embed_field_update_7000() {
// Get the list of fields of type 'video_embed_field'.
@@ -130,7 +124,7 @@ function video_embed_field_update_7000() {
$table = "field_data_{$field['field_name']}";
$revision_table = "field_revision_{$field['field_name']}";
}
$column = $field['field_name'] . '_' . 'description';
$column = $field['field_name'] . '_description';
db_add_field($table, $column, array('type' => 'text', 'not null' => FALSE));
db_add_field($revision_table, $column, array('type' => 'text', 'not null' => FALSE));
}
@@ -139,8 +133,7 @@ function video_embed_field_update_7000() {
}
/**
* Update 7001
* Add video style storage table
* Adds video style storage table.
*/
function video_embed_field_update_7001() {
if (!db_table_exists('vef_video_styles')) {
@@ -151,8 +144,7 @@ function video_embed_field_update_7001() {
return t('Video styles storage table created.');
}
/**
* Update 7002
* Add field for storing the path to the video thumbnail
* Adds field for storing the path to the video thumbnail.
*/
function video_embed_field_update_7002() {
// Get the list of fields of type 'video_embed_field'.
@@ -172,7 +164,7 @@ function video_embed_field_update_7002() {
$table = "field_data_{$field['field_name']}";
$revision_table = "field_revision_{$field['field_name']}";
}
$column = $field['field_name'] . '_' . 'thumbnail_path';
$column = $field['field_name'] . '_thumbnail_path';
db_add_field($table, $column, array(
'type' => 'varchar',
'length' => 512,
@@ -189,22 +181,22 @@ function video_embed_field_update_7002() {
}
/**
* Enable inline colorbox support if colorbox is installed [NO LONGER NEEDED - This update hook does nothing]
* Enables inline colorbox support if colorbox is installed.
*
* [NO LONGER NEEDED - This update hook does nothing]
*/
function video_embed_field_update_7003() {
//this is no longer needed
//variable_set('colorbox_inline', 1);
}
/**
* Enable colorbox load support if colorbox is installed, we no longer need inline support
* Enables colorbox load support if colorbox is installed.
*/
function video_embed_field_update_7004() {
variable_set('colorbox_load', 1);
}
/**
* Add data column to field database.
* Adds data column to field database.
*/
function video_embed_field_update_7005() {
// Get the list of fields of type 'video_embed_field'.
@@ -224,7 +216,7 @@ function video_embed_field_update_7005() {
$table = "field_data_{$field['field_name']}";
$revision_table = "field_revision_{$field['field_name']}";
}
$column = $field['field_name'] . '_' . 'video_data';
$column = $field['field_name'] . '_video_data';
db_add_field($table, $column, array(
'type' => 'blob',
'not null' => FALSE,
@@ -241,3 +233,159 @@ function video_embed_field_update_7005() {
return t('Data column added. Please clear cache.');
}
/**
* Updates vef_video_styles table structure.
*/
function video_embed_field_update_7006() {
// Convert the table structure.
db_drop_field('vef_video_styles', 'vsid');
db_add_primary_key('vef_video_styles', array('name'));
db_drop_unique_key('vef_video_styles', 'name');
db_add_field('vef_video_styles', 'title', array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
));
// Update title and name values.
$result = db_select('vef_video_styles', 'vef')
->fields('vef', array('name'))
->execute();
foreach ($result as $record) {
// Set current name as title.
db_update('vef_video_styles')
->fields(array(
'title' => $record->name,
))
->condition('name', $record->name)
->execute();
// Update name to fit with machine_name constraints.
$new_name = preg_replace('/[^a-z0-9_]+/', '_', drupal_strtolower($record->name));
if ($new_name != $record->name) {
// Check if new name already exists in the database.
$counter = 1;
$base_name = $new_name;
while (TRUE) {
$result = db_select('vef_video_styles', 'vef')
->fields('vef', array('name'))
->condition('name', $new_name)
->execute();
if ($result->rowCount()) {
$new_name = $base_name . '_' . $counter;
}
else {
db_update('vef_video_styles')
->fields(array(
'name' => $new_name,
))
->condition('name', $record->name)
->execute();
break;
}
}
}
}
return t('Database schema updated successfully');
}
/**
* Update youtube "hd" URL deprecated parameter.
*/
function video_embed_field_update_7007() {
drupal_get_schema('vef_video_styles', TRUE);
ctools_include('export');
$styles = ctools_export_load_object('vef_video_styles');
foreach ($styles as $style) {
if (isset($style->data['youtube']['hd'])) {
if ($style->data['youtube']['hd']) {
$style->data['youtube']['vq'] = 'hd720';
}
else {
$style->data['youtube']['vq'] = 'large';
}
unset($style->data['youtube']['hd']);
ctools_export_crud_save('vef_video_styles', $style);
}
}
return t('Parameter hd has been converted to vq.');
}
/**
* Updates naming of 'node' formatter setting to 'content'.
*/
function video_embed_field_update_7008() {
$instances = field_info_instances();
foreach ($instances as $entity_type) {
foreach ($entity_type as $bundle) {
foreach ($bundle as $instance) {
$field_info = field_info_field($instance['field_name']);
if ($field_info['type'] == 'video_embed_field') {
$update = FALSE;
foreach ($instance['display'] as &$display) {
if ($display['type'] == 'video_embed_field_thumbnail') {
if ($display['settings']['image_link'] == 'node') {
$display['settings']['image_link'] = 'content';
$update = TRUE;
}
if ($display['settings']['image_style'] == 'none') {
$display['settings']['image_style'] = '';
$update = TRUE;
}
}
}
if ($update) {
field_update_instance($instance);
}
}
}
}
}
return t("Updated 'node' setting to 'content'");
}
/**
* Adds new Allowed Providers setting to existing instances.
*/
function video_embed_field_update_7009() {
$allowed_providers = array_keys(video_embed_get_handlers());
$instances = field_info_instances();
foreach ($instances as $entity_type) {
foreach ($entity_type as $bundle) {
foreach ($bundle as $instance) {
$field_info = field_info_field($instance['field_name']);
if ($field_info['type'] == 'video_embed_field') {
$instance['settings']['allowed_providers'] = $allowed_providers;
field_update_instance($instance);
}
}
}
}
return t('Updated default instance settings');
}
/**
* Update styles with empty class parameter.
*/
function video_embed_field_update_7010() {
drupal_get_schema('vef_video_styles', TRUE);
ctools_include('export');
$styles = ctools_export_load_object('vef_video_styles');
foreach ($styles as $style) {
foreach ($style->data as &$provider) {
if (!isset($provider['class'])) {
$provider['class'] = '';
}
}
ctools_export_crud_save('vef_video_styles', $style);
}
return 'Parameter class added to existing styles';
}
@@ -0,0 +1,57 @@
<?php
/**
* @file
* Migrate support for Video Embed Field module.
*/
/**
* Implements hook_migrate_api().
*/
function video_embed_field_migrate_api() {
$api = array(
'api' => 2,
'field handlers' => array('MigrateVideoEmbedFieldFieldHandler'),
);
return $api;
}
/**
* Custom extended MigrateFieldHandler class for Video Embed Field module.
*/
class MigrateVideoEmbedFieldFieldHandler extends MigrateFieldHandler {
public function __construct() {
$this->registerTypes(array('video_embed_field'));
}
/**
* {@inheritdoc}
*/
public function fields($type, $parent_field, $migration = NULL) {
$fields = array(
'video_url' => t('Video: The video URL.'),
);
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepare($entity, array $field_info, array $instance, array $values) {
$arguments = array();
if (isset($values['arguments'])) {
$arguments = array_filter($values['arguments']);
unset($values['arguments']);
}
$language = $this->getFieldLanguage($entity, $field_info, $arguments);
// Setup the standard Field API array for saving.
$delta = 0;
foreach ($values as $value) {
$return[$language][$delta] = array('video_url' => $value);
$delta++;
}
return isset($return) ? $return : NULL;
}
}
+337 -185
View File
@@ -1,23 +1,28 @@
<?php
/**
* @file
* Provides a simple field for easily embedding videos from youtube or vimeo
*
* This module is not intended to replace media or video - it does not allow for any local storage of videos, custom players or anything else
* It simply allows users to embed videos from youtube and vimeo - and provides a hook to allow other modules to provide more providers.
* This module is not intended to replace media or video - it does not allow for
* any local storage of videos, custom players or anything else.
* It simply allows users to embed videos from youtube and vimeo - and provides
* hooks to allow other modules to provide more providers.
*
* Uses CTools Export UI to manage settings. @see ./plugins/export_ui/video_embed_field_export_ui.inc
* Uses CTools Export UI to manage settings.
*
* @see ./plugins/export_ui/video_embed_field_export_ui.inc
*
* @author jec006, jdelaune
*/
// Load all Field module hooks.
module_load_include('inc', 'video_embed_field', 'video_embed_field.field');
// Load the admin forms
// Load the admin forms.
module_load_include('inc', 'video_embed_field', 'video_embed_field.admin');
// Load our default handlers
// Load our default handlers.
module_load_include('inc', 'video_embed_field', 'video_embed_field.handlers');
// Load feeds mapping hooks
// Load feeds mapping hooks.
module_load_include('inc', 'video_embed_field', 'video_embed_field.feeds');
/**
@@ -48,20 +53,22 @@ function video_embed_field_default_video_embed_styles() {
$styles = array();
$handlers = video_embed_get_handlers();
//create the normal handler
$normal = new stdClass;
// Create the normal handler.
$normal = new stdClass();
$normal->disabled = FALSE; /* Edit this to true to make a default video_embed_style disabled initially */
$normal->api_version = 1;
$normal->name = 'normal';
$normal->title = 'Normal';
$normal->data = array();
$teaser = new stdClass;
$teaser = new stdClass();
$teaser->disabled = FALSE; /* Edit this to true to make a default video_embed_style disabled initially */
$teaser->api_version = 1;
$teaser->name = 'teaser';
$teaser->title = 'Teaser';
$teaser->data = array();
//add in our settings for each of the handlers
// Add in our settings for each of the handlers.
foreach ($handlers as $name => $handler) {
$normal->data[$name] = $handler['defaults'];
$teaser->data[$name] = $handler['defaults'];
@@ -86,71 +93,29 @@ function video_embed_field_menu() {
'type' => MENU_CALLBACK,
);
$items['admin/config/media/vef'] = array(
'title' => 'Video Embed Field',
'description' => 'Video Embed Field configuration',
'page callback' => 'system_admin_menu_block_page',
'access arguments' => array('administer video styles'),
'file' => 'system.admin.inc',
'file path' => drupal_get_path('module', 'system'),
'type' => MENU_NORMAL_ITEM,
);
$items['admin/config/media/vef/settings'] = array(
'title' => 'Settings',
'description' => 'Video Embed Field module settings',
'page callback' => 'drupal_get_form',
'page arguments' => array('video_embed_field_settings_form'),
'file' => 'video_embed_field.admin.inc',
'access arguments' => array('administer video styles'),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Get an array of all styles and their settings.
*
* @return
* An array of styles keyed by the video style ID (vsid).
* @see video_embed_field_video_style_load()
*/
function video_embed_field_video_styles() {
$styles = &drupal_static(__FUNCTION__);
// Grab from cache or build the array.
if (!isset($styles)) {
// load the style via ctools - which will handle all the caching for us -
// however, because it does a bit more logic, lets still statically cache this function
ctools_include('export');
$styles = ctools_export_load_object('vef_video_styles');
}
return $styles;
}
/**
* Load a style by style name or ID. May be used as a loader for menu items.
*
* Note that you may also use ctools_export_load_object with the key being vef_video_styles
*
* @param $name
* The name of the style.
* @param $isid
* Optional. The numeric id of a style if the name is not known.
* @return
* An video style array containing the following keys:
* - "vsid": The unique image style ID.
* - "name": The unique image style name.
* - "data": An array of video settings within this video style.
* If the video style name or ID is not valid, an empty array is returned.
*/
function video_embed_field_video_style_load($name = NULL, $vsid = NULL) {
$styles = video_embed_field_video_styles();
// If retrieving by name.
if (isset($name) && isset($styles[$name])) {
$style = $styles[$name];
} // If retrieving by image style id.
elseif (!isset($name) && isset($vsid)) {
foreach ($styles as $name => $database_style) {
if (isset($database_style['vsid']) && $database_style['vsid'] == $vsid) {
$style = $database_style;
break;
}
}
}
//if we found a style return it
if (isset($style)) {
return $style;
}
// Otherwise the style was not found.
return FALSE;
}
/**
* Implements hook_permission().
*/
@@ -174,36 +139,115 @@ function video_embed_field_theme() {
),
'video_embed_field_embed_code' => array(
'template' => 'video-embed-field-embed-code',
'variables' => array('url' => NULL, 'style' => 'normal', 'video_data' => array()),
'variables' => array(
'url' => NULL,
'style' => 'normal',
'video_data' => array(),
),
),
'video_embed_field_colorbox_code' => array(
'variables' => array('image_url' => NULL, 'image_style' => 'normal', 'video_url' => NULL, 'video_style' => NULL, 'video_data' => array()),
'variables' => array(
'image_url' => NULL,
'image_style' => 'normal',
'image_alt' => NULL,
'video_url' => NULL,
'video_style' => NULL,
'video_data' => array(),
),
),
);
}
/**
* Creates a hook that other modules can implement to get handlers - hook_video_embed_handler_info
* Can be used to add more handlers if needed - from other modules and such
* @see video_embed_field.api.php for more information
* Implements hook_views_api().
*/
function video_embed_field_views_api() {
return array(
'api' => 3,
'path' => drupal_get_path('module', 'video_embed_field') . '/views',
);
}
/**
* Get an array of all styles and their settings.
*
* @return array
* An array of styles keyed by the video style name (name).
*
* @see video_embed_field_video_style_load()
*/
function video_embed_field_video_styles() {
$styles = &drupal_static(__FUNCTION__);
// Grab from cache or build the array.
if (!isset($styles)) {
// Load the style via ctools - which will handle all the caching for us -
// Because it does a bit more logic, lets still statically cache this.
ctools_include('export');
$styles = ctools_export_load_object('vef_video_styles');
}
return $styles;
}
/**
* Load a style by style name. May be used as a loader for menu items.
*
* Note that you may also use ctools_export_load_object with the key being
* vef_video_styles
*
* @param string $name
* The name of the style.
*
* @return array
* An video style array containing the following keys:
* - "name": The unique video style ID.
* - "title": The human readable video style name.
* - "data": An array of video settings within this video style.
* If the video style name or ID is not valid, an empty array is returned.
*/
function video_embed_field_video_style_load($name) {
$styles = video_embed_field_video_styles();
return isset($styles[$name]) ? $styles[$name] : FALSE;
}
/**
* Creates a hook that other modules can implement to get handlers.
*
* Can be used to add more handlers if needed - from other modules and such.
*
* @see hook_video_embed_handler_info
* @see video_embed_field.api.php
*/
function video_embed_get_handlers() {
$handlers = cache_get('video_embed_field_handlers');
$handlers = &drupal_static(__FUNCTION__);
if ($handlers === FALSE) {
$handlers = module_invoke_all('video_embed_handler_info');
drupal_alter('video_embed_field_handlers', $handlers);
cache_set('video_embed_field_handlers', $handlers);
}
else {
$handlers = $handlers->data;
if (!isset($handlers)) {
if ($handlers = cache_get('video_embed_field_handlers')) {
$handlers = $handlers->data;
}
else {
$handlers = module_invoke_all('video_embed_handler_info');
drupal_alter('video_embed_handler_info', $handlers);
cache_set('video_embed_field_handlers', $handlers);
}
}
return $handlers;
}
/**
* Retrieves the video handler for a video URL.
*
* @param string $url
* The video URL.
*
* @return string|bool
* The handler name for the URL, FALSE in case there is no handler.
*/
function video_embed_get_handler($url) {
// Process video URL
// Process video URL.
if (!stristr($url, 'http://') && !stristr($url, 'https://')) {
$url = 'http://' . $url;
}
@@ -231,8 +275,13 @@ function video_embed_get_handler($url) {
}
/**
* Create a form from the player configuration options
* $defaults will be passed in with the default settings for the various fields
* Creates a form from the player configuration options.
*
* @param array $defaults
* The default settings for the various fields.
*
* @return array
* The configuration form array.
*/
function video_embed_field_get_form($defaults) {
$form = array();
@@ -248,8 +297,9 @@ function video_embed_field_get_form($defaults) {
$form[$name] += array(
'#type' => 'fieldset',
'#title' => t($handler['title']),
'#title' => t('@provider settings', array('@provider' => $handler['title'])),
'#tree' => TRUE,
'#element_validate' => isset($handler['form_validate']) ? array($handler['form_validate']) : array(),
);
}
}
@@ -257,12 +307,28 @@ function video_embed_field_get_form($defaults) {
return $form;
}
/**
* Validates the iframe CSS dimensions.
*
* @param array $element
* The element to validate.
*/
function video_embed_field_validate_dimensions($element) {
if (!preg_match('/^(\d*)(px|%)?$/', $element['width']['#value'], $results)) {
form_error($element['width'], t('You should use a valid CSS value for width in @plugin plugin', array('@plugin' => $element['#title'])));
}
if (!preg_match('/^(\d*)(px|%)?$/', $element['height']['#value'], $results)) {
form_error($element['height'], t('You should use a valid CSS value for height in @plugin plugin', array('@plugin' => $element['#title'])));
}
}
/**
* Get an array of image styles suitable for using as select list options.
*
* @param $include_empty
* @param bool $include_empty
* If TRUE a <none> option will be inserted in the options array.
* @return
*
* @return array
* Array of image styles both key and value are set to style name.
*/
function video_embed_field_video_style_options($include_empty = TRUE) {
@@ -271,7 +337,9 @@ function video_embed_field_video_style_options($include_empty = TRUE) {
if ($include_empty && !empty($styles)) {
$options[''] = t('<none>');
}
$options = array_merge($options, drupal_map_assoc(array_keys($styles)));
foreach ($styles as $style) {
$options[$style->name] = $style->title;
}
if (empty($options)) {
$options[''] = t('No defined styles');
}
@@ -286,11 +354,23 @@ function video_embed_field_filter_info() {
'title' => t('Video Embedding'),
'description' => t('Replaces [VIDEO::http://www.youtube.com/watch?v=someVideoID::aVideoStyle] tags with embedded videos.'),
'process callback' => 'video_embed_field_filter_process',
'tips callback' => '_filter_video_embed_tips',
);
return $filters;
}
/**
* Implements callback_filter_tips().
*
* Provides help for the URL filter.
*
* @see filter_filter_info()
*/
function _filter_video_embed_tips($filter, $format, $long = FALSE) {
return t('Replaces [VIDEO::http://www.youtube.com/watch?v=someVideoID::aVideoStyle] tags with embedded videos.');
}
/**
* Video Embed Field filter process callback.
*/
@@ -298,12 +378,11 @@ function video_embed_field_filter_process($text, $filter, $format) {
preg_match_all('/ \[VIDEO:: ( [^\[\]]+ )* \] /x', $text, $matches);
$tag_match = (array) array_unique($matches[1]);
$handlers = video_embed_get_handlers();
foreach ($tag_match as $tag) {
$parts = explode('::', $tag);
// Get video style
// Get video style.
if (isset($parts[1])) {
$style = $parts[1];
}
@@ -320,40 +399,41 @@ function video_embed_field_filter_process($text, $filter, $format) {
}
/**
* Process variables to format a video player.
* Processes variables to format a video player.
*
* $variables contains the following information:
* - $url
* - $style
* - $video_data
* @param array $variables
* Contains the following information:
* - $url
* - $style
* - $video_data
*
* @see video-embed.tpl.php
*/
function template_preprocess_video_embed_field_embed_code(&$variables) {
// Get the handler
// Get the handler.
$handler = video_embed_get_handler($variables['url']);
$variables['handler'] = $handler['name'];
// Load the style
// Load the style.
$style = video_embed_field_video_style_load($variables['style']);
// If there was an issue load in the default style
// If there was an issue load in the default style.
if ($style == FALSE) {
$style = video_embed_field_video_style_load('normal');
}
if (isset($style->data[$variables['handler']])) {
$variables['style_settings'] = $style->data[$variables['handler']];
} //safety valve for when we add new handlers and there are styles in the database.
}
// Safety value for when we add new handlers and there are styles stored.
else {
$variables['style_settings'] = $handler['defaults'];
}
// Prepare the URL
// Prepare the URL.
if (!stristr($variables['url'], 'http://') && !stristr($variables['url'], 'https://')) {
$variables['url'] = 'http://' . $variables['url'];
}
// Prepare embed code
// Prepare embed code.
if ($handler && isset($handler['function']) && function_exists($handler['function'])) {
$embed_code = call_user_func($handler['function'], $variables['url'], $variables['style_settings']);
$variables['embed_code'] = drupal_render($embed_code);
@@ -362,89 +442,76 @@ function template_preprocess_video_embed_field_embed_code(&$variables) {
$variables['embed_code'] = l($variables['url'], $variables['url']);
}
// Prepare video data
// Prepare video data.
$variables['data'] = $variables['video_data'];
unset($variables['video_data']);
}
/**
* Returns image style image with a link to
* an embedded video in colorbox.
* Returns image style image with a link to an embedded video in colorbox.
*
* @param $variables
* @param array $variables
* An associative array containing:
* - image_url: The image URL.
* - image_style: The image style to use.
* - image_alt: The image ALT attribute.
* - video_url: The video URL.
* - video_style: The video style to use.
* - video_data: An array of data about the video.
*
* @return string
* The themed output.
*
* @ingroup themeable
*/
function theme_video_embed_field_colorbox_code($variables) {
$style = video_embed_field_video_style_load($variables['video_style']);
$path = video_embed_field_get_ajax_url($variables['video_url'], $variables['video_style']);
// If there was an issue load in the default style
if ($style == FALSE) {
$style = video_embed_field_video_style_load('normal');
}
$image = array(
'#theme' => 'image_formatter',
'#item' => array('uri' => $variables['image_url'], 'alt' => $variables['image_alt']),
'#image_style' => $variables['image_style'],
'#path' => $path,
);
$handler = video_embed_get_handler($variables['video_url']);
$data = $style->data[$handler['name']];
//Create a unique ID for colorbox inline
$id = uniqid('video_embed_field-' . rand());
if ($variables['image_style'] == 'none') {
$image = array(
array(
'#theme' => 'image',
'#path' => $variables['image_url'],
),
);
}
else {
$image = array(
'#theme' => 'image_style',
'#path' => $variables['image_url'],
'#style_name' => $variables['image_style'],
);
}
$image = drupal_render($image);
// Write values for later AJAX load
$hash = _video_embed_field_store_video($variables['video_url'], $variables['video_style']);
$output = l($image, base_path() . '?q=vef/load/' . $hash . '&width=' . ($data['width']) . '&height=' . ($data['height'] + 3), array('html' => TRUE, 'external' => TRUE, 'attributes' => array('class' => array('colorbox-load'))));
return $output;
return drupal_render($image);
}
/**
* Get the thumbnail url for a given video url
* @param $url - the url of the video
* @return a string representing the url of the thumbnail, or FALSE on error
*/
function video_embed_field_thumbnail_url($url) {
$handler = video_embed_get_handler($url);
if ($handler && isset($handler['thumbnail_function']) && function_exists($handler['thumbnail_function'])) {
$info = call_user_func($handler['thumbnail_function'], $url);
$info['handler'] = $handler['name'];
return $info;
}
return FALSE;
}
/**
* Get a video data array for a given video url
* Gets the thumbnail url for a given video url.
*
* @param string $url
* A video URL of the data array you want returned
* The url of the video.
*
* @return array|false $data
* An array of video data, or FALSE on error
* @return string
* String representing the url of the thumbnail, or FALSE on error.
*/
function video_embed_field_thumbnail_url($url) {
$info = FALSE;
if ($handler = video_embed_get_handler($url)) {
if (isset($handler['thumbnail_function']) && function_exists($handler['thumbnail_function'])) {
$info = call_user_func($handler['thumbnail_function'], $url);
$info['handler'] = $handler['name'];
}
if (empty($info['url']) && isset($handler['thumbnail_default']) && file_exists($handler['thumbnail_default'])) {
$info = array(
'handler' => $handler['name'],
'id' => 'default_thumbnail',
'url' => $handler['thumbnail_default'],
);
}
}
return $info;
}
/**
* Gets a video data array for a given video url.
*
* @param string $url
* A video URL of the data array you want returned.
*
* @return array|false
* An array of video data, or FALSE on error.
*/
function video_embed_field_get_video_data($url) {
$handler = video_embed_get_handler($url);
@@ -457,7 +524,53 @@ function video_embed_field_get_video_data($url) {
}
/**
* Fetch all available provider domains.
* Generates the AJAX path array from the video URL and the video_style.
*
* @param string $video_url
* The URL to the video.
* @param string $video_style
* The video style to render the video.
*
* @return array
* The AJAX path array.
*/
function video_embed_field_get_ajax_url($video_url, $video_style) {
$style = video_embed_field_video_style_load($video_style);
// If there was an issue load in the default style.
if ($style == FALSE) {
$style = video_embed_field_video_style_load('normal');
}
$handler = video_embed_get_handler($video_url);
$data = $style->data[$handler['name']];
// Write values for later AJAX load.
$hash = _video_embed_field_store_video($video_url, $video_style);
return array(
'path' => 'vef/load/' . $hash,
'options' => array(
'attributes' => array(
'class' => array(
'colorbox-load',
'colorbox'
),
),
'query' => array(
'width' => $data['width'],
'height' => $data['height'] + 5,
),
),
);
}
/**
* Fetches all available provider domains.
*
* @return array
* An array containing the allowed video domains.
*/
function _video_embed_field_get_provider_domains() {
$domains = array();
@@ -475,13 +588,32 @@ function _video_embed_field_get_provider_domains() {
}
/**
* Fetch settings string
* Fetches all available provider domains for certain field instance.
*
* @param array $instance
* The instance definition.
*
* @return array
* An array containing the allowed video domains.
*/
function _video_embed_field_get_instance_provider_domains($instance) {
return array_intersect(_video_embed_field_get_provider_domains(), $instance['settings']['allowed_providers']);
}
/**
* Fetches settings string.
*
* @param array $settings
* The settings array.
*
* @return string
* The settings string generated from the settings array.
*/
function _video_embed_code_get_settings_str($settings = array()) {
$values = array();
foreach ($settings as $name => $value) {
if (empty($value)) {
if (!isset($value)) {
$values[] = $name;
}
else {
@@ -492,19 +624,22 @@ function _video_embed_code_get_settings_str($settings = array()) {
return implode('&amp;', $values);
}
//used to array filter in video_embed_field_requirements
function _video_embed_field_array_filter($item) {
return (isset($item['type']) && $item['type'] == 'video_embed_field');
}
/**
* Store a video to be loaded later from an _video_embed_field_load_video
* Stores a video to be loaded later from an _video_embed_field_load_video.
*
* @param string $video_url
* The video URL.
* @param string $video_style
* The video style.
*
* @return string
* The hash generated for the video URL and the given style.
*/
function _video_embed_field_store_video($video_url, $video_style) {
//create a hash key
// Create a hash key.
$hash = _video_embed_field_hash($video_url, $video_style);
//check that is record doesn't already exist before saving it
// Check that this record doesn't already exist before saving it.
if (!_video_embed_field_load_video($hash)) {
$record = array(
'vhash' => $hash,
@@ -514,7 +649,7 @@ function _video_embed_field_store_video($video_url, $video_style) {
cache_set('vef-store-' . $hash, $record);
//add it to our static cache so we won't have to go to the database
// Add it to our static cache so we won't have to go to the database.
$static_cache = &drupal_static('vef_video_store', array());
$static_cache[$hash] = $record;
}
@@ -522,7 +657,13 @@ function _video_embed_field_store_video($video_url, $video_style) {
}
/**
* Callback to render a video for an Ajax call
* Renders a video for an AJAX call.
*
* @param string $hash
* The video hash.
*
* @return Null
* Null because prints an AJAX output.
*/
function _video_embed_field_show_video($hash) {
$data = _video_embed_field_load_video($hash);
@@ -537,20 +678,24 @@ function _video_embed_field_show_video($hash) {
}
/**
* Loads a video from the video store given its hash
* Returns either the data - an array with hash, video_url and video_style keys
* Loads a video from the video store given its hash.
*
* @param string $hash
* The video hash.
*
* @return array|bool
* An array with video definition, FALSE if the hash does not exist.
*/
function _video_embed_field_load_video($hash) {
$static_cache = &drupal_static('vef_video_store', array());
//check if we've already loaded it
// Check if we've already loaded it.
if (isset($static_cache[$hash])) {
return $static_cache[$hash];
}
else {
$result = cache_get('vef-store-' . $hash);
if ($result) {
//cache it before returning
// Cache it before returning.
$data = $result->data;
$static_cache[$hash] = $data;
return $data;
@@ -562,9 +707,16 @@ function _video_embed_field_load_video($hash) {
}
/**
* Creates a hash for storing or looking up a video in the store table
* Creates a hash for storing or looking up a video in the store table.
*
* @param string $video_url
* The video URL.
* @param string $video_style
* The video style.
*
* @return string
* The hash generated for the video URL and the given style.
*/
function _video_embed_field_hash($video_url, $video_style) {
return md5('vef' . $video_url . $video_style);
}
@@ -0,0 +1,22 @@
<?php
/**
* @file
* Video embed field thumbnail_path column implementation.
*/
/**
* Defines a field handler that can display the thumbnail_path url instead of
* the drupal internal uri.
*/
class views_embed_field_views_handler_field_thumbnail_path extends views_handler_field {
/**
* {@inheritdoc}
*/
function get_value($values, $field = NULL) {
$value = parent::get_value($values, $field);
return file_create_url($value);
}
}
@@ -0,0 +1,72 @@
<?php
/**
* @file
* Hooks for Views integration.
*/
/**
* Implements hook_field_views_data().
*/
function video_embed_field_field_views_data($field) {
$data = field_views_field_default_views_data($field);
// Only expose these components as Views field handlers.
$implemented = array(
'video_url' => 'views_handler_field',
'thumbnail_path' => 'views_embed_field_views_handler_field_thumbnail_path',
'description' => 'views_handler_field',
);
// Get the translated field information.
$properties = video_embed_field_data_property_info();
// Iterate over video_embed_field defined tables.
foreach ($data as &$table) {
// Make sure the parent Views field (video_embed_field) is defined.
if (isset($table[$field['field_name']]['field'])) {
// Use the parent field definition as a template for component columns.
$field_def = $table[$field['field_name']]['field'];
// Remove 'additional fields' from the field definition. We don't
// necessarily want all our sibling columns.
unset($field_def['additional fields']);
// Define the valid columns.
$valid_columns = array();
foreach ($implemented as $implement => $handler) {
$column_name = $field['field_name'] . '_' . $implement;
$valid_columns[$column_name] = $handler;
}
// Iterate over the video_embed_field components.
foreach ($table as $column_name => &$column) {
if (empty($column['field']) && isset($valid_columns[$column_name])) {
// Assign the default component definition.
$column['field'] = $field_def;
$column['field']['real field'] = $column_name;
$column['field']['handler'] = $valid_columns[$column_name];
// Assign human-friendly labels for video_embed_field components.
$field_labels = field_views_field_label($field['field_name']);
$field_label = array_shift($field_labels);
$property = str_replace($field_def['field_name'] . '_', '', $column_name);
if (!empty($properties[$property])) {
$property_label = $properties[$property]['label'];
$title = t('@field-label - @property-label', array(
'@field-label' => $field_label,
'@property-label' => $property_label,
));
$column['title'] = $title;
$column['title short'] = $title;
}
}
}
}
}
return $data;
}