WhispeakAuthPlugin.php 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use Chamilo\CoreBundle\Entity\ExtraField;
  4. use Chamilo\CoreBundle\Entity\ExtraFieldValues;
  5. use Chamilo\PluginBundle\Entity\WhispeakAuth\LogEvent;
  6. use Chamilo\PluginBundle\Entity\WhispeakAuth\LogEventLp;
  7. use Chamilo\PluginBundle\Entity\WhispeakAuth\LogEventQuiz;
  8. use Chamilo\UserBundle\Entity\User;
  9. use Symfony\Component\Filesystem\Filesystem;
  10. /**
  11. * Class WhispeakAuthPlugin.
  12. */
  13. class WhispeakAuthPlugin extends Plugin implements HookPluginInterface
  14. {
  15. const SETTING_ENABLE = 'enable';
  16. const SETTING_MAX_ATTEMPTS = 'max_attempts';
  17. const SETTING_2FA = '2fa';
  18. const EXTRAFIELD_AUTH_UID = 'whispeak_auth_uid';
  19. const EXTRAFIELD_LP_ITEM = 'whispeak_lp_item';
  20. const EXTRAFIELD_QUIZ_QUESTION = 'whispeak_quiz_question';
  21. const API_URL = 'http://api.whispeak.io:8080/v1.1/';
  22. const SESSION_FAILED_LOGINS = 'whispeak_failed_logins';
  23. const SESSION_2FA_USER = 'whispeak_user_id';
  24. const SESSION_LP_ITEM = 'whispeak_lp_item';
  25. const SESSION_QUIZ_QUESTION = 'whispeak_quiz_question';
  26. const SESSION_AUTH_PASSWORD = 'whispeak_auth_password';
  27. const SESSION_SENTENCE_TEXT = 'whispeak_sentence_text';
  28. /**
  29. * StudentFollowUpPlugin constructor.
  30. */
  31. protected function __construct()
  32. {
  33. parent::__construct(
  34. '0.1',
  35. 'Angel Fernando Quiroz',
  36. [
  37. self::SETTING_ENABLE => 'boolean',
  38. self::SETTING_MAX_ATTEMPTS => 'text',
  39. self::SETTING_2FA => 'boolean',
  40. ]
  41. );
  42. }
  43. /**
  44. * Get the admin URL for the plugin if Plugin::isAdminPlugin is true.
  45. *
  46. * @return string
  47. */
  48. public function getAdminUrl()
  49. {
  50. $webPath = api_get_path(WEB_PLUGIN_PATH).$this->get_name();
  51. return "$webPath/admin.php";
  52. }
  53. /**
  54. * @return WhispeakAuthPlugin
  55. */
  56. public static function create()
  57. {
  58. static $result = null;
  59. return $result ? $result : $result = new self();
  60. }
  61. public function install()
  62. {
  63. $this->installExtraFields();
  64. $this->installEntities();
  65. $this->installHook();
  66. }
  67. public function uninstall()
  68. {
  69. $this->uninstallHook();
  70. $this->uninstallExtraFields();
  71. $this->uninstallEntities();
  72. }
  73. /**
  74. * @return string
  75. */
  76. public function getEntityPath()
  77. {
  78. return api_get_path(SYS_PATH).'src/Chamilo/PluginBundle/Entity/'.$this->getCamelCaseName();
  79. }
  80. /**
  81. * @return string
  82. */
  83. public function getAccessToken()
  84. {
  85. $token = file_get_contents(__DIR__.'/tokenTest');
  86. return trim($token);
  87. }
  88. /**
  89. * @return ExtraField
  90. */
  91. public static function getAuthUidExtraField()
  92. {
  93. $em = Database::getManager();
  94. $efRepo = $em->getRepository('ChamiloCoreBundle:ExtraField');
  95. /** @var ExtraField $extraField */
  96. $extraField = $efRepo->findOneBy(
  97. [
  98. 'variable' => self::EXTRAFIELD_AUTH_UID,
  99. 'extraFieldType' => ExtraField::USER_FIELD_TYPE,
  100. ]
  101. );
  102. return $extraField;
  103. }
  104. /**
  105. * @return ExtraField
  106. */
  107. public static function getLpItemExtraField()
  108. {
  109. $efRepo = Database::getManager()->getRepository('ChamiloCoreBundle:ExtraField');
  110. /** @var ExtraField $extraField */
  111. $extraField = $efRepo->findOneBy(
  112. [
  113. 'variable' => self::EXTRAFIELD_LP_ITEM,
  114. 'extraFieldType' => ExtraField::LP_ITEM_FIELD_TYPE,
  115. ]
  116. );
  117. return $extraField;
  118. }
  119. /**
  120. * @return ExtraField
  121. */
  122. public static function getQuizQuestionExtraField()
  123. {
  124. $efRepo = Database::getManager()->getRepository('ChamiloCoreBundle:ExtraField');
  125. /** @var ExtraField $extraField */
  126. $extraField = $efRepo->findOneBy(
  127. [
  128. 'variable' => self::EXTRAFIELD_QUIZ_QUESTION,
  129. 'extraFieldType' => ExtraField::QUESTION_FIELD_TYPE,
  130. ]
  131. );
  132. return $extraField;
  133. }
  134. /**
  135. * @param int $userId
  136. *
  137. * @return ExtraFieldValues
  138. */
  139. public static function getAuthUidValue($userId)
  140. {
  141. $extraField = self::getAuthUidExtraField();
  142. $em = Database::getManager();
  143. $efvRepo = $em->getRepository('ChamiloCoreBundle:ExtraFieldValues');
  144. /** @var ExtraFieldValues $value */
  145. $value = $efvRepo->findOneBy(['field' => $extraField, 'itemId' => $userId]);
  146. return $value;
  147. }
  148. /**
  149. * Get the whispeak_lp_item value for a LP item ID.
  150. *
  151. * @param int $lpItemId
  152. *
  153. * @return array|false
  154. */
  155. public static function getLpItemValue($lpItemId)
  156. {
  157. $efv = new ExtraFieldValue('lp_item');
  158. $value = $efv->get_values_by_handler_and_field_variable($lpItemId, self::EXTRAFIELD_LP_ITEM);
  159. return $value;
  160. }
  161. /**
  162. * @param int $lpItemId
  163. *
  164. * @return bool
  165. */
  166. public static function isLpItemMarked($lpItemId)
  167. {
  168. if (!self::create()->isEnabled()) {
  169. return false;
  170. }
  171. $value = self::getLpItemValue($lpItemId);
  172. return !empty($value) && !empty($value['value']);
  173. }
  174. /**
  175. * Get the whispeak_quiz_question value for a quiz question ID.
  176. *
  177. * @param int $questionId
  178. *
  179. * @return array|false
  180. */
  181. public static function getQuizQuestionValue($questionId)
  182. {
  183. $efv = new ExtraFieldValue('question');
  184. $value = $efv->get_values_by_handler_and_field_variable($questionId, self::EXTRAFIELD_QUIZ_QUESTION);
  185. return $value;
  186. }
  187. /**
  188. * @param int $questionId
  189. *
  190. * @return bool
  191. */
  192. public static function isQuizQuestionMarked($questionId)
  193. {
  194. if (!self::create()->isEnabled()) {
  195. return false;
  196. }
  197. $value = self::getQuizQuestionValue($questionId);
  198. return !empty($value) && !empty($value['value']);
  199. }
  200. /**
  201. * @param int $questionId
  202. *
  203. * @return bool
  204. */
  205. public static function questionRequireAuthentify($questionId)
  206. {
  207. $isMarked = self::isQuizQuestionMarked($questionId);
  208. if (!$isMarked) {
  209. return false;
  210. }
  211. $questionInfo = ChamiloSession::read(self::SESSION_QUIZ_QUESTION, []);
  212. if (empty($questionInfo)) {
  213. return true;
  214. }
  215. if ((int) $questionId !== $questionInfo['question']) {
  216. return true;
  217. }
  218. if (false === $questionInfo['passed']) {
  219. return true;
  220. }
  221. return false;
  222. }
  223. /**
  224. * @param int $userId
  225. *
  226. * @return bool
  227. */
  228. public static function checkUserIsEnrolled($userId)
  229. {
  230. $value = self::getAuthUidValue($userId);
  231. if (empty($value)) {
  232. return false;
  233. }
  234. return !empty($value->getValue());
  235. }
  236. /**
  237. * @return string
  238. */
  239. public static function getEnrollmentUrl()
  240. {
  241. return api_get_path(WEB_PLUGIN_PATH).'whispeakauth/enrollment.php';
  242. }
  243. /**
  244. * @param User $user
  245. * @param string $uid
  246. *
  247. * @throws \Doctrine\ORM\OptimisticLockException
  248. */
  249. public function saveEnrollment(User $user, $uid)
  250. {
  251. $em = Database::getManager();
  252. $extraFieldValue = self::getAuthUidValue($user->getId());
  253. if (empty($extraFieldValue)) {
  254. $extraField = self::getAuthUidExtraField();
  255. $now = new DateTime('now', new DateTimeZone('UTC'));
  256. $extraFieldValue = new ExtraFieldValues();
  257. $extraFieldValue
  258. ->setField($extraField)
  259. ->setItemId($user->getId())
  260. ->setUpdatedAt($now);
  261. }
  262. $extraFieldValue->setValue($uid);
  263. $em->persist($extraFieldValue);
  264. $em->flush();
  265. }
  266. /**
  267. * @return bool
  268. */
  269. public function toolIsEnabled()
  270. {
  271. return 'true' === $this->get(self::SETTING_ENABLE);
  272. }
  273. /**
  274. * Access not allowed when tool is not enabled.
  275. *
  276. * @param bool $printHeaders Optional. Print headers.
  277. */
  278. public function protectTool($printHeaders = true)
  279. {
  280. if ($this->toolIsEnabled()) {
  281. return;
  282. }
  283. api_not_allowed($printHeaders);
  284. }
  285. /**
  286. * Convert the language name to ISO-639-2 code (3 characters).
  287. *
  288. * @param string $languageName
  289. *
  290. * @return string
  291. */
  292. public static function getLanguageIsoCode($languageName)
  293. {
  294. $listIso3 = [
  295. 'ab' => 'abk',
  296. 'aa' => 'aar',
  297. 'af' => 'afr',
  298. 'ak' => 'aka',
  299. 'sq' => 'sqi',
  300. 'am' => 'amh',
  301. 'ar' => 'ara',
  302. 'an' => 'arg',
  303. 'hy' => 'hye',
  304. 'as' => 'asm',
  305. 'av' => 'ava',
  306. 'ae' => 'ave',
  307. 'ay' => 'aym',
  308. 'az' => 'aze',
  309. 'bm' => 'bam',
  310. 'ba' => 'bak',
  311. 'eu' => 'eus',
  312. 'be' => 'bel',
  313. 'bn' => 'ben',
  314. 'bh' => 'bih',
  315. 'bi' => 'bis',
  316. 'bs' => 'bos',
  317. 'br' => 'bre',
  318. 'bg' => 'bul',
  319. 'my' => 'mya',
  320. 'ca' => 'cat',
  321. 'ch' => 'cha',
  322. 'ce' => 'che',
  323. 'ny' => 'nya',
  324. 'zh' => 'zho',
  325. 'cv' => 'chv',
  326. 'kw' => 'cor',
  327. 'co' => 'cos',
  328. 'cr' => 'cre',
  329. 'hr' => 'hrv',
  330. 'cs' => 'ces',
  331. 'da' => 'dan',
  332. 'dv' => 'div',
  333. 'nl' => 'nld',
  334. 'dz' => 'dzo',
  335. 'en' => 'eng',
  336. 'eo' => 'epo',
  337. 'et' => 'est',
  338. 'ee' => 'ewe',
  339. 'fo' => 'fao',
  340. 'fj' => 'fij',
  341. 'fi' => 'fin',
  342. 'fr' => 'fra',
  343. 'ff' => 'ful',
  344. 'gl' => 'glg',
  345. 'ka' => 'kat',
  346. 'de' => 'deu',
  347. 'el' => 'ell',
  348. 'gn' => 'grn',
  349. 'gu' => 'guj',
  350. 'ht' => 'hat',
  351. 'ha' => 'hau',
  352. 'he' => 'heb',
  353. 'hz' => 'her',
  354. 'hi' => 'hin',
  355. 'ho' => 'hmo',
  356. 'hu' => 'hun',
  357. 'ia' => 'ina',
  358. 'id' => 'ind',
  359. 'ie' => 'ile',
  360. 'ga' => 'gle',
  361. 'ig' => 'ibo',
  362. 'ik' => 'ipk',
  363. 'io' => 'ido',
  364. 'is' => 'isl',
  365. 'it' => 'ita',
  366. 'iu' => 'iku',
  367. 'ja' => 'jpn',
  368. 'jv' => 'jav',
  369. 'kl' => 'kal',
  370. 'kn' => 'kan',
  371. 'kr' => 'kau',
  372. 'ks' => 'kas',
  373. 'kk' => 'kaz',
  374. 'km' => 'khm',
  375. 'ki' => 'kik',
  376. 'rw' => 'kin',
  377. 'ky' => 'kir',
  378. 'kv' => 'kom',
  379. 'kg' => 'kon',
  380. 'ko' => 'kor',
  381. 'ku' => 'kur',
  382. 'kj' => 'kua',
  383. 'la' => 'lat',
  384. 'lb' => 'ltz',
  385. 'lg' => 'lug',
  386. 'li' => 'lim',
  387. 'ln' => 'lin',
  388. 'lo' => 'lao',
  389. 'lt' => 'lit',
  390. 'lu' => 'lub',
  391. 'lv' => 'lav',
  392. 'gv' => 'glv',
  393. 'mk' => 'mkd',
  394. 'mg' => 'mlg',
  395. 'ms' => 'msa',
  396. 'ml' => 'mal',
  397. 'mt' => 'mlt',
  398. 'mi' => 'mri',
  399. 'mr' => 'mar',
  400. 'mh' => 'mah',
  401. 'mn' => 'mon',
  402. 'na' => 'nau',
  403. 'nv' => 'nav',
  404. 'nd' => 'nde',
  405. 'ne' => 'nep',
  406. 'ng' => 'ndo',
  407. 'nb' => 'nob',
  408. 'nn' => 'nno',
  409. 'no' => 'nor',
  410. 'ii' => 'iii',
  411. 'nr' => 'nbl',
  412. 'oc' => 'oci',
  413. 'oj' => 'oji',
  414. 'cu' => 'chu',
  415. 'om' => 'orm',
  416. 'or' => 'ori',
  417. 'os' => 'oss',
  418. 'pa' => 'pan',
  419. 'pi' => 'pli',
  420. 'fa' => 'fas',
  421. 'pl' => 'pol',
  422. 'ps' => 'pus',
  423. 'pt' => 'por',
  424. 'qu' => 'que',
  425. 'rm' => 'roh',
  426. 'rn' => 'run',
  427. 'ro' => 'ron',
  428. 'ru' => 'rus',
  429. 'sa' => 'san',
  430. 'sc' => 'srd',
  431. 'sd' => 'snd',
  432. 'se' => 'sme',
  433. 'sm' => 'smo',
  434. 'sg' => 'sag',
  435. 'sr' => 'srp',
  436. 'gd' => 'gla',
  437. 'sn' => 'sna',
  438. 'si' => 'sin',
  439. 'sk' => 'slk',
  440. 'sl' => 'slv',
  441. 'so' => 'som',
  442. 'st' => 'sot',
  443. 'es' => 'spa',
  444. 'su' => 'sun',
  445. 'sw' => 'swa',
  446. 'ss' => 'ssw',
  447. 'sv' => 'swe',
  448. 'ta' => 'tam',
  449. 'te' => 'tel',
  450. 'tg' => 'tgk',
  451. 'th' => 'tha',
  452. 'ti' => 'tir',
  453. 'bo' => 'bod',
  454. 'tk' => 'tuk',
  455. 'tl' => 'tgl',
  456. 'tn' => 'tsn',
  457. 'to' => 'ton',
  458. 'tr' => 'tur',
  459. 'ts' => 'tso',
  460. 'tt' => 'tat',
  461. 'tw' => 'twi',
  462. 'ty' => 'tah',
  463. 'ug' => 'uig',
  464. 'uk' => 'ukr',
  465. 'ur' => 'urd',
  466. 'uz' => 'uzb',
  467. 've' => 'ven',
  468. 'vi' => 'vie',
  469. 'vo' => 'vol',
  470. 'wa' => 'wln',
  471. 'cy' => 'cym',
  472. 'wo' => 'wol',
  473. 'fy' => 'fry',
  474. 'xh' => 'xho',
  475. 'yi' => 'yid',
  476. 'yo' => 'yor',
  477. 'za' => 'zha',
  478. 'zu' => 'zul',
  479. ];
  480. $iso2 = api_get_language_isocode($languageName);
  481. $iso3 = isset($listIso3[$iso2]) ? $listIso3[$iso2] : $listIso3['en'];
  482. return $iso3;
  483. }
  484. /**
  485. * Get the max_attemtps option.
  486. *
  487. * @return int
  488. */
  489. public function getMaxAttempts()
  490. {
  491. return (int) $this->get(self::SETTING_MAX_ATTEMPTS);
  492. }
  493. /**
  494. * Install hook when saving the plugin configuration.
  495. *
  496. * @return WhispeakAuthPlugin
  497. */
  498. public function performActionsAfterConfigure()
  499. {
  500. $observer = WhispeakConditionalLoginHook::create();
  501. if ('true' === $this->get(self::SETTING_2FA)) {
  502. HookConditionalLogin::create()->attach($observer);
  503. } else {
  504. HookConditionalLogin::create()->detach($observer);
  505. }
  506. return $this;
  507. }
  508. /**
  509. * This method will call the Hook management insertHook to add Hook observer from this plugin.
  510. */
  511. public function installHook()
  512. {
  513. $observer = WhispeakMyStudentsLpTrackingHook::create();
  514. HookMyStudentsLpTracking::create()->attach($observer);
  515. $observer = WhispeakMyStudentsQuizTrackingHook::create();
  516. HookMyStudentsQuizTracking::create()->attach($observer);
  517. }
  518. /**
  519. * This method will call the Hook management deleteHook to disable Hook observer from this plugin.
  520. */
  521. public function uninstallHook()
  522. {
  523. $observer = WhispeakConditionalLoginHook::create();
  524. HookConditionalLogin::create()->detach($observer);
  525. $observer = WhispeakMyStudentsLpTrackingHook::create();
  526. HookMyStudentsLpTracking::create()->detach($observer);
  527. }
  528. /**
  529. * @param int $userId
  530. *
  531. * @throws \Doctrine\ORM\OptimisticLockException
  532. *
  533. * @return bool
  534. */
  535. public static function deleteEnrollment($userId)
  536. {
  537. $extraFieldValue = self::getAuthUidValue($userId);
  538. if (empty($extraFieldValue)) {
  539. return false;
  540. }
  541. $em = Database::getManager();
  542. $em->remove($extraFieldValue);
  543. $em->flush();
  544. return true;
  545. }
  546. /**
  547. * Check if the WhispeakAuth plugin is installed and enabled.
  548. *
  549. * @return bool
  550. */
  551. public function isEnabled()
  552. {
  553. return parent::isEnabled() && 'true' === api_get_plugin_setting('whispeakauth', self::SETTING_ENABLE);
  554. }
  555. /**
  556. * @param int $lpItemId
  557. *
  558. * @return bool
  559. */
  560. public static function isAllowedToSaveLpItem($lpItemId)
  561. {
  562. if (!self::isLpItemMarked($lpItemId)) {
  563. return true;
  564. }
  565. $markedItem = ChamiloSession::read(self::SESSION_LP_ITEM, []);
  566. if (empty($markedItem)) {
  567. return true;
  568. }
  569. if ((int) $lpItemId !== (int) $markedItem['lp_item']) {
  570. return true;
  571. }
  572. return false;
  573. }
  574. /**
  575. * Display a error message.
  576. *
  577. * @param string|null $error Optional. The message text
  578. */
  579. public static function displayNotAllowedMessage($error = null)
  580. {
  581. $error = empty($error) ? get_lang('NotAllowed') : $error;
  582. echo Display::return_message($error, 'error', false);
  583. exit;
  584. }
  585. /**
  586. * @param int $questionId
  587. * @param Exercise $exercise
  588. *
  589. * @throws Exception
  590. *
  591. * @return string
  592. */
  593. public static function quizQuestionAuthentify($questionId, Exercise $exercise)
  594. {
  595. ChamiloSession::write(
  596. self::SESSION_QUIZ_QUESTION,
  597. [
  598. 'quiz' => (int) $exercise->iId,
  599. 'question' => (int) $questionId,
  600. 'url_params' => $_SERVER['QUERY_STRING'],
  601. 'passed' => false,
  602. ]
  603. );
  604. $template = new Template('', false, false, false, true, false, false);
  605. $template->assign('question', $questionId);
  606. $template->assign('exercise', $exercise->iId);
  607. $content = $template->fetch('whispeakauth/view/quiz_question.html.twig');
  608. echo $content;
  609. }
  610. /**
  611. * @param int $status
  612. * @param int $userId
  613. * @param int $lpItemId
  614. * @param int $lpId
  615. *
  616. * @throws \Doctrine\ORM\ORMException
  617. * @throws \Doctrine\ORM\OptimisticLockException
  618. * @throws \Doctrine\ORM\TransactionRequiredException
  619. *
  620. * @return LogEventLp|null
  621. */
  622. public function addAttemptInLearningPath($status, $userId, $lpItemId, $lpId)
  623. {
  624. $em = Database::getManager();
  625. $user = api_get_user_entity($userId);
  626. $lpItem = $em->find('ChamiloCourseBundle:CLpItem', $lpItemId);
  627. $lp = $em->find('ChamiloCourseBundle:CLp', $lpId);
  628. if (empty($lp) || empty($lpItem)) {
  629. return null;
  630. }
  631. $logEvent = new LogEventLp();
  632. $logEvent
  633. ->setLpItem($lpItem)
  634. ->setLp($lp)
  635. ->setUser($user)
  636. ->setDatetime(
  637. api_get_utc_datetime(null, false, true)
  638. )
  639. ->setActionStatus($status);
  640. $em->persist($logEvent);
  641. $em->flush();
  642. return $logEvent;
  643. }
  644. /**
  645. * @param int $status
  646. * @param int $userId
  647. * @param int $questionId
  648. * @param int $quizId
  649. *
  650. * @throws \Doctrine\ORM\ORMException
  651. * @throws \Doctrine\ORM\OptimisticLockException
  652. * @throws \Doctrine\ORM\TransactionRequiredException
  653. *
  654. * @return LogEventQuiz|null
  655. */
  656. public function addAttemptInQuiz($status, $userId, $questionId, $quizId)
  657. {
  658. $em = Database::getManager();
  659. $user = api_get_user_entity($userId);
  660. $question = $em->find('ChamiloCourseBundle:CQuizQuestion', $questionId);
  661. $quiz = $em->find('ChamiloCourseBundle:CQuiz', $quizId);
  662. if (empty($quiz) || empty($question)) {
  663. return null;
  664. }
  665. $logEvent = new LogEventQuiz();
  666. $logEvent
  667. ->setQuestion($question)
  668. ->setQuiz($quiz)
  669. ->setUser($user)
  670. ->setDatetime(
  671. api_get_utc_datetime(null, false, true)
  672. )
  673. ->setActionStatus($status);
  674. $em->persist($logEvent);
  675. $em->flush();
  676. return $logEvent;
  677. }
  678. /**
  679. * @param int $lpId
  680. * @param int $userId
  681. *
  682. * @throws \Doctrine\ORM\Query\QueryException
  683. *
  684. * @return string
  685. */
  686. public static function countAllAttemptsInLearningPath($lpId, $userId)
  687. {
  688. $query = Database::getManager()
  689. ->createQuery(
  690. 'SELECT COUNT(log) AS c FROM ChamiloPluginBundle:WhispeakAuth\LogEventLp log
  691. WHERE log.lp = :lp AND log.user = :user'
  692. )
  693. ->setParameters(['lp' => $lpId, 'user' => $userId]);
  694. $totalCount = (int) $query->getSingleScalarResult();
  695. return $totalCount;
  696. }
  697. /**
  698. * @param int $lpId
  699. * @param int $userId
  700. *
  701. * @throws \Doctrine\ORM\Query\QueryException
  702. *
  703. * @return string
  704. */
  705. public static function countSuccessAttemptsInLearningPath($lpId, $userId)
  706. {
  707. $query = Database::getManager()
  708. ->createQuery(
  709. 'SELECT COUNT(log) AS c FROM ChamiloPluginBundle:WhispeakAuth\LogEventLp log
  710. WHERE log.lp = :lp AND log.user = :user AND log.actionStatus = :status'
  711. )
  712. ->setParameters(['lp' => $lpId, 'user' => $userId, 'status' => LogEvent::STATUS_SUCCESS]);
  713. $totalCount = (int) $query->getSingleScalarResult();
  714. return $totalCount;
  715. }
  716. /**
  717. * @param int $quizId
  718. * @param int $userId
  719. *
  720. * @throws \Doctrine\ORM\Query\QueryException
  721. *
  722. * @return string
  723. */
  724. public static function countAllAttemptsInQuiz($quizId, $userId)
  725. {
  726. $query = Database::getManager()
  727. ->createQuery(
  728. 'SELECT COUNT(log) AS c FROM ChamiloPluginBundle:WhispeakAuth\LogEventQuiz log
  729. WHERE log.quiz = :quiz AND log.user = :user'
  730. )
  731. ->setParameters(['quiz' => $quizId, 'user' => $userId]);
  732. $totalCount = (int) $query->getSingleScalarResult();
  733. return $totalCount;
  734. }
  735. /**
  736. * @param int $quizId
  737. * @param int $userId
  738. *
  739. * @throws \Doctrine\ORM\Query\QueryException
  740. *
  741. * @return string
  742. */
  743. public static function countSuccessAttemptsInQuiz($quizId, $userId)
  744. {
  745. $query = Database::getManager()
  746. ->createQuery(
  747. 'SELECT COUNT(log) AS c FROM ChamiloPluginBundle:WhispeakAuth\LogEventQuiz log
  748. WHERE log.quiz = :quiz AND log.user = :user AND log.actionStatus = :status'
  749. )
  750. ->setParameters(['quiz' => $quizId, 'user' => $userId, 'status' => LogEvent::STATUS_SUCCESS]);
  751. $totalCount = (int) $query->getSingleScalarResult();
  752. return $totalCount;
  753. }
  754. /**
  755. * Install extra fields for user, learning path and quiz question.
  756. */
  757. private function installExtraFields()
  758. {
  759. UserManager::create_extra_field(
  760. self::EXTRAFIELD_AUTH_UID,
  761. \ExtraField::FIELD_TYPE_TEXT,
  762. $this->get_lang('Whispeak uid'),
  763. ''
  764. );
  765. LpItem::createExtraField(
  766. self::EXTRAFIELD_LP_ITEM,
  767. \ExtraField::FIELD_TYPE_CHECKBOX,
  768. $this->get_lang('MarkForSpeechAuthentication'),
  769. '0',
  770. true,
  771. true
  772. );
  773. $extraField = new \ExtraField('question');
  774. $params = [
  775. 'variable' => self::EXTRAFIELD_QUIZ_QUESTION,
  776. 'field_type' => \ExtraField::FIELD_TYPE_CHECKBOX,
  777. 'display_text' => $this->get_lang('MarkForSpeechAuthentication'),
  778. 'default_value' => '0',
  779. 'changeable' => true,
  780. 'visible_to_self' => true,
  781. 'visible_to_others' => false,
  782. ];
  783. $extraField->save($params);
  784. }
  785. /**
  786. * Install the Doctrine's entities.
  787. */
  788. private function installEntities()
  789. {
  790. $pluginEntityPath = $this->getEntityPath();
  791. if (!is_dir($pluginEntityPath)) {
  792. if (!is_writable(dirname($pluginEntityPath))) {
  793. Display::addFlash(
  794. Display::return_message(get_lang('ErrorCreatingDir').": $pluginEntityPath", 'error')
  795. );
  796. return;
  797. }
  798. mkdir($pluginEntityPath, api_get_permissions_for_new_directories());
  799. }
  800. $fs = new Filesystem();
  801. $fs->mirror(__DIR__.'/Entity/', $pluginEntityPath, null, ['override']);
  802. $schema = Database::getManager()->getConnection()->getSchemaManager();
  803. if (false === $schema->tablesExist('whispeak_log_event')) {
  804. $sql = "CREATE TABLE whispeak_log_event (
  805. id INT AUTO_INCREMENT NOT NULL,
  806. user_id INT NOT NULL,
  807. lp_item_id INT DEFAULT NULL,
  808. lp_id INT DEFAULT NULL,
  809. question_id INT DEFAULT NULL,
  810. quiz_id INT DEFAULT NULL,
  811. datetime DATETIME NOT NULL,
  812. action_status SMALLINT NOT NULL,
  813. discr VARCHAR(255) NOT NULL,
  814. INDEX IDX_A5C4B9FFA76ED395 (user_id),
  815. INDEX IDX_A5C4B9FFDBF72317 (lp_item_id),
  816. INDEX IDX_A5C4B9FF68DFD1EF (lp_id),
  817. INDEX IDX_A5C4B9FF1E27F6BF (question_id),
  818. INDEX IDX_A5C4B9FF853CD175 (quiz_id),
  819. PRIMARY KEY(id)
  820. ) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB";
  821. Database::query($sql);
  822. $sql = "ALTER TABLE whispeak_log_event ADD CONSTRAINT FK_A5C4B9FFA76ED395
  823. FOREIGN KEY (user_id) REFERENCES user (id)";
  824. Database::query($sql);
  825. $sql = "ALTER TABLE whispeak_log_event ADD CONSTRAINT FK_A5C4B9FFDBF72317
  826. FOREIGN KEY (lp_item_id) REFERENCES c_lp_item (iid)";
  827. Database::query($sql);
  828. $sql = "ALTER TABLE whispeak_log_event ADD CONSTRAINT FK_A5C4B9FF68DFD1EF
  829. FOREIGN KEY (lp_id) REFERENCES c_lp (iid)";
  830. Database::query($sql);
  831. $sql = "ALTER TABLE whispeak_log_event ADD CONSTRAINT FK_A5C4B9FF1E27F6BF
  832. FOREIGN KEY (question_id) REFERENCES c_quiz_question (iid)";
  833. Database::query($sql);
  834. $sql = "ALTER TABLE whispeak_log_event ADD CONSTRAINT FK_A5C4B9FF853CD175
  835. FOREIGN KEY (quiz_id) REFERENCES c_quiz (iid)";
  836. Database::query($sql);
  837. }
  838. }
  839. /**
  840. * Uninstall extra fields for user, learning path and quiz question.
  841. */
  842. private function uninstallExtraFields()
  843. {
  844. $em = Database::getManager();
  845. $authIdExtrafield = self::getAuthUidExtraField();
  846. if (!empty($authIdExtrafield)) {
  847. $em
  848. ->createQuery('DELETE FROM ChamiloCoreBundle:ExtraFieldValues efv WHERE efv.field = :field')
  849. ->execute(['field' => $authIdExtrafield]);
  850. $em->remove($authIdExtrafield);
  851. $em->flush();
  852. }
  853. $lpItemExtrafield = self::getLpItemExtraField();
  854. if (!empty($lpItemExtrafield)) {
  855. $em
  856. ->createQuery('DELETE FROM ChamiloCoreBundle:ExtraFieldValues efv WHERE efv.field = :field')
  857. ->execute(['field' => $lpItemExtrafield]);
  858. $em->remove($lpItemExtrafield);
  859. $em->flush();
  860. }
  861. $quizQuestionExtrafield = self::getQuizQuestionExtraField();
  862. if (!empty($quizQuestionExtrafield)) {
  863. $em
  864. ->createQuery('DELETE FROM ChamiloCoreBundle:ExtraFieldValues efv WHERE efv.field = :field')
  865. ->execute(['field' => $quizQuestionExtrafield]);
  866. $em->remove($quizQuestionExtrafield);
  867. $em->flush();
  868. }
  869. }
  870. /**
  871. * Uninstall the Doctrine's entities.
  872. */
  873. private function uninstallEntities()
  874. {
  875. $pluginEntityPath = $this->getEntityPath();
  876. $fs = new Filesystem();
  877. if ($fs->exists($pluginEntityPath)) {
  878. $fs->remove($pluginEntityPath);
  879. }
  880. $table = Database::get_main_table('whispeak_log_event');
  881. $sql = "DROP TABLE IF EXISTS $table";
  882. Database::query($sql);
  883. }
  884. }