certificate.lib.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * Certificate Class
  5. * Generate certificates based in the gradebook tool.
  6. * @package chamilo.library.certificates
  7. */
  8. class Certificate extends Model
  9. {
  10. public $table;
  11. public $columns = array(
  12. 'id',
  13. 'cat_id',
  14. 'score_certificate',
  15. 'created_at',
  16. 'path_certificate'
  17. );
  18. /**
  19. * Certification data
  20. */
  21. public $certificate_data = [];
  22. /**
  23. * Student's certification path
  24. */
  25. public $certification_user_path = null;
  26. public $certification_web_user_path = null;
  27. public $html_file = null;
  28. public $qr_file = null;
  29. public $user_id;
  30. /** If true every time we enter to the certificate URL
  31. * we would generate a new certificate (good thing because we can edit the
  32. * certificate and all users will have the latest certificate bad because we
  33. * load the certificate every time */
  34. public $force_certificate_generation = true;
  35. /**
  36. * Constructor
  37. * @param int $certificate_id ID of the certificate.
  38. * @param int $userId
  39. * @param bool $sendNotification send message to student
  40. *
  41. * If no ID given, take user_id and try to generate one
  42. */
  43. public function __construct(
  44. $certificate_id = 0,
  45. $userId = 0,
  46. $sendNotification = false
  47. ) {
  48. $this->table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
  49. $this->user_id = !empty($userId) ? $userId : api_get_user_id();
  50. if (!empty($certificate_id)) {
  51. $certificate = $this->get($certificate_id);
  52. if (!empty($certificate) && is_array($certificate)) {
  53. $this->certificate_data = $certificate;
  54. $this->user_id = $this->certificate_data['user_id'];
  55. }
  56. }
  57. if ($this->user_id) {
  58. // Need to be called before any operation
  59. $this->check_certificate_path();
  60. // To force certification generation
  61. if ($this->force_certificate_generation) {
  62. $this->generate([], $sendNotification);
  63. }
  64. if (isset($this->certificate_data) && $this->certificate_data) {
  65. if (empty($this->certificate_data['path_certificate'])) {
  66. $this->generate([], $sendNotification);
  67. }
  68. }
  69. }
  70. // Setting the qr and html variables
  71. if (isset($certificate_id) &&
  72. !empty($this->certification_user_path) &&
  73. isset($this->certificate_data['path_certificate'])
  74. ) {
  75. $pathinfo = pathinfo($this->certificate_data['path_certificate']);
  76. $this->html_file = $this->certification_user_path.basename($this->certificate_data['path_certificate']);
  77. $this->qr_file = $this->certification_user_path.$pathinfo['filename'].'_qr.png';
  78. }
  79. }
  80. /**
  81. * Checks if the certificate user path directory is created
  82. */
  83. public function check_certificate_path()
  84. {
  85. $this->certification_user_path = null;
  86. // Setting certification path
  87. $path_info = UserManager::getUserPathById($this->user_id, 'system');
  88. $web_path_info = UserManager::getUserPathById($this->user_id, 'web');
  89. if (!empty($path_info) && isset($path_info)) {
  90. $this->certification_user_path = $path_info.'certificate/';
  91. $this->certification_web_user_path = $web_path_info.'certificate/';
  92. if (!is_dir($path_info)) {
  93. mkdir($path_info, 0777, true);
  94. }
  95. if (!is_dir($this->certification_user_path)) {
  96. mkdir($this->certification_user_path, 0777);
  97. }
  98. }
  99. }
  100. /**
  101. * Deletes the current certificate object. This is generally triggered by
  102. * the teacher from the gradebook tool to re-generate the certificate because
  103. * the original version wa flawed.
  104. * @param bool $force_delete
  105. * @return bool
  106. */
  107. public function delete($force_delete = false)
  108. {
  109. $delete_db = false;
  110. if (!empty($this->certificate_data)) {
  111. if (!is_null($this->html_file) || $this->html_file != '' || strlen($this->html_file)) {
  112. // Deleting HTML file
  113. if (is_file($this->html_file)) {
  114. @unlink($this->html_file);
  115. if (is_file($this->html_file) === false) {
  116. $delete_db = true;
  117. } else {
  118. $delete_db = false;
  119. }
  120. }
  121. // Deleting QR code PNG image file
  122. if (is_file($this->qr_file)) {
  123. @unlink($this->qr_file);
  124. }
  125. if ($delete_db || $force_delete) {
  126. return parent::delete($this->certificate_data['id']);
  127. }
  128. } else {
  129. return parent::delete($this->certificate_data['id']);
  130. }
  131. }
  132. return false;
  133. }
  134. /**
  135. * Generates an HTML Certificate and fills the path_certificate field in the DB
  136. *
  137. * @param array $params
  138. * @param bool $sendNotification
  139. * @return bool|int
  140. */
  141. public function generate($params = [], $sendNotification = false)
  142. {
  143. // The user directory should be set
  144. if (empty($this->certification_user_path) &&
  145. $this->force_certificate_generation == false
  146. ) {
  147. return false;
  148. }
  149. $params['hide_print_button'] = isset($params['hide_print_button']) ? true : false;
  150. if (isset($this->certificate_data) && isset($this->certificate_data['cat_id'])) {
  151. $my_category = Category::load($this->certificate_data['cat_id']);
  152. }
  153. if (isset($my_category[0]) &&
  154. $my_category[0]->is_certificate_available($this->user_id)
  155. ) {
  156. $courseInfo = api_get_course_info($my_category[0]->get_course_code());
  157. $courseId = $courseInfo['real_id'];
  158. $sessionId = $my_category[0]->get_session_id();
  159. $skill = new Skill();
  160. $skill->addSkillToUser(
  161. $this->user_id,
  162. $this->certificate_data['cat_id'],
  163. $courseId,
  164. $sessionId
  165. );
  166. if (is_dir($this->certification_user_path)) {
  167. if (!empty($this->certificate_data)) {
  168. $new_content_html = GradebookUtils::get_user_certificate_content(
  169. $this->user_id,
  170. $my_category[0]->get_course_code(),
  171. $my_category[0]->get_session_id(),
  172. false,
  173. $params['hide_print_button']
  174. );
  175. if ($my_category[0]->get_id() == strval(intval($this->certificate_data['cat_id']))) {
  176. $name = $this->certificate_data['path_certificate'];
  177. $myPathCertificate = $this->certification_user_path.basename($name);
  178. if (file_exists($myPathCertificate) &&
  179. !empty($name) &&
  180. !is_dir($myPathCertificate) &&
  181. $this->force_certificate_generation == false
  182. ) {
  183. //Seems that the file was already generated
  184. return true;
  185. } else {
  186. // Creating new name
  187. $name = md5($this->user_id.$this->certificate_data['cat_id']).'.html';
  188. $myPathCertificate = $this->certification_user_path.$name;
  189. $path_certificate = '/'.$name;
  190. // Getting QR filename
  191. $file_info = pathinfo($path_certificate);
  192. $qr_code_filename = $this->certification_user_path.$file_info['filename'].'_qr.png';
  193. $newContent = str_replace(
  194. '((certificate_barcode))',
  195. Display::img(
  196. $this->certification_web_user_path.$file_info['filename'].'_qr.png',
  197. 'QR'
  198. ),
  199. $new_content_html['content']
  200. );
  201. $newContent = api_convert_encoding(
  202. $newContent,
  203. 'UTF-8',
  204. api_get_system_encoding()
  205. );
  206. $result = @file_put_contents($myPathCertificate, $newContent);
  207. if ($result) {
  208. // Updating the path
  209. self::updateUserCertificateInfo(
  210. $this->certificate_data['cat_id'],
  211. $this->user_id,
  212. $path_certificate
  213. );
  214. $this->certificate_data['path_certificate'] = $path_certificate;
  215. if ($this->isHtmlFileGenerated()) {
  216. if (!empty($file_info)) {
  217. $text = $this->parseCertificateVariables(
  218. $new_content_html['variables']
  219. );
  220. $this->generateQRImage(
  221. $text,
  222. $qr_code_filename
  223. );
  224. if ($sendNotification) {
  225. $subject = get_lang('NotificationCertificateSubject');
  226. $message = nl2br(get_lang('NotificationCertificateTemplate'));
  227. $score = $this->certificate_data['score_certificate'];
  228. Certificate::sendNotification(
  229. $subject,
  230. $message,
  231. api_get_user_info($this->user_id),
  232. $courseInfo,
  233. [
  234. 'score_certificate' => $score
  235. ]
  236. );
  237. }
  238. }
  239. }
  240. }
  241. return $result;
  242. }
  243. }
  244. }
  245. }
  246. }
  247. return false;
  248. }
  249. /**
  250. * @return array
  251. */
  252. public static function notificationTags()
  253. {
  254. $tags = [
  255. '((course_title))',
  256. '((user_first_name))',
  257. '((user_last_name))',
  258. '((author_first_name))',
  259. '((author_last_name))',
  260. '((score))',
  261. '((portal_name))'
  262. ];
  263. return $tags;
  264. }
  265. /**
  266. * @param string $subject
  267. * @param string $message
  268. * @param array $userInfo
  269. * @param array $courseInfo
  270. * @param array $certificateInfo
  271. *
  272. * @return bool
  273. */
  274. public static function sendNotification(
  275. $subject,
  276. $message,
  277. $userInfo,
  278. $courseInfo,
  279. $certificateInfo
  280. ) {
  281. if (empty($userInfo) || empty($courseInfo)) {
  282. return false;
  283. }
  284. $currentUserInfo = api_get_user_info();
  285. $replace = [
  286. $courseInfo['title'],
  287. $userInfo['firstname'],
  288. $userInfo['lastname'],
  289. $currentUserInfo['firstname'],
  290. $currentUserInfo['lastname'],
  291. $certificateInfo['score_certificate'],
  292. api_get_setting('Institution')
  293. ];
  294. $message = str_replace(self::notificationTags(), $replace, $message);
  295. MessageManager::send_message(
  296. $userInfo['id'],
  297. $subject,
  298. $message,
  299. [],
  300. [],
  301. 0,
  302. 0,
  303. 0,
  304. 0,
  305. $currentUserInfo['id']
  306. );
  307. $plugin = new AppPlugin();
  308. $smsPlugin = $plugin->getSMSPluginLibrary();
  309. if ($smsPlugin) {
  310. $additionalParameters = array(
  311. 'smsType' => SmsPlugin::CERTIFICATE_NOTIFICATION,
  312. 'userId' => $userInfo['id'],
  313. 'direct_message' => $message
  314. );
  315. $smsPlugin->send($additionalParameters);
  316. }
  317. }
  318. /**
  319. * update user info about certificate
  320. * @param int $cat_id category id
  321. * @param int $user_id user id
  322. * @param string $path_certificate the path name of the certificate
  323. * @return void
  324. */
  325. public function updateUserCertificateInfo(
  326. $cat_id,
  327. $user_id,
  328. $path_certificate
  329. ) {
  330. $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
  331. $now = api_get_utc_datetime();
  332. if (!UserManager::is_user_certified($cat_id, $user_id)) {
  333. $sql = 'UPDATE '.$table.' SET
  334. path_certificate="'.Database::escape_string($path_certificate).'",
  335. created_at = "'.$now.'"
  336. WHERE cat_id="'.intval($cat_id).'" AND user_id="'.intval($user_id).'" ';
  337. Database::query($sql);
  338. }
  339. }
  340. /**
  341. * Check if the file was generated
  342. *
  343. * @return boolean
  344. */
  345. public function isHtmlFileGenerated()
  346. {
  347. if (empty($this->certification_user_path)) {
  348. return false;
  349. }
  350. if (!empty($this->certificate_data) &&
  351. isset($this->certificate_data['path_certificate']) &&
  352. !empty($this->certificate_data['path_certificate'])
  353. ) {
  354. return true;
  355. }
  356. return false;
  357. }
  358. /**
  359. * Generates a QR code for the certificate. The QR code embeds the text given
  360. * @param string $text Text to be added in the QR code
  361. * @param string $path file path of the image
  362. * @return bool
  363. **/
  364. public function generateQRImage($text, $path)
  365. {
  366. //Make sure HTML certificate is generated
  367. if (!empty($text) && !empty($path)) {
  368. //L low, M - Medium, L large error correction
  369. return PHPQRCode\QRcode::png($text, $path, 'M', 2, 2);
  370. }
  371. return false;
  372. }
  373. /**
  374. * Transforms certificate tags into text values. This function is very static
  375. * (it doesn't allow for much flexibility in terms of what tags are printed).
  376. * @param array $array Contains two array entries: first are the headers,
  377. * second is an array of contents
  378. * @return string The translated string
  379. */
  380. public function parseCertificateVariables($array)
  381. {
  382. $headers = $array[0];
  383. $content = $array[1];
  384. $final_content = array();
  385. if (!empty($content)) {
  386. foreach ($content as $key => $value) {
  387. $my_header = str_replace(array('((', '))'), '', $headers[$key]);
  388. $final_content[$my_header] = $value;
  389. }
  390. }
  391. /* Certificate tags
  392. *
  393. 0 => string '((user_firstname))' (length=18)
  394. 1 => string '((user_lastname))' (length=17)
  395. 2 => string '((gradebook_institution))' (length=25)
  396. 3 => string '((gradebook_sitename))' (length=22)
  397. 4 => string '((teacher_firstname))' (length=21)
  398. 5 => string '((teacher_lastname))' (length=20)
  399. 6 => string '((official_code))' (length=17)
  400. 7 => string '((date_certificate))' (length=20)
  401. 8 => string '((course_code))' (length=15)
  402. 9 => string '((course_title))' (length=16)
  403. 10 => string '((gradebook_grade))' (length=19)
  404. 11 => string '((certificate_link))' (length=20)
  405. 12 => string '((certificate_link_html))' (length=25)
  406. 13 => string '((certificate_barcode))' (length=23)
  407. */
  408. $break_space = " \n\r ";
  409. $text =
  410. $final_content['gradebook_institution'].' - '.
  411. $final_content['gradebook_sitename'].' - '.
  412. get_lang('Certification').$break_space.
  413. get_lang('Student').': '.$final_content['user_firstname'].' '.$final_content['user_lastname'].$break_space.
  414. get_lang('Teacher').': '.$final_content['teacher_firstname'].' '.$final_content['teacher_lastname'].$break_space.
  415. get_lang('Date').': '.$final_content['date_certificate'].$break_space.
  416. get_lang('Score').': '.$final_content['gradebook_grade'].$break_space.
  417. 'URL'.': '.$final_content['certificate_link'];
  418. return $text;
  419. }
  420. /**
  421. * Check if the certificate is visible for the current user
  422. * If the global setting allow_public_certificates is set to 'false', no certificate can be printed.
  423. * If the global allow_public_certificates is set to 'true' and the course setting allow_public_certificates
  424. * is set to 0, no certificate *in this course* can be printed (for anonymous users).
  425. * Connected users can always print them.
  426. * @return bool
  427. */
  428. public function isVisible()
  429. {
  430. if (!api_is_anonymous()) {
  431. return true;
  432. }
  433. if (api_get_setting('allow_public_certificates') != 'true') {
  434. // The "non-public" setting is set, so do not print
  435. return false;
  436. }
  437. if (!isset($this->certificate_data, $this->certificate_data['cat_id'])) {
  438. return false;
  439. }
  440. $gradebook = new Gradebook();
  441. $gradebook_info = $gradebook->get($this->certificate_data['cat_id']);
  442. if (empty($gradebook_info['course_code'])) {
  443. return false;
  444. }
  445. if (api_get_course_setting('allow_public_certificates', $gradebook_info['course_code']) == 0) {
  446. // Printing not allowed
  447. return false;
  448. }
  449. return true;
  450. }
  451. /**
  452. * Check if the certificate is available
  453. * @return bool
  454. */
  455. public function isAvailable()
  456. {
  457. if (empty($this->certificate_data['path_certificate'])) {
  458. return false;
  459. }
  460. $user_certificate = $this->certification_user_path.basename($this->certificate_data['path_certificate']);
  461. if (!file_exists($user_certificate)) {
  462. return false;
  463. }
  464. return true;
  465. }
  466. /**
  467. * Shows the student's certificate (HTML file)
  468. */
  469. public function show()
  470. {
  471. header('Content-Type: text/html; charset='.api_get_system_encoding());
  472. $user_certificate = $this->certification_user_path.basename($this->certificate_data['path_certificate']);
  473. if (file_exists($user_certificate)) {
  474. $certificateContent = (string) file_get_contents($user_certificate);
  475. if ($this->user_id == api_get_user_id() && !empty($this->certificate_data)) {
  476. $certificateId = $this->certificate_data['id'];
  477. $extraFieldValue = new ExtraFieldValue('user_certificate');
  478. $value = $extraFieldValue->get_values_by_handler_and_field_variable(
  479. $certificateId,
  480. 'downloaded_at'
  481. );
  482. if (empty($value)) {
  483. $params = [
  484. 'item_id' => $this->certificate_data['id'],
  485. 'extra_downloaded_at' => api_get_utc_datetime(),
  486. ];
  487. $extraFieldValue->saveFieldValues($params);
  488. }
  489. }
  490. echo $certificateContent;
  491. exit;
  492. }
  493. api_not_allowed(true);
  494. }
  495. }