comment_goodness.module in Comment goodness 7
Comment goodness provides newest to oldest comment sorting
This module extends the sort_comments module by blackice78
File
comment_goodness.moduleView source
<?php
/**
* @file
*
* Comment goodness provides newest to oldest comment sorting
*
* This module extends the sort_comments module by blackice78
* @see http://drupal.org/project/sort_comments
*/
/**
* Constants
*/
define('comment_goodness_OLDER_FIRST', 1);
// default
define('comment_goodness_NEWER_FIRST', 2);
define('comment_goodness_FORM_PLACEMENT_SORT', 1);
define('comment_goodness_FORM_PLACEMENT_TOP', 2);
define('comment_goodness_FORM_PLACEMENT_BOTTOM', 3);
define('comment_goodness_COMMENT_SECTION_LABEL', t('Comments'));
define('comment_goodness_COMMENT_FORM_LABEL', t('Post new comment'));
/**
* Implements hook_form_FORM_ID_alter().
*
* Provide additional comment configurations on the content type edit form.
*/
function comment_goodness_form_node_type_form_alter(&$form, $form_state) {
$form['comment']['comment_created_date_format'] = array(
'#title' => t('Post date format'),
'#type' => 'select',
'#options' => comment_goodness_date_format_type_options(),
'#default_value' => variable_get('comment_created_date_format_' . $form['#node_type']->type, 'medium'),
);
$form['comment']['comment_changed_date_format'] = array(
'#title' => t('Update date format'),
'#type' => 'select',
'#options' => comment_goodness_date_format_type_options(),
'#default_value' => variable_get('comment_changed_date_format_' . $form['#node_type']->type, 'medium'),
);
$form['comment']['comment_default_sorting'] = array(
'#title' => t('Sort order'),
'#type' => 'select',
'#options' => array(
1 => t('Older first'),
2 => t('Newer first'),
),
'#default_value' => variable_get('comment_default_sorting_' . $form['#node_type']->type, comment_goodness_OLDER_FIRST),
);
$form['comment']['comment_form_placement'] = array(
'#title' => t('Comment form location'),
'#type' => 'select',
'#options' => array(
1 => t('Depending on sort order'),
2 => t('Above existing comments'),
3 => t('Below existing comments'),
),
'#default_value' => variable_get('comment_form_placement_' . $form['#node_type']->type, comment_goodness_FORM_PLACEMENT_BOTTOM),
);
$form['comment']['comment_section_label'] = array(
'#title' => t('Comment section label'),
'#type' => 'textfield',
'#default_value' => variable_get('comment_section_label_' . $form['#node_type']->type, comment_goodness_COMMENT_SECTION_LABEL),
);
$form['comment']['comment_form_label'] = array(
'#title' => t('New comment form label'),
'#type' => 'textfield',
'#default_value' => variable_get('comment_form_label_' . $form['#node_type']->type, comment_goodness_COMMENT_FORM_LABEL),
);
// Set "Comments per page" setting to textfield so it allows more gradual
// control over the number of comments to show.
$form['comment']['comment_default_per_page']['#type'] = 'textfield';
$form['comment']['comment_default_per_page']['#required'] = TRUE;
$form['comment']['comment_default_per_page']['#element_validate'] = array(
'element_validate_integer_positive',
);
unset($form['comment']['comment_default_per_page']['#options']);
$comment_expose_fields_value = variable_get('comment_expose_fields_' . $form['#node_type']->type, 0);
$form['comment']['comment_expose_fields'] = array(
'#title' => t('Expose comment properties as pseudo-fields'),
'#description' => t('Allow comment properties to be managed on the "Comment display" form.'),
'#type' => 'checkbox',
'#default_value' => $comment_expose_fields_value,
);
$form['#submit'][] = 'comment_goodness_node_type_form_submit';
}
/**
* Custom submit handler for node_type_form: clears fields cache if needed.
*/
function comment_goodness_node_type_form_submit($form, &$form_state) {
if ($form['comment']['comment_expose_fields']['#default_value'] != $form_state['values']['comment_expose_fields']) {
cache_clear_all('field_info_fields', 'cache_field');
}
}
/**
* Implements hook_query_TAG_alter().
*
* Alter comments query to order by DESC as well as the default ASC.
*
* The default ASC ordering of threaded comments looks like this
* where 1 is older than 2.
*
* 1
* 1.1
* 1.1.1
* 1.2
* 2
* 2.1
* 2.1.1
* 2.1.2
* 2.2
*
* DESC ordering of threaded comments (newest to oldest) should look like this.
*
* 2
* 2.2
* 2.1
* 2.1.2
* 2.1.1
* 1
* 1.2
* 1.1
* 1.1.1
*
*/
function comment_goodness_query_comment_filter_alter(QueryAlterableInterface $query) {
if (($node = $query
->getMetaData('node')) && get_class($query) == 'PagerDefault') {
// Get the configured default sort ordering for this node type.
$sort = variable_get('comment_default_sorting_' . $node->type, comment_goodness_OLDER_FIRST);
// The default ordering is ASC (older on top). If the configured sorting is
// newer on top, change the ordering to DESC.
if ($sort == comment_goodness_NEWER_FIRST) {
$orderby =& $query
->getOrderBy();
$expressions =& $query
->getExpressions();
// Sorting for threaded comments.
if (isset($orderby['torder'])) {
// Get rid of the expressions that prepare the threads for ASC ordering.
unset($expressions['torder']);
unset($orderby['torder']);
// Simply order by the thread field.
$orderby['c.thread'] = 'DESC';
}
else {
$direction = 'DESC';
if (isset($orderby['c.cid'])) {
unset($orderby['c.cid']);
}
$orderby['c.created'] = $direction;
$orderby['c.cid'] = $direction;
}
}
}
}
/**
* Options callback: get available date types with human readable labels.
*
* @return array
* date type => date type title (example date)
*/
function comment_goodness_date_format_type_options() {
$options = array();
$format_types = system_get_date_types();
if (!empty($format_types)) {
foreach ($format_types as $type => $type_info) {
$options[$type] = $type_info['title'] . ' (' . format_date(REQUEST_TIME, $type) . ')';
}
}
return $options;
}
/**
* Implements hook_menu().
*/
function comment_goodness_menu() {
$items = array();
$items['comment/%comment/delete-own'] = array(
'title' => 'Delete',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'comment_goodness_confirm_delete',
1,
),
'access callback' => 'comment_goodness_delete_comment_access',
'access arguments' => array(
1,
),
'type' => MENU_CALLBACK,
'weight' => 2,
);
return $items;
}
/**
* Implements hook_menu_alter().
*
* Override the comment_permalink function. The comment module assumes ASC order
* and because of this, permalinks do not work with DESC sorted comments.
*/
function comment_goodness_menu_alter(&$items) {
if (isset($items['comment/%'])) {
$items['comment/%']['page callback'] = 'comment_goodness_permalink';
}
}
/**
* Implements hook_theme_registry_alter().
*
* Override the core comment-wrapper and comment template with the template in
* this module.
*/
function comment_goodness_theme_registry_alter(&$theme_registry) {
if (isset($theme_registry['comment_wrapper'])) {
$comment_wrapper = $theme_registry['comment_wrapper'];
// If the current template belongs to the core comment module, replace it.
if (isset($comment_wrapper['template']) && $comment_wrapper['template'] === 'modules/comment/comment-wrapper') {
$path = drupal_get_path('module', 'comment_goodness');
$theme_registry['comment_wrapper']['template'] = $path . '/templates/comment-wrapper';
}
}
if (isset($theme_registry['comment'])) {
$comment = $theme_registry['comment'];
// If the current template belongs to the core comment module, replace it.
if (isset($comment['template']) && $comment['template'] === 'modules/comment/comment') {
$path = drupal_get_path('module', 'comment_goodness');
$theme_registry['comment']['template'] = $path . '/templates/comment';
}
}
}
/**
* Implements hook_permission().
*/
function comment_goodness_permission() {
return array(
'delete own comments' => array(
'title' => t('Delete own comments'),
'description' => t('Allows a user to delete own comments that have no replies (e.g. all if in flat mode).'),
),
);
}
/**
* Access callback for deleting own comment.
*/
function comment_goodness_delete_comment_access($comment) {
global $user;
$access = $user->uid && $user->uid == $comment->uid && $comment->status == COMMENT_PUBLISHED && (user_access('delete own comments') || user_access('administer comments'));
// Deletion is not allowed if any comment has this comment as a parent.
return $access && 0 == db_query("SELECT COUNT(cid) FROM {comment} WHERE pid = :cid", array(
':cid' => $comment->cid,
))
->fetchField();
}
/**
* Implements hook_comment_view().
*/
function comment_goodness_comment_view($comment, $view_mode, $langcode) {
// We only need to add the delete link if it's not there.
if (!isset($comment->content['links']['comment']['#links']['comment-delete']) && comment_goodness_delete_comment_access($comment)) {
$comment->content['links']['comment']['#links']['comment-delete'] = array(
'title' => t('delete'),
'href' => "comment/{$comment->cid}/delete-own",
'html' => TRUE,
);
}
$node = node_load($comment->nid);
if (variable_get('comment_expose_fields_' . $node->type, FALSE)) {
$account = user_load($comment->uid);
$uri = entity_uri('comment', $comment);
$uri['options'] += array(
'attributes' => array(
'class' => 'permalink',
'rel' => 'bookmark',
),
);
if (variable_get('comment_subject_field_' . $node->type, 1) == 1) {
if (!isset($comment->content['subject'])) {
$comment->content['subject'] = array(
'#type' => 'link',
'#title' => $comment->subject,
'#href' => $uri['path'],
'#options' => $uri['options'],
);
}
}
if (!isset($comment->content['author'])) {
$comment->content['author'] = array(
'#type' => 'item',
'#theme' => 'username',
'#account' => $account,
);
}
if (variable_get('user_signatures', 1) == 1 && !isset($comment->content['signature'])) {
$comment->content['signature'] = array(
'#type' => 'item',
'#markup' => !empty($comment->signature_format) ? check_markup($comment->signature, $comment->signature_format, $langcode) : check_plain($comment->signature),
);
}
if (theme_get_setting('toggle_comment_user_picture') && !isset($comment->content['picture'])) {
$comment->content['picture'] = array(
'#type' => 'item',
'#theme' => 'user_picture',
'#account' => $account,
);
}
if (!isset($comment->content['created'])) {
$comment->content['created'] = array(
'#type' => 'item',
'#markup' => format_date($comment->created, variable_get('comment_created_date_format_' . $node->type, 'medium')),
);
}
if (!isset($comment->content['changed'])) {
$comment->content['changed'] = array(
'#type' => 'item',
'#markup' => format_date($comment->created, variable_get('comment_changed_date_format_' . $node->type, 'medium')),
);
}
if (!isset($comment->content['new'])) {
$comment->content['new'] = array(
'#type' => 'item',
'#markup' => !empty($comment->new) ? t('new') : '',
);
}
if (!isset($comment->content['permalink'])) {
$comment->content['permalink'] = array(
'#type' => 'link',
'#title' => t('Permalink'),
'#href' => $uri['path'],
'#options' => $uri['options'],
);
}
if (!isset($comment->content['submitted'])) {
$date_type = variable_get('comment_created_date_format_' . $node->type, 'medium');
$submitted_string = _comment_goodness_get_submitted_string($date_type);
$comment->content['submitted'] = array(
'#type' => 'item',
'#markup' => t($submitted_string, array(
'!username' => theme('username', array(
'account' => $account,
)),
'!datetime' => format_date($comment->created, $date_type),
)),
);
}
}
}
/**
* Confirm form for deleting own comment.
*
* We can't use the core comment_confirm_delete() because it talks about
* deleting replies, and also mollom hooks into that form which is not
* appropriate for end-users.
*/
function comment_goodness_confirm_delete($form, &$form_state, $comment) {
$form['#comment'] = $comment;
// Always provide entity id in the same form key as in the entity edit form.
$form['cid'] = array(
'#type' => 'value',
'#value' => $comment->cid,
);
return confirm_form($form, t('Are you sure you want to delete the comment %title?', array(
'%title' => $comment->subject,
)), 'node/' . $comment->nid, t('This action cannot be undone.'), t('Delete'), t('Cancel'), 'comment_goodness_confirm_delete');
}
/**
* Form submit function copied from comment_confirm_delete_submit().
*
* The user-visible and watchdog messages are different from core.
*/
function comment_goodness_confirm_delete_submit($form, &$form_state) {
global $user;
$comment = $form['#comment'];
// Delete the comment and its replies.
comment_delete($comment->cid);
drupal_set_message(t('The comment has been deleted.'));
watchdog('content', 'User %name (@uid) deleted own comment @cid.', array(
'%name' => $user->name,
'@uid' => $user->uid,
'@cid' => $comment->cid,
));
// Clear the cache so an anonymous user sees that his comment was deleted.
cache_clear_all();
$form_state['redirect'] = "node/{$comment->nid}";
}
/**
* Implements hook_form_FORM_ID_alter().
*
* When a new comment is submitted, the core comment module calculates that new
* comment's position in the list of comments and then provides a redirect URL
* to the new comment on the subsequent page load. Because the comment module
* assumes ASC sort ordering, the redirect URL for paged comments is incorrect
* when the comments are DESC sort ordered. So this module overrides much of the
* core comment module code for calculating the position of a new comment in the
* paged list of DESC sorted comments.
*
* The overriding starts with adding an additional new comment form submit
* handler.
*
* Disabling the submit actions while the comment body is empty also happens
* here.
*/
function comment_goodness_form_comment_form_alter(&$form, &$form_state, $form_id) {
$form['#submit'][] = 'comment_goodness_form_submit';
// Disable preview button.
_comment_goodness_disable_actions($form);
}
/**
* Helper function to disable the form submit actions while the comment body is
* empty.
*/
function _comment_goodness_disable_actions(&$form) {
$node_language = $form['#node']->language;
$disable_button_state = array(
'disabled' => array(
"textarea[name=\"comment_body[{$node_language}][0][value]\"]" => array(
'empty' => TRUE,
),
),
);
foreach (array(
'submit',
'preview',
) as $button) {
if (!isset($form['actions']['preview']['#states'])) {
$form['actions'][$button]['#states'] = array();
}
$form['actions'][$button]['#states'] += $disable_button_state;
}
// Add css to indicate that the buttons are disabled.
$module_path = drupal_get_path('module', 'comment_goodness');
drupal_add_css("{$module_path}/css/disabled_actions.css");
drupal_add_js("{$module_path}/comment_goodness.js");
}
/**
* Form submit handler.
*
* @see comment_goodness_form_comment_form_alter().
*/
function comment_goodness_form_submit($form, &$form_state) {
$node = $form['#node'];
$comment = $form_state['comment'];
if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_NODE_OPEN)) {
// Find the current display page for this comment.
$query = array();
$page = comment_goodness_get_display_page($comment->cid, $node->type);
if ($page > 0) {
$query['page'] = $page;
}
// Redirect to the newly posted comment.
$form_state['redirect'] = array(
'node/' . $node->nid,
array(
'query' => $query,
'fragment' => 'comment-' . $comment->cid,
),
);
}
}
/**
* Implements hook_preprocess_HOOK().
*
* Provide template variables to change the comment created, changed and
* submitted by dates.
*/
function comment_goodness_preprocess_comment(&$variables) {
$comment = $variables['elements']['#comment'];
$variables['created'] = format_date($comment->created, variable_get('comment_created_date_format_' . $variables['node']->type, 'medium'));
$variables['changed'] = format_date($comment->changed, variable_get('comment_changed_date_format_' . $variables['node']->type, 'medium'));
$date_type = variable_get('comment_created_date_format_' . $variables['node']->type, 'medium');
$submitted_string = _comment_goodness_get_submitted_string($date_type);
$variables['submitted'] = t($submitted_string, array(
'!username' => theme('username', array(
'account' => $variables['author'],
)),
'!datetime' => $variables['created'],
));
}
/**
* Implements hook_preprocess_HOOK().
*
* Provide a template variable to vary the placement of the comment form; either
* above or below the comments dependent on the sort order. For ASC sorted
* comments, the comment form is placed at the bottom of the list, near the
* newest comment. For DESC sorted comments, the comment form is placed above
* the comments, again, near the newest comment.
*/
function comment_goodness_preprocess_comment_wrapper(&$variables) {
// Get the configured form placement for this node type.
$form_placement = variable_get('comment_form_placement_' . $variables['node']->type, comment_goodness_FORM_PLACEMENT_BOTTOM);
switch ($form_placement) {
case comment_goodness_FORM_PLACEMENT_TOP:
$variables['comment_form_placement'] = 'top';
break;
case comment_goodness_FORM_PLACEMENT_BOTTOM:
$variables['comment_form_placement'] = 'bottom';
break;
case comment_goodness_FORM_PLACEMENT_SORT:
default:
// Bottom placement of the comment form is the Drupal default.
$variables['comment_form_placement'] = 'bottom';
// Get the configured default sort ordering for this node type.
$sort = variable_get('comment_default_sorting_' . $variables['node']->type, comment_goodness_OLDER_FIRST);
// Indicate that the comment form should be rendered above the comments
// if the sort order is newest to oldest (ASC).
if ($sort == comment_goodness_NEWER_FIRST) {
$variables['comment_form_placement'] = 'top';
}
break;
}
// Get the section labels
$comment_section_label = variable_get('comment_section_label_' . $variables['node']->type, comment_goodness_COMMENT_SECTION_LABEL);
$comment_form_label = variable_get('comment_form_label_' . $variables['node']->type, comment_goodness_COMMENT_FORM_LABEL);
// Add the labels to the page variables
$variables['labels'] = array(
'section_label' => check_plain($comment_section_label),
'form_label' => check_plain($comment_form_label),
);
}
/**
* Return the page number for a comment.
*
* Finds the correct page number for a comment taking into account display,
* paging settings and sort order.
*
* @param $cid
* The comment ID.
* @param $node_type
* The node type the comment is attached to.
* @return
* The page number.
* @see comment_get_display_page
*/
function comment_goodness_get_display_page($cid, $node_type) {
$ordinal = comment_goodness_get_display_ordinal($cid, $node_type);
$comments_per_page = variable_get('comment_default_per_page_' . $node_type, 50);
return floor($ordinal / $comments_per_page);
}
/**
* Redirects comment links to the correct page depending on comment settings.
*
* Since comments are paged there is no way to guarantee which page a comment
* appears on. Comment paging and threading settings may be changed at any time.
* With threaded comments, an individual comment may move between pages as
* comments can be added either before or after it in the overall discussion.
* Therefore we use a central routing function for comment links, which
* calculates the page number based on current comment settings and returns
* the full comment view with the pager set dynamically.
*
* @param $cid
* A comment identifier.
* @return
* The comment listing set to the page on which the comment appears.
*
* @see comment_permalink()
*/
function comment_goodness_permalink($cid) {
if (($comment = comment_load($cid)) && ($node = node_load($comment->nid))) {
// Find the current display page for this comment.
$page = comment_goodness_get_display_page($comment->cid, $node->type);
// Set $_GET['q'] and $_GET['page'] ourselves so that the node callback
// behaves as it would when visiting the page directly.
$_GET['q'] = 'node/' . $node->nid;
$_GET['page'] = $page;
// Return the node view, this will show the correct comment in context.
return menu_execute_active_handler('node/' . $node->nid, FALSE);
}
return MENU_NOT_FOUND;
}
/**
* Get the display ordinal for a comment, starting from 0.
*
* Count the number of comments which appear before the comment we want to
* display, taking into account display settings, threading and sort order.
*
* @param $cid
* The comment ID.
* @param $node_type
* The node type of the comment's parent.
* @return
* The display ordinal for the comment.
*
* @see comment_get_display_ordinal()
*/
function comment_goodness_get_display_ordinal($cid, $node_type) {
// Count how many comments (c1) are before $cid (c2) in display order. This is
// the 0-based display ordinal.
$query = db_select('comment', 'c1');
$query
->innerJoin('comment', 'c2', 'c2.nid = c1.nid');
$query
->addExpression('COUNT(*)', 'count');
$query
->condition('c2.cid', $cid);
if (!user_access('administer comments')) {
$query
->condition('c1.status', COMMENT_PUBLISHED);
}
// Get the configured default sort ordering for this node type.
$sort = variable_get('comment_default_sorting_' . $node_type, comment_goodness_OLDER_FIRST);
$operation = $sort == comment_goodness_NEWER_FIRST ? '>' : '<';
$mode = variable_get('comment_default_mode_' . $node_type, COMMENT_MODE_THREADED);
if ($mode == COMMENT_MODE_FLAT) {
// For flat comments, cid is used for ordering comments due to
// unpredicatable behavior with timestamp, so we make the same assumption
// here.
$query
->condition('c1.cid', $cid, $operation);
}
else {
// For threaded comments, the c.thread column is used for ordering.
if ($sort == comment_goodness_NEWER_FIRST) {
// For newer to older sorted comments, we look for how many comments have
// a higher thread value than the current comment.
$query
->where('c1.thread ' . $operation . ' c2.thread');
}
else {
// We can use the vancode for comparison, but must remove the
// trailing slash.
// See comment_view_multiple().
$query
->where('SUBSTRING(c1.thread, 1, (LENGTH(c1.thread) -1)) ' . $operation . ' SUBSTRING(c2.thread, 1, (LENGTH(c2.thread) -1))');
}
}
return $query
->execute()
->fetchField();
}
/**
* Implements hook_field_extra_fields().
*/
function comment_goodness_field_extra_fields() {
$return = array();
foreach (node_type_get_types() as $type) {
$comment_bundle = 'comment_node_' . $type->type;
if (variable_get('comment_expose_fields_' . $type->type, FALSE)) {
$return['comment'][$comment_bundle]['display']['author'] = array(
'label' => t('Author'),
'description' => t('Author of comment'),
'weight' => -10,
);
if (variable_get('comment_subject_field_' . $type->type, 1) == 1) {
$return['comment'][$comment_bundle]['display']['subject'] = array(
'label' => t('Subject'),
'description' => t('Subject of comment'),
'weight' => -9,
);
}
if (variable_get('user_signatures', 1) == 1) {
$return['comment'][$comment_bundle]['display']['signature'] = array(
'label' => t('Signature'),
'description' => t('Signature of author'),
'weight' => -8,
);
}
if (theme_get_setting('toggle_comment_user_picture')) {
$return['comment'][$comment_bundle]['display']['picture'] = array(
'label' => t('Picture'),
'description' => t('Avatar image of author'),
'weight' => -7,
);
}
$return['comment'][$comment_bundle]['display']['created'] = array(
'label' => t('Created'),
'description' => t('Comment creation date'),
'weight' => -6,
);
$return['comment'][$comment_bundle]['display']['changed'] = array(
'label' => t('Changed'),
'description' => t('Comment update date'),
'weight' => -5,
);
$return['comment'][$comment_bundle]['display']['new'] = array(
'label' => t('New'),
'description' => t('String showing if comment is new'),
'weight' => -4,
);
$return['comment'][$comment_bundle]['display']['permalink'] = array(
'label' => t('Permalink'),
'description' => t('Permalink to comment'),
'weight' => -3,
);
$return['comment'][$comment_bundle]['display']['submitted'] = array(
'label' => t('Submitted by'),
'description' => t('Information about who and when submitted the comment'),
'weight' => -2,
);
$return['comment'][$comment_bundle]['display']['links'] = array(
'label' => t('Links'),
'description' => t('Comment action links'),
'weight' => -1,
);
}
}
return $return;
}
/**
* Checks if $date_type uses a Timeago date format.
*
* @param string $date_type
* The name of an existing date type.
*
* @return bool
* If $date_type uses a Timeago date format.
*/
function _comment_goodness_uses_timeago_format($date_type) {
if (module_exists('timeago')) {
$date_type_format = variable_get('date_format_' . $date_type);
$timeago_formats = array(
TIMEAGO_FORMAT_SHORT_US,
TIMEAGO_FORMAT_SHORT,
TIMEAGO_FORMAT_MEDIUM_US,
TIMEAGO_FORMAT_MEDIUM,
TIMEAGO_FORMAT_LONG_US,
TIMEAGO_FORMAT_LONG,
);
return in_array($date_type_format, $timeago_formats);
}
else {
return FALSE;
}
}
/**
* Returns the proper format of the 'Submitted' string.
*
* Checks if Timeago is used by $date_type and returns the correct 'Submitted'
* string.
*
* @param $date_type
* Date type to check.
*
* @return string
* 'Submitted' string.
*
* @see _comment_goodness_uses_timeago_format()
*/
function _comment_goodness_get_submitted_string($date_type) {
if (_comment_goodness_uses_timeago_format($date_type)) {
$submitted_string = 'Submitted by !username !datetime';
}
else {
$submitted_string = 'Submitted by !username on !datetime';
}
return $submitted_string;
}