career.lib.php 49 KB

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