PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/mod/forum/mod_form.php

http://github.com/moodle/moodle
PHP | 522 lines | 374 code | 68 blank | 80 comment | 52 complexity | b48f8e6d62349a32b9b0aeae9915186d MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * @package mod_forum
  18. * @copyright Jamie Pratt <me@jamiep.org>
  19. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  20. */
  21. if (!defined('MOODLE_INTERNAL')) {
  22. die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
  23. }
  24. require_once ($CFG->dirroot.'/course/moodleform_mod.php');
  25. use core_grades\component_gradeitems;
  26. class mod_forum_mod_form extends moodleform_mod {
  27. function definition() {
  28. global $CFG, $COURSE, $DB;
  29. $mform =& $this->_form;
  30. //-------------------------------------------------------------------------------
  31. $mform->addElement('header', 'general', get_string('general', 'form'));
  32. $mform->addElement('text', 'name', get_string('forumname', 'forum'), array('size'=>'64'));
  33. if (!empty($CFG->formatstringstriptags)) {
  34. $mform->setType('name', PARAM_TEXT);
  35. } else {
  36. $mform->setType('name', PARAM_CLEANHTML);
  37. }
  38. $mform->addRule('name', null, 'required', null, 'client');
  39. $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
  40. $this->standard_intro_elements(get_string('forumintro', 'forum'));
  41. $forumtypes = forum_get_forum_types();
  42. core_collator::asort($forumtypes, core_collator::SORT_STRING);
  43. $mform->addElement('select', 'type', get_string('forumtype', 'forum'), $forumtypes);
  44. $mform->addHelpButton('type', 'forumtype', 'forum');
  45. $mform->setDefault('type', 'general');
  46. $mform->addElement('header', 'availability', get_string('availability', 'forum'));
  47. $name = get_string('duedate', 'forum');
  48. $mform->addElement('date_time_selector', 'duedate', $name, array('optional' => true));
  49. $mform->addHelpButton('duedate', 'duedate', 'forum');
  50. $name = get_string('cutoffdate', 'forum');
  51. $mform->addElement('date_time_selector', 'cutoffdate', $name, array('optional' => true));
  52. $mform->addHelpButton('cutoffdate', 'cutoffdate', 'forum');
  53. // Attachments and word count.
  54. $mform->addElement('header', 'attachmentswordcounthdr', get_string('attachmentswordcount', 'forum'));
  55. $choices = get_max_upload_sizes($CFG->maxbytes, $COURSE->maxbytes, 0, $CFG->forum_maxbytes);
  56. $choices[1] = get_string('uploadnotallowed');
  57. $mform->addElement('select', 'maxbytes', get_string('maxattachmentsize', 'forum'), $choices);
  58. $mform->addHelpButton('maxbytes', 'maxattachmentsize', 'forum');
  59. $mform->setDefault('maxbytes', $CFG->forum_maxbytes);
  60. $choices = array(
  61. 0 => 0,
  62. 1 => 1,
  63. 2 => 2,
  64. 3 => 3,
  65. 4 => 4,
  66. 5 => 5,
  67. 6 => 6,
  68. 7 => 7,
  69. 8 => 8,
  70. 9 => 9,
  71. 10 => 10,
  72. 20 => 20,
  73. 50 => 50,
  74. 100 => 100
  75. );
  76. $mform->addElement('select', 'maxattachments', get_string('maxattachments', 'forum'), $choices);
  77. $mform->addHelpButton('maxattachments', 'maxattachments', 'forum');
  78. $mform->setDefault('maxattachments', $CFG->forum_maxattachments);
  79. $mform->addElement('selectyesno', 'displaywordcount', get_string('displaywordcount', 'forum'));
  80. $mform->addHelpButton('displaywordcount', 'displaywordcount', 'forum');
  81. $mform->setDefault('displaywordcount', 0);
  82. // Subscription and tracking.
  83. $mform->addElement('header', 'subscriptionandtrackinghdr', get_string('subscriptionandtracking', 'forum'));
  84. $options = forum_get_subscriptionmode_options();
  85. $mform->addElement('select', 'forcesubscribe', get_string('subscriptionmode', 'forum'), $options);
  86. $mform->addHelpButton('forcesubscribe', 'subscriptionmode', 'forum');
  87. if (isset($CFG->forum_subscription)) {
  88. $defaultforumsubscription = $CFG->forum_subscription;
  89. } else {
  90. $defaultforumsubscription = FORUM_CHOOSESUBSCRIBE;
  91. }
  92. $mform->setDefault('forcesubscribe', $defaultforumsubscription);
  93. $options = array();
  94. $options[FORUM_TRACKING_OPTIONAL] = get_string('trackingoptional', 'forum');
  95. $options[FORUM_TRACKING_OFF] = get_string('trackingoff', 'forum');
  96. if ($CFG->forum_allowforcedreadtracking) {
  97. $options[FORUM_TRACKING_FORCED] = get_string('trackingon', 'forum');
  98. }
  99. $mform->addElement('select', 'trackingtype', get_string('trackingtype', 'forum'), $options);
  100. $mform->addHelpButton('trackingtype', 'trackingtype', 'forum');
  101. $default = $CFG->forum_trackingtype;
  102. if ((!$CFG->forum_allowforcedreadtracking) && ($default == FORUM_TRACKING_FORCED)) {
  103. $default = FORUM_TRACKING_OPTIONAL;
  104. }
  105. $mform->setDefault('trackingtype', $default);
  106. if ($CFG->enablerssfeeds && isset($CFG->forum_enablerssfeeds) && $CFG->forum_enablerssfeeds) {
  107. //-------------------------------------------------------------------------------
  108. $mform->addElement('header', 'rssheader', get_string('rss'));
  109. $choices = array();
  110. $choices[0] = get_string('none');
  111. $choices[1] = get_string('discussions', 'forum');
  112. $choices[2] = get_string('posts', 'forum');
  113. $mform->addElement('select', 'rsstype', get_string('rsstype'), $choices);
  114. $mform->addHelpButton('rsstype', 'rsstype', 'forum');
  115. if (isset($CFG->forum_rsstype)) {
  116. $mform->setDefault('rsstype', $CFG->forum_rsstype);
  117. }
  118. $choices = array();
  119. $choices[0] = '0';
  120. $choices[1] = '1';
  121. $choices[2] = '2';
  122. $choices[3] = '3';
  123. $choices[4] = '4';
  124. $choices[5] = '5';
  125. $choices[10] = '10';
  126. $choices[15] = '15';
  127. $choices[20] = '20';
  128. $choices[25] = '25';
  129. $choices[30] = '30';
  130. $choices[40] = '40';
  131. $choices[50] = '50';
  132. $mform->addElement('select', 'rssarticles', get_string('rssarticles'), $choices);
  133. $mform->addHelpButton('rssarticles', 'rssarticles', 'forum');
  134. $mform->hideIf('rssarticles', 'rsstype', 'eq', '0');
  135. if (isset($CFG->forum_rssarticles)) {
  136. $mform->setDefault('rssarticles', $CFG->forum_rssarticles);
  137. }
  138. }
  139. $mform->addElement('header', 'discussionlocking', get_string('discussionlockingheader', 'forum'));
  140. $options = [
  141. 0 => get_string('discussionlockingdisabled', 'forum'),
  142. 1 * DAYSECS => get_string('numday', 'core', 1),
  143. 1 * WEEKSECS => get_string('numweek', 'core', 1),
  144. 2 * WEEKSECS => get_string('numweeks', 'core', 2),
  145. 30 * DAYSECS => get_string('nummonth', 'core', 1),
  146. 60 * DAYSECS => get_string('nummonths', 'core', 2),
  147. 90 * DAYSECS => get_string('nummonths', 'core', 3),
  148. 180 * DAYSECS => get_string('nummonths', 'core', 6),
  149. 1 * YEARSECS => get_string('numyear', 'core', 1),
  150. ];
  151. $mform->addElement('select', 'lockdiscussionafter', get_string('lockdiscussionafter', 'forum'), $options);
  152. $mform->addHelpButton('lockdiscussionafter', 'lockdiscussionafter', 'forum');
  153. $mform->disabledIf('lockdiscussionafter', 'type', 'eq', 'single');
  154. //-------------------------------------------------------------------------------
  155. $mform->addElement('header', 'blockafterheader', get_string('blockafter', 'forum'));
  156. $options = array();
  157. $options[0] = get_string('blockperioddisabled','forum');
  158. $options[60*60*24] = '1 '.get_string('day');
  159. $options[60*60*24*2] = '2 '.get_string('days');
  160. $options[60*60*24*3] = '3 '.get_string('days');
  161. $options[60*60*24*4] = '4 '.get_string('days');
  162. $options[60*60*24*5] = '5 '.get_string('days');
  163. $options[60*60*24*6] = '6 '.get_string('days');
  164. $options[60*60*24*7] = '1 '.get_string('week');
  165. $mform->addElement('select', 'blockperiod', get_string('blockperiod', 'forum'), $options);
  166. $mform->addHelpButton('blockperiod', 'blockperiod', 'forum');
  167. $mform->addElement('text', 'blockafter', get_string('blockafter', 'forum'));
  168. $mform->setType('blockafter', PARAM_INT);
  169. $mform->setDefault('blockafter', '0');
  170. $mform->addRule('blockafter', null, 'numeric', null, 'client');
  171. $mform->addHelpButton('blockafter', 'blockafter', 'forum');
  172. $mform->hideIf('blockafter', 'blockperiod', 'eq', 0);
  173. $mform->addElement('text', 'warnafter', get_string('warnafter', 'forum'));
  174. $mform->setType('warnafter', PARAM_INT);
  175. $mform->setDefault('warnafter', '0');
  176. $mform->addRule('warnafter', null, 'numeric', null, 'client');
  177. $mform->addHelpButton('warnafter', 'warnafter', 'forum');
  178. $mform->hideIf('warnafter', 'blockperiod', 'eq', 0);
  179. $coursecontext = context_course::instance($COURSE->id);
  180. // To be removed (deprecated) with MDL-67526.
  181. plagiarism_get_form_elements_module($mform, $coursecontext, 'mod_forum');
  182. //-------------------------------------------------------------------------------
  183. // Add the whole forum grading options.
  184. $this->add_forum_grade_settings($mform, 'forum');
  185. $this->standard_coursemodule_elements();
  186. //-------------------------------------------------------------------------------
  187. // buttons
  188. $this->add_action_buttons();
  189. }
  190. /**
  191. * Add the whole forum grade settings to the mform.
  192. *
  193. * @param \mform $mform
  194. * @param string $itemname
  195. */
  196. private function add_forum_grade_settings($mform, string $itemname) {
  197. global $COURSE;
  198. $component = "mod_{$this->_modname}";
  199. $defaultgradingvalue = 0;
  200. $itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);
  201. $gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade');
  202. $gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradecat');
  203. $gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradepass');
  204. $sendstudentnotificationsfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber,
  205. 'sendstudentnotifications');
  206. // The advancedgradingmethod is different in that it is suffixed with an area name... which is not the
  207. // itemnumber.
  208. $methodfieldname = "advancedgradingmethod_{$itemname}";
  209. $headername = "{$gradefieldname}_header";
  210. $mform->addElement('header', $headername, get_string("grade_{$itemname}_header", $component));
  211. $isupdate = !empty($this->_cm);
  212. $gradeoptions = [
  213. 'isupdate' => $isupdate,
  214. 'currentgrade' => false,
  215. 'hasgrades' => false,
  216. 'canrescale' => false,
  217. 'useratings' => false,
  218. ];
  219. if ($isupdate) {
  220. $gradeitem = grade_item::fetch([
  221. 'itemtype' => 'mod',
  222. 'itemmodule' => $this->_cm->modname,
  223. 'iteminstance' => $this->_cm->instance,
  224. 'itemnumber' => $itemnumber,
  225. 'courseid' => $COURSE->id,
  226. ]);
  227. if ($gradeitem) {
  228. $gradeoptions['currentgrade'] = $gradeitem->grademax;
  229. $gradeoptions['currentgradetype'] = $gradeitem->gradetype;
  230. $gradeoptions['currentscaleid'] = $gradeitem->scaleid;
  231. $gradeoptions['hasgrades'] = $gradeitem->has_grades();
  232. }
  233. }
  234. $mform->addElement(
  235. 'modgrade',
  236. $gradefieldname,
  237. get_string("{$gradefieldname}_title", $component),
  238. $gradeoptions
  239. );
  240. $mform->addHelpButton($gradefieldname, 'modgrade', 'grades');
  241. $mform->setDefault($gradefieldname, $defaultgradingvalue);
  242. if (!empty($this->current->_advancedgradingdata['methods']) && !empty($this->current->_advancedgradingdata['areas'])) {
  243. $areadata = $this->current->_advancedgradingdata['areas'][$itemname];
  244. $mform->addElement(
  245. 'select',
  246. $methodfieldname,
  247. get_string('gradingmethod', 'core_grading'),
  248. $this->current->_advancedgradingdata['methods']
  249. );
  250. $mform->addHelpButton($methodfieldname, 'gradingmethod', 'core_grading');
  251. $mform->hideIf($methodfieldname, "{$gradefieldname}[modgrade_type]", 'eq', 'none');
  252. }
  253. // Grade category.
  254. $mform->addElement(
  255. 'select',
  256. $gradecatfieldname,
  257. get_string('gradecategoryonmodform', 'grades'),
  258. grade_get_categories_menu($COURSE->id, $this->_outcomesused)
  259. );
  260. $mform->addHelpButton($gradecatfieldname, 'gradecategoryonmodform', 'grades');
  261. $mform->hideIf($gradecatfieldname, "{$gradefieldname}[modgrade_type]", 'eq', 'none');
  262. // Grade to pass.
  263. $mform->addElement('text', $gradepassfieldname, get_string('gradepass', 'grades'));
  264. $mform->addHelpButton($gradepassfieldname, 'gradepass', 'grades');
  265. $mform->setDefault($gradepassfieldname, '');
  266. $mform->setType($gradepassfieldname, PARAM_RAW);
  267. $mform->hideIf($gradepassfieldname, "{$gradefieldname}[modgrade_type]", 'eq', 'none');
  268. $mform->addElement(
  269. 'selectyesno',
  270. $sendstudentnotificationsfieldname,
  271. get_string('sendstudentnotificationsdefault', 'forum')
  272. );
  273. $mform->addHelpButton($sendstudentnotificationsfieldname, 'sendstudentnotificationsdefault', 'forum');
  274. $mform->hideIf($sendstudentnotificationsfieldname, "{$gradefieldname}[modgrade_type]", 'eq', 'none');
  275. }
  276. function definition_after_data() {
  277. parent::definition_after_data();
  278. $mform =& $this->_form;
  279. $type =& $mform->getElement('type');
  280. $typevalue = $mform->getElementValue('type');
  281. //we don't want to have these appear as possible selections in the form but
  282. //we want the form to display them if they are set.
  283. if ($typevalue[0]=='news') {
  284. $type->addOption(get_string('namenews', 'forum'), 'news');
  285. $mform->addHelpButton('type', 'namenews', 'forum');
  286. $type->freeze();
  287. $type->setPersistantFreeze(true);
  288. }
  289. if ($typevalue[0]=='social') {
  290. $type->addOption(get_string('namesocial', 'forum'), 'social');
  291. $type->freeze();
  292. $type->setPersistantFreeze(true);
  293. }
  294. }
  295. public function validation($data, $files) {
  296. $errors = parent::validation($data, $files);
  297. if ($data['duedate'] && $data['cutoffdate']) {
  298. if ($data['duedate'] > $data['cutoffdate']) {
  299. $errors['cutoffdate'] = get_string('cutoffdatevalidation', 'forum');
  300. }
  301. }
  302. $this->validation_forum_grade($data, $files, $errors);
  303. return $errors;
  304. }
  305. /**
  306. * Handle definition after data for grade settings.
  307. *
  308. * @param array $data
  309. * @param array $files
  310. * @param array $errors
  311. */
  312. private function validation_forum_grade(array $data, array $files, array $errors) {
  313. global $COURSE;
  314. $mform =& $this->_form;
  315. $component = "mod_forum";
  316. $itemname = 'forum';
  317. $itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);
  318. $gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade');
  319. $gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade');
  320. $gradeitem = grade_item::fetch([
  321. 'itemtype' => 'mod',
  322. 'itemmodule' => $data['modulename'],
  323. 'iteminstance' => $data['instance'],
  324. 'itemnumber' => $itemnumber,
  325. 'courseid' => $COURSE->id,
  326. ]);
  327. if ($mform->elementExists('cmidnumber') && $this->_cm) {
  328. if (!grade_verify_idnumber($data['cmidnumber'], $COURSE->id, $gradeitem, $this->_cm)) {
  329. $errors['cmidnumber'] = get_string('idnumbertaken');
  330. }
  331. }
  332. // Check that the grade pass is a valid number.
  333. $gradepassvalid = false;
  334. if (isset($data[$gradepassfieldname])) {
  335. if (unformat_float($data[$gradepassfieldname], true) === false) {
  336. $errors[$gradepassfieldname] = get_string('err_numeric', 'form');
  337. } else {
  338. $gradepassvalid = true;
  339. }
  340. }
  341. // Grade to pass: ensure that the grade to pass is valid for points and scales.
  342. // If we are working with a scale, convert into a positive number for validation.
  343. if ($gradepassvalid && isset($data[$gradepassfieldname]) && (!empty($data[$gradefieldname]))) {
  344. $grade = $data[$gradefieldname];
  345. if (unformat_float($data[$gradepassfieldname]) > $grade) {
  346. $errors[$gradepassfieldname] = get_string('gradepassgreaterthangrade', 'grades', $grade);
  347. }
  348. }
  349. }
  350. function data_preprocessing(&$default_values) {
  351. parent::data_preprocessing($default_values);
  352. // Set up the completion checkboxes which aren't part of standard data.
  353. // We also make the default value (if you turn on the checkbox) for those
  354. // numbers to be 1, this will not apply unless checkbox is ticked.
  355. $default_values['completiondiscussionsenabled']=
  356. !empty($default_values['completiondiscussions']) ? 1 : 0;
  357. if (empty($default_values['completiondiscussions'])) {
  358. $default_values['completiondiscussions']=1;
  359. }
  360. $default_values['completionrepliesenabled']=
  361. !empty($default_values['completionreplies']) ? 1 : 0;
  362. if (empty($default_values['completionreplies'])) {
  363. $default_values['completionreplies']=1;
  364. }
  365. // Tick by default if Add mode or if completion posts settings is set to 1 or more.
  366. if (empty($this->_instance) || !empty($default_values['completionposts'])) {
  367. $default_values['completionpostsenabled'] = 1;
  368. } else {
  369. $default_values['completionpostsenabled'] = 0;
  370. }
  371. if (empty($default_values['completionposts'])) {
  372. $default_values['completionposts']=1;
  373. }
  374. }
  375. /**
  376. * Add custom completion rules.
  377. *
  378. * @return array Array of string IDs of added items, empty array if none
  379. */
  380. public function add_completion_rules() {
  381. $mform =& $this->_form;
  382. $group=array();
  383. $group[] =& $mform->createElement('checkbox', 'completionpostsenabled', '', get_string('completionposts','forum'));
  384. $group[] =& $mform->createElement('text', 'completionposts', '', array('size'=>3));
  385. $mform->setType('completionposts',PARAM_INT);
  386. $mform->addGroup($group, 'completionpostsgroup', get_string('completionpostsgroup','forum'), array(' '), false);
  387. $mform->disabledIf('completionposts','completionpostsenabled','notchecked');
  388. $group=array();
  389. $group[] =& $mform->createElement('checkbox', 'completiondiscussionsenabled', '', get_string('completiondiscussions','forum'));
  390. $group[] =& $mform->createElement('text', 'completiondiscussions', '', array('size'=>3));
  391. $mform->setType('completiondiscussions',PARAM_INT);
  392. $mform->addGroup($group, 'completiondiscussionsgroup', get_string('completiondiscussionsgroup','forum'), array(' '), false);
  393. $mform->disabledIf('completiondiscussions','completiondiscussionsenabled','notchecked');
  394. $group=array();
  395. $group[] =& $mform->createElement('checkbox', 'completionrepliesenabled', '', get_string('completionreplies','forum'));
  396. $group[] =& $mform->createElement('text', 'completionreplies', '', array('size'=>3));
  397. $mform->setType('completionreplies',PARAM_INT);
  398. $mform->addGroup($group, 'completionrepliesgroup', get_string('completionrepliesgroup','forum'), array(' '), false);
  399. $mform->disabledIf('completionreplies','completionrepliesenabled','notchecked');
  400. return array('completiondiscussionsgroup','completionrepliesgroup','completionpostsgroup');
  401. }
  402. function completion_rule_enabled($data) {
  403. return (!empty($data['completiondiscussionsenabled']) && $data['completiondiscussions']!=0) ||
  404. (!empty($data['completionrepliesenabled']) && $data['completionreplies']!=0) ||
  405. (!empty($data['completionpostsenabled']) && $data['completionposts']!=0);
  406. }
  407. /**
  408. * Return submitted data if properly submitted or returns NULL if validation fails or
  409. * if there is no submitted data.
  410. *
  411. * Do not override this method, override data_postprocessing() instead.
  412. *
  413. * @return object submitted data; NULL if not valid or not submitted or cancelled
  414. */
  415. public function get_data() {
  416. $data = parent::get_data();
  417. if ($data) {
  418. $itemname = 'forum';
  419. $component = 'mod_forum';
  420. $gradepassfieldname = component_gradeitems::get_field_name_for_itemname($component, $itemname, 'gradepass');
  421. // Convert the grade pass value - we may be using a language which uses commas,
  422. // rather than decimal points, in numbers. These need to be converted so that
  423. // they can be added to the DB.
  424. if (isset($data->{$gradepassfieldname})) {
  425. $data->{$gradepassfieldname} = unformat_float($data->{$gradepassfieldname});
  426. }
  427. }
  428. return $data;
  429. }
  430. /**
  431. * Allows module to modify the data returned by form get_data().
  432. * This method is also called in the bulk activity completion form.
  433. *
  434. * Only available on moodleform_mod.
  435. *
  436. * @param stdClass $data the form data to be modified.
  437. */
  438. public function data_postprocessing($data) {
  439. parent::data_postprocessing($data);
  440. // Turn off completion settings if the checkboxes aren't ticked
  441. if (!empty($data->completionunlocked)) {
  442. $autocompletion = !empty($data->completion) && $data->completion==COMPLETION_TRACKING_AUTOMATIC;
  443. if (empty($data->completiondiscussionsenabled) || !$autocompletion) {
  444. $data->completiondiscussions = 0;
  445. }
  446. if (empty($data->completionrepliesenabled) || !$autocompletion) {
  447. $data->completionreplies = 0;
  448. }
  449. if (empty($data->completionpostsenabled) || !$autocompletion) {
  450. $data->completionposts = 0;
  451. }
  452. }
  453. }
  454. }