'UC Node Access autocomplete',
'page callback' => 'uc_node_access_autocomplete',
'access arguments' => array('administer product features'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implementation of hook_cron().
*/
function uc_node_access_cron() {
$items = array();
// Load and process any expired node access rules.
$result = db_query("SELECT uid, access_nid FROM {uc_node_access_expirations} WHERE expiration <= %d", time());
while ($access = db_fetch_array($result)) {
// Revoke access for the user through the current node access handler.
uc_node_access_op('revoke', $access['access_nid'], $access['uid']);
// Delete the expiration from the database.
db_query("DELETE FROM {uc_node_access_expirations} WHERE uid = %d AND access_nid = %d", $access['uid'], $access['access_nid']);
$items[] = t('User @uid lost access to node @nid.', array('@uid' => $access['uid'], '@nid' => $access['access_nid'], '!url' => url('node/'. $access['access_nid'])));
$node = node_load($access['access_nid']);
$account = user_load($access['uid']);
ca_pull_trigger('uc_node_access_revoke', $node, $account);
}
if (!empty($items)) {
watchdog('uc_node_access', 'The following expirations occurred: !expirations', array('!expirations' => theme('item_list', $items)));
}
$items = array();
// Load and process any delayed node access rules.
$result = db_query("SELECT uid, pfid FROM {uc_node_access_delays} WHERE inception <= %d", time());
while ($access = db_fetch_array($result)) {
// Grant access for the user through the current node access handler.
uc_node_access_grant_user_access($access['pfid'], $access['uid']);
// Delete the delay from the database.
db_query("DELETE FROM {uc_node_access_delays} WHERE uid = %d AND pfid = %d", $access['uid'], $access['pfid']);
}
}
/**
* Implementation of hook_product_feature().
*/
function uc_node_access_product_feature() {
$features[] = array(
'id' => 'node_access',
'title' => t('Node access'),
'callback' => 'uc_node_access_feature_form',
'delete' => 'uc_node_access_feature_delete',
'settings' => 'uc_node_access_feature_settings_form',
);
return $features;
}
// Display the settings form for the node access product feature.
function uc_node_access_feature_form($form_state, $node, $feature) {
$handler = variable_get('ucna_handler', '');
// Throw up a warning if the handler hasn't been selected yet.
if (empty($handler) || !function_exists($handler)) {
drupal_set_message(t('The node access handler is either not specified or missing. Node access features can be added to products but cannot be processed until this is resolved.'), 'error');
}
drupal_add_css(drupal_get_path('module', 'uc_node_access') .'/uc_node_access.css');
if (!empty($feature)) {
$access = uc_node_access_feature_load($feature['pfid']);
}
$options = array('' => t(''), $node->model => $node->model);
if (module_exists('uc_attribute')) {
$result = db_query("SELECT model FROM {uc_product_adjustments} WHERE nid = %d", $node->nid);
while ($row = db_fetch_object($result)) {
if (!in_array($row->model, $options)) {
$options[$row->model] = $row->model;
}
}
}
$form['model'] = array(
'#type' => 'select',
'#title' => t('Applicable Model/SKU'),
'#description' => t('Select the applicable product model/SKU.'),
'#options' => $options,
'#default_value' => $access['model'],
);
$form['access_nid'] = array(
'#type' => 'textfield',
'#title' => t('Node ID'),
'#description' => t('Specify the node the customer will get access to after purchasing this product.
Start typing in a node title to view an autocomplete list that will change to an nid upon selection.'),
'#default_value' => $access['access_nid'],
'#autocomplete_path' => 'uc_node_access/autocomplete',
'#required' => TRUE,
);
$form['delay_period'] = array(
'#type' => 'fieldset',
'#title' => t('Access time delay'),
'#collapsible' => FALSE,
'#description' => t('Specify the length of time after purchase the customer must wait to have access to the node.'),
'#attributes' => array('class' => 'time-period-fieldset'),
);
$form['delay_period']['delay_period_value'] = array(
'#type' => 'select',
'#options' => drupal_map_assoc(uc_range(0, 52)),
'#default_value' => $access['delay_period_value'],
);
$form['delay_period']['delay_period_unit'] = array(
'#type' => 'select',
'#options' => array(
'days' => t('day(s)'),
'weeks' => t('week(s)'),
'months' => t('month(s)'),
'years' => t('year(s)'),
),
'#default_value' => $access['delay_period_unit'],
);
$form['access_limit'] = array(
'#type' => 'radios',
'#title' => t('Access limit type'),
'#description' => t('Select an access limit type to govern how long the customer will have access to the node.'),
'#options' => array(
'indefinite' => t('The customer can have indefinite access to the node.'),
'time_period' => t('The customer can access the node for the time specified below.'),
'end_date' => t('The customer can access the node until the date specified below.'),
),
'#default_value' => $access['access_limit'],
'#required' => TRUE,
);
$form['delay_period'] = array(
'#type' => 'fieldset',
'#title' => t('Access time delay'),
'#collapsible' => FALSE,
'#description' => t('Specify the length of time after purchase the customer must wait to have access to the node.'),
'#attributes' => array('class' => 'time-period-fieldset'),
);
$form['delay_period']['delay_period_value'] = array(
'#type' => 'select',
'#options' => drupal_map_assoc(uc_range(1, 52)),
'#default_value' => $access['delay_period_value'],
);
$form['delay_period']['delay_period_unit'] = array(
'#type' => 'select',
'#options' => array(
'days' => t('day(s)'),
'weeks' => t('week(s)'),
'months' => t('month(s)'),
'years' => t('year(s)'),
),
'#default_value' => $access['delay_period_unit'],
);
$form['time_period'] = array(
'#type' => 'fieldset',
'#title' => t('Access time period'),
'#collapsible' => FALSE,
'#description' => t('Specify the length of time the customer should have access to the node.'),
'#attributes' => array('class' => 'time-period-fieldset'),
);
$form['time_period']['time_period_value'] = array(
'#type' => 'select',
'#options' => drupal_map_assoc(uc_range(1, 52)),
'#default_value' => $access['time_period_value'],
);
$form['time_period']['time_period_unit'] = array(
'#type' => 'select',
'#options' => array(
'days' => t('day(s)'),
'weeks' => t('week(s)'),
'months' => t('month(s)'),
'years' => t('year(s)'),
),
'#default_value' => $access['time_period_unit'],
);
$form['end_date_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t('Access end date'),
'#collapsible' => FALSE,
'#description' => t('Specify the end date on which a customer will lose access to the node.'),
);
$form['end_date_fieldset']['end_date'] = array(
'#type' => 'date',
'#default_value' => $access['end_date']
);
return uc_product_feature_form($form);
}
function uc_node_access_feature_form_submit($form, &$form_state) {
$access = array(
'pfid' => $form_state['values']['pfid'],
'model' => $form_state['values']['model'],
'access_nid' => $form_state['values']['access_nid'],
'access_limit' => $form_state['values']['access_limit'],
'delay_period' => $form_state['values']['delay_period_value'] .' '. $form_state['values']['delay_period_unit'],
'time_period' => $form_state['values']['time_period_value'] .' '. $form_state['values']['time_period_unit'],
'end_date' => $form_state['values']['end_date'],
);
switch ($form_state['values']['access_limit']) {
case 'time_period':
$description = t('Grant access to node @nid for @period, @delay after purchase.', array('@nid' => $access['access_nid'], '@period' => $access['time_period'], '@delay' => $access['delay_period'], '!url' => url('node/'. $access['access_nid'])));
break;
case 'end_date':
$description = t('Grant access to node @nid until @date, @delay after purchase.', array('@nid' => $access['access_nid'], '@date' => $access['end_date']['month'] .'/'. $access['end_date']['day'] .'/'. $access['end_date']['year'], '@delay' => $access['delay_period'], '!url' => url('node/'. $access['access_nid'])));
break;
case 'indefinite':
default:
$description = t('Grant indefinite access to node @nid, @delay after purchase.', array('@nid' => $access['access_nid'], '!url' => url('node/'. $access['access_nid']), '@delay' => $access['delay_period']));
break;
}
if (!empty($access['model'])) {
$description .= '
'. t('Applies to SKU: @sku', array('@sku' => $access['model']));
}
else {
$description .= '
'. t('Applies to any SKU.');
}
$data = array(
'pfid' => $access['pfid'],
'nid' => $form_state['values']['nid'],
'fid' => 'node_access',
'description' => $description,
);
$form_state['redirect'] = uc_product_feature_save($data);
// On an insert, get the pfid used for the node access feature.
if (empty($access['pfid'])) {
$access['pfid'] = db_last_insert_id('uc_product_features', 'pfid');
}
uc_node_access_feature_save($access);
}
// Adds the fields for this feature in the product features settings form.
function uc_node_access_feature_settings_form() {
$options = array();
$handlers = module_invoke_all('ucna_handler');
foreach ($handlers as $handler) {
$options[$handler['callback']] = $handler['title'];
}
if (empty($options)) {
$options[''] = t('No handlers available. You must install one to use this feature.');
}
$form['ucna_handler'] = array(
'#type' => 'radios',
'#title' => t('Node access handler'),
'#description' => t('Node access features are processed using handlers that integrate UC Node Access with other access control modules.
You must select one in order to start using this feature.'),
'#options' => $options,
'#default_value' => variable_get('ucna_handler', ''),
);
return $form;
}
function uc_node_access_feature_save($data) {
// First attempt to update an existing row.
db_query("UPDATE {uc_node_access_products} SET model = '%s', access_nid = %d, access_limit = '%s', delay_period = '%s', time_period = '%s', end_date = '%s' WHERE pfid = %d",
$data['model'], $data['access_nid'], $data['access_limit'], $data['delay_period'], $data['time_period'], serialize($data['end_date']), $data['pfid']);
// Otherwise insert this feature as a new row.
if (db_affected_rows() == 0) {
db_query("INSERT INTO {uc_node_access_products} (pfid, model, access_nid, access_limit, delay_period, time_period, end_date) VALUES (%d, '%s', %d, '%s', '%s', '%s', '%s')",
$data['pfid'], $data['model'], $data['access_nid'], $data['access_limit'], $data['delay_period'], $data['time_period'], serialize($data['end_date']));
}
}
function uc_node_access_feature_load($pfid) {
$access = db_fetch_array(db_query("SELECT nap.*, pf.nid FROM {uc_node_access_products} AS nap LEFT JOIN {uc_product_features} AS pf ON nap.pfid = pf.pfid WHERE nap.pfid = %d", $pfid));
if (!empty($access)) {
list($access['delay_period_value'], $access['delay_period_unit']) = explode(' ', $access['delay_period']);
list($access['time_period_value'], $access['time_period_unit']) = explode(' ', $access['time_period']);
$access['end_date'] = unserialize($access['end_date']);
}
return $access;
}
function uc_node_access_feature_delete($feature) {
db_query("DELETE FROM {uc_node_access_delays} WHERE pfid = %d", $feature['pfid']);
db_query("DELETE FROM {uc_node_access_products} WHERE pfid = %d", $feature['pfid']);
}
function uc_node_access_op($op, $nid, $uid) {
$handler = variable_get('ucna_handler', '');
// Fail if the handler hasn't been specified or has been uninstalled.
if (empty($handler) || !function_exists($handler)) {
watchdog('uc_node_access', 'The node access handler is either not specified or missing. Node access features cannot be processed until this is resolved.', array(), WATCHDOG_WARNING);
return;
}
// Fail if we receive an invalid nid or uid.
if (intval($nid) <= 0 || intval($uid) <= 0) {
watchdog('uc_node_access', 'Invalid arguments passed to UC Node Access for processing.', array(), WATCHDOG_WARNING);
return;
}
// Pass the arguments onto the handler for processing.
$result = $handler($op, $nid, $uid);
// Log failed node access operations.
if ($result === FALSE) {
watchdog('uc_node_access', 'A node access operation failed! $op = @op, $nid = @nid, $uid = @uid', array('@op' => $op, '@nid' => $nid, '@uid' => $uid), WATCHDOG_WARNING);
}
}
function uc_node_access_delay_user_access($pfid, $uid) {
$access = uc_node_access_feature_load($pfid);
if (empty($access) || $uid == 0) {
return;
}
if ($access['delay_period_value']) {
$inception = strtotime('+'. $access['delay_period']);
}
else {
$inception = FALSE;
}
// Set the inception in the DB if necessary.
if ($inception) {
// Get a prior inception if one exists.
$result = db_result(db_query("SELECT inception FROM {uc_node_access_delays} WHERE uid = %d AND pfid = %d", $uid, $access['pfid']));
// If there was no prior inception, insert a new one.
if (empty($result)) {
db_query("INSERT INTO {uc_node_access_delays} (uid, pfid, inception) VALUES (%d, %d, %d)",
$uid, $access['pfid'], $inception);
watchdog('uc_node_access', 'User @uid will be granted access to node @nid on @date.', array('@uid' => $uid, '@nid' => $access['access_nid'], '@date' => format_date($inception, 'custom', 'm/d/Y')));
}
else if ($result > $inception) {
// Otherwise we need to update the inception to be later.
db_query("UPDATE {uc_node_access_delays} SET inception = %d WHERE uid = %d AND pfid = %d", $inception, $uid, $access['pfid']);
watchdog('uc_node_access', 'User @uid hastened access to node @nid to @date.', array('@uid' => $uid, '@nid' => $access['access_nid'], '@date' => format_date($inception, 'custom', 'm/d/Y')));
}
else {
watchdog('uc_node_access', 'User @uid purchased duplicate access for node @nid that will be granted later than their current access.', array('@uid' => $uid, '@nid' => $access['access_nid']));
}
}
else {
// Delete any prior inception reference.
db_query("DELETE FROM {uc_node_access_delays} WHERE uid = %d AND pfid = %d", $uid, $access['pfid']);
watchdog('uc_node_access', 'User @uid granted immediate access to node @nid.', array('@uid' => $uid, '@nid' => $access['access_nid']));
uc_node_access_grant_user_access($pfid, $uid);
}
}
function uc_node_access_grant_user_access($pfid, $uid) {
// Load up the node access feature and target node data.
$access = uc_node_access_feature_load($pfid);
$node = node_load($access['access_nid']);
if (empty($access) || empty($node) || $uid == 0) {
return;
}
// Grant access for the user through the current node access handler.
uc_node_access_op('grant', $access['access_nid'], $uid);
// Let Drupal know to adjust the access for the target node.
node_access_acquire_grants($node);
// Calculate an expiration timestamp based on the site's timezone.
$expiration = FALSE;
switch ($access['access_limit']) {
case 'time_period':
$expiration = strtotime('+'. $access['time_period']);
break;
case 'end_date':
$expiration = gmmktime(0, 0, 0, $access['end_date']['month'], $access['end_date']['day'], $access['end_date']['year']);
break;
}
// Set the expiration in the DB if necessary.
if ($expiration) {
// Get a prior expiration if one exists.
$result = db_result(db_query("SELECT expiration FROM {uc_node_access_expirations} WHERE uid = %d AND access_nid = %d", $uid, $access['access_nid']));
// If there was no prior expiration, insert a new one.
if (empty($result)) {
db_query("INSERT INTO {uc_node_access_expirations} (uid, access_nid, expiration) VALUES (%d, %d, %d)",
$uid, $access['access_nid'], $expiration);
watchdog('uc_node_access', 'User @uid granted access to node @nid until @date.', array('@uid' => $uid, '@nid' => $access['access_nid'], '@date' => format_date($expiration, 'custom', 'm/d/Y')));
}
else if ($result < $expiration) {
// Otherwise we need to update the expiration to be later.
db_query("UPDATE {uc_node_access_expirations} SET expiration = %d WHERE uid = %d AND access_nid = %d", $expiration, $uid, $access['access_nid']);
watchdog('uc_node_access', 'User @uid extended access for node @nid until @date.', array('@uid' => $uid, '@nid' => $access['access_nid'], '@date' => format_date($expiration, 'custom', 'm/d/Y')));
}
else {
watchdog('uc_node_access', 'User @uid purchased duplicate access for node @nid that expires earlier than their current access.', array('@uid' => $uid, '@nid' => $access['access_nid'], '@date' => format_date($expiration, 'custom', 'm/d/Y')));
}
}
else {
// Delete any prior expiration reference.
db_query("DELETE FROM {uc_node_access_expirations} WHERE uid = %d AND access_nid = %d", $uid, $access['access_nid']);
watchdog('uc_node_access', 'User @uid granted indefinite access to node @nid.', array('@uid' => $uid, '@nid' => $access['access_nid']));
}
$account = user_load($uid);
ca_pull_trigger('uc_node_access_grant', $node, $account);
}
// Returns an autocomplete list for nodes on the node access feature form.
function uc_node_access_autocomplete($string = '') {
$matches = array();
if ($string) {
$result = db_query_range("SELECT nid, title FROM {node} WHERE LOWER(title) LIKE LOWER('%s%%')", $string, 0, 10);
while ($node = db_fetch_object($result)) {
$matches[$node->nid] = check_plain($node->title);
}
}
print drupal_to_js($matches);
exit();
}
/*******************************************************************************
* Conditional Actions Integration
******************************************************************************/
/**
* Implementation of hook_ca_trigger().
*/
function uc_node_access_ca_trigger() {
$triggers = array();
$triggers['uc_node_access_grant'] = array(
'#title' => t('Node access is granted to a user'),
'#category' => t('Node access'),
'#arguments' => array(
'node' => array(
'#entity' => 'node',
'#title' => t('Node'),
),
'user' => array(
'#entity' => 'user',
'#title' => t('User'),
),
),
);
$triggers['uc_node_access_revoke'] = array(
'#title' => t('Node access is revoked from a user'),
'#category' => t('Node access'),
'#arguments' => array(
'node' => array(
'#entity' => 'node',
'#title' => t('Node'),
),
'user' => array(
'#entity' => 'user',
'#title' => t('User'),
),
),
);
return $triggers;
}
/**
* Implementation of hook_ca_predicate().
*/
function uc_node_access_ca_predicate() {
$predicates = array();
$predicates['uc_node_access_grant_on_payment'] = array(
'#title' => t('Grant appropriate node access on full payment'),
'#class' => 'uc_node_access',
'#status' => 1,
'#weight' => -1,
'#trigger' => 'uc_payment_entered',
'#conditions' => array(
'#operator' => 'AND',
'#conditions' => array(
array(
'#name' => 'uc_payment_condition_order_balance',
'#title' => t('If the balance is less than or equal to $0.00.'),
'#argument_map' => array(
'order' => 'order',
),
'#settings' => array(
'negate' => FALSE,
'balance_comparison' => 'less_equal',
),
),
array(
'#name' => 'uc_order_status_condition',
'#title' => t('If the order status is not already Payment Received.'),
'#argument_map' => array(
'order' => 'order',
),
'#settings' => array(
'negate' => TRUE,
'order_status' => 'payment_received',
),
),
),
),
'#actions' => array(
array(
'#name' => 'uc_node_access_delay_access',
'#title' => t('Grant access to any nodes based on the products on the order'),
'#argument_map' => array(
'order' => 'order',
),
),
),
);
return $predicates;
}
/**
* Implementation of hook_ca_action().
*/
function uc_node_access_ca_action() {
$actions['uc_node_access_delay_access'] = array(
'#title' => t('Grant node access to customer at a later time'),
'#category' => t('UC Node Access'),
'#callback' => 'uc_node_access_delay_access',
'#arguments' => array(
'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
),
);
$actions['uc_node_access_grant_access'] = array(
'#title' => t('Grant node access to customer'),
'#category' => t('UC Node Access'),
'#callback' => 'uc_node_access_grant_access',
'#arguments' => array(
'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
),
);
$actions['uc_node_access_email_node'] = array(
'#title' => t('Email the node the user has access to'),
'#category' => t('UC Node Access'),
'#callback' => 'uc_node_access_email_node',
'#arguments' => array(
'order' => array('#entity' => 'uc_order', '#title' => t('Order')), ),
);
return $actions;
}
function uc_node_access_email_node($order, $settings) {
$language = user_preferred_language($account);
// Token replacements for the subject and body
$settings['replacements'] = array(
'global' => NULL,
'order' => $order,
);
$recipients = array();
$addresses = token_replace_multiple($settings['addresses'], $settings['replacements']);
foreach (explode("\n", $addresses) as $address) {
$recipients[] = trim($address);
}
if (is_array($order->products)) {
$nids = array();
foreach ($order->products as $product) {
$nids[] = $product->nid;
$models[] = $product->model;
}
}
$settings['message'] = token_replace_multiple($settings['message'], $settings['replacements']) ;
if (empty($recipients)) {
watchdog('ca', 'Attempted to e-mail an invoice with no recipient.', array(), WATCHDOG_ERROR);
return;
}
foreach ($recipients as $email) {
$sent = drupal_mail('uc_order', 'action-mail', $email, $language, $settings, empty($settings['from']) ? uc_store_email_from() : $settings['form']);
if (!$sent['result']) {
watchdog('ca', 'Attempt to e-mail invoice for order @order_id to @email failed.', array('@email' => $email, '@order_id' => $order->order_id), WATCHDOG_ERROR);
}
}
}
function uc_node_access_email_node_form($form_state, $settings = array()) {
$form['from'] = array(
'#type' => 'textfield',
'#title' => t('Sender'),
'#default_value' => isset($settings['from']) ? $settings['from'] : uc_store_email_from(),
'#description' => t('The "From" address.'),
'#required' => TRUE,
);
$form['addresses'] = array(
'#type' => 'textarea',
'#title' => t('Recipients'),
'#default_value' => isset($settings['addresses']) ? $settings['addresses'] : '[order-email]',
'#description' => t('Enter the email addresses to receive the notifications, one on each line. You may use order tokens for dynamic email addresses.'),
'#required' => TRUE,
);
$form['subject'] = array(
'#type' => 'textfield',
'#title' => t('Subject'),
'#default_value' => $settings['subject'],
'#required' => TRUE,
);
$form['message'] = array(
'#type' => 'textarea',
'#title' => t('Message'),
'#default_value' => isset($settings['message']) ? $settings['message'] : t('You now have access to the following content:
[site-url]/node/[nid]'),
);
// We add the #is_format element to allow us to locate and configure the filters.
$form['format'] = filter_form($settings['format']) + array('#is_format' => TRUE);
$form['token_help'] = array(
'#type' => 'fieldset',
'#title' => t('Replacement patterns'),
'#description' => t('You can make use of the replacement patterns in the recipients, the subject, and the template file.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
foreach (array('global', 'order') as $name) {
$form['token_help'][$name] = array(
'#type' => 'fieldset',
'#title' => t('@name replacement patterns', array('@name' => drupal_ucfirst($name))),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['token_help'][$name]['content'] = array(
'#value' => theme('token_help', $name),
);
}
return $form;
}
function uc_node_access_delay_access($order, $settings) {
if (is_array($order->products)) {
$nids = array();
$models = array();
foreach ($order->products as $product) {
$nids[] = $product->nid;
$models[] = $product->model;
}
$result = db_query("SELECT pf.pfid, nap.model FROM {uc_product_features} AS pf LEFT JOIN {uc_node_access_products} AS nap ON pf.pfid = nap.pfid WHERE pf.nid IN (". implode(", ", $nids) .") AND pf.fid = 'node_access'");
while ($row = db_fetch_array($result)) {
if (empty($row['model']) || in_array($row['model'], $models)) {
uc_node_access_delay_user_access($row['pfid'], $order->uid);
}
}
}
}
function uc_node_access_grant_access($order, $settings) {
if (is_array($order->products)) {
$nids = array();
$models = array();
foreach ($order->products as $product) {
$nids[] = $product->nid;
$models[] = $product->model;
}
$result = db_query("SELECT pf.pfid, nap.model FROM {uc_product_features} AS pf LEFT JOIN {uc_node_access_products} AS nap ON pf.pfid = nap.pfid WHERE pf.nid IN (". implode(", ", $nids) .") AND pf.fid = 'node_access'");
while ($row = db_fetch_array($result)) {
if (empty($row['model']) || in_array($row['model'], $models)) {
uc_node_access_grant_user_access($row['pfid'], $order->uid);
}
}
}
}
function uc_node_access_token_values($type, $object = NULL, $options = array()) {
$order = $object;
if ($type == 'order') {
foreach ($order->products as $product) {
$nids[] = $product->nid;
}
$tokens['nid'] = $product->nid;
return $tokens;
}
}
function uc_node_access_token_list($type = 'all') {
if ($type == 'order' || $type == 'all') {
$tokens['order']['nid'] = t("The product's node id");
return $tokens;
}
}