Commit caec9598 authored by Morbus Iff's avatar Morbus Iff

Activity-based coder review. Not full-pass, but less now.

parent 974d34fa
......@@ -4,10 +4,9 @@
* @file
* Hooks provided by the CRM Core module.
*/
/**
* Allows modules to deny or provide access for a user to perform a non-view
* operation on an entity before any other access check occurs.
* Deny or allow access to entity CRUD before any other access check.
*
* Modules implementing this hook can return FALSE to provide a blanket
* prevention for the user to perform the requested operation on the specified
......@@ -18,16 +17,16 @@
* If no modules return FALSE but none return TRUE either, normal permission
* based checking will apply.
*
* @param $op
* @param string $op
* The request operation: update, create, or delete.
* @param $entity
* @param object $entity
* The entity to perform the operation on.
* @param $account
* @param object $account
* The user account whose access should be determined.
* @param $entity_type
* @param string $entity_type
* The machine-name of the entity type of the given $entity.
*
* @return
* @return bool
* TRUE or FALSE indicating an explicit denial of permission or a grant in the
* presence of no other denials; NULL to not affect the access check at all.
*/
......@@ -36,12 +35,8 @@ function hook_crm_core_entity_access($op, $entity, $account, $entity_type) {
}
/**
* Custom label for contact of bundle CONTACT_BUNDLE.
*
* It is possible to use different label for different contact types. For example
* different label for Organization (organization textfield name) and Individual
* (name field).
* Use a custom label for a contact of bundle CONTACT_BUNDLE.
*/
function crm_core_contact_CONTACT_BUNDLE_label($entity) {
}
\ No newline at end of file
// No example.
}
......@@ -5,7 +5,6 @@
* Provides basic functionality for a Drupal CRM.
*/
/**
* Implements hook_hook_info().
*/
......@@ -19,8 +18,6 @@ function crm_core_hook_info() {
/**
* Return permission names for a given entity type.
*
* Copied from commerce_entity_access_permissions().
*/
function crm_core_entity_access_permissions($entity_type) {
$entity_info = entity_get_info($entity_type);
......@@ -70,7 +67,7 @@ function crm_core_entity_access_permissions($entity_type) {
}
/**
* Define per bundle permissions
* Define per-bundle permissions.
*/
function crm_core_bundle_access_permissions($bundle_name, $bundle_info, $entity_type, $entity_info = array()) {
$labels = $entity_info['permission labels'];
......
......@@ -55,7 +55,7 @@ function crm_core_activity_type_form($form, &$form_state, $crm_activity_type, $o
'#value' => t('Delete activity type'),
'#weight' => 45,
'#limit_validation_errors' => array(),
'#submit' => array('crm_core_activity_type_form_submit_delete')
'#submit' => array('crm_core_activity_type_form_submit_delete'),
);
}
return $form;
......@@ -89,13 +89,11 @@ function crm_core_activity_type_form_submit_delete(&$form, &$form_state) {
* Add default fields to newly created activity type.
*/
function crm_core_activity_type_add_default_fields($activity_type) {
$type = $activity_type->type;
module_load_include('inc', 'crm_core_activity', 'crm_core_activity.fields');
$fields = _crm_core_activity_type_default_fields();
// Create the fields if they don't exist
foreach ($fields as $field) {
$info = field_info_field($field['field_name']);
if (empty($info)) {
......@@ -103,7 +101,6 @@ function crm_core_activity_type_add_default_fields($activity_type) {
}
}
// Create field instances for the type if they don't exist
$instances = array();
// Participants field.
......@@ -217,7 +214,6 @@ function crm_core_activity_type_add_default_fields($activity_type) {
),
);
// Create field instances if they don't exist
foreach ($instances as $instance) {
$info_instance = field_info_instance('crm_core_activity', $instance['field_name'], $type);
if (empty($info_instance)) {
......
......@@ -2,8 +2,7 @@
/**
* @file
* Extends EntityAPIControllerInterface with classes for
* Activities used by CRM Core.
* Extends EntityAPIControllerInterface with classes for Activities.
*/
/**
......@@ -29,9 +28,7 @@ class CRMCoreActivityEntity extends Entity {
*/
class CRMCoreActivityController extends EntityAPIController {
public function create(array $values = array()) {
global $user;
// Get activity_type_id.
$query = new EntityFieldQuery;
$query = new EntityFieldQuery();
$results = $query
->entityCondition('entity_type', 'crm_core_activity_type')
->propertyCondition('type', $values['type'])
......@@ -49,7 +46,7 @@ class CRMCoreActivityController extends EntityAPIController {
'title' => '',
'created' => REQUEST_TIME,
'changed' => REQUEST_TIME,
'uid' => $user->uid,
'uid' => $GLOBALS['user']->uid,
);
return parent::create($values);
......@@ -57,11 +54,9 @@ class CRMCoreActivityController extends EntityAPIController {
}
/**
*
* Provides a class for activities.
*
* Use a separate class for profile types so we can specify some defaults
* modules may alter.
*
* Use a separate class so we can specify some defaults modules may alter.
*/
class CRMActivityType extends Entity {
public $type;
......@@ -95,15 +90,15 @@ class CRMCoreActivityTypeController extends EntityAPIControllerExportable {
/**
* Implements EntityAPIControllerInterface.
*
* @param array @ids
* List of IDs.
* @param $transaction
* @param array $ids
* List of IDs.
* @param object $transaction
* Optionally a DatabaseTransaction object to use. Allows overrides to pass
* in their transaction object.
*/
public function delete($ids, DatabaseTransaction $transaction = NULL) {
// Delete all instances of the given type.
$query = new EntityFieldQuery;
$query = new EntityFieldQuery();
$results = $query
->entityCondition('entity_type', 'crm_core_activity')
->entityCondition('bundle', $ids, 'IN')
......@@ -118,5 +113,4 @@ class CRMCoreActivityTypeController extends EntityAPIControllerExportable {
// Finally delete the type itself.
return parent::delete($ids, $transaction);
}
}
......@@ -3,23 +3,17 @@
/**
* @file
* Adds fields to the activity entity type used by CRM Core.
*
* By default, an activity will include:
*
* - A list of participants
* - A date
* - Notes
*/
/**
* @return
* Return default fields definition that have to be attached
* to newly created activity type.
* Define the default fields for the CRM Core Activity entity type.
*
* @return array
* Return default fields to be attached to newly created activity type.
*
* @see field_create_field()
*/
function _crm_core_activity_type_default_fields() {
$fields = array();
// Participants field.
......@@ -77,7 +71,6 @@ function _crm_core_activity_type_default_fields() {
'settings' => array(),
'cardinality' => 1,
);
return $fields;
}
name = CRM Core Activity
description = Provide activity entity.
description = Provides an entity for recording a contact's activities.
package = CRM
core = 7.x
......
......@@ -70,7 +70,6 @@ function crm_core_activity_entity_property_info() {
'schema field' => 'activity_type_id',
);
// Metadata of Activity Type.
$properties = &$info['crm_core_activity_type']['properties'];
......
<?php
/**
* Implements hook_install()
* @file
* Install, update, and uninstall functions for the CRM Core Activity module.
*/
/**
* Implements hook_install().
*/
function crm_core_activity_install() {
$t = get_t()
// create a default meeting activity type
// Create a default meeting activity type.
$meeting = entity_create('crm_core_activity_type', array(
'type' => 'meeting',
'label' => st('Meeting'),
'description' => st('A meeting between 2 or more contacts'),
'label' => $t('Meeting'),
'description' => $t('A meeting between 2 or more contacts'),
));
crm_core_activity_type_save($meeting);
// create a phone call activity type
// Create a phone call activity type.
$phone_call = entity_create('crm_core_activity_type', array(
'type' => 'phone_call',
'label' => st('Phone call'),
'description' => st('A phone call between 2 or more contacts'),
'label' => $t('Phone call'),
'description' => $t('A phone call between 2 or more contacts'),
));
crm_core_activity_type_save($phone_call);
}
function crm_core_activity_uninstall() {
/**
* Implements hook_enable().
*/
function crm_core_activity_enable() {
// Clear the cache to display in Feeds as available plugin.
cache_clear_all('plugins:feeds:plugins', 'cache');
}
// delete all the fields
// right now we only have the default fields, what if we can add more fields to the
// activities? (we will need to go through the field array and idenfity activity fields?)
/**
* Implements hook_uninstall().
*/
function crm_core_activity_uninstall() {
// @todo These are the default fields only. What if there are more?
module_load_include('inc', 'crm_core_activity', 'crm_core_activity.fields');
foreach (_crm_core_activity_type_default_fields() as $field) {
field_delete_field($field['field_name']);
}
}
/**
......@@ -137,11 +150,10 @@ function crm_core_activity_schema() {
/**
* Update crm_core_activity schema
*/
* Update crm_core_activity schema.
*/
function crm_core_activity_update_7001(&$sandbox) {
if (!db_field_exists('crm_core_activity', 'activity_type_id')) {
// add new field
$spec = array(
'description' => 'The current {crm_core_activity_type}.id of this activity.',
'type' => 'int',
......@@ -155,8 +167,8 @@ function crm_core_activity_update_7001(&$sandbox) {
}
/**
* Populate crm_core_activity.activity_type_id
*/
* Populate crm_core_activity.activity_type_id.
*/
function crm_core_activity_update_7002(&$sandbox) {
// Update 10 activities at a time to change {crm_core_activity}.type to
// int value of {crm_core_activity_type}.id
......@@ -174,7 +186,7 @@ function crm_core_activity_update_7002(&$sandbox) {
->execute();
foreach ($activities as $activity) {
// get the matching id from {crm_core_activity_type}
// Get the matching id from {crm_core_activity_type}.
$id = db_select('crm_core_activity_type', 'at')
->fields('at', array('id'))
->condition('type', $activity->type, '=')
......@@ -193,12 +205,7 @@ function crm_core_activity_update_7002(&$sandbox) {
$sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']);
if ($sandbox['#finished']) {
// run drush cc all afterwards
drupal_flush_all_caches();
return t('Added and populated field values for new {crm_core_activity}.activity_type_id field');
}
// In case of an error, simply throw an exception with an error message.
//throw new DrupalUpdateException('Error: updating {crm_core_activity}.activity_type_id field values failed.');
}
......@@ -2,8 +2,7 @@
/**
* @file
* Defines an entity called "activity" which is used to track
* contact relationships.
* Provides an entity for recording a contact's activities.
*/
/**
......@@ -25,7 +24,7 @@ function crm_core_activity_entity_info() {
'entity keys' => array(
'id' => 'activity_id',
'bundle' => 'type',
'label' => 'title'
'label' => 'title',
),
'bundle keys' => array(
'bundle' => 'type',
......@@ -114,10 +113,8 @@ function crm_core_activity_permission() {
* Access callback for activity.
*/
function crm_core_activity_access($op, $activity, $account = NULL, $entity_type = NULL) {
global $user;
if (!isset($account)) {
$account = $user;
$account = $GLOBALS['user'];
}
if (is_object($activity)) {
$activity_type = $activity->type;
......@@ -142,21 +139,24 @@ function crm_core_activity_access($op, $activity, $account = NULL, $entity_type
switch ($op) {
case 'create':
return (user_access('administer crm_core_activity entities', $account)
|| user_access('create crm_core_activity entities', $account)
|| user_access('create crm_core_activity entities of bundle ' . $activity_type, $account));
|| user_access('create crm_core_activity entities', $account)
|| user_access('create crm_core_activity entities of bundle ' . $activity_type, $account));
case 'view':
return (user_access('administer crm_core_activity entities', $account)
|| user_access('view any crm_core_activity entity', $account)
|| user_access('view any crm_core_activity entity of bundle ' . $activity_type, $account));
|| user_access('view any crm_core_activity entity', $account)
|| user_access('view any crm_core_activity entity of bundle ' . $activity_type, $account));
case 'edit':
case 'update':
return (user_access('administer crm_core_activity entities', $account)
|| user_access('edit any crm_core_activity entity', $account)
|| user_access('edit any crm_core_activity entity of bundle ' . $activity_type));
|| user_access('edit any crm_core_activity entity', $account)
|| user_access('edit any crm_core_activity entity of bundle ' . $activity_type));
case 'delete':
return (user_access('administer crm_core_activity entities', $account)
|| user_access('delete any crm_core_activity entity', $account)
|| user_access('delete any crm_core_activity entity of bundle ' . $activity_type));
|| user_access('delete any crm_core_activity entity', $account)
|| user_access('delete any crm_core_activity entity of bundle ' . $activity_type));
}
}
......@@ -167,9 +167,9 @@ function crm_core_activity_type_access() {
return user_access('administer activity types');
}
/*******************************************************************************
********************** CRM Activity helpers ***********************************
******************************************************************************/
/* ***************************************************************************
* *** CRM Activity helpers ************************************************
* ************************************************************************* */
/**
* Load a CRM Activity object.
......@@ -245,9 +245,9 @@ function crm_core_activity_field_extra_fields() {
return $extra;
}
/*******************************************************************************
********************** CRM Activity Type helpers ******************************
******************************************************************************/
/* ***************************************************************************
* *** CRM Activity Type helpers *******************************************
* ************************************************************************* */
/**
* Load Activity Type.
......@@ -265,13 +265,13 @@ function crm_core_activity_types($type_name = NULL) {
}
/**
*
* Returns the human-readable name of an activity type.
*
*
* @param string|null $type_name
* (Optional) The machine name for an activity type.
* Leave NULL to return all activity types.
* @return array $names
*
* @return array
* An array of human readable names for the activity type.
*/
function crm_core_activity_type_get_name($type_name = NULL) {
......@@ -294,7 +294,7 @@ function crm_core_activity_type_get_name($type_name = NULL) {
}
/**
* Creates or saves an activity type
* Creates or saves an activity type.
*
* @param object $activity_type
* The activity type info to be saved
......@@ -305,8 +305,6 @@ function crm_core_activity_type_save($activity_type) {
/**
* Implements hook_crm_core_activity_type_insert().
*
* Add default fields.
*/
function crm_core_activity_crm_core_activity_type_insert($activity_type) {
module_load_include('inc', 'crm_core_activity', 'crm_core_activity.admin');
......@@ -319,7 +317,7 @@ function crm_core_activity_crm_core_activity_type_insert($activity_type) {
* Create form for adding/editing crm_activity.
* Their id is like 'crm_activity_edit_' . $bundle . '_form'.
*
* @see entity_ui_get_form().
* @see entity_ui_get_form()
*/
function crm_core_activity_forms($form_id, $args) {
$forms = array();
......@@ -362,12 +360,9 @@ function crm_core_activity_views_api() {
* Implements hook_crm_core_contact_delete().
*
* Adjusts Activities where contact is used in primary participants.
*
* @param object $crm_core_contact
* A fully loaded object representing Contact being deleted.
*/
function crm_core_activity_crm_core_contact_delete($crm_core_contact) {
$query = new EntityFieldQuery;
$query = new EntityFieldQuery();
$results = $query
->entityCondition('entity_type', 'crm_core_activity')
->fieldCondition('field_activity_participants', 'target_id', $crm_core_contact->contact_id)
......@@ -388,7 +383,7 @@ function crm_core_activity_crm_core_contact_delete($crm_core_contact) {
$wrapped_activity = entity_metadata_wrapper('crm_core_activity', $crm_core_activity);
$participants = $wrapped_activity->field_activity_participants->value(array('identifier' => TRUE));
// Remove Contact from participants array
// Remove Contact from participants array.
$participants = array_diff($participants, array($crm_core_contact->contact_id));
if (empty($participants)) {
......@@ -404,9 +399,10 @@ function crm_core_activity_crm_core_contact_delete($crm_core_contact) {
if (!empty($activities_to_remove)) {
watchdog('crm_core_activity', 'Deleted !count activities due to deleting contact id=%contact_id.',
array('!count' => count($activities_to_remove),
'%contact_id' => $crm_core_contact->contact_id),
WATCHDOG_INFO);
array(
'!count' => count($activities_to_remove),
'%contact_id' => $crm_core_contact->contact_id,
), WATCHDOG_INFO);
crm_core_activity_delete_multiple($activities_to_remove);
}
}
......@@ -451,8 +447,6 @@ function crm_core_activity_search_status() {
* Implements hook_search_execute().
*/
function crm_core_activity_search_execute($keys = NULL, $conditions = NULL) {
// Build matching conditions
$query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
$query->join('crm_core_activity', 'a', 'a.activity_id = i.sid');
$query->searchExpression($keys, 'crm_core_activity');
......@@ -496,7 +490,7 @@ function crm_core_activity_search_execute($keys = NULL, $conditions = NULL) {
/**
* Search condition callback.
*/
function crm_core_activity_search_conditions_callback($keys) {
function crm_core_activity_search_conditions_callback($keys) {
$conditions = array();
if (!empty($_REQUEST['keys'])) {
......@@ -508,6 +502,7 @@ function crm_core_activity_search_execute($keys = NULL, $conditions = NULL) {
if ($force_keys = variable_get('sample_search_force_keywords', '')) {
$conditions['sample_search_force_keywords'] = $force_keys;
}
return $conditions;
}
......@@ -525,12 +520,10 @@ function crm_core_activity_update_index() {
// Render the activity.
$text = drupal_render(crm_core_activity_view($activity));
// Update index
search_index($activity->activity_id, 'crm_core_activity', $text);
}
}
/**
* Implements hook_feeds_plugins().
*/
......@@ -549,15 +542,7 @@ function crm_core_activity_feeds_plugins() {
return $info;
}
/*
* Implements hook_enable().
*/
function crm_core_activity_enable() {
// clear the cache to display in Feeds as available plugin.
cache_clear_all('plugins:feeds:plugins', 'cache');
}
/*
/**
* Implements hook_file_download_access().
*/
function crm_core_activity_file_download_access($file_item, $entity_type, $entity) {
......@@ -575,7 +560,7 @@ function crm_core_activity_theme($existing, $type, $theme, $path) {
'render element' => 'elements',
'template' => 'activity',
'path' => $path . '/templates',
)
),
);
}
......@@ -587,12 +572,11 @@ function crm_core_activity_preprocess_activity(&$variables) {
$variables['activity'] = $variables['elements']['#activity'];
$variables['content'] = entity_build_content('crm_core_activity', $variables['activity']);
$entity = $variables['activity'];
// add classes based on the type of activity
// Add classes based upon activity type.
$variables['classes_array'][] = 'activity';
$variables['classes_array'][] = 'activity-' . $variables['activity']->type;
$entity = $variables['activity'];
$variables['theme_hook_suggestions'][] = 'activity__' . $entity->type;
$variables['theme_hook_suggestions'][] = 'activity__' . $entity->activity_id;
}
......@@ -42,15 +42,15 @@ class CRMFeedsActivityProcessor extends FeedsProcessor {
/**
* Loads an existing activity.
*
* If the update existing method is not FEEDS_UPDATE_EXISTING, only the activity
* table will be loaded, foregoing the crm_core_activity_load API for better performance.
* If the method is not FEEDS_UPDATE_EXISTING, only the activity table will
* be loaded, foregoing the crm_core_activity_load API for better performance.
*/
protected function entityLoad(FeedsSource $source, $activity_id) {
if ($this->config['update_existing'] == FEEDS_UPDATE_EXISTING) {
$activity = crm_core_activity_load($activity_id, TRUE);
}
else {
// We're replacing the existing activity. Only save the absolutely necessary.
// We're replacing the existing activity. Only save the necessary.
$activity = db_query("SELECT created, activity_id, type FROM {crm_core_activity} WHERE activity_id = :activity_id", array(':activity_id' => $activity_id))->fetchObject();
$activity->uid = $this->config['author'];
}
......@@ -145,12 +145,14 @@ class CRMFeedsActivityProcessor extends FeedsProcessor {
'#autocomplete_path' => 'user/autocomplete',
'#default_value' => empty($author->name) ? 'anonymous' : check_plain($author->name),
);
$period = drupal_map_assoc(array(FEEDS_EXPIRE_NEVER, 3600, 10800, 21600, 43200, 86400, 259200, 604800, 2592000, 2592000 * 3, 2592000 * 6, 31536000), 'feeds_format_expire');
$period = drupal_map_assoc(array(FEEDS_EXPIRE_NEVER, 3600, 10800, 21600,
43200, 86400, 259200, 604800, 2592000, 2592000 * 3, 2592000 * 6, 31536000),
'feeds_format_expire');
$form['expire'] = array(
'#type' => 'select',
'#title' => t('Expire activities'),
'#options' => $period,
'#description' => t('Select after how much time activities should be deleted. The activity\'s published date will be used for determining the activity\'s age, see Mapping settings.'),
'#description' => t("Select after how much time activities should be deleted. The activity's published date will be used for determining the activity's age, see Mapping settings."),
'#default_value' => $this->config['expire'],
);
$form['update_existing']['#options'] = array(
......@@ -191,9 +193,10 @@ class CRMFeedsActivityProcessor extends FeedsProcessor {
case 'created':
$target_activity->created = feeds_to_unixtime($value, REQUEST_TIME);
break;
case 'feeds_source':
// Get the class of the feed activity importer's fetcher and set the source
// property. See feeds_activity_update() how $activity->feeds gets stored.
// Get class of the feed activity importer's fetcher and set the source
// property. See feeds_activity_update() for $activity->feeds storage.
if ($id = feeds_get_importer_id($this->config['activity_type'])) {
$class = get_class(feeds_importer($id)->fetcher);
$target_activity->feeds[$class]['source'] = $value;
......@@ -202,6 +205,7 @@ class CRMFeedsActivityProcessor extends FeedsProcessor {
$target_activity->feeds['suppress_import'] = TRUE;
}
break;
default:
parent::setTargetElement($source, $target_activity, $target_element, $value);
break;
......
......@@ -9,8 +9,7 @@
* Implements hook_views_data_alter().
*/
function crm_core_activity_views_data_alter(&$data) {
$data['crm_core_activity']['table']['group'] = $data['crm_core_activity_type']['table']['group']
= t('CRM activity');
$data['crm_core_activity']['table']['group'] = $data['crm_core_activity_type']['table']['group'] = t('CRM activity');
$data['crm_core_activity_type']['type']['field']['handler'] = 'views_handler_field_activity_type';
$data['crm_core_activity']['type']['filter']['handler'] = 'views_handler_filter_activity_type';
......@@ -38,7 +37,7 @@ function crm_core_activity_views_data_alter(&$data) {
),
);
// Integration with Drupal search index
// Integration with Drupal search index.
$data['search_index']['table']['join']['crm_core_activity'] = array(
'left_field' => 'activity_id ',
'field' => 'sid',
......@@ -58,9 +57,9 @@ function crm_core_activity_views_data_alter(&$data) {
'type' => 'INNER',
);
// implicit join for activity_type
// Implicit join for activity_type.
$data['crm_core_activity_type']['table']['join']['crm_core_activity'] = array(
'left_field' => 'activity_type_id',
'field' => 'id',
'left_field' => 'activity_type_id',
'field' => 'id',
);
}
......@@ -8,9 +8,8 @@
/**
* Provides a field handler for activity_type.
*
* This is used in views that provide the ability to add
* an activity for a contact.
*
* Used in views that provide the ability to add an activity for a contact.
*
* The link is strucuted like so:
* 'crm-core/contact/' . [node nid] . '/activity/add/' . [activity machine name]
*/
......
......@@ -4,6 +4,7 @@
* @file
* Views support. Allows users to filter by activity type
*/
class views_handler_filter_activity_type extends views_handler_filter_in_operator {
function get_value_options() {
if (!isset($this->value_options)) {
......
......@@ -38,7 +38,7 @@ function crm_core_activity_ui_disable() {
// Remove links from crm-core-menu and crm-core-admin-menu.
$conditions = array(
'crm-core-admin-menu' => array(
'admin/structure/crm-core/activity-types'
'admin/structure/crm-core/activity-types',
),
);
crm_core_ui_remove_links($conditions);
......
......@@ -2,17 +2,13 @@
/**
* @file
*
* Provides the UI for viewing activities.
*/
/**
* Implements hook_menu()
* Implements hook_menu().
*/
function crm_core_activity_ui_menu() {
$items = array();
// Menu items for creation of new Activities.
foreach (crm_core_activity_types() as $type => $info) {
$items['crm-core/contact/%crm_core_contact/activity/add/' . $type] = array(
'title' => 'Add activity',
......@@ -26,7 +22,7 @@ function crm_core_activity_ui_menu() {
);
}
$items['crm-core/activity/%crm_core_activity'] = array(
$items['crm-core/activity/%crm_core_activity'] = array(
'title' => 'Activity',
'page callback' => 'crm_core_activity_view',
'page arguments' => array(2),
......@@ -36,7 +32,7 @@ function crm_core_activity_ui_menu() {