You are here

class OpignoModuleManagerController in Opigno module 8

Same name and namespace in other branches
  1. 3.x src/Controller/OpignoModuleManagerController.php \Drupal\opigno_module\Controller\OpignoModuleManagerController

Controller for all the actions of the Opigno module manager.

Hierarchy

Expanded class hierarchy of OpignoModuleManagerController

File

src/Controller/OpignoModuleManagerController.php, line 30

Namespace

Drupal\opigno_module\Controller
View source
class OpignoModuleManagerController extends ControllerBase {
  protected $H5PActivitiesDetails;

  /**
   * OpignoModuleManagerController constructor.
   */
  public function __construct() {
    $this->H5PActivitiesDetails = $this
      ->getH5PActivitiesDetails();
  }

  /**
   * Check the access for the activity create/edit form.
   */
  public function accessItemForm(OpignoModuleInterface $opigno_module, AccountInterface $account) {

    // Allow access if the user is a platform-level content manager.
    if ($account
      ->hasPermission('manage group content in any group')) {
      return AccessResult::allowed();
    }
    $item = \Drupal::routeMatch()
      ->getParameter('item');
    if (isset($item) && $item > 0) {

      // If it is edit form, check activity update access.
      $activity = OpignoActivity::load($item);
      return $activity
        ->access('update', $account, TRUE);
    }

    // Check activity create access.
    $create_access = \Drupal::entityTypeManager()
      ->getAccessControlHandler('opigno_activity')
      ->createAccess('opigno_activity', $account);
    if ($create_access) {
      return AccessResult::allowed();
    }
    return AccessResult::forbidden();
  }

  /**
   * Method called when the manager needs a create or edit form.
   */
  public function getItemForm(OpignoModuleInterface $opigno_module, $type = NULL, $item = 0) {

    // Get the good form from the corresponding content type.
    if ($item > 0) {
      $entity = OpignoActivity::load($item);
      if (!$entity
        ->access('update')) {
        throw new AccessDeniedHttpException();
      }
    }
    else {
      $entity = OpignoActivity::create([
        'type' => $type,
      ]);
    }

    /** @var \Drupal\Core\Entity\EntityFormBuilder $form_builder */
    $form_builder = \Drupal::service('entity.form_builder');
    $form_build = $form_builder
      ->getForm($entity, 'default');
    $form_build['#attached']['library'][] = 'opigno_module/ajax_form';

    // Returns the form.
    return $form_build;
  }

  /**
   * Returns activity preview.
   */
  public function getActivityPreview(OpignoActivity $opigno_activity) {
    $view_builder = \Drupal::entityTypeManager()
      ->getViewBuilder($opigno_activity
      ->getEntityTypeId());
    return $view_builder
      ->view($opigno_activity, 'activity');
  }

  /**
   * Get activities bank for module with 'add' operation.
   *
   * This method does the same think as
   * \Drupal\opigno_module\Controller\LearningPathController::activitiesBank()
   * but is using in Training context (on Learning Path Manager page).
   */
  public function activitiesBankLPM() {

    // Output activities bank view.
    $build['activities_bank'] = views_embed_view('opigno_activities_bank_lp_interface');
    return $build;
  }

  /**
   * Ajax callback.
   */
  public function ajaxCheckedActivities() {
    if (!empty($_POST['data'])) {
      $data = json_decode($_POST['data']);
      $checkboxes_ids = !empty($_SESSION['activities_bank']['checkboxes_ids']) ? $_SESSION['activities_bank']['checkboxes_ids'] : [];
      $activities_ids = !empty($_SESSION['activities_bank']['activities_ids']) ? $_SESSION['activities_bank']['activities_ids'] : [];
      if (!empty($data->checked) && array_search($data->checked, $checkboxes_ids) === FALSE) {
        $checkboxes_ids[] = $data->checked;
        $activities_ids[] = $data->activityID;
      }
      elseif (!empty($data->unchecked)) {
        $key = array_search($data->unchecked, $checkboxes_ids);
        if ($key !== FALSE) {
          unset($checkboxes_ids[$key]);
        }
        $key = array_search($data->activityID, $activities_ids);
        if ($key !== FALSE) {
          unset($activities_ids[$key]);
        }
      }
      $_SESSION['activities_bank'] = [
        'checkboxes_ids' => $checkboxes_ids,
        'activities_ids' => $activities_ids,
      ];
    }
    die;
  }

  /**
   * Check access.
   */
  public function ajaxCheckedActivitiesAccess() {
    $account = $this
      ->currentUser();
    $is_content_manager = LearningPathAccess::memberHasRole('content_manager', $account);
    if ($is_content_manager > 0 || $account
      ->hasPermission('administer module entities')) {
      return AccessResult::allowed();
    }
    else {
      return AccessResult::forbidden();
    }
  }

  /**
   * Ajax form callback.
   */
  public static function ajaxFormEntityCallback(&$form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    // If errors, returns the form with errors and messages.
    if ($form_state
      ->hasAnyErrors()) {
      return $form;
    }
    $entity = $form_state
      ->getBuildInfo()['callback_object']
      ->getEntity();
    $item = [];
    $item['id'] = $entity
      ->id();
    $item['name'] = $entity
      ->getName();
    $command = new SettingsCommand([
      'formValues' => $item,
      'messages' => \Drupal::messenger()
        ->all(),
    ], TRUE);
    \Drupal::messenger()
      ->deleteAll();
    $response
      ->addCommand($command);
    return $response;
  }

  /**
   * Submit handler added in the form by opigno_module_form_alter().
   *
   * @see opigno_module_form_alter()
   */
  public static function ajaxFormEntityFormSubmit($form, FormState &$form_state) {

    // Gets back the content type and module id.
    $build_info = $form_state
      ->getBuildInfo();
    $params = \Drupal::routeMatch()
      ->getParameters();
    $module = $params
      ->get('opigno_module');
    $type_id = $params
      ->get('type');
    $item_id = $params
      ->get('item');

    // If one information missing, return an error.
    if (!isset($module) || !isset($type_id)) {

      // TODO: Add an error message here.
      return;
    }

    // Get the newly or edited entity.

    /** @var \Drupal\Core\Entity\EntityInterface $entity */
    $entity = $build_info['callback_object']
      ->getEntity();

    // Clear user input.
    $input = $form_state
      ->getUserInput();

    // We should not clear the system items from the user input.
    $clean_keys = $form_state
      ->getCleanValueKeys();
    $clean_keys[] = 'ajax_page_state';
    foreach ($input as $key => $item) {
      if (!in_array($key, $clean_keys) && substr($key, 0, 1) !== '_') {
        unset($input[$key]);
      }
    }

    // Store new entity for display in the AJAX callback.
    $input['entity'] = $entity;
    $form_state
      ->setUserInput($input);

    // Rebuild the form state values.
    $form_state
      ->setRebuild();
    $form_state
      ->setStorage([]);

    // Assign activity to module if entity is new.
    if (!isset($item_id)) {

      /** @var \Drupal\opigno_module\Controller\OpignoModuleController $opigno_module_controller */
      $opigno_module_controller = \Drupal::service('opigno_module.opigno_module');
      $opigno_module_controller
        ->activitiesToModule([
        $entity,
      ], $module);
    }
  }

  /**
   * Checks access for the activities overview.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Current user.
   *
   * @return \Drupal\Core\Access\AccessResult
   *   Access result.
   */
  public function accessActivitiesOverview(AccountInterface $account) {

    // Allow access if the user is a platform-level content manager.
    if ($account
      ->hasPermission('manage group content in any group')) {
      return AccessResult::allowed();
    }

    // Allow access if the user is a group-level content manager in any group.
    $membership_service = \Drupal::service('group.membership_loader');
    $memberships = $membership_service
      ->loadByUser($account);
    foreach ($memberships as $membership) {

      /** @var \Drupal\group\GroupMembership $membership */
      $group = $membership
        ->getGroup();
      if ($group
        ->access('update', $account)) {
        return AccessResult::allowed();
      }
    }
    return AccessResult::forbidden();
  }

  /**
   * Get the list of the existing activity types.
   */
  public function getActivityTypes() {

    // Get activity types.
    $types = \Drupal::entityTypeManager()
      ->getStorage('opigno_activity_type')
      ->loadMultiple();
    $types = array_filter($types, function ($type) {

      /** @var \Drupal\opigno_module\Entity\OpignoActivityType $type */
      return $type
        ->id() !== 'opigno_h5p';
    });
    $types = array_map(function ($type) {

      /** @var \Drupal\opigno_module\Entity\OpignoActivityType $type */
      return [
        'bundle' => $type
          ->id(),
        'name' => $type
          ->label(),
        'description' => $this
          ->getNonH5PDescription($type),
        'external_package' => FALSE,
      ];
    }, $types);

    // Get H5P libraries.

    /** @var \Drupal\h5p\H5PDrupal\H5PDrupal $interface */
    $interface = H5PDrupal::getInstance();
    $libraries = $interface
      ->loadLibraries();

    // Flatten libraries array.
    $libraries = array_map(function ($library) {
      return end($library);
    }, $libraries);

    // Filter runnable libraries.
    $libraries = array_filter($libraries, function ($library) {
      return $library->runnable == 1;
    });

    // Get library data.
    $libraries = array_map(function ($library) {
      return [
        'bundle' => 'opigno_h5p',
        'library' => $library->name,
        'name' => $library->title,
        'description' => $this
          ->getH5PDescription($library->name),
        'major_version' => $library->major_version,
        'minor_version' => $library->minor_version,
        'external_package' => FALSE,
      ];
    }, $libraries);
    $types = array_merge($types, $libraries);

    // Remove unwanted activities.
    $config = \Drupal::config('opigno_module.settings');
    $disabled = $config
      ->get('disabled_h5p');
    foreach ($disabled as $item) {
      unset($types[$item]);
    }

    // Set external package types.
    $types['opigno_scorm']['external_package'] = TRUE;
    $types['opigno_tincan']['external_package'] = TRUE;
    if (self::getPptConvertAllow()) {
      $ppt = [
        'bundle' => 'external_package_ppt',
        'name' => $this
          ->t('PPT(X) Course Presentation'),
        'description' => $this
          ->t('This activity allows creation H5P Course Presentation content imported from PowerPoint files. Slides from PowerPoint presentation will be imported as images into H5P Course Presentation slider. Allowed file extensions are ppt/pptx.'),
      ];
      if (array_key_exists('H5P.CoursePresentation', $types)) {

        // Place new item after H5P.CoursePresentation element.
        $types_updated = [];
        foreach ($types as $key => $type) {
          $types_updated[$key] = $type;
          if ($key == 'H5P.CoursePresentation') {
            $types_updated['external_package_ppt'] = $ppt;
          }
        }
        $types = $types_updated;
      }
      else {

        // Place new item at the end of the types array.
        $types['external_package_ppt'] = $ppt;
      }
    }
    $this
      ->array_unshift_assoc($types, 'external_package', [
      'bundle' => 'external_package',
      'name' => $this
        ->t('External package'),
      'description' => $this
        ->t('This activity allows to easily load training packages created externally. The following formats are supported: SCORM 1.2 and 2004, TinCan and H5P.'),
    ]);
    return new JsonResponse($types, Response::HTTP_OK);
  }

  /**
   * Returns H5P Activities Details.
   */
  public function getH5PActivitiesDetails() {
    $editor = H5PEditorUtilities::getInstance();
    $content_types = $editor->ajaxInterface
      ->getContentTypeCache();
    return $content_types;
  }

  /**
   * Returns non H5P description.
   *
   * @param \Drupal\opigno_module\Entity\OpignoActivityType $activity
   *   Activity.
   *
   * @return null|string
   *   Non H5P description.
   */
  public function getNonH5PDescription(OpignoActivityType $activity) {
    $html = NULL;
    $html .= '<p class="summary">' . $activity
      ->getSummary() . '</p>';
    $html .= '<p class="description">' . $activity
      ->getDescription() . '</p>';
    if ($image_id = $activity
      ->getImageId()) {
      if ($image = File::load($image_id)) {
        $image_url = ImageStyle::load('large')
          ->buildUrl($image
          ->getFileUri());
        $html .= '<p class="images">';
        $html .= '<img src="' . $image_url . '" alt="" />';
        $html .= '</p>';
      }
    }
    return $html;
  }

  /**
   * Returns ppt convert allow flag.
   */
  public static function getPptConvertAllow() {
    static $allow;
    if (!isset($allow)) {
      $allow = FALSE;
      $module = FALSE;
      $moduleHandler = \Drupal::service('module_handler');
      if ($moduleHandler
        ->moduleExists('h5p')) {
        $module = TRUE;
      }
      $libreoffice = FALSE;
      if (shell_exec(ExternalPackageController::getLibreOfficeBinPath() . ' --version')) {
        $libreoffice = TRUE;
      }
      $imagemagick = FALSE;
      if (shell_exec(ExternalPackageController::getImagemagickBinPath() . ' --version')) {
        $imagemagick = TRUE;
      }
      $h5p_library = FALSE;
      $editor = H5PEditorUtilities::getInstance();
      $content_types = $editor->ajaxInterface
        ->getContentTypeCache();
      if ($content_types) {
        foreach ($content_types as $activity) {
          if ($activity->machine_name == 'H5P.CoursePresentation') {
            $h5p_library = TRUE;
            break;
          }
        }
      }
      if ($module && $libreoffice && $imagemagick && $h5p_library) {
        $allow = TRUE;
      }
    }
    return $allow;
  }

  /**
   * Returns H5P description.
   */
  public function getH5PDescription($libTitle) {
    $html = NULL;
    foreach ($this->H5PActivitiesDetails as $H5PActivityDetail) {
      if ($H5PActivityDetail->machine_name == $libTitle) {
        $html .= '<p class="summary">' . $H5PActivityDetail->summary . '</p>';
        $html .= '<p class="description">' . $H5PActivityDetail->description . '</p>';
        $screenshots = json_decode($H5PActivityDetail->screenshots);
        if ($screenshots) {
          $html .= '<p class="images">';
          foreach ($screenshots as $screenshot) {
            $html .= '<img src="' . $screenshot->url . '" alt="" />';
          }
          $html .= '</p>';
        }
        break;
      }
    }
    return $html;
  }

  /**
   * Get the list of the existing activities.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   Response.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function getActivitiesList(OpignoModule $opigno_module) {
    $activities_storage = \Drupal::entityTypeManager()
      ->getStorage('opigno_activity');
    $query = $activities_storage
      ->getQuery();
    if ($opigno_module
      ->getSkillsActive()) {
      $query
        ->condition('auto_skills', 1);
      if ($opigno_module
        ->getTargetSkill() && $opigno_module
        ->getTargetSkill() > 0) {
        $target_skill = $opigno_module
          ->getTargetSkill();
        $term_storage = \Drupal::entityTypeManager()
          ->getStorage('taxonomy_term');
        $skills_tree = $term_storage
          ->loadTree('skills', $target_skill);
        $skills_ids = [];
        foreach ($skills_tree as $skill) {
          $skills_ids[] = $skill->tid;
        }
        if (!empty($skills_ids)) {
          $query
            ->condition('skills_list', $skills_ids, 'IN');
        }
      }
      $activities_ids = $query
        ->execute();
    }
    else {
      $group = $query
        ->orConditionGroup()
        ->condition('auto_skills', 0)
        ->notExists('auto_skills');
      $activities_ids = $query
        ->condition($group)
        ->execute();
    }

    // Use cache because it's too hard upload all every time.
    $hash = md5(serialize($activities_ids));
    $list = \Drupal::cache()
      ->get($hash);
    if (empty($list) && !empty($activities_ids)) {
      $activities = $activities_storage
        ->loadMultiple($activities_ids);
      $list = array_map(function ($activity) {

        /** @var \Drupal\opigno_module\Entity\OpignoActivity $activity */
        $data = [];
        $data['name'] = $activity
          ->label();
        $data['activity_id'] = $activity
          ->id();
        $data['type'] = $activity
          ->bundle();

        // If H5P content, add library info.
        if ($data['type'] === 'opigno_h5p') {
          $value = $activity
            ->get('opigno_h5p')
            ->getValue();
          if ($value && $activity
            ->get('opigno_h5p')
            ->getValue()[0]['h5p_content_id'] !== NULL) {
            $cid = $activity
              ->get('opigno_h5p')
              ->getValue()[0]['h5p_content_id'];
            if ($content = H5PContent::load($cid)) {
              $library = $content
                ->getLibrary();
              $data['library'] = $library->name;
            }
          }
        }
        return $data;
      }, $activities);
      \Drupal::cache()
        ->set($hash, $list);
    }
    elseif (!empty($list)) {
      $list = $list->data;
    }
    return new JsonResponse($list, Response::HTTP_OK);
  }

  /**
   * Add existing activity to the module.
   */
  public function addActivityToModule(OpignoModule $opigno_module, OpignoActivity $opigno_activity, $group = NULL) {
    $opigno_module_controller = \Drupal::service('opigno_module.opigno_module');
    $opigno_module_controller
      ->activitiesToModule([
      $opigno_activity,
    ], $opigno_module, $group);
    return new JsonResponse([], Response::HTTP_OK);
  }

  /**
   * Checks access for the activity update weight.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Current user.
   *
   * @return \Drupal\Core\Access\AccessResult
   *   Access result.
   */
  public function accessActivityUpdateWeight(AccountInterface $account) {
    $datas = json_decode(\Drupal::request()
      ->getContent());

    // Check that current user can edit all parent Opigno modules.

    /* @var $db_connection \Drupal\Core\Database\Connection */
    $db_connection = \Drupal::service('database');
    $omr_ids = array_map(function ($value) {
      return $value->omr_id;
    }, $datas->acitivies_weight);
    $module_ids = $db_connection
      ->select('opigno_module_relationship', 'omr')
      ->fields('omr', [
      'parent_id',
    ])
      ->condition('omr_id', $omr_ids, 'IN')
      ->groupBy('parent_id')
      ->execute()
      ->fetchCol();
    $modules = OpignoModule::loadMultiple($module_ids);
    foreach ($modules as $module) {

      /** @var \Drupal\opigno_module\Entity\OpignoModule $module */
      if (!$module
        ->access('update')) {
        return AccessResult::forbidden();
      }
    }
    return AccessResult::allowed();
  }

  /**
   * Update activity weight.
   */
  public function activityUpdateWeight(Request $request) {
    $datas = json_decode($request
      ->getContent());
    if (empty($datas->acitivies_weight)) {
      return new JsonResponse(NULL, Response::HTTP_BAD_REQUEST);
    }
    $db_connection = \Drupal::service('database');
    foreach ($datas->acitivies_weight as $value) {
      $db_connection
        ->merge('opigno_module_relationship')
        ->keys([
        'omr_id' => $value->omr_id,
      ])
        ->fields([
        'weight' => $value->weight,
      ])
        ->execute();
    }
    return new JsonResponse(NULL, Response::HTTP_OK);
  }

  /**
   * Array unshift.
   */
  public function array_unshift_assoc(&$arr, $key, $val) {
    $arr = array_reverse($arr, TRUE);
    $arr[$key] = $val;
    $arr = array_reverse($arr, TRUE);
    return $arr;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 1
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityManager protected property The entity manager.
ControllerBase::$entityTypeManager protected property The entity type manager.
ControllerBase::$formBuilder protected property The form builder. 2
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 2
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::create public static function Instantiates a new instance of this class. Overrides ContainerInjectionInterface::create 40
ControllerBase::currentUser protected function Returns the current user. 1
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityManager Deprecated protected function Retrieves the entity manager service.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 2
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 2
ControllerBase::redirect protected function Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait::redirect
ControllerBase::state protected function Returns the state storage service.
LinkGeneratorTrait::$linkGenerator protected property The link generator. 1
LinkGeneratorTrait::getLinkGenerator Deprecated protected function Returns the link generator.
LinkGeneratorTrait::l Deprecated protected function Renders a link to a route given a route name and its parameters.
LinkGeneratorTrait::setLinkGenerator Deprecated public function Sets the link generator service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
OpignoModuleManagerController::$H5PActivitiesDetails protected property
OpignoModuleManagerController::accessActivitiesOverview public function Checks access for the activities overview.
OpignoModuleManagerController::accessActivityUpdateWeight public function Checks access for the activity update weight.
OpignoModuleManagerController::accessItemForm public function Check the access for the activity create/edit form.
OpignoModuleManagerController::activitiesBankLPM public function Get activities bank for module with 'add' operation.
OpignoModuleManagerController::activityUpdateWeight public function Update activity weight.
OpignoModuleManagerController::addActivityToModule public function Add existing activity to the module.
OpignoModuleManagerController::ajaxCheckedActivities public function Ajax callback.
OpignoModuleManagerController::ajaxCheckedActivitiesAccess public function Check access.
OpignoModuleManagerController::ajaxFormEntityCallback public static function Ajax form callback.
OpignoModuleManagerController::ajaxFormEntityFormSubmit public static function Submit handler added in the form by opigno_module_form_alter().
OpignoModuleManagerController::array_unshift_assoc public function Array unshift.
OpignoModuleManagerController::getActivitiesList public function Get the list of the existing activities.
OpignoModuleManagerController::getActivityPreview public function Returns activity preview.
OpignoModuleManagerController::getActivityTypes public function Get the list of the existing activity types.
OpignoModuleManagerController::getH5PActivitiesDetails public function Returns H5P Activities Details.
OpignoModuleManagerController::getH5PDescription public function Returns H5P description.
OpignoModuleManagerController::getItemForm public function Method called when the manager needs a create or edit form.
OpignoModuleManagerController::getNonH5PDescription public function Returns non H5P description.
OpignoModuleManagerController::getPptConvertAllow public static function Returns ppt convert allow flag.
OpignoModuleManagerController::__construct public function OpignoModuleManagerController constructor.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
UrlGeneratorTrait::$urlGenerator protected property The url generator.
UrlGeneratorTrait::getUrlGenerator Deprecated protected function Returns the URL generator service.
UrlGeneratorTrait::setUrlGenerator Deprecated public function Sets the URL generator service.
UrlGeneratorTrait::url Deprecated protected function Generates a URL or path for a specific route based on the given parameters.