Merge branch '8.x' of git.drupal.org:project/drupal into 8.x

8.0.x
Dries 2012-01-31 14:13:57 -05:00
commit a7e6d4af19
17 changed files with 562 additions and 163 deletions

View File

@ -51,12 +51,12 @@ interface ArchiverInterface {
* @param $files
* Optionally specify a list of files to be extracted. Files are
* relative to the root of the archive. If not specified, all files
* in the archive will be extracted
* in the archive will be extracted.
*
* @return ArchiverInterface
* The called object.
*/
public function extract($path, Array $files = array());
public function extract($path, array $files = array());
/**
* Lists all files in the archive.

View File

@ -1850,7 +1850,9 @@ function format_interval($interval, $granularity = 2, $langcode = NULL) {
* A UNIX timestamp to format.
* @param $type
* (optional) The format to use, one of:
* - 'short', 'medium', or 'long' (the corresponding built-in date formats).
* - One of the built-in formats: 'short', 'medium', 'long', 'html_datetime',
* 'html_date', 'html_time', 'html_yearless_date', 'html_week',
* 'html_month', 'html_year'.
* - The name of a date type defined by a module in hook_date_format_types(),
* if it's been assigned a format.
* - The machine name of an administrator-defined date format.
@ -1903,6 +1905,34 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL
$format = variable_get('date_format_long', 'l, F j, Y - H:i');
break;
case 'html_datetime':
$format = variable_get('date_format_html_datetime', 'Y-m-d\TH:i:sO');
break;
case 'html_date':
$format = variable_get('date_format_html_date', 'Y-m-d');
break;
case 'html_time':
$format = variable_get('date_format_html_time', 'H:i:s');
break;
case 'html_yearless_date':
$format = variable_get('date_format_html_yearless_date', 'm-d');
break;
case 'html_week':
$format = variable_get('date_format_html_week', 'Y-\WW');
break;
case 'html_month':
$format = variable_get('date_format_html_month', 'Y-m');
break;
case 'html_year':
$format = variable_get('date_format_html_year', 'Y');
break;
case 'custom':
// No change to format.
break;
@ -6720,6 +6750,9 @@ function drupal_common_theme() {
'render element' => 'elements',
'template' => 'region',
),
'datetime' => array(
'variables' => array('timestamp' => NULL, 'text' => NULL, 'attributes' => array(), 'html' => FALSE),
),
'status_messages' => array(
'variables' => array('display' => NULL),
),

View File

@ -447,18 +447,10 @@ function menu_get_item($path = NULL, $router_item = NULL) {
}
$original_map = arg(NULL, $path);
// Since there is no limit to the length of $path, use a hash to keep it
// short yet unique.
$cid = 'menu_item:' . hash('sha256', $path);
if ($cached = cache('menu')->get($cid)) {
$router_item = $cached->data;
}
else {
$parts = array_slice($original_map, 0, MENU_MAX_PARTS);
$ancestors = menu_get_ancestors($parts);
$router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc();
cache('menu')->set($cid, $router_item);
}
$parts = array_slice($original_map, 0, MENU_MAX_PARTS);
$ancestors = menu_get_ancestors($parts);
$router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc();
if ($router_item) {
// Allow modules to alter the router item before it is translated and
// checked for access.

View File

@ -1474,6 +1474,66 @@ function theme_disable($theme_list) {
* @{
*/
/**
* Preprocess variables for theme_datetime().
*/
function template_preprocess_datetime(&$variables) {
// Format the 'datetime' attribute based on the timestamp.
// @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime
if (!isset($variables['attributes']['datetime']) && isset($variables['timestamp'])) {
$variables['attributes']['datetime'] = format_date($variables['timestamp'], 'html_datetime', '', 'UTC');
}
// If no text was provided, try to auto-generate it.
if (!isset($variables['text'])) {
// Format and use a human-readable version of the timestamp, if any.
if (isset($variables['timestamp'])) {
$variables['text'] = format_date($variables['timestamp']);
$variables['html'] = FALSE;
}
// Otherwise, use the literal datetime attribute.
elseif (isset($variables['attributes']['datetime'])) {
$variables['text'] = $variables['attributes']['datetime'];
$variables['html'] = FALSE;
}
}
}
/**
* Returns HTML for a date / time.
*
* @param $variables
* An associative array containing:
* - timestamp: (optional) A UNIX timestamp for the datetime attribute. If the
* datetime cannot be represented as a UNIX timestamp, use a valid datetime
* attribute value in $variables['attributes']['datetime'].
* - text: (optional) The content to display within the <time> element. Set
* 'html' to TRUE if this value is already sanitized for output in HTML.
* Defaults to a human-readable representation of the timestamp value or the
* datetime attribute value using format_date().
* When invoked as #theme or #theme_wrappers of a render element, the
* rendered #children are autoamtically taken over as 'text', unless #text
* is explicitly set.
* - attributes: (optional) An associative array of HTML attributes to apply
* to the <time> element. A datetime attribute in 'attributes' overrides the
* 'timestamp'. To create a valid datetime attribute value from a UNIX
* timestamp, use format_date() with one of the predefined 'html_*' formats.
* - html: (optional) Whether 'text' is HTML markup (TRUE) or plain-text
* (FALSE). Defaults to FALSE. For example, to use a SPAN tag within the
* TIME element, this must be set to TRUE, or the SPAN tag will be escaped.
* It is the responsibility of the caller to properly sanitize the value
* contained in 'text' (or within the SPAN tag in aforementioned example).
*
* @see template_preprocess_datetime()
* @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime
*/
function theme_datetime($variables) {
$output = '<time' . drupal_attributes($variables['attributes']) . '>';
$output .= !empty($variables['html']) ? $variables['text'] : check_plain($variables['text']);
$output .= '</time>';
return $output;
}
/**
* Returns HTML for status and/or error messages, grouped by type.
*

View File

@ -21,7 +21,7 @@ Drupal.behaviors.states = {
new states.Dependent({
element: $(selector),
state: states.State.sanitize(state),
dependees: settings.states[selector][state]
constraints: settings.states[selector][state]
});
}
}
@ -40,12 +40,14 @@ Drupal.behaviors.states = {
* Object with the following keys (all of which are required):
* - element: A jQuery object of the dependent element
* - state: A State object describing the state that is dependent
* - dependees: An object with dependency specifications. Lists all elements
* that this element depends on.
* - constraints: An object with dependency specifications. Lists all elements
* that this element depends on. It can be nested and can contain arbitrary
* AND and OR clauses.
*/
states.Dependent = function (args) {
$.extend(this, { values: {}, oldValue: undefined }, args);
$.extend(this, { values: {}, oldValue: null }, args);
this.dependees = this.getDependees();
for (var selector in this.dependees) {
this.initializeDependee(selector, this.dependees[selector]);
}
@ -69,7 +71,7 @@ states.Dependent.comparisons = {
// as a string before applying the strict comparison in compare(). Otherwise
// numeric keys in the form's #states array fail to match string values
// returned from jQuery's val().
return (value.constructor.name === 'String') ? compare(String(reference), value) : compare(reference, value);
return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value);
}
};
@ -84,26 +86,33 @@ states.Dependent.prototype = {
* dependee's compliance status.
*/
initializeDependee: function (selector, dependeeStates) {
var self = this;
var state;
// Cache for the states of this dependee.
self.values[selector] = {};
this.values[selector] = {};
$.each(dependeeStates, function (state, value) {
state = states.State.sanitize(state);
for (var i in dependeeStates) {
if (dependeeStates.hasOwnProperty(i)) {
state = dependeeStates[i];
// Make sure we're not initializing this selector/state combination twice.
if ($.inArray(state, dependeeStates) === -1) {
continue;
}
// Initialize the value of this state.
self.values[selector][state.pristine] = undefined;
state = states.State.sanitize(state);
// Monitor state changes of the specified state for this dependee.
$(selector).bind('state:' + state, function (e) {
var complies = self.compare(value, e.value);
self.update(selector, state, complies);
});
// Initialize the value of this state.
this.values[selector][state.name] = null;
// Make sure the event we just bound ourselves to is actually fired.
new states.Trigger({ selector: selector, state: state });
});
// Monitor state changes of the specified state for this dependee.
$(selector).bind('state:' + state, $.proxy(function (e) {
this.update(selector, state, e.value);
}, this));
// Make sure the event we just bound ourselves to is actually fired.
new states.Trigger({ selector: selector, state: state });
}
}
},
/**
@ -111,12 +120,16 @@ states.Dependent.prototype = {
*
* @param reference
* The value used for reference.
* @param value
* The value to compare with the reference value.
* @param selector
* CSS selector describing the dependee.
* @param state
* A State object describing the dependee's updated state.
*
* @return
* true, undefined or false.
* true or false.
*/
compare: function (reference, value) {
compare: function (reference, selector, state) {
var value = this.values[selector][state.name];
if (reference.constructor.name in states.Dependent.comparisons) {
// Use a custom compare function for certain reference value types.
return states.Dependent.comparisons[reference.constructor.name](reference, value);
@ -139,8 +152,8 @@ states.Dependent.prototype = {
*/
update: function (selector, state, value) {
// Only act when the 'new' value is actually new.
if (value !== this.values[selector][state.pristine]) {
this.values[selector][state.pristine] = value;
if (value !== this.values[selector][state.name]) {
this.values[selector][state.name] = value;
this.reevaluate();
}
},
@ -149,16 +162,8 @@ states.Dependent.prototype = {
* Triggers change events in case a state changed.
*/
reevaluate: function () {
var value = undefined;
// Merge all individual values to find out whether this dependee complies.
for (var selector in this.values) {
for (var state in this.values[selector]) {
state = states.State.sanitize(state);
var complies = this.values[selector][state.pristine];
value = ternary(value, invert(complies, state.invert));
}
}
// Check whether any constraint for this dependent state is satisifed.
var value = this.verifyConstraints(this.constraints);
// Only invoke a state change event when the value actually changed.
if (value !== this.oldValue) {
@ -173,6 +178,124 @@ states.Dependent.prototype = {
// infinite loops.
this.element.trigger({ type: 'state:' + this.state, value: value, trigger: true });
}
},
/**
* Evaluates child constraints to determine if a constraint is satisfied.
*
* @param constraints
* A constraint object or an array of constraints.
* @param selector
* The selector for these constraints. If undefined, there isn't yet a
* selector that these constraints apply to. In that case, the keys of the
* object are interpreted as the selector if encountered.
*
* @return
* true or false, depending on whether these constraints are satisfied.
*/
verifyConstraints: function(constraints, selector) {
var result;
if ($.isArray(constraints)) {
// This constraint is an array (OR or XOR).
var hasXor = $.inArray('xor', constraints) === -1;
for (var i = 0, len = constraints.length; i < len; i++) {
if (constraints[i] != 'xor') {
var constraint = this.checkConstraints(constraints[i], selector, i);
// Return if this is OR and we have a satisfied constraint or if this
// is XOR and we have a second satisfied constraint.
if (constraint && (hasXor || result)) {
return hasXor;
}
result = result || constraint;
}
}
}
// Make sure we don't try to iterate over things other than objects. This
// shouldn't normally occur, but in case the condition definition is bogus,
// we don't want to end up with an infinite loop.
else if ($.isPlainObject(constraints)) {
// This constraint is an object (AND).
for (var n in constraints) {
if (constraints.hasOwnProperty(n)) {
result = ternary(result, this.checkConstraints(constraints[n], selector, n));
// False and anything else will evaluate to false, so return when any
// false condition is found.
if (result === false) { return false; }
}
}
}
return result;
},
/**
* Checks whether the value matches the requirements for this constraint.
*
* @param value
* Either the value of a state or an array/object of constraints. In the
* latter case, resolving the constraint continues.
* @param selector
* The selector for this constraint. If undefined, there isn't yet a
* selector that this constraint applies to. In that case, the state key is
* propagates to a selector and resolving continues.
* @param state
* The state to check for this constraint. If undefined, resolving
* continues.
* If both selector and state aren't undefined and valid non-numeric
* strings, a lookup for the actual value of that selector's state is
* performed. This parameter is not a State object but a pristine state
* string.
*
* @return
* true or false, depending on whether this constraint is satisfied.
*/
checkConstraints: function(value, selector, state) {
// Normalize the last parameter. If it's non-numeric, we treat it either as
// a selector (in case there isn't one yet) or as a trigger/state.
if (typeof state !== 'string' || (/[0-9]/).test(state[0])) {
state = null;
}
else if (typeof selector === 'undefined') {
// Propagate the state to the selector when there isn't one yet.
selector = state;
state = null;
}
if (state !== null) {
// constraints is the actual constraints of an element to check for.
state = states.State.sanitize(state);
return invert(this.compare(value, selector, state), state.invert);
}
else {
// Resolve this constraint as an AND/OR operator.
return this.verifyConstraints(value, selector);
}
},
/**
* Gathers information about all required triggers.
*/
getDependees: function() {
var cache = {};
// Swivel the lookup function so that we can record all available selector-
// state combinations for initialization.
var _compare = this.compare;
this.compare = function(reference, selector, state) {
(cache[selector] || (cache[selector] = [])).push(state.name);
// Return nothing (=== undefined) so that the constraint loops are not
// broken.
};
// This call doesn't actually verify anything but uses the resolving
// mechanism to go through the constraints array, trying to look up each
// value. Since we swivelled the compare function, this comparison returns
// undefined and lookup continues until the very end. Instead of lookup up
// the value, we record that combination of selector and state so that we
// can initialize all triggers.
this.verifyConstraints(this.constraints);
// Restore the original function.
this.compare = _compare;
return cache;
}
};
@ -192,7 +315,6 @@ states.Trigger = function (args) {
states.Trigger.prototype = {
initialize: function () {
var self = this;
var trigger = states.Trigger.states[this.state];
if (typeof trigger == 'function') {
@ -200,9 +322,11 @@ states.Trigger.prototype = {
trigger.call(window, this.element);
}
else {
$.each(trigger, function (event, valueFn) {
self.defaultTrigger(event, valueFn);
});
for (var event in trigger) {
if (trigger.hasOwnProperty(event)) {
this.defaultTrigger(event, trigger[event]);
}
}
}
// Mark this trigger as initialized for this element.
@ -210,23 +334,22 @@ states.Trigger.prototype = {
},
defaultTrigger: function (event, valueFn) {
var self = this;
var oldValue = valueFn.call(this.element);
// Attach the event callback.
this.element.bind(event, function (e) {
var value = valueFn.call(self.element, e);
this.element.bind(event, $.proxy(function (e) {
var value = valueFn.call(this.element, e);
// Only trigger the event if the value has actually changed.
if (oldValue !== value) {
self.element.trigger({ type: 'state:' + self.state, value: value, oldValue: oldValue });
this.element.trigger({ type: 'state:' + this.state, value: value, oldValue: oldValue });
oldValue = value;
}
});
}, this));
states.postponed.push(function () {
states.postponed.push($.proxy(function () {
// Trigger the event once for initialization purposes.
self.element.trigger({ type: 'state:' + self.state, value: oldValue, oldValue: undefined });
});
this.element.trigger({ type: 'state:' + this.state, value: oldValue, oldValue: null });
}, this));
}
};
@ -286,7 +409,7 @@ states.Trigger.states = {
collapsed: {
'collapsed': function(e) {
return (e !== undefined && 'value' in e) ? e.value : this.is('.collapsed');
return (typeof e !== 'undefined' && 'value' in e) ? e.value : this.is('.collapsed');
}
}
};
@ -318,7 +441,7 @@ states.State = function(state) {
};
/**
* Create a new State object by sanitizing the passed value.
* Creates a new State object by sanitizing the passed value.
*/
states.State.sanitize = function (state) {
if (state instanceof states.State) {
@ -363,72 +486,71 @@ states.State.prototype = {
* bubble up to these handlers. We use this system so that themes and modules
* can override these state change handlers for particular parts of a page.
*/
{
$(document).bind('state:disabled', function(e) {
// Only act when this change was triggered by a dependency and not by the
// element monitoring itself.
if (e.trigger) {
$(e.target)
.attr('disabled', e.value)
.filter('.form-element')
.closest('.form-item, .form-submit, .form-wrapper')[e.value ? 'addClass' : 'removeClass']('form-disabled');
// Note: WebKit nightlies don't reflect that change correctly.
// See https://bugs.webkit.org/show_bug.cgi?id=23789
}
});
$(document).bind('state:disabled', function(e) {
// Only act when this change was triggered by a dependency and not by the
// element monitoring itself.
if (e.trigger) {
$(e.target)
.attr('disabled', e.value)
.filter('.form-element')
.closest('.form-item, .form-submit, .form-wrapper')[e.value ? 'addClass' : 'removeClass']('form-disabled');
$(document).bind('state:required', function(e) {
if (e.trigger) {
if (e.value) {
$(e.target).closest('.form-item, .form-wrapper').find('label').append('<abbr class="form-required" title="' + Drupal.t('This field is required.') + '">*</abbr>');
}
else {
$(e.target).closest('.form-item, .form-wrapper').find('label .form-required').remove();
}
}
});
// Note: WebKit nightlies don't reflect that change correctly.
// See https://bugs.webkit.org/show_bug.cgi?id=23789
}
});
$(document).bind('state:visible', function(e) {
if (e.trigger) {
$(e.target).closest('.form-item, .form-submit, .form-wrapper')[e.value ? 'show' : 'hide']();
$(document).bind('state:required', function(e) {
if (e.trigger) {
if (e.value) {
$(e.target).closest('.form-item, .form-wrapper').find('label').append('<abbr class="form-required" title="' + Drupal.t('This field is required.') + '">*</abbr>');
}
});
else {
$(e.target).closest('.form-item, .form-wrapper').find('label .form-required').remove();
}
}
});
$(document).bind('state:checked', function(e) {
if (e.trigger) {
$(e.target).attr('checked', e.value);
}
});
$(document).bind('state:visible', function(e) {
if (e.trigger) {
$(e.target).closest('.form-item, .form-submit, .form-wrapper')[e.value ? 'show' : 'hide']();
}
});
$(document).bind('state:collapsed', function(e) {
if (e.trigger) {
if ($(e.target).is('.collapsed') !== e.value) {
$('> legend a', e.target).click();
}
$(document).bind('state:checked', function(e) {
if (e.trigger) {
$(e.target).attr('checked', e.value);
}
});
$(document).bind('state:collapsed', function(e) {
if (e.trigger) {
if ($(e.target).is('.collapsed') !== e.value) {
$('> legend a', e.target).click();
}
});
}
}
});
/**
* These are helper functions implementing addition "operators" and don't
* implement any logic that is particular to states.
*/
{
// Bitwise AND with a third undefined state.
function ternary (a, b) {
return a === undefined ? b : (b === undefined ? a : a && b);
};
// Inverts a (if it's not undefined) when invert is true.
function invert (a, invert) {
return (invert && a !== undefined) ? !a : a;
};
// Bitwise AND with a third undefined state.
function ternary (a, b) {
return typeof a === 'undefined' ? b : (typeof b === 'undefined' ? a : a && b);
}
// Compares two values while ignoring undefined values.
function compare (a, b) {
return (a === b) ? (a === undefined ? a : true) : (a === undefined || b === undefined);
}
// Inverts a (if it's not undefined) when invert is true.
function invert (a, invert) {
return (invert && typeof a !== 'undefined') ? !a : a;
}
// Compares two values while ignoring undefined values.
function compare (a, b) {
return (a === b) ? (typeof a === 'undefined' ? a : true) : (typeof a === 'undefined' || typeof b === 'undefined');
}
})(jQuery);

View File

@ -2149,24 +2149,26 @@ function template_preprocess_comment(&$variables) {
else {
$variables['status'] = ($comment->status == COMMENT_NOT_PUBLISHED) ? 'comment-unpublished' : 'comment-published';
}
// Gather comment classes.
if ($comment->uid == 0) {
// 'comment-published' class is not needed, it is either 'comment-preview' or
// 'comment-unpublished'.
if ($variables['status'] != 'comment-published') {
$variables['classes_array'][] = $variables['status'];
}
if ($variables['new']) {
$variables['classes_array'][] = 'comment-new';
}
if (!$comment->uid) {
$variables['classes_array'][] = 'comment-by-anonymous';
}
else {
// Published class is not needed. It is either 'comment-preview' or 'comment-unpublished'.
if ($variables['status'] != 'comment-published') {
$variables['classes_array'][] = $variables['status'];
}
if ($comment->uid === $variables['node']->uid) {
if ($comment->uid == $variables['node']->uid) {
$variables['classes_array'][] = 'comment-by-node-author';
}
if ($comment->uid === $variables['user']->uid) {
if ($comment->uid == $variables['user']->uid) {
$variables['classes_array'][] = 'comment-by-viewer';
}
if ($variables['new']) {
$variables['classes_array'][] = 'comment-new';
}
}
}

View File

@ -291,8 +291,6 @@ class CommentInterfaceTest extends CommentHelperCase {
$comment = $this->postComment($this->node, $comment_text);
$comment_loaded = comment_load($comment->id);
$this->assertTrue($this->commentExists($comment), t('Comment found.'));
$by_viewer_class = $this->xpath('//a[@id=:comment_id]/following-sibling::div[1][contains(@class, "comment-by-viewer")]', array(':comment_id' => 'comment-' . $comment->id));
$this->assertTrue(!empty($by_viewer_class), t('HTML class for comments by viewer found.'));
// Set comments to have subject and preview to required.
$this->drupalLogout();
@ -379,11 +377,6 @@ class CommentInterfaceTest extends CommentHelperCase {
$this->assertTrue($this->commentExists($reply, TRUE), t('Page two exists. %s'));
$this->setCommentsPerPage(50);
// Create comment #5 to assert HTML class.
$comment = $this->postComment($this->node, $this->randomName(), $this->randomName());
$by_node_author_class = $this->xpath('//a[@id=:comment_id]/following-sibling::div[1][contains(@class, "comment-by-node-author")]', array(':comment_id' => 'comment-' . $comment->id));
$this->assertTrue(!empty($by_node_author_class), t('HTML class for node author found.'));
// Attempt to post to node with comments disabled.
$this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_HIDDEN));
$this->assertTrue($this->node, t('Article node created.'));
@ -482,6 +475,111 @@ class CommentInterfaceTest extends CommentHelperCase {
$this->assertTrue(count($count) == 2, print_r($count, TRUE));
}
/**
* Tests CSS classes on comments.
*/
function testCommentClasses() {
// Create all permutations for comments, users, and nodes.
$parameters = array(
'node_uid' => array(0, $this->web_user->uid),
'comment_uid' => array(0, $this->web_user->uid, $this->admin_user->uid),
'comment_status' => array(COMMENT_PUBLISHED, COMMENT_NOT_PUBLISHED),
'user' => array('anonymous', 'authenticated', 'admin'),
);
$permutations = $this->generatePermutations($parameters);
foreach ($permutations as $case) {
// Create a new node.
$node = $this->drupalCreateNode(array('type' => 'article', 'uid' => $case['node_uid']));
// Add a comment.
$comment = entity_create('comment', array(
'nid' => $node->nid,
'uid' => $case['comment_uid'],
'status' => $case['comment_status'],
'subject' => $this->randomName(),
'language' => LANGUAGE_NONE,
'comment_body' => array(LANGUAGE_NONE => array($this->randomName())),
));
comment_save($comment);
// Adjust the current/viewing user.
switch ($case['user']) {
case 'anonymous':
$this->drupalLogout();
$case['user_uid'] = 0;
break;
case 'authenticated':
$this->drupalLogin($this->web_user);
$case['user_uid'] = $this->web_user->uid;
break;
case 'admin':
$this->drupalLogin($this->admin_user);
$case['user_uid'] = $this->admin_user->uid;
break;
}
// Request the node with the comment.
$this->drupalGet('node/' . $node->nid);
// Verify classes if the comment is visible for the current user.
if ($case['comment_status'] == COMMENT_PUBLISHED || $case['user'] == 'admin') {
// Verify the comment-by-anonymous class.
$comments = $this->xpath('//*[contains(@class, "comment-by-anonymous")]');
if ($case['comment_uid'] == 0) {
$this->assertTrue(count($comments) == 1, 'comment-by-anonymous class found.');
}
else {
$this->assertFalse(count($comments), 'comment-by-anonymous class not found.');
}
// Verify the comment-by-node-author class.
$comments = $this->xpath('//*[contains(@class, "comment-by-node-author")]');
if ($case['comment_uid'] > 0 && $case['comment_uid'] == $case['node_uid']) {
$this->assertTrue(count($comments) == 1, 'comment-by-node-author class found.');
}
else {
$this->assertFalse(count($comments), 'comment-by-node-author class not found.');
}
// Verify the comment-by-viewer class.
$comments = $this->xpath('//*[contains(@class, "comment-by-viewer")]');
if ($case['comment_uid'] > 0 && $case['comment_uid'] == $case['user_uid']) {
$this->assertTrue(count($comments) == 1, 'comment-by-viewer class found.');
}
else {
$this->assertFalse(count($comments), 'comment-by-viewer class not found.');
}
}
// Verify the comment-unpublished class.
$comments = $this->xpath('//*[contains(@class, "comment-unpublished")]');
if ($case['comment_status'] == COMMENT_NOT_PUBLISHED && $case['user'] == 'admin') {
$this->assertTrue(count($comments) == 1, 'comment-unpublished class found.');
}
else {
$this->assertFalse(count($comments), 'comment-unpublished class not found.');
}
// Verify the comment-new class.
if ($case['comment_status'] == COMMENT_PUBLISHED || $case['user'] == 'admin') {
$comments = $this->xpath('//*[contains(@class, "comment-new")]');
if ($case['user'] != 'anonymous') {
$this->assertTrue(count($comments) == 1, 'comment-new class found.');
// Request the node again. The comment-new class should disappear.
$this->drupalGet('node/' . $node->nid);
$comments = $this->xpath('//*[contains(@class, "comment-new")]');
$this->assertFalse(count($comments), 'comment-new class not found.');
}
else {
$this->assertFalse(count($comments), 'comment-new class not found.');
}
}
}
}
/**
* Tests the node comment statistics.
*/
@ -982,8 +1080,6 @@ class CommentAnonymous extends CommentHelperCase {
// Post anonymous comment without contact info.
$anonymous_comment1 = $this->postComment($this->node, $this->randomName(), $this->randomName());
$this->assertTrue($this->commentExists($anonymous_comment1), t('Anonymous comment without contact info found.'));
$anonymous_class = $this->xpath('//a[@id=:comment_id]/following-sibling::div[1][contains(@class, "comment-by-anonymous")]', array(':comment_id' => 'comment-' . $anonymous_comment1->id));
$this->assertTrue(!empty($anonymous_class), t('HTML class for anonymous comments found.'));
// Allow contact info.
$this->drupalLogin($this->admin_user);

View File

@ -617,8 +617,9 @@ function field_info_fields() {
*
* @param $field_name
* The name of the field to retrieve. $field_name can only refer to a
* non-deleted, active field. Use field_read_fields() to retrieve information
* on deleted or inactive fields.
* non-deleted, active field. For deleted fields, use
* field_info_field_by_id(). To retrieve information about inactive fields,
* use field_read_fields().
*
* @return
* The field array, as returned by field_read_fields(), with an
@ -639,7 +640,7 @@ function field_info_field($field_name) {
*
* @param $field_id
* The id of the field to retrieve. $field_id can refer to a
* deleted field.
* deleted field, but not an inactive one.
*
* @return
* The field array, as returned by field_read_fields(), with an

View File

@ -1448,7 +1448,7 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) {
* viewed.
*
* @return
* A $page element suitable for use by drupal_page_render().
* A $page element suitable for use by drupal_render().
*
* @see node_menu()
*/

View File

@ -167,7 +167,7 @@ function node_access_entity_test_page() {
}
/**
* Implements hook_form_node_form_alter().
* Implements hook_form_BASE_FORM_ID_alter().
*/
function node_access_test_form_node_form_alter(&$form, $form_state) {
// Only show this checkbox for NodeAccessBaseTableTestCase.

View File

@ -2394,6 +2394,14 @@ class CommonFormatDateTestCase extends DrupalWebTestCase {
$this->assertIdentical(format_date($timestamp, 'medium'), '25. marzo 2007 - 17:00', t('Test medium date format.'));
$this->assertIdentical(format_date($timestamp, 'short'), '2007 Mar 25 - 5:00pm', t('Test short date format.'));
$this->assertIdentical(format_date($timestamp), '25. marzo 2007 - 17:00', t('Test default date format.'));
// Test HTML time element formats.
$this->assertIdentical(format_date($timestamp, 'html_datetime'), '2007-03-25T17:00:00-0700', t('Test html_datetime date format.'));
$this->assertIdentical(format_date($timestamp, 'html_date'), '2007-03-25', t('Test html_date date format.'));
$this->assertIdentical(format_date($timestamp, 'html_time'), '17:00:00', t('Test html_time date format.'));
$this->assertIdentical(format_date($timestamp, 'html_yearless_date'), '03-25', t('Test html_yearless_date date format.'));
$this->assertIdentical(format_date($timestamp, 'html_week'), '2007-W12', t('Test html_week date format.'));
$this->assertIdentical(format_date($timestamp, 'html_month'), '2007-03', t('Test html_month date format.'));
$this->assertIdentical(format_date($timestamp, 'html_year'), '2007', t('Test html_year date format.'));
// Restore the original user and language, and enable session saving.
$user = $real_user;

View File

@ -592,3 +592,65 @@ class ThemeRegistryTestCase extends DrupalWebTestCase {
$this->assertTrue($registry['theme_test_template_test_2'], 'Offset was returned correctly from the theme registry');
}
}
/**
* Tests for theme_datetime().
*/
class ThemeDatetime extends DrupalWebTestCase {
protected $profile = 'testing';
public static function getInfo() {
return array(
'name' => 'Theme Datetime',
'description' => 'Test the theme_datetime() function.',
'group' => 'Theme',
);
}
/**
* Test function theme_datetime().
*/
function testThemeDatetime() {
// Create timestamp and formatted date for testing.
$timestamp = 280281600;
$date = format_date($timestamp);
// Test with timestamp.
$variables = array(
'timestamp' => $timestamp,
);
$this->assertEqual('<time datetime="1978-11-19T00:00:00+0000">' . $date . '</time>', theme('datetime', $variables));
// Test with text and timestamp.
$variables = array(
'timestamp' => $timestamp,
'text' => "Dries' birthday",
);
$this->assertEqual('<time datetime="1978-11-19T00:00:00+0000">Dries&#039; birthday</time>', theme('datetime', $variables));
// Test with datetime attribute.
$variables = array(
'attributes' => array(
'datetime' => '1978-11-19',
),
);
$this->assertEqual('<time datetime="1978-11-19">1978-11-19</time>', theme('datetime', $variables));
// Test with text and datetime attribute.
$variables = array(
'text' => "Dries' birthday",
'attributes' => array(
'datetime' => '1978-11-19',
),
);
$this->assertEqual('<time datetime="1978-11-19">Dries&#039; birthday</time>', theme('datetime', $variables));
// Test with HTML text.
$variables = array(
'timestamp' => $timestamp,
'text' => "<span>Dries' birthday</span>",
'html' => TRUE,
);
$this->assertEqual('<time datetime="1978-11-19T00:00:00+0000"><span>Dries\' birthday</span></time>', theme('datetime', $variables));
}
}

View File

@ -609,7 +609,11 @@ function hook_menu_get_item_alter(&$router_item, $path, $original_map) {
* @endcode
* This 'abc' object will then be passed into the callback functions defined
* for the menu item, such as the page callback function mymodule_abc_edit()
* to replace the integer 1 in the argument array.
* to replace the integer 1 in the argument array. Note that a load function
* should return FALSE when it is unable to provide a loadable object. For
* example, the node_load() function for the 'node/%node/edit' menu item will
* return FALSE for the path 'node/999/edit' if a node with a node ID of 999
* does not exist. The menu routing system will return a 404 error in this case.
*
* You can also define a %wildcard_to_arg() function (for the example menu
* entry above this would be 'mymodule_abc_to_arg()'). The _to_arg() function

View File

@ -2747,8 +2747,8 @@ function system_region_list($theme_key, $show = REGIONS_ALL) {
* Implements hook_system_info_alter().
*/
function system_system_info_alter(&$info, $file, $type) {
// Remove page-top from the blocks UI since it is reserved for modules to
// populate from outside the blocks system.
// Remove page-top and page-bottom from the blocks UI since they are reserved for
// modules to populate from outside the blocks system.
if ($type == 'theme') {
$info['regions_hidden'][] = 'page_top';
$info['regions_hidden'][] = 'page_bottom';

View File

@ -45,13 +45,13 @@
* would be an in-memory queue backend which might lose items if it crashes.
* However, such a backend would be able to deal with significantly more writes
* than a reliable queue and for many tasks this is more important. See
* aggregator_cron() for an example of how can this not be a problem. Another
* example is doing Twitter statistics -- the small possibility of losing a few
* items is insignificant next to power of the queue being able to keep up with
* writes. As described in the processing section, regardless of the queue
* being reliable or not, the processing code should be aware that an item
* might be handed over for processing more than once (because the processing
* code might time out before it finishes).
* aggregator_cron() for an example of how to effectively utilize a
* non-reliable queue. Another example is doing Twitter statistics -- the small
* possibility of losing a few items is insignificant next to power of the
* queue being able to keep up with writes. As described in the processing
* section, regardless of the queue being reliable or not, the processing code
* should be aware that an item might be handed over for processing more than
* once (because the processing code might time out before it finishes).
*/
/**

View File

@ -1128,6 +1128,21 @@ class TaxonomyTermIndexTestCase extends TaxonomyWebTestCase {
))->fetchField();
$this->assertEqual(0, $index_count, t('Term 2 is not indexed.'));
}
/**
* Tests that there is a link to the parent term on the child term page.
*/
function testTaxonomyTermHierarchyBreadcrumbs() {
// Create two taxonomy terms and set term2 as the parent of term1.
$term1 = $this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
$term1->parent = array($term2->tid);
taxonomy_term_save($term1);
// Verify that the page breadcrumbs include a link to the parent term.
$this->drupalGet('taxonomy/term/' . $term1->tid);
$this->assertRaw(l($term2->name, 'taxonomy/term/' . $term2->tid), t('Parent term link is displayed when viewing the node.'));
}
}
/**

View File

@ -3476,23 +3476,27 @@ function user_preferred_language($account, $default = NULL) {
* @see drupal_mail()
*
* @param $op
* The operation being performed on the account. Possible values:
* 'register_admin_created': Welcome message for user created by the admin
* 'register_no_approval_required': Welcome message when user self-registers
* 'register_pending_approval': Welcome message, user pending admin approval
* 'password_reset': Password recovery request
* 'status_activated': Account activated
* 'status_blocked': Account blocked
* 'cancel_confirm': Account cancellation request
* 'status_canceled': Account canceled
* The operation being performed on the account. Possible values:
* - 'register_admin_created': Welcome message for user created by the admin.
* - 'register_no_approval_required': Welcome message when user
* self-registers.
* - 'register_pending_approval': Welcome message, user pending admin
* approval.
* - 'password_reset': Password recovery request.
* - 'status_activated': Account activated.
* - 'status_blocked': Account blocked.
* - 'cancel_confirm': Account cancellation request.
* - 'status_canceled': Account canceled.
*
* @param $account
* The user object of the account being notified. Must contain at
* least the fields 'uid', 'name', and 'mail'.
* The user object of the account being notified. Must contain at
* least the fields 'uid', 'name', and 'mail'.
* @param $language
* Optional language to use for the notification, overriding account language.
* Optional language to use for the notification, overriding account language.
*
* @return
* The return value from drupal_mail_system()->mail(), if ends up being called.
* The return value from drupal_mail_system()->mail(), if ends up being
* called.
*/
function _user_mail_notify($op, $account, $language = NULL) {
// By default, we always notify except for canceled and blocked.