career.lib.php 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use Fhaculty\Graph\Graph;
  4. use Fhaculty\Graph\Vertex;
  5. /**
  6. * Class Career.
  7. */
  8. class Career extends Model
  9. {
  10. public $table;
  11. public $columns = [
  12. 'id',
  13. 'name',
  14. 'description',
  15. 'status',
  16. 'created_at',
  17. 'updated_at',
  18. ];
  19. /**
  20. * Constructor.
  21. */
  22. public function __construct()
  23. {
  24. $this->table = Database::get_main_table(TABLE_CAREER);
  25. }
  26. /**
  27. * Get the count of elements.
  28. *
  29. * @return int
  30. */
  31. public function get_count()
  32. {
  33. $row = Database::select(
  34. 'count(*) as count',
  35. $this->table,
  36. [],
  37. 'first'
  38. );
  39. return $row['count'];
  40. }
  41. /**
  42. * @param array $where_conditions
  43. *
  44. * @return array
  45. */
  46. public function get_all($where_conditions = [])
  47. {
  48. return Database::select(
  49. '*',
  50. $this->table,
  51. ['where' => $where_conditions, 'order' => 'name ASC']
  52. );
  53. }
  54. /**
  55. * Update all promotion status by career.
  56. *
  57. * @param int $career_id
  58. * @param int $status (1 or 0)
  59. */
  60. public function update_all_promotion_status_by_career_id($career_id, $status)
  61. {
  62. $promotion = new Promotion();
  63. $promotion_list = $promotion->get_all_promotions_by_career_id($career_id);
  64. if (!empty($promotion_list)) {
  65. foreach ($promotion_list as $item) {
  66. $params['id'] = $item['id'];
  67. $params['status'] = $status;
  68. $promotion->update($params);
  69. $promotion->update_all_sessions_status_by_promotion_id($params['id'], $status);
  70. }
  71. }
  72. }
  73. /**
  74. * Returns HTML the title + grid.
  75. *
  76. * @return string
  77. */
  78. public function display()
  79. {
  80. $html = '<div class="actions" style="margin-bottom:20px">';
  81. $html .= '<a href="career_dashboard.php">'.
  82. Display::return_icon('back.png', get_lang('Back'), '', ICON_SIZE_MEDIUM).'</a>';
  83. if (api_is_platform_admin()) {
  84. $html .= '<a href="'.api_get_self().'?action=add">'.
  85. Display::return_icon('new_career.png', get_lang('Add'), '', ICON_SIZE_MEDIUM).'</a>';
  86. }
  87. $html .= '</div>';
  88. $html .= Display::grid_html('careers');
  89. return $html;
  90. }
  91. /**
  92. * @return array
  93. */
  94. public function get_status_list()
  95. {
  96. return [
  97. CAREER_STATUS_ACTIVE => get_lang('Unarchived'),
  98. CAREER_STATUS_INACTIVE => get_lang('Archived'),
  99. ];
  100. }
  101. /**
  102. * Returns a Form validator Obj.
  103. *
  104. * @todo the form should be auto generated
  105. *
  106. * @param string $url
  107. * @param string $action add, edit
  108. *
  109. * @return FormValidator
  110. */
  111. public function return_form($url, $action)
  112. {
  113. $form = new FormValidator('career', 'post', $url);
  114. // Setting the form elements
  115. $header = get_lang('Add');
  116. if ($action == 'edit') {
  117. $header = get_lang('Edit');
  118. }
  119. $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
  120. $form->addHeader($header);
  121. $form->addHidden('id', $id);
  122. $form->addElement('text', 'name', get_lang('Name'), ['size' => '70']);
  123. $form->addHtmlEditor(
  124. 'description',
  125. get_lang('Description'),
  126. false,
  127. false,
  128. [
  129. 'ToolbarSet' => 'Careers',
  130. 'Width' => '100%',
  131. 'Height' => '250',
  132. ]
  133. );
  134. $status_list = $this->get_status_list();
  135. $form->addElement('select', 'status', get_lang('Status'), $status_list);
  136. if ($action == 'edit') {
  137. $extraField = new ExtraField('career');
  138. $extraField->addElements($form, $id);
  139. $form->addElement('text', 'created_at', get_lang('Created at'));
  140. $form->freeze('created_at');
  141. $form->addButtonSave(get_lang('Edit'));
  142. } else {
  143. $form->addButtonCreate(get_lang('Add'));
  144. }
  145. // Setting the defaults
  146. $defaults = $this->get($id);
  147. if (!empty($defaults['created_at'])) {
  148. $defaults['created_at'] = api_convert_and_format_date($defaults['created_at']);
  149. }
  150. if (!empty($defaults['updated_at'])) {
  151. $defaults['updated_at'] = api_convert_and_format_date($defaults['updated_at']);
  152. }
  153. $form->setDefaults($defaults);
  154. // Setting the rules
  155. $form->addRule('name', get_lang('Required field'), 'required');
  156. return $form;
  157. }
  158. /**
  159. * Copies the career to a new one.
  160. *
  161. * @param int Career ID
  162. * @param bool Whether or not to copy the promotions inside
  163. *
  164. * @return int New career ID on success, false on failure
  165. */
  166. public function copy($id, $copy_promotions = false)
  167. {
  168. $career = $this->get($id);
  169. $new = [];
  170. foreach ($career as $key => $val) {
  171. switch ($key) {
  172. case 'id':
  173. case 'updated_at':
  174. break;
  175. case 'name':
  176. $val .= ' '.get_lang('Copy');
  177. $new[$key] = $val;
  178. break;
  179. case 'created_at':
  180. $val = api_get_utc_datetime();
  181. $new[$key] = $val;
  182. break;
  183. default:
  184. $new[$key] = $val;
  185. break;
  186. }
  187. }
  188. $cid = $this->save($new);
  189. if ($copy_promotions) {
  190. //Now also copy each session of the promotion as a new session and register it inside the promotion
  191. $promotion = new Promotion();
  192. $promo_list = $promotion->get_all_promotions_by_career_id($id);
  193. if (!empty($promo_list)) {
  194. foreach ($promo_list as $item) {
  195. $promotion->copy($item['id'], $cid, true);
  196. }
  197. }
  198. }
  199. return $cid;
  200. }
  201. /**
  202. * @param int $career_id
  203. *
  204. * @return bool
  205. */
  206. public function get_status($career_id)
  207. {
  208. $table = Database::get_main_table(TABLE_CAREER);
  209. $career_id = intval($career_id);
  210. $sql = "SELECT status FROM $table WHERE id = '$career_id'";
  211. $result = Database::query($sql);
  212. if (Database::num_rows($result) > 0) {
  213. $data = Database::fetch_array($result);
  214. return $data['status'];
  215. } else {
  216. return false;
  217. }
  218. }
  219. /**
  220. * @param array $params
  221. * @param bool $show_query
  222. *
  223. * @return int
  224. */
  225. public function save($params, $show_query = false)
  226. {
  227. $career = new \Chamilo\CoreBundle\Entity\Career();
  228. $career
  229. ->setName($params['name'])
  230. ->setStatus($params['status'])
  231. ->setDescription($params['description']);
  232. Database::getManager()->persist($career);
  233. Database::getManager()->flush();
  234. if ($career->getId()) {
  235. Event::addEvent(
  236. LOG_CAREER_CREATE,
  237. LOG_CAREER_ID,
  238. $career->getId(),
  239. api_get_utc_datetime(),
  240. api_get_user_id()
  241. );
  242. }
  243. return $career->getId();
  244. }
  245. /**
  246. * Delete a record from the career table and report in the default events log table.
  247. *
  248. * @param int $id The ID of the career to delete
  249. *
  250. * @return bool True if the career could be deleted, false otherwise
  251. */
  252. public function delete($id)
  253. {
  254. $res = parent::delete($id);
  255. if ($res) {
  256. $extraFieldValues = new ExtraFieldValue('career');
  257. $extraFieldValues->deleteValuesByItem($id);
  258. Event::addEvent(
  259. LOG_CAREER_DELETE,
  260. LOG_CAREER_ID,
  261. $id,
  262. api_get_utc_datetime(),
  263. api_get_user_id()
  264. );
  265. }
  266. return $res;
  267. }
  268. /**
  269. * {@inheritdoc}
  270. */
  271. public function update($params, $showQuery = false)
  272. {
  273. if (isset($params['description'])) {
  274. $params['description'] = Security::remove_XSS($params['description']);
  275. }
  276. return parent::update($params, $showQuery);
  277. }
  278. /**
  279. * @param array
  280. * @param Graph $graph
  281. *
  282. * @return string
  283. */
  284. public static function renderDiagram($careerInfo, $graph)
  285. {
  286. if (!($graph instanceof Graph)) {
  287. return '';
  288. }
  289. // Getting max column
  290. $maxColumn = 0;
  291. foreach ($graph->getVertices() as $vertex) {
  292. $groupId = (int) $vertex->getGroup();
  293. if ($groupId > $maxColumn) {
  294. $maxColumn = $groupId;
  295. }
  296. }
  297. $list = [];
  298. /** @var Vertex $vertex */
  299. foreach ($graph->getVertices() as $vertex) {
  300. $group = $vertex->getAttribute('Group');
  301. $groupData = explode(':', $group);
  302. $group = $groupData[0];
  303. $groupLabel = isset($groupData[1]) ? $groupData[1] : '';
  304. $subGroup = $vertex->getAttribute('SubGroup');
  305. $subGroupData = explode(':', $subGroup);
  306. $column = $vertex->getGroup();
  307. $row = $vertex->getAttribute('Row');
  308. $subGroupId = $subGroupData[0];
  309. $label = isset($subGroupData[1]) ? $subGroupData[1] : '';
  310. $list[$group][$subGroupId]['columns'][$column][$row] = $vertex;
  311. $list[$group][$subGroupId]['label'] = $label;
  312. $list[$group]['label'] = $groupLabel;
  313. }
  314. $maxGroups = count($list);
  315. $widthGroup = 30;
  316. if (!empty($maxGroups)) {
  317. $widthGroup = 85 / $maxGroups;
  318. }
  319. $connections = '';
  320. $groupDrawLine = [];
  321. $groupCourseList = [];
  322. // Read Connections column
  323. foreach ($list as $group => $subGroupList) {
  324. foreach ($subGroupList as $subGroupData) {
  325. $columns = isset($subGroupData['columns']) ? $subGroupData['columns'] : [];
  326. $showGroupLine = true;
  327. if (count($columns) == 1) {
  328. $showGroupLine = false;
  329. }
  330. $groupDrawLine[$group] = $showGroupLine;
  331. //if ($showGroupLine == false) {
  332. /** @var Vertex $vertex */
  333. foreach ($columns as $row => $items) {
  334. foreach ($items as $vertex) {
  335. if ($vertex instanceof Vertex) {
  336. $groupCourseList[$group][] = $vertex->getId();
  337. $connectionList = $vertex->getAttribute('Connections');
  338. $firstConnection = '';
  339. $secondConnection = '';
  340. if (!empty($connectionList)) {
  341. $explode = explode('-', $connectionList);
  342. $pos = strpos($explode[0], 'SG');
  343. if ($pos === false) {
  344. $pos = strpos($explode[0], 'G');
  345. if (is_numeric($pos)) {
  346. // group_123 id
  347. $groupValueId = (int) str_replace(
  348. 'G',
  349. '',
  350. $explode[0]
  351. );
  352. $firstConnection = 'group_'.$groupValueId;
  353. $groupDrawLine[$groupValueId] = true;
  354. } else {
  355. // Course block (row_123 id)
  356. if (!empty($explode[0])) {
  357. $firstConnection = 'row_'.(int) $explode[0];
  358. }
  359. }
  360. } else {
  361. // subgroup__123 id
  362. $firstConnection = 'subgroup_'.(int) str_replace('SG', '', $explode[0]);
  363. }
  364. $pos = strpos($explode[1], 'SG');
  365. if ($pos === false) {
  366. $pos = strpos($explode[1], 'G');
  367. if (is_numeric($pos)) {
  368. $groupValueId = (int) str_replace(
  369. 'G',
  370. '',
  371. $explode[1]
  372. );
  373. $secondConnection = 'group_'.$groupValueId;
  374. $groupDrawLine[$groupValueId] = true;
  375. } else {
  376. // Course block (row_123 id)
  377. if (!empty($explode[0])) {
  378. $secondConnection = 'row_'.(int) $explode[1];
  379. }
  380. }
  381. } else {
  382. $secondConnection = 'subgroup_'.(int) str_replace('SG', '', $explode[1]);
  383. }
  384. if (!empty($firstConnection) && !empty($firstConnection)) {
  385. $connections .= self::createConnection(
  386. $firstConnection,
  387. $secondConnection,
  388. ['Left', 'Right']
  389. );
  390. }
  391. }
  392. }
  393. }
  394. }
  395. //}
  396. }
  397. }
  398. $graphHtml = '<div class="container">';
  399. foreach ($list as $group => $subGroupList) {
  400. $showGroupLine = false;
  401. if (isset($groupDrawLine[$group]) && $groupDrawLine[$group]) {
  402. $showGroupLine = true;
  403. }
  404. $graphHtml .= self::parseSubGroups(
  405. $groupCourseList,
  406. $group,
  407. $list[$group]['label'],
  408. $showGroupLine,
  409. $subGroupList,
  410. $widthGroup
  411. );
  412. }
  413. $graphHtml .= '</div>';
  414. $graphHtml .= $connections;
  415. return $graphHtml;
  416. }
  417. /**
  418. * @param array $careerInfo
  419. * @param Template $tpl
  420. * @param int $loadUserIdData
  421. *
  422. * @return string
  423. */
  424. public static function renderDiagramByColumn($careerInfo, $tpl, $loadUserIdData = 0)
  425. {
  426. $careerId = isset($careerInfo['id']) ? $careerInfo['id'] : 0;
  427. if (empty($careerId)) {
  428. return '';
  429. }
  430. $extraFieldValue = new ExtraFieldValue('career');
  431. $item = $extraFieldValue->get_values_by_handler_and_field_variable(
  432. $careerId,
  433. 'career_diagram',
  434. false,
  435. false,
  436. false
  437. );
  438. $graph = null;
  439. if (!empty($item) && isset($item['value']) && !empty($item['value'])) {
  440. /** @var Graph $graph */
  441. $graph = UnserializeApi::unserialize('career', $item['value']);
  442. }
  443. if (!($graph instanceof Graph)) {
  444. return '';
  445. }
  446. // Getting max column
  447. $maxColumn = 0;
  448. foreach ($graph->getVertices() as $vertex) {
  449. $groupId = (int) $vertex->getGroup();
  450. if ($groupId > $maxColumn) {
  451. $maxColumn = $groupId;
  452. }
  453. }
  454. $userResult = [];
  455. if (!empty($loadUserIdData)) {
  456. $careerData = UserManager::getUserCareer($loadUserIdData, $careerId);
  457. if (isset($careerData['extra_data']) && !empty($careerData['extra_data'])) {
  458. $userResult = unserialize($careerData['extra_data']);
  459. }
  460. }
  461. $list = [];
  462. $subGroups = [];
  463. /** @var Vertex $vertex */
  464. foreach ($graph->getVertices() as $vertex) {
  465. $column = $vertex->getGroup();
  466. $group = $vertex->getAttribute('Group');
  467. $groupData = explode(':', $group);
  468. $group = $groupData[0];
  469. $groupLabel = isset($groupData[1]) ? $groupData[1] : '';
  470. $subGroup = $vertex->getAttribute('SubGroup');
  471. $subGroupData = explode(':', $subGroup);
  472. $row = $vertex->getAttribute('Row');
  473. $subGroupId = $subGroupData[0];
  474. $subGroupLabel = isset($subGroupData[1]) ? $subGroupData[1] : '';
  475. if (!empty($subGroupId) && !in_array($subGroupId, $subGroups)) {
  476. $subGroups[$subGroupId]['items'][] = $vertex->getId();
  477. $subGroups[$subGroupId]['label'] = $subGroupLabel;
  478. }
  479. $list[$column]['rows'][$row]['items'][] = $vertex;
  480. $list[$column]['rows'][$row]['label'] = $subGroupId;
  481. $list[$column]['rows'][$row]['group'] = $group;
  482. $list[$column]['rows'][$row]['group_label'] = $groupLabel;
  483. $list[$column]['rows'][$row]['subgroup'] = $subGroup;
  484. $list[$column]['rows'][$row]['subgroup_label'] = $subGroupLabel;
  485. $list[$column]['label'] = $groupLabel;
  486. $list[$column]['column'] = $column;
  487. }
  488. $groupCourseList = [];
  489. $simpleConnectionList = [];
  490. // Read Connections column
  491. foreach ($list as $column => $groupList) {
  492. foreach ($groupList['rows'] as $subGroupList) {
  493. /** @var Vertex $vertex */
  494. foreach ($subGroupList['items'] as $vertex) {
  495. if ($vertex instanceof Vertex) {
  496. $groupCourseList[$vertex->getAttribute('Column')][] = $vertex->getId();
  497. $connectionList = $vertex->getAttribute('Connections');
  498. if (empty($connectionList)) {
  499. continue;
  500. }
  501. $simpleFirstConnection = '';
  502. $simpleSecondConnection = '';
  503. $explode = explode('-', $connectionList);
  504. $pos = strpos($explode[0], 'SG');
  505. if ($pos === false) {
  506. $pos = strpos($explode[0], 'G');
  507. if (is_numeric($pos)) {
  508. // Is group
  509. $groupValueId = (int) str_replace(
  510. 'G',
  511. '',
  512. $explode[0]
  513. );
  514. $simpleFirstConnection = 'g'.(int) $groupValueId;
  515. } else {
  516. // Course block (row_123 id)
  517. if (!empty($explode[0])) {
  518. $simpleFirstConnection = 'v'.$explode[0];
  519. }
  520. }
  521. } else {
  522. // subgroup__123 id
  523. $simpleFirstConnection = 'sg'.(int) str_replace('SG', '', $explode[0]);
  524. }
  525. $pos = false;
  526. if (isset($explode[1])) {
  527. $pos = strpos($explode[1], 'SG');
  528. }
  529. if ($pos === false) {
  530. if (isset($explode[1])) {
  531. $pos = strpos($explode[1], 'G');
  532. $value = $explode[1];
  533. }
  534. if (is_numeric($pos)) {
  535. $groupValueId = (int) str_replace(
  536. 'G',
  537. '',
  538. $value
  539. );
  540. $simpleSecondConnection = 'g'.(int) $groupValueId;
  541. } else {
  542. // Course block (row_123 id)
  543. if (!empty($explode[0]) && isset($explode[1])) {
  544. $simpleSecondConnection = 'v'.(int) $explode[1];
  545. }
  546. }
  547. } else {
  548. $simpleSecondConnection = 'sg'.(int) str_replace('SG', '', $explode[1]);
  549. }
  550. if (!empty($simpleFirstConnection) && !empty($simpleSecondConnection)) {
  551. $simpleConnectionList[] = [
  552. 'from' => $simpleFirstConnection,
  553. 'to' => $simpleSecondConnection,
  554. ];
  555. }
  556. }
  557. }
  558. }
  559. }
  560. $graphHtml = '';
  561. $groupsBetweenColumns = [];
  562. foreach ($list as $column => $columnList) {
  563. foreach ($columnList['rows'] as $subGroupList) {
  564. $newGroup = $subGroupList['group'];
  565. $label = $subGroupList['group_label'];
  566. $newOrder[$newGroup]['items'][] = $subGroupList;
  567. $newOrder[$newGroup]['label'] = $label;
  568. $groupsBetweenColumns[$newGroup][] = $subGroupList;
  569. }
  570. }
  571. // Creates graph
  572. $graph = new stdClass();
  573. $graph->blockWidth = 280;
  574. $graph->blockHeight = 150;
  575. $graph->xGap = 70;
  576. $graph->yGap = 55;
  577. $graph->xDiff = 70;
  578. $graph->yDiff = 55;
  579. if (!empty($userResult)) {
  580. $graph->blockHeight = 180;
  581. $graph->yGap = 60;
  582. $graph->yDiff = 60;
  583. }
  584. foreach ($groupsBetweenColumns as $group => $items) {
  585. self::parseColumnList($groupCourseList, $items, $graph, $simpleConnectionList, $userResult);
  586. }
  587. $graphHtml .= '<style>
  588. .panel-title {
  589. font-size: 11px;
  590. height: 40px;
  591. }
  592. </style>';
  593. // Create groups
  594. if (!empty($graph->groupList)) {
  595. $groupList = [];
  596. $groupDiffX = 20;
  597. $groupDiffY = 50;
  598. $style = 'whiteSpace=wrap;rounded;html=1;strokeColor=red;fillColor=none;strokeWidth=2;align=left;verticalAlign=top;';
  599. foreach ($graph->groupList as $id => $data) {
  600. if (empty($id)) {
  601. continue;
  602. }
  603. $x = $data['min_x'] - $groupDiffX;
  604. $y = $data['min_y'] - $groupDiffY;
  605. $width = $data['max_width'] + ($groupDiffX * 2);
  606. $height = $data['max_height'] + $groupDiffY * 2;
  607. $label = '<h4>'.$data['label'].'</h4>';
  608. $vertexData = "var g$id = graph.insertVertex(parent, null, '$label', $x, $y, $width, $height, '$style');";
  609. $groupList[] = $vertexData;
  610. }
  611. $tpl->assign('group_list', $groupList);
  612. }
  613. // Create subgroups
  614. $subGroupList = [];
  615. $subGroupListData = [];
  616. foreach ($subGroups as $subGroupId => $vertexData) {
  617. $label = $vertexData['label'];
  618. $vertexIdList = $vertexData['items'];
  619. foreach ($vertexIdList as $rowId) {
  620. $data = $graph->allData[$rowId];
  621. $originalRow = $data['row'];
  622. $column = $data['column'];
  623. $x = $data['x'];
  624. $y = $data['y'];
  625. $width = $data['width'];
  626. $height = $data['height'];
  627. if (!isset($subGroupListData[$subGroupId])) {
  628. $subGroupListData[$subGroupId]['min_x'] = 1000;
  629. $subGroupListData[$subGroupId]['min_y'] = 1000;
  630. $subGroupListData[$subGroupId]['max_width'] = 0;
  631. $subGroupListData[$subGroupId]['max_height'] = 0;
  632. $subGroupListData[$subGroupId]['label'] = $label;
  633. }
  634. if ($x < $subGroupListData[$subGroupId]['min_x']) {
  635. $subGroupListData[$subGroupId]['min_x'] = $x;
  636. }
  637. if ($y < $subGroupListData[$subGroupId]['min_y']) {
  638. $subGroupListData[$subGroupId]['min_y'] = $y;
  639. }
  640. $subGroupListData[$subGroupId]['max_width'] = ($column + 1) * ($width + $graph->xGap) - $subGroupListData[$subGroupId]['min_x'];
  641. $subGroupListData[$subGroupId]['max_height'] = ($originalRow + 1) * ($height + $graph->yGap) - $subGroupListData[$subGroupId]['min_y'];
  642. }
  643. $style = 'whiteSpace=wrap;rounded;dashed=1;strokeColor=blue;fillColor=none;strokeWidth=2;align=left;verticalAlign=bottom;';
  644. $subGroupDiffX = 5;
  645. foreach ($subGroupListData as $subGroupId => $data) {
  646. $x = $data['min_x'] - $subGroupDiffX;
  647. $y = $data['min_y'] - $subGroupDiffX;
  648. $spaceForSubGroupTitle = 0;
  649. if (!empty($data['label'])) {
  650. $spaceForSubGroupTitle = 40;
  651. }
  652. $width = $data['max_width'] + $subGroupDiffX * 2;
  653. $height = $data['max_height'] + $subGroupDiffX * 2 + $spaceForSubGroupTitle;
  654. $label = '<h4 style="background: white">'.$data['label'].'</h4>';
  655. $vertexData = "var sg$subGroupId = graph.insertVertex(parent, null, '$label', $x, $y, $width, $height, '$style');";
  656. $subGroupList[] = $vertexData;
  657. }
  658. }
  659. // Create connections (arrows)
  660. if (!empty($simpleConnectionList)) {
  661. $connectionList = [];
  662. //$style = 'endArrow=classic;html=1;strokeWidth=4;exitX=1;exitY=0.5;entryX=0;entryY=0.5;';
  663. $style = '';
  664. foreach ($simpleConnectionList as $connection) {
  665. $from = $connection['from'];
  666. $to = $connection['to'];
  667. $vertexData = "var e1 = graph.insertEdge(parent, null, '', $from, $to, '$style')";
  668. $connectionList[] = $vertexData;
  669. }
  670. $tpl->assign('connections', $connectionList);
  671. }
  672. $tpl->assign('subgroup_list', $subGroupList);
  673. $tpl->assign('vertex_list', $graph->elementList);
  674. $graphHtml .= '<div id="graphContainer"></div>';
  675. return $graphHtml;
  676. }
  677. /**
  678. * @param $groupCourseList
  679. * @param $columnList
  680. * @param $graph
  681. * @param $connections
  682. * @param $userResult
  683. *
  684. * @return string
  685. */
  686. public static function parseColumnList($groupCourseList, $columnList, &$graph, &$connections, $userResult)
  687. {
  688. $graphHtml = '';
  689. $oldGroup = null;
  690. $newOrder = [];
  691. foreach ($columnList as $key => $subGroupList) {
  692. $newGroup = $subGroupList['group'];
  693. $label = $subGroupList['group_label'];
  694. $newOrder[$newGroup]['items'][] = $subGroupList;
  695. $newOrder[$newGroup]['label'] = $label;
  696. }
  697. foreach ($newOrder as $newGroup => $data) {
  698. $label = $data['label'];
  699. $subGroupList = $data['items'];
  700. if (!isset($graph->groupList[$newGroup])) {
  701. $graph->groupList[$newGroup]['min_x'] = 1000;
  702. $graph->groupList[$newGroup]['min_y'] = 1000;
  703. $graph->groupList[$newGroup]['max_width'] = 0;
  704. $graph->groupList[$newGroup]['max_height'] = 0;
  705. $graph->groupList[$newGroup]['label'] = $label;
  706. }
  707. $maxColumn = 0;
  708. $maxRow = 0;
  709. $minColumn = 100;
  710. $minRow = 100;
  711. foreach ($subGroupList as $item) {
  712. /** @var Vertex $vertex */
  713. foreach ($item['items'] as $vertex) {
  714. $column = $vertex->getAttribute('Column');
  715. $realRow = $vertex->getAttribute('Row');
  716. if ($column > $maxColumn) {
  717. $maxColumn = $column;
  718. }
  719. if ($realRow > $maxRow) {
  720. $maxRow = $realRow;
  721. }
  722. if ($column < $minColumn) {
  723. $minColumn = $column;
  724. }
  725. if ($realRow < $minRow) {
  726. $minRow = $realRow;
  727. }
  728. }
  729. }
  730. if (!empty($newGroup)) {
  731. $graphHtml .= '<div
  732. id ="group_'.$newGroup.'"
  733. class="group'.$newGroup.' group_class"
  734. style="display:grid;
  735. align-self: start;
  736. grid-gap: 10px;
  737. justify-items: stretch;
  738. align-items: start;
  739. align-content: start;
  740. justify-content: stretch;
  741. grid-area:'.$minRow.'/'.$minColumn.'/'.$maxRow.'/'.$maxColumn.'">'; //style="display:grid"
  742. }
  743. $addRow = 0;
  744. if (!empty($label)) {
  745. $graphHtml .= "<div class='my_label' style='grid-area:$minRow/$minColumn/$maxRow/$maxColumn'>$label</div>";
  746. $addRow = 1;
  747. }
  748. foreach ($subGroupList as $item) {
  749. $graphHtml .= self::parseVertexList(
  750. $groupCourseList,
  751. $item['items'],
  752. $addRow,
  753. $graph,
  754. $newGroup,
  755. $connections,
  756. $userResult
  757. );
  758. }
  759. if (!empty($newGroup)) {
  760. $graphHtml .= '</div >';
  761. }
  762. }
  763. return $graphHtml;
  764. }
  765. /**
  766. * @param array $groupCourseList
  767. * @param array $vertexList
  768. * @param int $addRow
  769. * @param stdClass $graph
  770. * @param int $group
  771. * @param array $connections
  772. * @param array $userResult
  773. *
  774. * @return string
  775. */
  776. public static function parseVertexList($groupCourseList, $vertexList, $addRow = 0, &$graph, $group, &$connections, $userResult)
  777. {
  778. if (empty($vertexList)) {
  779. return '';
  780. }
  781. $graphHtml = '';
  782. /** @var Vertex $vertex */
  783. foreach ($vertexList as $vertex) {
  784. $column = $vertex->getAttribute('Column');
  785. $realRow = $originalRow = $vertex->getAttribute('Row');
  786. if ($addRow) {
  787. $realRow = $realRow + $addRow;
  788. }
  789. $id = $vertex->getId();
  790. $area = "$realRow/$column";
  791. $graphHtml .= '<div
  792. id = "row_wrapper_'.$id.'"
  793. data= "'.$originalRow.'-'.$column.'"
  794. style="
  795. align-self: start;
  796. justify-content: stretch;
  797. grid-area:'.$area.'"
  798. >';
  799. $color = '';
  800. if (!empty($vertex->getAttribute('DefinedColor'))) {
  801. $color = $vertex->getAttribute('DefinedColor');
  802. }
  803. $content = '<div class="pull-left">'.$vertex->getAttribute('Notes').'</div>';
  804. $content .= '<div class="pull-right">['.$id.']</div>';
  805. if (!empty($userResult) && isset($userResult[$id])) {
  806. $results = '';
  807. $size = 2;
  808. foreach ($userResult[$id] as $resultId => $iconData) {
  809. $icon = '';
  810. switch ($iconData['Icon']) {
  811. case 0:
  812. $icon = Display::returnFontAwesomeIcon('times-circle', $size);
  813. break;
  814. case 1:
  815. $icon = Display::returnFontAwesomeIcon('check-circle', $size);
  816. break;
  817. case 2:
  818. $icon = Display::returnFontAwesomeIcon('info-circle', $size);
  819. break;
  820. }
  821. if (substr($resultId, 0, 1) == 2) {
  822. $iconData['Description'] = 'Result Id = '.$resultId;
  823. }
  824. if (!empty($icon)) {
  825. $params = [
  826. 'id' => 'course_'.$id.'_'.$resultId,
  827. 'data-toggle' => 'popover',
  828. 'title' => 'Popover title',
  829. 'class' => 'popup',
  830. 'data-description' => $iconData['Description'],
  831. 'data-period' => $iconData['Period'],
  832. 'data-teacher-text' => $iconData['TeacherText'],
  833. 'data-teacher' => $iconData['TeacherUsername'],
  834. 'data-score' => $iconData['ScoreText'],
  835. 'data-score-value' => $iconData['ScoreValue'],
  836. 'data-info' => $iconData['Info'],
  837. 'data-background-color' => $iconData['BgColor'],
  838. 'data-color' => $iconData['Color'],
  839. 'data-border-color' => $iconData['BorderColor'],
  840. 'style' => 'color:'.$iconData['IconColor'],
  841. ];
  842. $results .= Display::url($icon, 'javascript:void(0);', $params);
  843. }
  844. }
  845. if (!empty($results)) {
  846. $content .= '<div class="row"></div><div class="pull-right">'.$results.'</div>';
  847. }
  848. }
  849. $title = $vertex->getAttribute('graphviz.label');
  850. if (!empty($vertex->getAttribute('LinkedElement'))) {
  851. $title = Display::url($title, $vertex->getAttribute('LinkedElement'));
  852. }
  853. $originalRow--;
  854. $column--;
  855. //$title = "$originalRow / $column";
  856. $graphHtml .= Display::panel(
  857. $content,
  858. $title,
  859. null,
  860. null,
  861. null,
  862. "row_$id",
  863. $color
  864. );
  865. $panel = Display::panel(
  866. $content,
  867. $title,
  868. null,
  869. null,
  870. null,
  871. "row_$id",
  872. $color
  873. );
  874. $x = $column * $graph->blockWidth + $graph->xDiff;
  875. $y = $originalRow * $graph->blockHeight + $graph->yDiff;
  876. $width = $graph->blockWidth - $graph->xGap;
  877. $height = $graph->blockHeight - $graph->yGap;
  878. $style = 'text;html=1;strokeColor=green;fillColor=#ffffff;overflow=fill;rounded=0;align=left;';
  879. $panel = str_replace(["\n", "\r"], '', $panel);
  880. $vertexData = "var v$id = graph.insertVertex(parent, null, '".addslashes($panel)."', $x, $y, $width, $height, '$style');";
  881. $graph->elementList[$id] = $vertexData;
  882. $graph->allData[$id] = [
  883. 'x' => $x,
  884. 'y' => $y,
  885. 'width' => $width,
  886. 'height' => $height,
  887. 'row' => $originalRow,
  888. 'column' => $column,
  889. 'label' => $title,
  890. ];
  891. if ($x < $graph->groupList[$group]['min_x']) {
  892. $graph->groupList[$group]['min_x'] = $x;
  893. }
  894. if ($y < $graph->groupList[$group]['min_y']) {
  895. $graph->groupList[$group]['min_y'] = $y;
  896. }
  897. $graph->groupList[$group]['max_width'] = ($column + 1) * ($width + $graph->xGap) - $graph->groupList[$group]['min_x'];
  898. $graph->groupList[$group]['max_height'] = ($originalRow + 1) * ($height + ($graph->yGap)) - $graph->groupList[$group]['min_y'];
  899. $graphHtml .= '</div>';
  900. $arrow = $vertex->getAttribute('DrawArrowFrom');
  901. $found = false;
  902. if (!empty($arrow)) {
  903. $pos = strpos($arrow, 'SG');
  904. if ($pos === false) {
  905. $pos = strpos($arrow, 'G');
  906. if (is_numeric($pos)) {
  907. $parts = explode('G', $arrow);
  908. if (empty($parts[0]) && count($parts) == 2) {
  909. $groupArrow = $parts[1];
  910. $graphHtml .= self::createConnection(
  911. "group_$groupArrow",
  912. "row_$id",
  913. ['Left', 'Right']
  914. );
  915. $found = true;
  916. $connections[] = [
  917. 'from' => "g$groupArrow",
  918. 'to' => "v$id",
  919. ];
  920. }
  921. }
  922. } else {
  923. // Case is only one subgroup value example: SG1
  924. $parts = explode('SG', $arrow);
  925. if (empty($parts[0]) && count($parts) == 2) {
  926. $subGroupArrow = $parts[1];
  927. $graphHtml .= self::createConnection(
  928. "subgroup_$subGroupArrow",
  929. "row_$id",
  930. ['Left', 'Right']
  931. );
  932. $found = true;
  933. $connections[] = [
  934. 'from' => "sg$subGroupArrow",
  935. 'to' => "v$id",
  936. ];
  937. }
  938. }
  939. if ($found == false) {
  940. // case is connected to 2 subgroups: Example SG1-SG2
  941. $parts = explode('-', $arrow);
  942. if (count($parts) == 2 && !empty($parts[0]) && !empty($parts[1])) {
  943. $defaultArrow = ['Top', 'Bottom'];
  944. $firstPrefix = '';
  945. $firstId = '';
  946. $secondId = '';
  947. $secondPrefix = '';
  948. if (is_numeric($pos = strpos($parts[0], 'SG'))) {
  949. $firstPrefix = 'sg';
  950. $firstId = str_replace('SG', '', $parts[0]);
  951. }
  952. if (is_numeric($pos = strpos($parts[1], 'SG'))) {
  953. $secondPrefix = 'sg';
  954. $secondId = str_replace('SG', '', $parts[1]);
  955. }
  956. if (!empty($secondId) && !empty($firstId)) {
  957. $connections[] = [
  958. 'from' => $firstPrefix.$firstId,
  959. 'to' => $secondPrefix.$secondId,
  960. $defaultArrow,
  961. ];
  962. $found = true;
  963. }
  964. }
  965. }
  966. if ($found == false) {
  967. // case DrawArrowFrom is an integer
  968. $defaultArrow = ['Left', 'Right'];
  969. if (isset($groupCourseList[$column]) &&
  970. in_array($arrow, $groupCourseList[$column])
  971. ) {
  972. $defaultArrow = ['Top', 'Bottom'];
  973. }
  974. $graphHtml .= self::createConnection(
  975. "row_$arrow",
  976. "row_$id",
  977. $defaultArrow
  978. );
  979. $connections[] = [
  980. 'from' => "v$arrow",
  981. 'to' => "v$id",
  982. ];
  983. }
  984. }
  985. }
  986. return $graphHtml;
  987. }
  988. /**
  989. * @param array $groupCourseList list of groups and their courses
  990. * @param int $group
  991. * @param string $groupLabel
  992. * @param bool $showGroupLine
  993. * @param array $subGroupList
  994. * @param $widthGroup
  995. *
  996. * @return string
  997. */
  998. public static function parseSubGroups(
  999. $groupCourseList,
  1000. $group,
  1001. $groupLabel,
  1002. $showGroupLine,
  1003. $subGroupList,
  1004. $widthGroup
  1005. ) {
  1006. $topValue = 90;
  1007. $defaultSpace = 40;
  1008. $leftGroup = $defaultSpace.'px';
  1009. if ($group == 1) {
  1010. $leftGroup = 0;
  1011. }
  1012. $groupIdTag = "group_$group";
  1013. $borderLine = $showGroupLine === true ? 'border-style:solid;' : '';
  1014. $graphHtml = '<div
  1015. id="'.$groupIdTag.'" class="career_group"
  1016. style=" '.$borderLine.' padding:15px; float:left; margin-left:'.$leftGroup.'; width:'.$widthGroup.'%">';
  1017. if (!empty($groupLabel)) {
  1018. $graphHtml .= '<h3>'.$groupLabel.'</h3>';
  1019. }
  1020. foreach ($subGroupList as $subGroup => $subGroupData) {
  1021. $subGroupLabel = isset($subGroupData['label']) ? $subGroupData['label'] : '';
  1022. $columnList = isset($subGroupData['columns']) ? $subGroupData['columns'] : [];
  1023. if (empty($columnList)) {
  1024. continue;
  1025. }
  1026. $line = '';
  1027. if (!empty($subGroup)) {
  1028. $line = 'border-style:solid;';
  1029. }
  1030. // padding:15px;
  1031. $graphHtml .= '<div
  1032. id="subgroup_'.$subGroup.'" class="career_subgroup"
  1033. style="'.$line.' margin-bottom:20px; padding:15px; float:left; margin-left:0px; width:100%">';
  1034. if (!empty($subGroupLabel)) {
  1035. $graphHtml .= '<h3>'.$subGroupLabel.'</h3>';
  1036. }
  1037. foreach ($columnList as $column => $rows) {
  1038. $leftColumn = $defaultSpace.'px';
  1039. if ($column == 1) {
  1040. $leftColumn = 0;
  1041. }
  1042. if (count($columnList) == 1) {
  1043. $leftColumn = 0;
  1044. }
  1045. $widthColumn = 85 / count($columnList);
  1046. $graphHtml .= '<div
  1047. id="col_'.$column.'" class="career_column"
  1048. style="padding:15px;float:left; margin-left:'.$leftColumn.'; width:'.$widthColumn.'%">';
  1049. $maxRow = 0;
  1050. foreach ($rows as $row => $vertex) {
  1051. if ($row > $maxRow) {
  1052. $maxRow = $row;
  1053. }
  1054. }
  1055. $newRowList = [];
  1056. $defaultSubGroup = -1;
  1057. $subGroupCountList = [];
  1058. for ($i = 0; $i < $maxRow; $i++) {
  1059. /** @var Vertex $vertex */
  1060. $vertex = isset($rows[$i + 1]) ? $rows[$i + 1] : null;
  1061. if (!is_null($vertex)) {
  1062. $subGroup = $vertex->getAttribute('SubGroup');
  1063. if ($subGroup == '' || empty($subGroup)) {
  1064. $defaultSubGroup = 0;
  1065. } else {
  1066. $defaultSubGroup = (int) $subGroup;
  1067. }
  1068. }
  1069. $newRowList[$i + 1][$defaultSubGroup][] = $vertex;
  1070. if (!isset($subGroupCountList[$defaultSubGroup])) {
  1071. $subGroupCountList[$defaultSubGroup] = 1;
  1072. } else {
  1073. $subGroupCountList[$defaultSubGroup]++;
  1074. }
  1075. }
  1076. $subGroup = null;
  1077. $subGroupAdded = [];
  1078. /** @var Vertex $vertex */
  1079. foreach ($newRowList as $row => $subGroupList) {
  1080. foreach ($subGroupList as $subGroup => $vertexList) {
  1081. if (!empty($subGroup) && $subGroup != -1) {
  1082. if (!isset($subGroupAdded[$subGroup])) {
  1083. $subGroupAdded[$subGroup] = 1;
  1084. } else {
  1085. $subGroupAdded[$subGroup]++;
  1086. }
  1087. }
  1088. foreach ($vertexList as $vertex) {
  1089. if (is_null($vertex)) {
  1090. $graphHtml .= '<div class="career_empty" style="height: 130px">';
  1091. $graphHtml .= '</div>';
  1092. continue;
  1093. }
  1094. $id = $vertex->getId();
  1095. $rowId = "row_$row";
  1096. $graphHtml .= '<div id = "row_'.$id.'" class="'.$rowId.' career_row" >';
  1097. $color = '';
  1098. if (!empty($vertex->getAttribute('DefinedColor'))) {
  1099. $color = $vertex->getAttribute('DefinedColor');
  1100. }
  1101. $content = $vertex->getAttribute('Notes');
  1102. $content .= '<div class="pull-right">['.$id.']</div>';
  1103. $title = $vertex->getAttribute('graphviz.label');
  1104. if (!empty($vertex->getAttribute('LinkedElement'))) {
  1105. $title = Display::url($title, $vertex->getAttribute('LinkedElement'));
  1106. }
  1107. $graphHtml .= Display::panel(
  1108. $content,
  1109. $title,
  1110. null,
  1111. null,
  1112. null,
  1113. null,
  1114. $color
  1115. );
  1116. $graphHtml .= '</div>';
  1117. $arrow = $vertex->getAttribute('DrawArrowFrom');
  1118. $found = false;
  1119. if (!empty($arrow)) {
  1120. $pos = strpos($arrow, 'SG');
  1121. if ($pos === false) {
  1122. $pos = strpos($arrow, 'G');
  1123. if (is_numeric($pos)) {
  1124. $parts = explode('G', $arrow);
  1125. if (empty($parts[0]) && count($parts) == 2) {
  1126. $groupArrow = $parts[1];
  1127. $graphHtml .= self::createConnection(
  1128. "group_$groupArrow",
  1129. "row_$id",
  1130. ['Left', 'Right']
  1131. );
  1132. $found = true;
  1133. }
  1134. }
  1135. } else {
  1136. $parts = explode('SG', $arrow);
  1137. if (empty($parts[0]) && count($parts) == 2) {
  1138. $subGroupArrow = $parts[1];
  1139. $graphHtml .= self::createConnection(
  1140. "subgroup_$subGroupArrow",
  1141. "row_$id",
  1142. ['Left', 'Right']
  1143. );
  1144. $found = true;
  1145. }
  1146. }
  1147. }
  1148. if ($found == false) {
  1149. $defaultArrow = ['Left', 'Right'];
  1150. if (isset($groupCourseList[$group]) &&
  1151. in_array($arrow, $groupCourseList[$group])
  1152. ) {
  1153. $defaultArrow = ['Top', 'Bottom'];
  1154. }
  1155. $graphHtml .= self::createConnection(
  1156. "row_$arrow",
  1157. "row_$id",
  1158. $defaultArrow
  1159. );
  1160. }
  1161. }
  1162. }
  1163. }
  1164. $graphHtml .= '</div>';
  1165. }
  1166. $graphHtml .= '</div>';
  1167. }
  1168. $graphHtml .= '</div>';
  1169. return $graphHtml;
  1170. }
  1171. /**
  1172. * @param string $source
  1173. * @param string $target
  1174. * @param array $anchor
  1175. *
  1176. * @return string
  1177. */
  1178. public static function createConnection($source, $target, $anchor = [])
  1179. {
  1180. if (empty($anchor)) {
  1181. // Default
  1182. $anchor = ['Bottom', 'Right'];
  1183. }
  1184. $anchor = implode('","', $anchor);
  1185. $html = '<script>
  1186. var connectorPaintStyle = {
  1187. strokeWidth: 2,
  1188. stroke: "#a31ed3",
  1189. joinstyle: "round",
  1190. outlineStroke: "white",
  1191. outlineWidth: 2
  1192. },
  1193. // .. and this is the hover style.
  1194. connectorHoverStyle = {
  1195. strokeWidth: 3,
  1196. stroke: "#216477",
  1197. outlineWidth: 5,
  1198. outlineStroke: "white"
  1199. },
  1200. endpointHoverStyle = {
  1201. fill: "#E80CAF",
  1202. stroke: "#E80CAF"
  1203. };
  1204. jsPlumb.ready(function() { ';
  1205. $html .= 'jsPlumb.connect({
  1206. source:"'.$source.'",
  1207. target:"'.$target.'",
  1208. endpoint:[ "Rectangle", { width:1, height:1 }],
  1209. connector: ["Flowchart"],
  1210. paintStyle: connectorPaintStyle,
  1211. hoverPaintStyle: endpointHoverStyle,
  1212. anchor: ["'.$anchor.'"],
  1213. overlays: [
  1214. [
  1215. "Arrow",
  1216. {
  1217. location:1,
  1218. width:11,
  1219. length:11
  1220. }
  1221. ],
  1222. ],
  1223. });';
  1224. $html .= '});</script>'.PHP_EOL;
  1225. return $html;
  1226. }
  1227. }