clockworksms.lib.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. <?php
  2. /* For licensing terms, see /vendor/license.txt */
  3. /**
  4. * Class Clockworksms
  5. * This script handles incoming SMS information, process it and sends an SMS if everything is right
  6. *
  7. * @package chamilo.plugin.clockworksms.lib
  8. * @author Imanol Losada <imanol.losada@beeznest.com>
  9. *
  10. * Clockworksms-Chamilo connector class
  11. */
  12. class Clockworksms implements SmsPluginLibraryInterface
  13. {
  14. public $apiKey;
  15. public $api;
  16. public $plugin_enabled = false;
  17. /**
  18. * Constructor (generates a connection to the API)
  19. * @param string Clockworksms API key required to use the plugin
  20. */
  21. public function __construct($apiKey = null)
  22. {
  23. $plugin = ClockworksmsPlugin::create();
  24. $clockWorkSMSPlugin = $plugin->get('tool_enable');
  25. if (empty($apiKey)) {
  26. $clockWorkSMSApiKey = $plugin->get('api_key');
  27. } else {
  28. $clockWorkSMSApiKey = $apiKey;
  29. }
  30. $this->table = Database::get_main_table('user_field_values');
  31. if ($clockWorkSMSPlugin == true) {
  32. $this->apiKey = $clockWorkSMSApiKey;
  33. // Setting Clockworksms api
  34. if (!defined('CONFIG_SECURITY_API_KEY')) {
  35. define('CONFIG_SECURITY_API_KEY', $this->apiKey);
  36. }
  37. $trimmedApiKey = trim(CONFIG_SECURITY_API_KEY);
  38. if (!empty($trimmedApiKey)) {
  39. $this->api = new Clockwork(CONFIG_SECURITY_API_KEY);
  40. } else {
  41. $this->api = new Clockwork(' ');
  42. $recipient_name = api_get_person_name(
  43. api_get_setting('administratorName'),
  44. api_get_setting('administratorSurname'),
  45. null,
  46. PERSON_NAME_EMAIL_ADDRESS
  47. );
  48. $email_form = api_get_setting('emailAdministrator');
  49. $emailsubject = 'Clockworksms error';
  50. $emailbody = 'Key cannot be blank';
  51. $sender_name = $recipient_name;
  52. $email_admin = $email_form;
  53. api_mail_html(
  54. $recipient_name,
  55. $email_form,
  56. $emailsubject,
  57. $emailbody,
  58. $sender_name,
  59. $email_admin
  60. );
  61. }
  62. $this->plugin_enabled = true;
  63. }
  64. }
  65. /**
  66. * @inheritdoc
  67. */
  68. public function getMobilePhoneNumberById($userId)
  69. {
  70. $mobilePhoneNumberExtraField = new ExtraField('user');
  71. $mobilePhoneNumberExtraField = $mobilePhoneNumberExtraField->get_handler_field_info_by_field_variable('mobile_phone_number');
  72. $mobilePhoneNumberExtraFieldValue = new ExtraFieldValue('user');
  73. $mobilePhoneNumberExtraFieldValue = $mobilePhoneNumberExtraFieldValue->get_values_by_handler_and_field_id($userId, $mobilePhoneNumberExtraField['id']);
  74. return $mobilePhoneNumberExtraFieldValue['value'];
  75. }
  76. /**
  77. * send (sends an SMS to the user)
  78. * @param array Data needed to send the SMS. It is mandatory to include the
  79. * 'smsType' and 'userId' (or 'mobilePhoneNumber') fields at least.
  80. * More data may be neccesary depending on the message type
  81. * Example: $additional_parameters = array(
  82. * 'smsType' => EXAMPLE_SMS_TYPE,
  83. * 'userId' => $userId,
  84. * 'moreData' => $moreData
  85. * );
  86. * @return void
  87. */
  88. public function send($additionalParameters)
  89. {
  90. $trimmedKey = trim(CONFIG_SECURITY_API_KEY);
  91. if (!empty($trimmedKey)) {
  92. $phoneExists = array_key_exists("mobilePhoneNumber", $additionalParameters);
  93. $to = $phoneExists ? $additionalParameters['mobilePhoneNumber'] : $this->getMobilePhoneNumberById($additionalParameters['userId']);
  94. $message = array(
  95. "to" => $to,
  96. "message" => $this->getSms($additionalParameters)
  97. );
  98. if (!empty($message['message'])) {
  99. $result = $this->api->send($message);
  100. // Commented for future message logging / tracking purposes
  101. /*if ($result["success"]) {
  102. echo "Message sent - ID: " . $result["id"];
  103. } else {
  104. echo "Message failed - Error: " . $result["error_message"];
  105. }*/
  106. }
  107. }
  108. }
  109. /**
  110. * buildSms (builds an SMS from a template and data)
  111. * @param object ClockworksmsPlugin object
  112. * @param object Template object
  113. * @param string Template file name
  114. * @param string Text key from lang file
  115. * @param array Data to fill message variables (if any)
  116. * @return object Template object with message property updated
  117. */
  118. public function buildSms($plugin, $tpl, $templateName, $messageKey, $parameters = null)
  119. {
  120. $result = Database::select(
  121. 'selected_value',
  122. 'settings_current',
  123. array(
  124. 'where'=> array('variable = ?' => array('clockworksms_message'.$messageKey))
  125. )
  126. );
  127. //if (empty($result)) {
  128. if (0) {
  129. $tpl->assign('message', '');
  130. } else {
  131. $templatePath = 'clockworksms/sms_templates/';
  132. $content = $tpl->fetch($templatePath.$templateName);
  133. $message = $plugin->get_lang($messageKey);
  134. if ($parameters !== null) {
  135. $message = vsprintf($message, $parameters);
  136. }
  137. $tpl->assign('message', $message);
  138. }
  139. return $tpl->params['message'];
  140. }
  141. /**
  142. * getSms (returns an SMS message depending of its type)
  143. * @param array Data needed to send the SMS. It is mandatory to include the
  144. * 'smsType' and 'userId' (or 'mobilePhoneNumber') fields at least.
  145. * More data may be neccesary depending on the message type
  146. * Example: $additional_parameters = array(
  147. * 'smsType' => EXAMPLE_SMS_TYPE,
  148. * 'userId' => $userId,
  149. * 'moreData' => $moreData
  150. * );
  151. * @return string A ready to be sent SMS
  152. */
  153. public function getSms($additionalParameters)
  154. {
  155. $plugin = ClockworksmsPlugin::create();
  156. $tool_name = $plugin->get_lang('plugin_title');
  157. $tpl = new Template($tool_name);
  158. switch ($additionalParameters['smsType']) {
  159. case SmsPlugin::WELCOME_LOGIN_PASSWORD:
  160. $userInfo = api_get_user_info($additionalParameters['userId']);
  161. return $this->buildSms(
  162. $plugin,
  163. $tpl,
  164. 'welcome_login_password.tpl',
  165. 'WelcomeXLoginXPasswordX',
  166. array(
  167. api_get_setting('siteName'),
  168. $userInfo['username'],
  169. $additionalParameters['password']
  170. )
  171. );
  172. break;
  173. case SmsPlugin::NEW_FILE_SHARED_COURSE_BY:
  174. return $this->buildSms(
  175. $plugin,
  176. $tpl,
  177. 'new_file_shared_course_by.tpl',
  178. 'XNewFileSharedCourseXByX',
  179. array(
  180. api_get_setting('siteName'),
  181. $additionalParameters['courseTitle'],
  182. $additionalParameters['userUsername']
  183. )
  184. );
  185. break;
  186. case SmsPlugin::ACCOUNT_APPROVED_CONNECT:
  187. return $this->buildSms(
  188. $plugin,
  189. $tpl,
  190. 'account_approved_connect.tpl',
  191. 'XAccountApprovedConnectX',
  192. array(
  193. api_get_setting('siteName'),
  194. $tpl->params['_p']['web']
  195. )
  196. );
  197. break;
  198. case SmsPlugin::NEW_COURSE_BEEN_CREATED:
  199. return $this->buildSms(
  200. $plugin,
  201. $tpl,
  202. 'new_course_been_created.tpl',
  203. 'XNewCourseXBeenCreatedX',
  204. array(
  205. api_get_setting('siteName'),
  206. $additionalParameters['courseName'],
  207. $additionalParameters['creatorUsername']
  208. )
  209. );
  210. break;
  211. case SmsPlugin::NEW_USER_SUBSCRIBED_COURSE:
  212. return $this->buildSms(
  213. $plugin,
  214. $tpl,
  215. 'new_user_subscribed_course.tpl',
  216. 'XNewUserXSubscribedCourseX',
  217. array(
  218. api_get_setting('siteName'),
  219. $additionalParameters['userUsername'],
  220. $additionalParameters['courseCode']
  221. )
  222. );
  223. break;
  224. case SmsPlugin::NEW_COURSE_SUGGESTED_TEACHER:
  225. return $this->buildSms(
  226. $plugin,
  227. $tpl,
  228. 'new_course_suggested_teacher.tpl',
  229. 'XNewCourseSuggestedTeacherX',
  230. array(
  231. api_get_setting('siteName'),
  232. $additionalParameters['userUsername']
  233. )
  234. );
  235. break;
  236. case SmsPlugin::COURSE_OPENING_REQUEST_CODE_REGISTERED:
  237. return $this->buildSms(
  238. $plugin,
  239. $tpl,
  240. 'course_opening_request_code_registered.tpl',
  241. 'XCourseOpeningRequestCodeXRegistered',
  242. array(
  243. api_get_setting('siteName'),
  244. $additionalParameters['courseCode']
  245. )
  246. );
  247. break;
  248. case SmsPlugin::COURSE_OPENING_REQUEST_CODE_APPROVED:
  249. return $this->buildSms(
  250. $plugin,
  251. $tpl,
  252. 'course_opening_request_course_code_approved.tpl',
  253. 'XCourseOpeningRequestCourseCodeXApproved',
  254. array(
  255. api_get_setting('siteName'),
  256. $additionalParameters['courseCode']
  257. )
  258. );
  259. break;
  260. case SmsPlugin::COURSE_OPENING_REQUEST_CODE_REJECTED:
  261. return $this->buildSms(
  262. $plugin,
  263. $tpl,
  264. 'request_open_course_code_rejected.tpl',
  265. 'XRequestOpenCourseCodeXReject',
  266. array(
  267. api_get_setting('siteName'),
  268. $additionalParameters['courseCode']
  269. )
  270. );
  271. break;
  272. case SmsPlugin::COURSE_OPENING_REQUEST_CODE:
  273. return $this->buildSms(
  274. $plugin,
  275. $tpl,
  276. 'course_opening_request_course_code.tpl',
  277. 'XCourseOpeningRequestCourseCodeX',
  278. array(
  279. api_get_setting('siteName'),
  280. $additionalParameters['courseCode']
  281. )
  282. );
  283. break;
  284. case SmsPlugin::BEEN_SUBSCRIBED_COURSE:
  285. return $this->buildSms(
  286. $plugin,
  287. $tpl,
  288. 'been_subscribed_course.tpl',
  289. 'XBeenSubscribedCourseX',
  290. array(
  291. api_get_setting('siteName'),
  292. $additionalParameters['courseTitle']
  293. )
  294. );
  295. break;
  296. case SmsPlugin::ASSIGNMENT_BEEN_CREATED_COURSE:
  297. return $this->buildSms(
  298. $plugin,
  299. $tpl,
  300. 'assignment_been_created_course.tpl',
  301. 'XAssignmentBeenCreatedCourseX',
  302. array(
  303. api_get_setting('siteName'),
  304. $additionalParameters['courseTitle']
  305. )
  306. );
  307. break;
  308. // Message types to be implemented. Fill the array parameter with arguments.
  309. /*case SmsPlugin::ACCOUNT_CREATED_UPDATED_LOGIN_PASSWORD:
  310. return $this->buildSms(
  311. $plugin,
  312. $tpl,
  313. 'account_created_updated_login_password.tpl',
  314. 'XAccountCreatedUpdatedLoginXPasswordX',
  315. array(
  316. api_get_setting('siteName')
  317. )
  318. );
  319. break;*/
  320. /*case SmsPlugin::PASSWORD_UPDATED_LOGIN_PASSWORD:
  321. return $this->buildSms(
  322. $plugin,
  323. $tpl,
  324. 'password_updated_login_password.tpl',
  325. 'XPasswordUpdatedLoginXPasswordX',
  326. array(
  327. api_get_setting('siteName')
  328. )
  329. );
  330. break;*/
  331. /*case SmsPlugin::REQUESTED_PASSWORD_CHANGE:
  332. return $this->buildSms(
  333. $plugin,
  334. $tpl,
  335. 'requested_password_change.tpl',
  336. 'XPasswordUpdatedLoginXPasswordX',
  337. array(
  338. api_get_setting('siteName')
  339. )
  340. );
  341. break;*/
  342. /*case SmsPlugin::RECEIVED_NEW_PERSONAL_MESSAGES:
  343. return $this->buildSms(
  344. $plugin,
  345. $tpl,
  346. 'received_new_personal_messages.tpl',
  347. 'XReceivedNewPersonalMessages',
  348. array(
  349. api_get_setting('siteName')
  350. )
  351. );
  352. break;*/
  353. /*case SmsPlugin::NEW_USER_PENDING_APPROVAL:
  354. return $this->buildSms(
  355. $plugin,
  356. $tpl,
  357. 'new_user_pending_approval.tpl',
  358. 'XNewUserXPendingApproval',
  359. array(
  360. api_get_setting('siteName')
  361. )
  362. );
  363. break;*/
  364. /*case SmsPlugin::POSTED_FORUM_COURSE:
  365. return $this->buildSms(
  366. $plugin,
  367. $tpl,
  368. 'posted_forum_course.tpl',
  369. 'XXPostedForumXCourseX',
  370. array(
  371. api_get_setting('siteName')
  372. )
  373. );
  374. break;*/
  375. /*case SmsPlugin::CHECK_EMAIL_CONNECT_MORE_INFO:
  376. return $this->buildSms(
  377. $plugin,
  378. $tpl,
  379. 'check_email_connect_more_info.tpl',
  380. 'XXXCheckEmailConnectMoreInfo',
  381. array(
  382. api_get_setting('siteName')
  383. )
  384. );
  385. break;*/
  386. /*case SmsPlugin::STUDENT_ANSWERED_TEST:
  387. return $this->buildSms(
  388. $plugin,
  389. $tpl,
  390. 'student_answered_test.tpl',
  391. 'XXStudentXAnsweredTestX',
  392. array(
  393. api_get_setting('siteName')
  394. )
  395. );
  396. break;*/
  397. /*case SmsPlugin::STUDENT_ANSWERED_TEST_OPEN_QUESTION:
  398. return $this->buildSms(
  399. $plugin,
  400. $tpl,
  401. 'student_answered_test_open_question.tpl',
  402. 'XXStudentXAnsweredTestXOpenQuestion',
  403. array(
  404. api_get_setting('siteName')
  405. )
  406. );
  407. break;*/
  408. /*case SmsPlugin::STUDENT_ANSWERED_TEST_VOICE_QUESTION:
  409. return $this->buildSms(
  410. $plugin,
  411. $tpl,
  412. 'student_answered_test_voice_question.tpl',
  413. 'XXStudentXAnsweredTestXVoiceQuestion',
  414. array(
  415. api_get_setting('siteName')
  416. )
  417. );
  418. break;*/
  419. /*case SmsPlugin::ANSWER_OPEN_QUESTION_TEST_REVIEWED:
  420. return $this->buildSms(
  421. $plugin,
  422. $tpl,
  423. 'answer_open_question_test_reviewed.tpl',
  424. 'XXAnswerOpenQuestionTestXReviewed',
  425. array(
  426. api_get_setting('siteName')
  427. )
  428. );
  429. break;*/
  430. /*case SmsPlugin::NEW_THREAD_STARTED_FORUM:
  431. return $this->buildSms(
  432. $plugin,
  433. $tpl,
  434. 'new_thread_started_forum.tpl',
  435. 'XXNewThreadXStartedForumX',
  436. array(
  437. api_get_setting('siteName')
  438. )
  439. );
  440. break;*/
  441. /*case SmsPlugin::NEW_ANSWER_POSTED_FORUM:
  442. return $this->buildSms(
  443. $plugin,
  444. $tpl,
  445. 'new_answer_posted_forum.tpl',
  446. 'XXNewAnswerPostedXForumX',
  447. array(
  448. api_get_setting('siteName')
  449. )
  450. );
  451. break;*/
  452. /*case SmsPlugin::NEW_SYSTEM_ANNOUNCEMENT_ADDED:
  453. return $this->buildSms(
  454. $plugin,
  455. $tpl,
  456. 'new_system_announcement_added.tpl',
  457. 'XXNewSystemAnnouncementAdded',
  458. array(
  459. api_get_setting('siteName')
  460. )
  461. );
  462. break;*/
  463. /*case SmsPlugin::TEST_NEW_SYSTEM_ANNOUNCEMENT_ADDED:
  464. return $this->buildSms(
  465. $plugin,
  466. $tpl,
  467. 'test_new_system_announcement_added.tpl',
  468. 'XTestXNewSystemAnnouncementAdded',
  469. array(
  470. api_get_setting('siteName')
  471. )
  472. );
  473. break;*/
  474. /*case SmsPlugin::SYSTEM_ANNOUNCEMENT_UPDATE:
  475. return $this->buildSms(
  476. $plugin,
  477. $tpl,
  478. 'system_announcement_update.tpl',
  479. 'XXSystemAnnouncementUpdate',
  480. array(
  481. api_get_setting('siteName')
  482. )
  483. );
  484. break;*/
  485. /*case SmsPlugin::TEST_SYSTEM_ANNOUNCEMENT_UPDATE:
  486. return $this->buildSms(
  487. $plugin,
  488. $tpl,
  489. 'test_system_announcement_update.tpl',
  490. 'XXSystemAnnouncementUpdate',
  491. array(
  492. api_get_setting('siteName')
  493. )
  494. );
  495. break;*/
  496. /*case SmsPlugin::USER_UPLOADED_ASSIGNMENT_COURSE_STUDENT_SUBMITS_PAPER:
  497. return $this->buildSms(
  498. $plugin,
  499. $tpl,
  500. 'user_uploaded_assignment_course_student_submits_paper.tpl',
  501. 'XUserXUploadedAssignmentXCourseXStudentSubmitsPaper',
  502. array(
  503. api_get_setting('siteName')
  504. )
  505. );
  506. break;*/
  507. /*case SmsPlugin::USER_UPLOADED_ASSIGNMENT_CHECK_STUDENT_SUBMITS_PAPER:
  508. return $this->buildSms(
  509. $plugin,
  510. $tpl,
  511. 'user_uploaded_assignment_check_student_submits_paper.tpl',
  512. 'XUserXUploadedAssignmentXCheckXStudentSubmitsPaper',
  513. array(
  514. api_get_setting('siteName')
  515. )
  516. );
  517. break;*/
  518. /*case SmsPlugin::USER_UPLOADED_ASSIGNMENT_COURSE:
  519. return $this->buildSms(
  520. $plugin,
  521. $tpl,
  522. 'user_uploaded_assignment_course.tpl',
  523. 'XUserXUploadedAssignmentXCourseX',
  524. array(
  525. api_get_setting('siteName')
  526. )
  527. );
  528. break;*/
  529. /*case SmsPlugin::USER_UPLOADED_ASSIGNMENT_CHECK:
  530. return $this->buildSms(
  531. $plugin,
  532. $tpl,
  533. 'user_uploaded_assignment_check.tpl',
  534. 'XUserXUploadedAssignmentXCheckX',
  535. array(
  536. api_get_setting('siteName')
  537. )
  538. );
  539. break;*/
  540. /*case SmsPlugin::SUBSCRIBED_SESSION:
  541. return $this->buildSms(
  542. $plugin,
  543. $tpl,
  544. 'subscribed_session.tpl',
  545. 'XSubscribedSessionX',
  546. array(
  547. api_get_setting('siteName')
  548. )
  549. );
  550. break;*/
  551. /*case SmsPlugin::SUBSCRIBED_SESSION_CSV:
  552. return $this->buildSms(
  553. $plugin,
  554. $tpl,
  555. 'subscribed_session_csv.tpl',
  556. 'XSubscribedSessionXCSV',
  557. array(
  558. api_get_setting('siteName')
  559. )
  560. );
  561. break;*/
  562. /*case SmsPlugin::USER_SUGGESTED_BE_FRIENDS:
  563. return $this->buildSms(
  564. $plugin,
  565. $tpl,
  566. 'user_suggested_be_friends.tpl',
  567. 'XUserXSuggestedBeFriends',
  568. array(
  569. api_get_setting('siteName')
  570. )
  571. );
  572. break;*/
  573. /*case SmsPlugin::USER_ANSWERED_INBOX_MESSAGE:
  574. return $this->buildSms(
  575. $plugin,
  576. $tpl,
  577. 'user_answered_inbox_message.tpl',
  578. 'XUserXAnsweredInboxMessage',
  579. array(
  580. api_get_setting('siteName')
  581. )
  582. );
  583. break;*/
  584. /*case SmsPlugin::BEEN_INVITED_JOIN_GROUP:
  585. return $this->buildSms(
  586. $plugin,
  587. $tpl,
  588. 'been_invited_join_group.tpl',
  589. 'XBeenInvitedJoinGroupX',
  590. array(
  591. api_get_setting('siteName')
  592. )
  593. );
  594. break;*/
  595. /*case SmsPlugin::MESSAGES_SENT_EDITED_GROUP_EDITED:
  596. return $this->buildSms(
  597. $plugin,
  598. $tpl,
  599. 'messages_sent_edited_group_edited.tpl',
  600. 'XMessagesSentEditedGroupXEdited',
  601. array(
  602. api_get_setting('siteName')
  603. )
  604. );
  605. break;*/
  606. /*case SmsPlugin::MESSAGES_SENT_EDITED_GROUP_ADDED:
  607. return $this->buildSms(
  608. $plugin,
  609. $tpl,
  610. 'messages_sent_edited_group_added.tpl',
  611. 'XMessagesSentEditedGroupXAdded',
  612. array(
  613. api_get_setting('siteName')
  614. )
  615. );
  616. break;*/
  617. /*case SmsPlugin::BEEN_INVITED_COMPLETE_SURVEY_COURSE:
  618. return $this->buildSms(
  619. $plugin,
  620. $tpl,
  621. 'been_invited_complete_survey_course.tpl',
  622. 'XBeenInvitedCompleteSurveyXCourseX',
  623. array(
  624. api_get_setting('siteName')
  625. )
  626. );
  627. break;*/
  628. /*case SmsPlugin::REMINDER_ASSIGNMENT_COURSE_DUE:
  629. return $this->buildSms(
  630. $plugin,
  631. $tpl,
  632. 'reminder_assignment_course_due.tpl',
  633. 'XReminderAssignmentXCourseXDue',
  634. array(
  635. api_get_setting('siteName')
  636. )
  637. );
  638. break;*/
  639. /*case SmsPlugin::USER_DETAILS_MODIFIED:
  640. return $this->buildSms(
  641. $plugin,
  642. $tpl,
  643. 'user_details_modified.tpl',
  644. 'XUserDetailsModified',
  645. array(
  646. api_get_setting('siteName')
  647. )
  648. );
  649. break;*/
  650. default:
  651. return '';
  652. }
  653. }
  654. }