array('token_list' => array(), 'prefix' => '[', 'suffix' => ']', 'headers' => array(t('Token'), t('Name'), t('Description'))),
);
$theme['comment_subject_token_chained_list'] = array(
'variables' => array('token_list' => array(), 'prefix' => '*:', 'suffix' => '', 'headers' => array(t('Token chains'), t('Name'), t('Description'))),
);
return $theme;
}
/**
* Implements hook_help().
*/
function comment_subject_help($path, $arg) {
switch ($path) {
case 'admin/help#comment_subject':
$output = '
' . t('About') . '
';
$output .= '' . t('Provides a default comment subject like: "Re: [parent comment/node title]", auto-incremental numbering, and optional integration with token for author, date, etc. This option can be set on a per content type basis.') . '
';
$output .= '' . t('Users can still edit the comment subject to their liking if comment titles are enabled in the comment settings.') . '
';
$output .= '' . t('Uses') . '
';
$output .= '';
// don't translate isolated token names
$output .= '- ' . '[comment:parent-title]' . '
';
$output .= '- ' . t('When using [comment:parent-title] comment\'s subject is derived from the node title, or the comment that the new comment is a reply to. In addition enabling "strip prefix/suffix from parent\'s title" allows (for instance) avoiding repeated "Re:" prefix and/or "by username" suffix.') . '
';
$output .= '- ' . t('Note that stripping prefix/suffix fully suppports [comment:*] replacements, while global replacements (e.g. [site:*]) won\'t let the stripping algorithm behave as expected. Nevertheless, some immutable tokens might be used (for instance [site:name], [site:mail]), but if their values change in time the prefix/suffix stripping shall fail.') . '
';
// don't translate isolated token names
$output .= '- ' . '[comment:numbering]' . '
';
$output .= '- ' . t('When using [comment:numbering] option comment\'s subject are auto-incremental (e.g. "#1", "#2", an so on).')
. '
' . t('CAUTION: to achieve [comment:numbering] replacement works as expected previous existing comments shouldn\'t be deleted. Otherwise numbering sequence won\'t be guaranted (i.e. existing comments won\'t get updated and new comments will count just existing ones).') . ' ';
$output .= '
';
$output .= '' . t('Special treatment') . '
';
$output .= '';
$output .= '- ' . t('WHEN the comment has NOT a parent') . '
';
$output .= '- ' . t('[comment:pid] evaluates to zero.') . ' '. t('ONLY when generating the subject.')
. '
' . t('(other uses of this token still evaluate to empty string according to core\'s behavior)') . ' ';
$output .= '- ' . t('[comment:parent:numbering] evaluates to zero.')
. '
' . t('(even if used in other context, since it is owned by this module)') . ' ';
$output .= '- ' . t('[comment:parent:numbering] doesn\'t requires subject to be disabled.')
. '
' . t('(even when [comment:parent:*] appears marked with "**")') . ' ';
$output .= '
';
return $output;
}
}
/**
* Implements hook_node_type_delete().
*/
function comment_subject_node_type_delete($info) {
$node_type = $info->type;
variable_del('comment_subject_field:default_subject_pattern_' . $node_type);
variable_del('comment_subject_field:strip_parent_title_' . $node_type);
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function comment_subject_form_node_type_form_alter(&$form, $form_state) {
// keep this condition consistent with comment.module
if (isset($form['type'])) {
// sink all subject related settings to the bottom
$form['comment']['comment_subject_field']['#weight'] = 15;
$node_type = $form['#node_type']->type;
$form['comment']['comment_subject_field:default_subject_pattern'] = array(
'#title' => 'Default title pattern',
'#description' => t('Default value for comment\'s title.') . '
' . t('e.g. "Re: [comment:parent-title]", "#[comment:numbering]".'),
'#type' => 'textfield',
'#default_value' => variable_get('comment_subject_field:default_subject_pattern_' . $node_type, COMMENT_SUBJECT__DEFAULT_SUBJECT_PATTERN),
'#weight' => 12,
'#maxlength' => NULL, // avoid maxlength=128
);
$form['comment']['comment_subject_field:strip_parent_title'] = array(
'#title' => 'Strip prefix/suffix from parent\'s title',
'#description' => t('Avoids repetitive prefix/suffix when using the parent\'s title in the above pattern (recommended).'),
'#type' => 'checkbox',
'#default_value' => variable_get('comment_subject_field:strip_parent_title_' . $node_type, TRUE),
'#weight' => 13,
);
$form['comment']['token_replacements'] = array(
'#type' => 'fieldset',
'#title' => t('Replacements for default subject pattern'),
//'#collapsible' => TRUE,
//'#collapsed' => TRUE,
'#weight' => 20,
);
$mark = '**';
$chained_list = array();
$token_list = comment_subject_get_token_list(array('comment', 'site'), $chained_list);
foreach($token_list['comment'] as $key => $value) {
if (comment_subject_requires_comment_subject_field_disabled('comment:' . $key)) {
$token_list['comment'][$key]['description'] .= $mark;
}
}
$form['#validate'][] = 'comment_subject_validate_replacement';
$form['comment']['token_replacements']['#description'] =
//t('Note that not every available pattern makes sense for comment\'s default subject.') .
//'
' .
t('Replacements marked with "!mark" will only be available after submission, therefore require comment title to be disallowed.', array('!mark' => $mark));
$form['comment']['token_replacements']['table'] = array(
// if issue #576592 gets into token module this would be theme('token_list', ...) instead
'#markup' => theme('comment_subject_token_list', array('token_list' => $token_list)) . theme('comment_subject_token_chained_list', array('token_list' => $chained_list)),
);
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function comment_subject_form_comment_form_alter(&$form, &$form_state) {
if (empty($form['subject']['#default_value'])) {
// if !$form['subject']['#access'] it means it is disabled and will be handled on hook_comment_[insert/update]
if (!$form['subject']['#access']) {
// nevertheless support comment preview
$form['subject']['#access'] = TRUE; // not required, but won't harm either
$form['subject']['#type'] = 'value'; // use 'hidden' when testing for quick inspect DOM
$node_type = node_load($form['nid']['#value'])->type;
$pattern = variable_get('comment_subject_field:default_subject_pattern_' . $node_type, COMMENT_SUBJECT__DEFAULT_SUBJECT_PATTERN);
$unsupported = comment_subject_unsupported_on_preview($pattern);
if (!empty($unsupported)) {
// computing its candidate value not only is a waste of time/performance
// but can't be achieved without errors, since many missing properties
// will be used by every [comment:*] requiring subject to be disabled
$form['subject']['#default_value'] = t('(generated subject)');
return; // don't fall through
}
// fall through (the subject is disable, but there are no unsupported tokens for preview)
}
// keep this consistent with internal properties added in comment_form
foreach (array('cid', 'pid', 'nid', 'language', 'uid') as $key) {
$comment[$key] = $form[$key]['#value'];
}
$comment = (object)$comment;
$form['subject']['#default_value'] = _comment_subject_get_subject($comment);
}
}
function comment_subject_requires_comment_subject_field_disabled($key) {
// consider both 'comment:node' and 'comment:node:*' separately (avoid matching 'comment:nodeXYZ')
if (strpos($key, 'comment:node:') === 0) return FALSE;
return (!in_array($key, array('comment:node', 'comment:parent-title', 'comment:nid', 'comment:pid', 'comment:uid', 'comment:parent:numbering')));
}
function _comment_subject_token_replace($str, $comment) {
// [comment:pid] evaluates to zero when there is no parent (legacy behavior)
// also avoid "Warning: array_flip(): Can only flip STRING and INTEGER values! in DrupalDefaultEntityController->load() (line 108 of .../includes/entity.inc)."
if (empty($comment->pid)) $comment->pid = 0;
$result = token_replace($str, array('comment' => $comment), array('sanitize' => FALSE));
return $result;
}
function _comment_subject_get_subject($comment) {
$node_type = node_load($comment->nid)->type;
$pattern = variable_get('comment_subject_field:default_subject_pattern_' . $node_type, COMMENT_SUBJECT__DEFAULT_SUBJECT_PATTERN);
$strip_parent_title = variable_get('comment_subject_field:strip_parent_title_' . $node_type, TRUE);
if ($strip_parent_title && $comment->pid) {
// stripping algorithm
if (strpos($pattern, '[comment:parent-title]') !== FALSE) {
list($prefix, $suffix) = explode('[comment:parent-title]', $pattern);
$parent_comment = comment_load($comment->pid);
$parent_prefix = _comment_subject_token_replace($prefix, $parent_comment);
$parent_suffix = _comment_subject_token_replace($suffix, $parent_comment);
$parent_title = _comment_subject_token_replace('[comment:parent-title]', $comment);
if (!empty($parent_prefix) && strpos($parent_title, $parent_prefix) === 0) {
$parent_title = substr($parent_title, strlen($parent_prefix));
}
// strrpos only works for a single character in PHP4
if (!empty($parent_suffix) && strpos(strrev($parent_title), strrev($parent_suffix)) === 0) {
$parent_title = strrev(substr(strrev($parent_title), strlen($parent_suffix)));
}
// $parent_title was already stripped of $parent_prefix and $parent_suffix
$pattern = str_replace('[comment:parent-title]', $parent_title, $pattern);
}
}
$subject = _comment_subject_token_replace($pattern, $comment);
// comment subjects can not be longer than 64 characters
$subject = truncate_utf8($subject, 64, TRUE, TRUE);
return $subject;
}
/**
* Implements hook_comment_insert().
*/
function comment_subject_comment_insert($comment) {
_comment_subject_update($comment);
}
/**
* Implements hook_comment_update().
*/
function comment_subject_comment_update($comment) {
_comment_subject_update($comment);
}
function _comment_subject_update($comment) {
// admin can change the author or other info exposed by tokens
// therefore requires update (only if comment subject is disabled)
// having subject enabled the existing text is considered user input
// (for those cases only #default_value is provided by form_alter)
$node_type = node_load($comment->nid)->type;
$cid = $comment->cid;
// if user subject is enabled can't figure out whether should be updated or leave user input as is
if (!variable_get('comment_subject_field_' . $node_type, 1)) {
// update required for every [comment:*] replacement requiring subject to be disabled
$comment = comment_load($cid);
$subject = _comment_subject_get_subject($comment);
db_update('comment')
->fields(array('subject' => $subject))
->condition('cid', $cid)
->execute();
}
}
function comment_subject_validate_replacement($form, &$form_state) {
// if "comment subject field" is enabled
// check for tokens which only will be available after submission
$matches = array();
if ($form_state['values']['comment_subject_field']) {
$element_key = 'comment_subject_field:default_subject_pattern';
$unsupported = comment_subject_unsupported_on_preview($form_state['values'][$element_key]);
if (!empty($unsupported)) {
form_set_error($element_key, t('There are some token replacements in use which requires comment subject field to be disabled: @unsupported', array('@unsupported' => '[' . implode('], [', $unsupported) . ']')));
}
}
}
function comment_subject_unsupported_on_preview($default_subject_pattern) {
$unsupported = array();
if (preg_match_all('/\\[(comment:.*)\\]/U', $default_subject_pattern, $matches)) {
foreach($matches[1] as $replacement) {
if (comment_subject_requires_comment_subject_field_disabled($replacement)) {
$unsupported[] = $replacement;
}
}
}
return $unsupported;
}
function comment_subject_get_token_list(array $types = array('site'), array &$chained_list = array()) {
$token_info = token_info();
$token_list = array();
$chains = array();
foreach ($token_info['tokens'] as $type => $tokens) {
if (in_array($type, $types)) {
$token_list[$type] = $tokens;
foreach ($tokens as $key => $data) {
if (!empty($data['type'])) {
$chains[$data['type']][] = $key;
}
}
}
}
// @TODO: refactor this whole function so chains can be discovered in deep
// right now only discovers the 1st chain level
// e.g. [comment:created:*] and its chains *:created:long, etc
// e.g. [comment:node:*] and its chains *:node:changed, etc
// but it doesn't go deep to find chain *:changed:long (e.g. [comment:node:changed:long])
// now look for the chained types
$chained_types = array_keys($chains);
//$chained_list = array(); received by ref
foreach ($token_info['tokens'] as $type => $tokens) {
if (in_array($type, $chained_types)) {
foreach ($chains[$type] as $chain) {
$chained_list[$chain] = $tokens;
}
unset($chained_types[$type]);
}
}
return $token_list;
}
// if issue #576592 gets into token module this will require a rollback
// to conditional declare theme_token_list (if !module_exists('token'))
function theme_comment_subject_token_list(array $variables) {
$token_list = $variables['token_list'];
$prefix = $variables['prefix'];
$suffix = $variables['suffix'];
$headers = $variables['headers'];
$rows = array();
$chains_text = '' . t('chains:') . ' ';
foreach ($token_list as $type => $tokens) {
$rows[] = array(array('data' => drupal_ucfirst($type) .' '. t('tokens'), 'class' => 'region', 'colspan' => 3));
foreach ($tokens as $key => $data) {
$chains = '';
if (!empty($data['type'])) {
$key = '' . $key . ':*';
$chains = $chains_text;
}
$row = array();
$row[] = $prefix . $type . ':' . $key . $suffix;
$row[] = t($data['name']);
$row[] = $chains . t($data['description']);
$rows[] = $row;
}
}
$output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('class' => array('description'))));
return $output;
}
function theme_comment_subject_token_chained_list(array $variables) {
return theme_comment_subject_token_list($variables);
}