123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774 |
- <?php
- /**
- * @file
- * Support workflows made up of arbitrary states.
- */
- define('WORKFLOW_CREATION_STATE', 1);
- define('WORKFLOW_CREATION_DEFAULT_WEIGHT', -50);
- define('WORKFLOW_DELETION', 0);
- // Couldn't find a more elegant way to preserve translation.
- define('WORKFLOW_CREATION_STATE_NAME', 'creation');
- /**
- * Role ID for anonymous users.
- */
- // #2657072 brackets are added later to indicate a special role,
- // and distinguish from frequently used 'author' role.
- define('WORKFLOW_ROLE_AUTHOR_NAME', 'Author');
- define('WORKFLOW_ROLE_AUTHOR_RID', 'workflow_author');
- use Drupal\Core\Entity\EntityInterface;
- use Drupal\Core\Routing\RouteMatchInterface;
- use Drupal\Core\Session\AccountInterface;
- use Drupal\Core\Url;
- use Drupal\field\Entity\FieldStorageConfig;
- use Drupal\user\Entity\User;
- use Drupal\workflow\Entity\Workflow;
- use Drupal\workflow\Entity\WorkflowManager;
- use Drupal\workflow\Entity\WorkflowScheduledTransition;
- use Drupal\workflow\Entity\WorkflowState;
- use Drupal\workflow\Entity\WorkflowTransition;
- use Drupal\workflow\Entity\WorkflowTransitionInterface;
- \Drupal::moduleHandler()->loadInclude('workflow', 'inc', 'workflow.form');
- \Drupal::moduleHandler()->loadInclude('workflow', 'inc', 'workflow.field');
- /**********************************************************************
- * Info hooks.
- */
- /**
- * Implements hook_help().
- *
- * @param string $route_name
- * A route.
- *
- * @return string
- * The html output.
- */
- function workflow_help($route_name) {
- $output = '';
- switch ($route_name) {
- case 'help.page.workflow':
- $output .= '<h3>' . t('About') . '</h3>';
- $output .= '<p>' . t('The Workflow module adds a field to Entities to
- store field values as Workflow states. You can control "state transitions"
- and add action to specific transitions.') . '</p>';
- }
- return $output;
- }
- /**
- * Implements hook_hook_info().
- *
- * Allow adopters to place their hook implementations in either
- * their main module or in a module.workflow.inc file.
- */
- function workflow_hook_info() {
- $hooks['workflow'] = ['group' => 'workflow'];
- return $hooks;
- }
- /**********************************************************************
- * CRUD hooks.
- */
- /**
- * Implements hook_user_cancel().
- *
- * Update tables for deleted account, move account to user 0 (anon.)
- * ALERT: This may cause previously non-Anonymous posts to suddenly
- * be accessible to Anonymous.
- *
- * @param $edit
- * @param \Drupal\Core\Session\AccountInterface $account
- * An account object.
- * @param string $method
- *
- * @see hook_user_cancel()
- */
- function workflow_user_cancel($edit, AccountInterface $account, $method) {
- WorkflowManager::cancelUser($edit, $account, $method);
- }
- /**
- * Implements hook_user_delete().
- *
- * @param \Drupal\Core\Session\AccountInterface $account
- * An account object.
- *
- * @todo: hook_user_delete does not exist. hook_ENTITY_TYPE_delete?
- */
- function workflow_user_delete($account) {
- WorkflowManager::deleteUser($account);
- }
- /**
- * Implements hook_ENTITY_TYPE_insert().
- *
- * Is called when adding a new Workflow type.
- * The technical name for the Workflow entity is 'workflow_type'.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * An Entity.
- */
- function workflow_workflow_type_insert(EntityInterface $entity) {
- WorkflowManager::participateUserRoles($entity);
- }
- /**
- * Implements hook_entity_insert().
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * An Entity.
- */
- function workflow_entity_insert(EntityInterface $entity) {
- // Execute updates in hook_presave() to revert executions,
- // Execute inserts in hook_insert, to have the Entity ID determined.
- _workflow_execute_transitions($entity);
- }
- /**
- * Implements hook_entity_presave().
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * An EntityInterface object.
- */
- function workflow_entity_presave(EntityInterface $entity) {
- if (!$entity->isNew()) {
- // Avoid a double call by hook_entity_presave and hook_entity_insert.
- _workflow_execute_transitions($entity);
- }
- }
- /**
- * Execute transitions. if prohibited, restore original field value.
- * - insert: use hook_insert(), to have the Entity ID determined when saving transitions.
- * - update: use hook_presave() to revert executions,
- * - so, do not use hook_update().
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * An Entity.
- */
- function _workflow_execute_transitions(EntityInterface $entity) {
- // Avoid this hook on workflow objects.
- if (WorkflowManager::isWorkflowEntityType($entity->getEntityTypeId())) {
- return;
- }
- // Execute/save the transitions fom the widgets in the entity form.
- WorkflowManager::executeTransitionsOfEntity($entity);
- }
- /**
- * Implements hook_entity_delete().
- *
- * Delete the corresponding workflow table records.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * An Entity.
- */
- function workflow_entity_delete(EntityInterface $entity) {
- // @todo: test with multiple workflows.
- if (get_class($entity) == 'Drupal\field\Entity\FieldConfig'
- || get_class($entity) == 'Drupal\field\Entity\FieldStorageConfig') {
- // A Workflow Field is removed from an entity.
- $field_config = $entity;
- /** @var \Drupal\Core\Entity\ContentEntityBase $field_config */
- $entity_type = $field_config->get('entity_type');
- $field_name = $field_config->get('field_name');
- /** @var $transition Drupal\workflow\Entity\WorkflowTransitionInterface */
- foreach (WorkflowScheduledTransition::loadMultipleByProperties($entity_type, [], [], $field_name) as $transition) {
- $transition->delete();
- }
- foreach (WorkflowTransition::loadMultipleByProperties($entity_type, [], [], $field_name) as $transition) {
- $transition->delete();
- }
- }
- elseif (!WorkflowManager::isWorkflowEntityType($entity->getEntityTypeId())) {
- // A 'normal' entity is deleted.
- foreach ($fields = _workflow_info_fields($entity) as $field_id => $field_storage) {
- $entity_id = $entity->id();
- $entity_type = $field_storage->getTargetEntityTypeId();
- $field_name = $field_storage->getName();
- /** @var $transition Drupal\workflow\Entity\WorkflowTransitionInterface */
- foreach (WorkflowScheduledTransition::loadMultipleByProperties($entity_type, [$entity_id], [], $field_name) as $transition) {
- $transition->delete();
- }
- foreach (WorkflowTransition::loadMultipleByProperties($entity_type, [$entity_id], [], $field_name) as $transition) {
- $transition->delete();
- }
- }
- }
- }
- /**
- * Implements hook_cron().
- *
- * Given a time frame, execute all scheduled transitions.
- */
- function workflow_cron() {
- WorkflowManager::executeScheduledTransitionsBetween(0, \Drupal::time()->getRequestTime());
- }
- /**
- * Business related functions, the API.
- */
- /**
- * @param \Drupal\workflow\Entity\WorkflowTransitionInterface $transition
- * A WorkflowTransition.
- * @param bool $force
- * Indicator if the transition must be forces.
- *
- * @return string
- * A string.
- */
- function workflow_execute_transition(WorkflowTransitionInterface $transition, $force = FALSE) {
- // Execute transition and update the attached entity.
- return $transition->executeAndUpdateEntity($force);
- }
- /**
- * Functions to get an options list (to show in a Widget).
- * To be used in non-OO modules, like workflow_rules, workflow_views.
- *
- * The naming convention is workflow_get_<entity_type>_names.
- * (A bit different from 'user_role_names'.)
- * Can be used for hook_allowed_values from list.module:
- * - user_role
- * - workflow
- * - workflow_state
- * - sid.
- */
- /**
- * Retrieves the names of roles matching specified conditions.
- *
- * Deprecated D8: workflow_get_roles --> workflow_get_user_role_names.
- *
- * Usage:
- * D7: $roles = workflow_get_user_role_names('participate in workflow');
- * D8: $type_id = $workflow->id();
- * D8: $roles = workflow_get_user_role_names("create $type_id workflow_transition");
- *
- * @param string $permission
- * (optional) A string containing a permission. If set, only roles
- * containing that permission are returned. Defaults to NULL, which
- * returns all roles.
- * Normal usage for filtering roles that are enabled in a workflow_type
- * would be: $permission = 'create $type_id transition'.
- *
- * @return array
- * Array of role names keyed by role ID, including the 'author' role.
- */
- function workflow_get_user_role_names($permission) {
- static $roles = NULL;
- if ($roles[$permission]) {
- return $roles[$permission];
- }
- // Copied from AccountForm::form().
- $roles[$permission] = array_map(['\Drupal\Component\Utility\Html', 'escape'],
- [WORKFLOW_ROLE_AUTHOR_RID => '(' . t(WORKFLOW_ROLE_AUTHOR_NAME) . ')']
- + user_role_names(FALSE, $permission));
- return $roles[$permission];
- }
- /**
- * Get an options list for workflow states.
- *
- * @param mixed $wid
- * The Workflow ID.
- * @param bool $grouped
- * Indicates if the value must be grouped per workflow.
- * This influences the rendering of the select_list options.
- *
- * @return array
- * An array of $sid => state->label(), grouped per Workflow.
- */
- function workflow_get_workflow_state_names($wid = '', $grouped = FALSE) {
- $options = [];
- // @todo: implement $add parameter.
- //
- // @todo: follow Options pattern
- // @see callback_allowed_values_function()
- // @see options_allowed_values()
- // Get the (user-dependent) options.
- // Since this function is only used in UI, it is save to use the global $user.
- $user = workflow_current_user();
- /** @var $workflows Workflow[] */
- $workflows = Workflow::loadMultiple($wid ? [$wid] : NULL);
- // Do not group if only 1 Workflow is configured or selected.
- $grouped = count($workflows) == 1 ? FALSE : $grouped;
- foreach ($workflows as $wid => $workflow) {
- /** @var $state WorkflowState */
- $state = WorkflowState::create(['wid' => $wid]);
- $workflow_options = $state->getOptions(NULL, '', $user, FALSE);
- if (!$grouped) {
- $options += $workflow_options;
- }
- else {
- // Make a group for each Workflow.
- $options[$workflow->label()] = $workflow_options;
- }
- }
- return $options;
- }
- /**
- * Get an options list for workflows. Include an initial empty value
- * if requested. Validate each workflow, and generate a message if not complete.
- *
- * @param bool $required
- * Indicates if the resulting list contains a options value.
- *
- * @return array
- * An array of $wid => workflow->label().
- */
- function workflow_get_workflow_names($required = TRUE) {
- $options = [];
- if (!$required) {
- $options[''] = t('- Select a value -');
- }
- foreach (Workflow::loadMultiple() as $wid => $workflow) {
- /** @var $workflow Workflow */
- if ($workflow->isValid()) {
- $options[$wid] = $workflow->label();
- }
- }
- return $options;
- }
- /**
- * Gets an Options list of field names.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * An entity.
- * @param string $entity_type
- * An entity_type.
- * @param string $entity_bundle
- * An entity.
- * @param string $field_name
- * A field name.
- *
- * @return array
- * An list of field names.
- */
- function workflow_get_workflow_field_names(EntityInterface $entity = NULL, $entity_type = '', $entity_bundle = '', $field_name = '') {
- $result = [];
- foreach (_workflow_info_fields($entity, $entity_type, $entity_bundle, $field_name) as $definition) {
- $field_name2 = $definition->getName();
- $result[$field_name2] = $field_name2;
- }
- return $result;
- }
- /**
- * Helper function, to get the label of a given State Id.
- * deprecated: workflow_get_sid_label() --> workflow_get_sid_name()
- *
- * @param string $sid
- * A State ID.
- *
- * @return string
- * An translated label.
- */
- function workflow_get_sid_name($sid) {
- if (empty($sid)) {
- $label = 'No state';
- }
- /** @noinspection PhpAssignmentInConditionInspection */
- elseif ($state = WorkflowState::load($sid)) {
- $label = $state->label();
- }
- else {
- $label = 'Unknown state';
- }
- return t($label);
- }
- /**
- * Determines the Workflow field_name of an entity.
- * If an entity has multiple workflows, only returns the first one.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * The entity at hand.
- * @param string $field_name
- * The field name. If given, will be passed as return value.
- *
- * @return string
- * The found Field name.
- */
- function workflow_get_field_name(EntityInterface $entity, $field_name = '') {
- if (!$entity) {
- // $entity may be empty on Entity Add page.
- return '';
- }
- if ($field_name) {
- return $field_name;
- }
- $fields = workflow_get_workflow_field_names($entity);
- $field_name = reset($fields);
- return $field_name;
- }
- /**
- * Functions to get the state of an entity.
- */
- /**
- * Wrapper function to get a UserInterface object.
- * We use UserInterface to check permissions.
- *
- * @param \Drupal\Core\Session\AccountInterface|null $account
- * An Account.
- *
- * @return \Drupal\user\UserInterface
- * A User.
- */
- function workflow_current_user(AccountInterface $account = NULL) {
- $account = ($account) ? $account : \Drupal::currentUser();
- return User::load($account->id());
- }
- /**
- * Gets the current state ID of a given entity.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * An entity.
- * @param string $field_name
- * A Field name.
- *
- * @return string
- * The current State ID.
- */
- function workflow_node_current_state(EntityInterface $entity, $field_name = '') {
- return WorkflowManager::getCurrentStateId($entity, $field_name);
- }
- /**
- * Gets the previous state ID of a given entity.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * An entity.
- * @param string $field_name
- * A field_name.
- *
- * @return string
- * The previous State ID.
- */
- function workflow_node_previous_state(EntityInterface $entity, $field_name = '') {
- return WorkflowManager::getPreviousStateId($entity, $field_name);
- }
- /**
- * Get a specific workflow, given an entity type.
- * Only one workflow is possible per node type.
- * Caveat: gives undefined results with multiple workflows per entity.
- * @todo: support multiple workflows per entity.
- *
- * @param string $entity_bundle
- * An entity bundle.
- * @param string $entity_type
- * An entity type. This is passed when also the Field API must be checked.
- *
- * @return \Drupal\workflow\Entity\Workflow
- * A Workflow object, or NULL if no workflow is retrieved.
- */
- function workflow_get_workflows_by_type($entity_bundle, $entity_type) {
- static $map = [];
- if (isset($map[$entity_type][$entity_bundle])) {
- return $map[$entity_type][$entity_bundle];
- }
- $wid = FALSE;
- if (isset($entity_type)) {
- foreach (_workflow_info_fields(NULL, $entity_type, $entity_bundle) as $field_info) {
- $wid = $field_info->getSetting('workflow_type');
- }
- }
- // Set the cache with a workflow object.
- $map[$entity_type][$entity_bundle] = NULL;
- if ($wid) {
- $map[$entity_type][$entity_bundle] = Workflow::load($wid);
- }
- return $map[$entity_type][$entity_bundle];
- }
- /**
- * Finds the Workflow fields on a given Entity type.
- *
- * @return mixed
- *
- * @see EntityFieldManager::getFieldMapByFieldType
- */
- function workflow_get_workflow_fields_by_entity_type() {
- return \Drupal::service('entity_field.manager')->getFieldMapByFieldType('workflow');
- }
- /**
- * Gets the workflow field names, if not known already.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * Object to work with. May be empty, e.g., on menu build.
- * @param string $entity_type
- * Entity type of object. Optional, but required if $entity provided.
- * @param string $entity_bundle
- * Bundle of entity. Optional.
- * @param string $field_name
- * Field name. Optional.
- *
- * @return Drupal\field\Entity\FieldStorageConfig[]
- * An array of FieldStorageConfig objects.
- */
- function _workflow_info_fields($entity = NULL, $entity_type = '', $entity_bundle = '', $field_name = '') {
- $field_info = [];
- // Figure out the $entity's bundle and id.
- if ($entity) {
- $entity_type = $entity->getEntityTypeId();
- $entity_bundle = $entity->bundle();
- }
- else {
- // Entity type and bundle should be specified.
- }
- $field_list = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('workflow');
- foreach ($field_list as $e_type => $data) {
- if (!$entity_type || ($entity_type == $e_type)) {
- foreach ($data as $f_name => $value) {
- if (!$entity_bundle || isset($value['bundles'][$entity_bundle])) {
- if (!$field_name || ($field_name == $f_name)) {
- // Do not use the field_name as ID, but the
- // unique <entity_type>.<field_name> since you cannot share the
- // same field on multiple entity_types (unlike D7).
- $field_config = FieldStorageConfig::loadByName($e_type, $f_name);
- if ($field_config) {
- $field_info[$field_config->id()] = $field_config;
- }
- else {
- // The field is a base/extra field.
- // not a configurable Field via Field UI.
- // Re-fetch the field definitions, with extra data.
- $field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($e_type, $entity_bundle);
- // @todo: ?? Loop over bundles?
- /** @var \Drupal\Core\Field\BaseFieldDefinition $field_config */
- $field_config = $field_definitions[$f_name];
- if ($field_config) {
- $field_info[$field_config->getUniqueStorageIdentifier()] = $field_config;
- }
- else {
- // @todo: ?? Loop over bundles?
- }
- }
- }
- }
- }
- }
- }
- return $field_info;
- }
- /**
- * Helper function to get the entity from a route.
- *
- * This is a hack. It should be solved by using $route_match.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * An optional entity.
- * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
- * A route.
- *
- * @return \Drupal\Core\Entity\EntityInterface
- * Entity from the route.
- */
- function workflow_url_get_entity(EntityInterface $entity = NULL, RouteMatchInterface $route_match = NULL) {
- if ($entity) {
- return $entity;
- }
- if (!$route_match) {
- $route_match = \Drupal::routeMatch();
- }
- $entities = [];
- foreach ($route_match->getParameters() as $param) {
- if ($param instanceof EntityInterface) {
- $entities[] = $param;
- }
- }
- $value = reset($entities);
- if ($value && is_object($value)) {
- return $value;
- }
- if ($value && !is_object($value)) {
- // On workflow tab, we'd get an id.
- // This is an indicator that the route is mal-configured.
- workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'route declaration is not optimal.');
- /* Return $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($value); */
- return NULL;
- }
- return $value;
- }
- /**
- * Helper function to get the field name from a route.
- *
- * For now only used for ../{entity_id}/workflow history tab.
- *
- * @return string|null
- * Return $field_name
- */
- function workflow_url_get_field_name() {
- return workflow_url_get_parameter('field_name');
- }
- /**
- * Helper function to get the entity from a route.
- *
- * @return mixed|string
- * Return $operation
- */
- function workflow_url_get_operation() {
- $url = Url::fromRoute('<current>');
- // The last part of the path is the operation: edit, workflow, devel.
- $url_parts = explode('/', $url->toString());
- $operation = array_pop($url_parts);
- // Except for view pages.
- if (is_numeric($operation) || $operation == 'view') {
- $operation = '';
- }
- return $operation;
- }
- /**
- * Helper function to get arbitrary parameter from a route.
- *
- * @param string $parameter
- * The requested parameter.
- *
- * @return string
- * field_name
- */
- function workflow_url_get_parameter($parameter) {
- return \Drupal::routeMatch()->getParameter($parameter);
- // Return \Drupal::request()->get($parameter);
- }
- /**
- * Helper function to determine Workflow from Workflow UI URL.
- *
- * @return \Drupal\workflow\Entity\Workflow
- * Workflow Object.
- */
- function workflow_url_get_workflow() {
- /** @var $workflows \Drupal\workflow\Entity\Workflow[] */
- static $workflows = [];
- $wid = workflow_url_get_parameter('workflow_type');
- if (is_object($wid)) {
- // $wid is a Workflow object.
- return $wid;
- }
- if (!isset($workflows[$wid])) {
- // $wid is a string.
- $workflows[$wid] = $wid ? Workflow::load($wid) : NULL;
- }
- return $workflows[$wid];
- }
- /**
- * Helper function to determine the title of the page.
- *
- * Used in file workflow_ui.routing.yml.
- *
- * @return \Drupal\Core\StringTranslation\TranslatableMarkup
- * the page title.
- */
- function workflow_url_get_title() {
- $label = '';
- // Get the Workflow from the page.
- /** @var $workflow \Drupal\workflow\Entity\Workflow */
- /** @noinspection PhpAssignmentInConditionInspection */
- if ($workflow = workflow_url_get_workflow()) {
- $label = $workflow->label();
- }
- $title = t('Edit @entity %label', ['@entity' => 'Workflow', '%label' => $label]);
- return $title;
- }
- /**
- * Helper function to determine Workflow from Workflow UI URL.
- *
- * @param string $url
- * URL.
- *
- * @return mixed
- * the Workflow type.
- */
- function workflow_url_get_form_type($url = '') {
- // For some reason, $_SERVER is not allowed as default.
- $url = ($url == '') ? $_SERVER['REQUEST_URI'] : $url;
- $base_url = '/config/workflow/workflow/';
- $string = substr($url, strpos($url, $base_url) + strlen($base_url));
- $type = explode('/', $string)[1];
- return $type;
- }
- /**
- * Helper function for D8-port: Get some info on screen.
- *
- * @param string $class_name
- * @param string $function_name
- * @param string $line_nr
- * @param string $value1
- * @param string $value2
- *
- * @see workflow_devel-module
- *
- * Usage:
- * workflow_debug( __FILE__, __FUNCTION__, __LINE__, '', '');
- */
- function workflow_debug($class_name, $function_name, $line_nr = '', $value1 = '', $value2 = '') {
- $debug_switch = FALSE;
- // $debug_switch = TRUE;
- if (!$debug_switch) {
- return;
- }
- $class_name_elements = explode("\\", $class_name);
- $output = 'Testing... function ' . end($class_name_elements) . '::' . $function_name . '/' . $line_nr;
- if ($value1) {
- $output .= ' = ' . $value1;
- }
- if ($value2) {
- $output .= ' > ' . $value2;
- }
- drupal_set_message($output, 'warning', TRUE);
- }
|