career.lib.php 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260
  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']) ? intval($_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. $form->addElement('text', 'created_at', get_lang('CreatedAt'));
  138. $form->freeze('created_at');
  139. }
  140. if ($action == 'edit') {
  141. $form->addButtonSave(get_lang('Modify'), 'submit');
  142. } else {
  143. $form->addButtonCreate(get_lang('Add'), 'submit');
  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 Graph $graph
  416. * @param Template $tpl
  417. *
  418. * @return string
  419. */
  420. public static function renderDiagramByColumn($graph, $tpl)
  421. {
  422. if (!($graph instanceof Graph)) {
  423. return '';
  424. }
  425. // Getting max column
  426. $maxColumn = 0;
  427. foreach ($graph->getVertices() as $vertex) {
  428. $groupId = (int) $vertex->getGroup();
  429. if ($groupId > $maxColumn) {
  430. $maxColumn = $groupId;
  431. }
  432. }
  433. $list = [];
  434. $subGroups = [];
  435. /** @var Vertex $vertex */
  436. foreach ($graph->getVertices() as $vertex) {
  437. $column = $vertex->getGroup();
  438. $group = $vertex->getAttribute('Group');
  439. $groupData = explode(':', $group);
  440. $group = $groupData[0];
  441. $groupLabel = isset($groupData[1]) ? $groupData[1] : '';
  442. $subGroup = $vertex->getAttribute('SubGroup');
  443. $subGroupData = explode(':', $subGroup);
  444. $row = $vertex->getAttribute('Row');
  445. $subGroupId = $subGroupData[0];
  446. $subGroupLabel = isset($subGroupData[1]) ? $subGroupData[1] : '';
  447. if (!empty($subGroupId) && !in_array($subGroupId, $subGroups)) {
  448. //$subGroups[$subGroupId][] = $vertex->getId();
  449. $subGroups[$subGroupId]['items'][] = $vertex->getId();
  450. $subGroups[$subGroupId]['label'] = $subGroupLabel;
  451. }
  452. $list[$column]['rows'][$row]['items'][] = $vertex;
  453. $list[$column]['rows'][$row]['label'] = $subGroupId;
  454. $list[$column]['rows'][$row]['group'] = $group;
  455. $list[$column]['rows'][$row]['group_label'] = $groupLabel;
  456. $list[$column]['rows'][$row]['subgroup'] = $subGroup;
  457. $list[$column]['rows'][$row]['subgroup_label'] = $subGroupLabel;
  458. $list[$column]['label'] = $groupLabel;
  459. $list[$column]['column'] = $column;
  460. }
  461. $connections = '';
  462. $groupDrawLine = [];
  463. $groupCourseList = [];
  464. $simpleConnectionList = [];
  465. // Read Connections column
  466. foreach ($list as $column => $groupList) {
  467. foreach ($groupList['rows'] as $subGroupList) {
  468. /** @var Vertex $vertex */
  469. foreach ($subGroupList['items'] as $vertex) {
  470. if ($vertex instanceof Vertex) {
  471. $rowId = $vertex->getId();
  472. $groupCourseList[$vertex->getAttribute('Column')][] = $vertex->getId();
  473. $connectionList = $vertex->getAttribute('Connections');
  474. if (empty($connectionList)) {
  475. continue;
  476. }
  477. $simpleFirstConnection = '';
  478. $simpleSecondConnection = '';
  479. $explode = explode('-', $connectionList);
  480. $pos = strpos($explode[0], 'SG');
  481. if ($pos === false) {
  482. $pos = strpos($explode[0], 'G');
  483. if (is_numeric($pos)) {
  484. // Is group
  485. $groupValueId = (int) str_replace(
  486. 'G',
  487. '',
  488. $explode[0]
  489. );
  490. $groupDrawLine[$groupValueId] = true;
  491. $simpleFirstConnection = 'g'.(int) $groupValueId;
  492. } else {
  493. // Course block (row_123 id)
  494. if (!empty($explode[0])) {
  495. $simpleFirstConnection = 'v'.(int) $explode[0];
  496. }
  497. }
  498. } else {
  499. // subgroup__123 id
  500. $simpleFirstConnection = 'sg'.(int) str_replace('SG', '', $explode[0]);
  501. }
  502. $pos = false;
  503. if (isset($explode[1])) {
  504. $pos = strpos($explode[1], 'SG');
  505. }
  506. if ($pos === false) {
  507. if (isset($explode[1])) {
  508. $pos = strpos($explode[1], 'G');
  509. $value = $explode[1];
  510. }
  511. if (is_numeric($pos)) {
  512. $groupValueId = (int) str_replace(
  513. 'G',
  514. '',
  515. $value
  516. );
  517. $simpleSecondConnection = 'g'.(int) $groupValueId;
  518. $groupDrawLine[$groupValueId] = true;
  519. } else {
  520. // Course block (row_123 id)
  521. if (!empty($explode[0]) && isset($explode[1])) {
  522. $simpleSecondConnection = 'v'.(int) $explode[1];
  523. }
  524. }
  525. } else {
  526. $simpleSecondConnection = 'sg'.(int) str_replace('SG', '', $explode[1]);
  527. }
  528. if (!empty($simpleFirstConnection) && !empty($simpleSecondConnection)) {
  529. $simpleConnectionList[] = [
  530. 'from' => $simpleFirstConnection,
  531. 'to' => $simpleSecondConnection,
  532. ];
  533. }
  534. }
  535. }
  536. }
  537. }
  538. $graphHtml = '';
  539. $groupsBetweenColumns = [];
  540. foreach ($list as $column => $columnList) {
  541. foreach ($columnList['rows'] as $subGroupList) {
  542. $newGroup = $subGroupList['group'];
  543. $label = $subGroupList['group_label'];
  544. $newOrder[$newGroup]['items'][] = $subGroupList;
  545. $newOrder[$newGroup]['label'] = $label;
  546. $groupsBetweenColumns[$newGroup][] = $subGroupList;
  547. }
  548. }
  549. // Creates graph
  550. $graph = new stdClass();
  551. $graph->blockWidth = 240;
  552. $graph->blockHeight = 120;
  553. $graph->xGap = 70;
  554. $graph->yGap = 40;
  555. $graph->xDiff = 70;
  556. $graph->yDiff = 40;
  557. $graph->groupXGap = 50;
  558. foreach ($groupsBetweenColumns as $group => $items) {
  559. self::parseColumnList($groupCourseList, $items, '', $graph, $simpleConnectionList);
  560. }
  561. $graphHtml .= '<style>
  562. .panel-title {
  563. font-size: 11px;
  564. }
  565. </style>';
  566. // Create groups
  567. if (!empty($graph->groupList)) {
  568. $groupList = [];
  569. $groupDiffX = 20;
  570. $groupDiffY = 10;
  571. $style = 'whiteSpace=wrap;rounded;html=1;strokeColor=red;fillColor=none;strokeWidth=2;align=left;verticalAlign=top;';
  572. foreach ($graph->groupList as $id => $data) {
  573. if (empty($id)) {
  574. continue;
  575. }
  576. $x = $data['min_x'] - $groupDiffX;
  577. $y = $data['min_y'] - $groupDiffY;
  578. $width = $data['max_width'] + ($groupDiffX * 2);
  579. $height = $data['max_height'] + $groupDiffY * 2;
  580. $label = '<h4>'.$data['label'].'</h4>';
  581. $vertexData = "var g$id = graph.insertVertex(parent, null, '$label', $x, $y, $width, $height, '$style');";
  582. $groupList[] = $vertexData;
  583. }
  584. $tpl->assign('group_list', $groupList);
  585. }
  586. // Create subgroups
  587. $subGroupList = [];
  588. $subGroupListData = [];
  589. foreach ($subGroups as $subGroupId => $vertexData) {
  590. $label = $vertexData['label'];
  591. $vertexIdList = $vertexData['items'];
  592. foreach ($vertexIdList as $rowId) {
  593. $data = $graph->allData[$rowId];
  594. $originalRow = $data['row'];
  595. $column = $data['column'];
  596. $x = $data['x'];
  597. $y = $data['y'];
  598. $width = $data['width'];
  599. $height = $data['height'];
  600. if (!isset($subGroupListData[$subGroupId])) {
  601. $subGroupListData[$subGroupId]['min_x'] = 1000;
  602. $subGroupListData[$subGroupId]['min_y'] = 1000;
  603. $subGroupListData[$subGroupId]['max_width'] = 0;
  604. $subGroupListData[$subGroupId]['max_height'] = 0;
  605. $subGroupListData[$subGroupId]['label'] = $label;
  606. }
  607. if ($x < $subGroupListData[$subGroupId]['min_x']) {
  608. $subGroupListData[$subGroupId]['min_x'] = $x;
  609. }
  610. if ($y < $subGroupListData[$subGroupId]['min_y']) {
  611. $subGroupListData[$subGroupId]['min_y'] = $y;
  612. }
  613. $subGroupListData[$subGroupId]['max_width'] = ($column + 1) * ($width + $graph->xGap) - $subGroupListData[$subGroupId]['min_x'];
  614. $subGroupListData[$subGroupId]['max_height'] = ($originalRow + 1) * ($height + $graph->yGap) - $subGroupListData[$subGroupId]['min_y'];
  615. }
  616. $style = 'whiteSpace=wrap;rounded;dashed=1;strokeColor=blue;fillColor=none;strokeWidth=2;align=left;verticalAlign=bottom;';
  617. $subGroupDiffX = 5;
  618. foreach ($subGroupListData as $subGroupId => $data) {
  619. $x = $data['min_x'] - $subGroupDiffX;
  620. $y = $data['min_y'] - $subGroupDiffX;
  621. $width = $data['max_width'] + $subGroupDiffX * 2;
  622. $height = $data['max_height'] + $subGroupDiffX * 2;
  623. $label = '<h4>'.$data['label'].'</h4>';
  624. $vertexData = "var sg$subGroupId = graph.insertVertex(parent, null, '$label', $x, $y, $width, $height, '$style');";
  625. $subGroupList[] = $vertexData;
  626. }
  627. }
  628. // Create connections (arrows)
  629. if (!empty($simpleConnectionList)) {
  630. $connectionList = [];
  631. //$style = 'endArrow=classic;html=1;strokeWidth=4;exitX=1;exitY=0.5;entryX=0;entryY=0.5;';
  632. $style = '';
  633. foreach ($simpleConnectionList as $connection) {
  634. $from = $connection['from'];
  635. $to = $connection['to'];
  636. $vertexData = "var e1 = graph.insertEdge(parent, null, '', $from, $to, '$style')";
  637. $connectionList[] = $vertexData;
  638. }
  639. $tpl->assign('connections', $connectionList);
  640. }
  641. $tpl->assign('subgroup_list', $subGroupList);
  642. $tpl->assign('vertex_list', $graph->elementList);
  643. $graphHtml .= '<div id="graphContainer"></div>';
  644. return $graphHtml;
  645. }
  646. public static function parseColumnList($groupCourseList, $columnList, $width, &$graph, &$connections)
  647. {
  648. $graphHtml = '';
  649. $oldGroup = null;
  650. $newOrder = [];
  651. foreach ($columnList as $key => $subGroupList) {
  652. $newGroup = $subGroupList['group'];
  653. $label = $subGroupList['group_label'];
  654. $newOrder[$newGroup]['items'][] = $subGroupList;
  655. $newOrder[$newGroup]['label'] = $label;
  656. }
  657. foreach ($newOrder as $newGroup => $data) {
  658. $label = $data['label'];
  659. $subGroupList = $data['items'];
  660. if (!isset($graph->groupList[$newGroup])) {
  661. $graph->groupList[$newGroup]['min_x'] = 1000;
  662. $graph->groupList[$newGroup]['min_y'] = 1000;
  663. $graph->groupList[$newGroup]['max_width'] = 0;
  664. $graph->groupList[$newGroup]['max_height'] = 0;
  665. $graph->groupList[$newGroup]['label'] = $label;
  666. }
  667. $maxColumn = 0;
  668. $maxRow = 0;
  669. $minColumn = 100;
  670. $minRow = 100;
  671. foreach ($subGroupList as $item) {
  672. /** @var Vertex $vertex */
  673. foreach ($item['items'] as $vertex) {
  674. $column = $vertex->getAttribute('Column');
  675. $realRow = $vertex->getAttribute('Row');
  676. if ($column > $maxColumn) {
  677. $maxColumn = $column;
  678. }
  679. if ($realRow > $maxRow) {
  680. $maxRow = $realRow;
  681. }
  682. if ($column < $minColumn) {
  683. $minColumn = $column;
  684. }
  685. if ($realRow < $minRow) {
  686. $minRow = $realRow;
  687. }
  688. }
  689. }
  690. if (!empty($newGroup)) {
  691. $graphHtml .= '<div
  692. id ="group_'.$newGroup.'"
  693. class="group'.$newGroup.' group_class"
  694. style="display:grid;
  695. align-self: start;
  696. grid-gap: 10px;
  697. justify-items: stretch;
  698. align-items: start;
  699. align-content: start;
  700. justify-content: stretch;
  701. grid-area:'.$minRow.'/'.$minColumn.'/'.$maxRow.'/'.$maxColumn.'">'; //style="display:grid"
  702. }
  703. $addRow = 0;
  704. if (!empty($label)) {
  705. $graphHtml .= "<div class='my_label' style='grid-area:$minRow/$minColumn/$maxRow/$maxColumn'>$label</div>";
  706. $addRow = 1;
  707. }
  708. foreach ($subGroupList as $item) {
  709. $graphHtml .= self::parseVertexList(
  710. $groupCourseList,
  711. $item['items'],
  712. $addRow,
  713. $graph,
  714. $newGroup,
  715. $connections
  716. );
  717. }
  718. if (!empty($newGroup)) {
  719. $graphHtml .= '</div >';
  720. }
  721. }
  722. return $graphHtml;
  723. }
  724. /**
  725. * @param array $groupCourseList
  726. * @param array $vertexList
  727. * @param int $addRow
  728. * @param stdClass $graph
  729. * @param int $group
  730. * @param array $connections
  731. *
  732. * @return string
  733. */
  734. public static function parseVertexList($groupCourseList, $vertexList, $addRow = 0, &$graph, $group, &$connections)
  735. {
  736. if (empty($vertexList)) {
  737. return '';
  738. }
  739. $graphHtml = '';
  740. /** @var Vertex $vertex */
  741. foreach ($vertexList as $vertex) {
  742. $column = $vertex->getAttribute('Column');
  743. $realRow = $originalRow = $vertex->getAttribute('Row');
  744. if ($addRow) {
  745. $realRow = $realRow + $addRow;
  746. }
  747. $id = $vertex->getId();
  748. $area = "$realRow/$column";
  749. $graphHtml .= '<div
  750. id = "row_wrapper_'.$id.'"
  751. data= "'.$originalRow.'-'.$column.'"
  752. style="
  753. align-self: start;
  754. justify-content: stretch;
  755. grid-area:'.$area.'"
  756. >';
  757. $color = '';
  758. if (!empty($vertex->getAttribute('DefinedColor'))) {
  759. $color = $vertex->getAttribute('DefinedColor');
  760. }
  761. $content = '<div class="pull-left">'.$vertex->getAttribute('Notes').'</div>';
  762. $content .= '<div class="pull-right">['.$id.']</div>';
  763. $title = $vertex->getAttribute('graphviz.label');
  764. if (!empty($vertex->getAttribute('LinkedElement'))) {
  765. $title = Display::url($title, $vertex->getAttribute('LinkedElement'));
  766. }
  767. $originalRow--;
  768. $column--;
  769. //$title = "$originalRow / $column";
  770. $graphHtml .= Display::panel(
  771. $content,
  772. $title,
  773. null,
  774. null,
  775. null,
  776. "row_$id",
  777. $color
  778. );
  779. $panel = Display::panel(
  780. $content,
  781. $title,
  782. null,
  783. null,
  784. null,
  785. "row_$id",
  786. $color
  787. );
  788. $x = $column * $graph->blockWidth + $graph->xDiff;
  789. $y = $originalRow * $graph->blockHeight + $graph->yDiff;
  790. $width = $graph->blockWidth - $graph->xGap;
  791. $height = $graph->blockHeight - $graph->yGap;
  792. $style = 'text;html=1;strokeColor=green;fillColor=#ffffff;overflow=fill;rounded=0;align=left;';
  793. $panel = str_replace(["\n", "\r"], '', $panel);
  794. $vertexData = "var v$id = graph.insertVertex(parent, null, '".addslashes($panel)."', $x, $y, $width, $height, '$style');";
  795. $graph->elementList[$id] = $vertexData;
  796. $graph->allData[$id] = [
  797. 'x' => $x,
  798. 'y' => $y,
  799. 'width' => $width,
  800. 'height' => $height,
  801. 'row' => $originalRow,
  802. 'column' => $column,
  803. 'label' => $title,
  804. ];
  805. if ($x < $graph->groupList[$group]['min_x']) {
  806. $graph->groupList[$group]['min_x'] = $x;
  807. }
  808. if ($y < $graph->groupList[$group]['min_y']) {
  809. $graph->groupList[$group]['min_y'] = $y;
  810. }
  811. $graph->groupList[$group]['max_width'] = ($column + 1) * ($width + $graph->xGap) - $graph->groupList[$group]['min_x'];
  812. $graph->groupList[$group]['max_height'] = ($originalRow + 1) * ($height + ($graph->yGap)) - $graph->groupList[$group]['min_y'];
  813. $graphHtml .= '</div>';
  814. $arrow = $vertex->getAttribute('DrawArrowFrom');
  815. $found = false;
  816. if (!empty($arrow)) {
  817. $pos = strpos($arrow, 'SG');
  818. if ($pos === false) {
  819. $pos = strpos($arrow, 'G');
  820. if (is_numeric($pos)) {
  821. $parts = explode('G', $arrow);
  822. if (empty($parts[0]) && count($parts) == 2) {
  823. $groupArrow = $parts[1];
  824. $graphHtml .= self::createConnection(
  825. "group_$groupArrow",
  826. "row_$id",
  827. ['Left', 'Right']
  828. );
  829. $found = true;
  830. $connections[] = [
  831. 'from' => "g$groupArrow",
  832. 'to' => "v$id",
  833. ];
  834. }
  835. }
  836. } else {
  837. // Case is only one subgroup value example: SG1
  838. $parts = explode('SG', $arrow);
  839. if (empty($parts[0]) && count($parts) == 2) {
  840. $subGroupArrow = $parts[1];
  841. $graphHtml .= self::createConnection(
  842. "subgroup_$subGroupArrow",
  843. "row_$id",
  844. ['Left', 'Right']
  845. );
  846. $found = true;
  847. $connections[] = [
  848. 'from' => "sg$subGroupArrow",
  849. 'to' => "v$id",
  850. ];
  851. }
  852. }
  853. if ($found == false) {
  854. // case is connected to 2 subgroups: Example SG1-SG2
  855. $parts = explode('-', $arrow);
  856. if (count($parts) == 2 && !empty($parts[0]) && !empty($parts[1])) {
  857. $defaultArrow = ['Top', 'Bottom'];
  858. $firstPrefix = '';
  859. $firstId = '';
  860. $secondId = '';
  861. $secondPrefix = '';
  862. if (is_numeric($pos = strpos($parts[0], 'SG'))) {
  863. $firstPrefix = 'sg';
  864. $firstId = str_replace('SG', '', $parts[0]);
  865. }
  866. if (is_numeric($pos = strpos($parts[1], 'SG'))) {
  867. $secondPrefix = 'sg';
  868. $secondId = str_replace('SG', '', $parts[1]);
  869. }
  870. if (!empty($secondId) && !empty($firstId)) {
  871. $connections[] = [
  872. 'from' => $firstPrefix.$firstId,
  873. 'to' => $secondPrefix.$secondId,
  874. $defaultArrow,
  875. ];
  876. $found = true;
  877. }
  878. }
  879. }
  880. if ($found == false) {
  881. // case DrawArrowFrom is an integer
  882. $defaultArrow = ['Left', 'Right'];
  883. if (isset($groupCourseList[$column]) &&
  884. in_array($arrow, $groupCourseList[$column])
  885. ) {
  886. $defaultArrow = ['Top', 'Bottom'];
  887. }
  888. $graphHtml .= self::createConnection(
  889. "row_$arrow",
  890. "row_$id",
  891. $defaultArrow
  892. );
  893. $connections[] = [
  894. 'from' => "v$arrow",
  895. 'to' => "v$id",
  896. ];
  897. }
  898. }
  899. }
  900. return $graphHtml;
  901. }
  902. /**
  903. * @param array $groupCourseList list of groups and their courses
  904. * @param int $group
  905. * @param string $groupLabel
  906. * @param bool $showGroupLine
  907. * @param array $subGroupList
  908. * @param $widthGroup
  909. *
  910. * @return string
  911. */
  912. public static function parseSubGroups(
  913. $groupCourseList,
  914. $group,
  915. $groupLabel,
  916. $showGroupLine,
  917. $subGroupList,
  918. $widthGroup
  919. ) {
  920. $topValue = 90;
  921. $defaultSpace = 40;
  922. $leftGroup = $defaultSpace.'px';
  923. if ($group == 1) {
  924. $leftGroup = 0;
  925. }
  926. $groupIdTag = "group_$group";
  927. $borderLine = $showGroupLine === true ? 'border-style:solid;' : '';
  928. $graphHtml = '<div
  929. id="'.$groupIdTag.'" class="career_group"
  930. style=" '.$borderLine.' padding:15px; float:left; margin-left:'.$leftGroup.'; width:'.$widthGroup.'%">';
  931. if (!empty($groupLabel)) {
  932. $graphHtml .= '<h3>'.$groupLabel.'</h3>';
  933. }
  934. foreach ($subGroupList as $subGroup => $subGroupData) {
  935. $subGroupLabel = isset($subGroupData['label']) ? $subGroupData['label'] : '';
  936. $columnList = isset($subGroupData['columns']) ? $subGroupData['columns'] : [];
  937. if (empty($columnList)) {
  938. continue;
  939. }
  940. $line = '';
  941. if (!empty($subGroup)) {
  942. $line = 'border-style:solid;';
  943. }
  944. // padding:15px;
  945. $graphHtml .= '<div
  946. id="subgroup_'.$subGroup.'" class="career_subgroup"
  947. style="'.$line.' margin-bottom:20px; padding:15px; float:left; margin-left:0px; width:100%">';
  948. if (!empty($subGroupLabel)) {
  949. $graphHtml .= '<h3>'.$subGroupLabel.'</h3>';
  950. }
  951. foreach ($columnList as $column => $rows) {
  952. $leftColumn = $defaultSpace.'px';
  953. if ($column == 1) {
  954. $leftColumn = 0;
  955. }
  956. if (count($columnList) == 1) {
  957. $leftColumn = 0;
  958. }
  959. $widthColumn = 85 / count($columnList);
  960. $graphHtml .= '<div
  961. id="col_'.$column.'" class="career_column"
  962. style="padding:15px;float:left; margin-left:'.$leftColumn.'; width:'.$widthColumn.'%">';
  963. $maxRow = 0;
  964. foreach ($rows as $row => $vertex) {
  965. if ($row > $maxRow) {
  966. $maxRow = $row;
  967. }
  968. }
  969. $newRowList = [];
  970. $defaultSubGroup = -1;
  971. $subGroupCountList = [];
  972. for ($i = 0; $i < $maxRow; $i++) {
  973. /** @var Vertex $vertex */
  974. $vertex = isset($rows[$i + 1]) ? $rows[$i + 1] : null;
  975. if (!is_null($vertex)) {
  976. $subGroup = $vertex->getAttribute('SubGroup');
  977. if ($subGroup == '' || empty($subGroup)) {
  978. $defaultSubGroup = 0;
  979. } else {
  980. $defaultSubGroup = (int) $subGroup;
  981. }
  982. }
  983. $newRowList[$i + 1][$defaultSubGroup][] = $vertex;
  984. if (!isset($subGroupCountList[$defaultSubGroup])) {
  985. $subGroupCountList[$defaultSubGroup] = 1;
  986. } else {
  987. $subGroupCountList[$defaultSubGroup]++;
  988. }
  989. }
  990. $subGroup = null;
  991. $subGroupAdded = [];
  992. /** @var Vertex $vertex */
  993. foreach ($newRowList as $row => $subGroupList) {
  994. foreach ($subGroupList as $subGroup => $vertexList) {
  995. if (!empty($subGroup) && $subGroup != -1) {
  996. if (!isset($subGroupAdded[$subGroup])) {
  997. $subGroupAdded[$subGroup] = 1;
  998. } else {
  999. $subGroupAdded[$subGroup]++;
  1000. }
  1001. }
  1002. foreach ($vertexList as $vertex) {
  1003. if (is_null($vertex)) {
  1004. $graphHtml .= '<div class="career_empty" style="height: 130px">';
  1005. $graphHtml .= '</div>';
  1006. continue;
  1007. }
  1008. $id = $vertex->getId();
  1009. $rowId = "row_$row";
  1010. $graphHtml .= '<div id = "row_'.$id.'" class="'.$rowId.' career_row" >';
  1011. $color = '';
  1012. if (!empty($vertex->getAttribute('DefinedColor'))) {
  1013. $color = $vertex->getAttribute('DefinedColor');
  1014. }
  1015. $content = $vertex->getAttribute('Notes');
  1016. $content .= '<div class="pull-right">['.$id.']</div>';
  1017. $title = $vertex->getAttribute('graphviz.label');
  1018. if (!empty($vertex->getAttribute('LinkedElement'))) {
  1019. $title = Display::url($title, $vertex->getAttribute('LinkedElement'));
  1020. }
  1021. $graphHtml .= Display::panel(
  1022. $content,
  1023. $title,
  1024. null,
  1025. null,
  1026. null,
  1027. null,
  1028. $color
  1029. );
  1030. $graphHtml .= '</div>';
  1031. $arrow = $vertex->getAttribute('DrawArrowFrom');
  1032. $found = false;
  1033. if (!empty($arrow)) {
  1034. $pos = strpos($arrow, 'SG');
  1035. if ($pos === false) {
  1036. $pos = strpos($arrow, 'G');
  1037. if (is_numeric($pos)) {
  1038. $parts = explode('G', $arrow);
  1039. if (empty($parts[0]) && count($parts) == 2) {
  1040. $groupArrow = $parts[1];
  1041. $graphHtml .= self::createConnection(
  1042. "group_$groupArrow",
  1043. "row_$id",
  1044. ['Left', 'Right']
  1045. );
  1046. $found = true;
  1047. }
  1048. }
  1049. } else {
  1050. $parts = explode('SG', $arrow);
  1051. if (empty($parts[0]) && count($parts) == 2) {
  1052. $subGroupArrow = $parts[1];
  1053. $graphHtml .= self::createConnection(
  1054. "subgroup_$subGroupArrow",
  1055. "row_$id",
  1056. ['Left', 'Right']
  1057. );
  1058. $found = true;
  1059. }
  1060. }
  1061. }
  1062. if ($found == false) {
  1063. $defaultArrow = ['Left', 'Right'];
  1064. if (isset($groupCourseList[$group]) &&
  1065. in_array($arrow, $groupCourseList[$group])
  1066. ) {
  1067. $defaultArrow = ['Top', 'Bottom'];
  1068. }
  1069. $graphHtml .= self::createConnection(
  1070. "row_$arrow",
  1071. "row_$id",
  1072. $defaultArrow
  1073. );
  1074. }
  1075. }
  1076. }
  1077. }
  1078. $graphHtml .= '</div>';
  1079. }
  1080. $graphHtml .= '</div>';
  1081. }
  1082. $graphHtml .= '</div>';
  1083. return $graphHtml;
  1084. }
  1085. /**
  1086. * @param string $source
  1087. * @param string $target
  1088. * @param array $anchor
  1089. *
  1090. * @return string
  1091. */
  1092. public static function createConnection($source, $target, $anchor = [])
  1093. {
  1094. if (empty($anchor)) {
  1095. // Default
  1096. $anchor = ['Bottom', 'Right'];
  1097. }
  1098. $anchor = implode('","', $anchor);
  1099. $html = '<script>
  1100. var connectorPaintStyle = {
  1101. strokeWidth: 2,
  1102. stroke: "#a31ed3",
  1103. joinstyle: "round",
  1104. outlineStroke: "white",
  1105. outlineWidth: 2
  1106. },
  1107. // .. and this is the hover style.
  1108. connectorHoverStyle = {
  1109. strokeWidth: 3,
  1110. stroke: "#216477",
  1111. outlineWidth: 5,
  1112. outlineStroke: "white"
  1113. },
  1114. endpointHoverStyle = {
  1115. fill: "#E80CAF",
  1116. stroke: "#E80CAF"
  1117. };
  1118. jsPlumb.ready(function() { ';
  1119. $html .= 'jsPlumb.connect({
  1120. source:"'.$source.'",
  1121. target:"'.$target.'",
  1122. endpoint:[ "Rectangle", { width:1, height:1 }],
  1123. connector: ["Flowchart"],
  1124. paintStyle: connectorPaintStyle,
  1125. hoverPaintStyle: endpointHoverStyle,
  1126. anchor: ["'.$anchor.'"],
  1127. overlays: [
  1128. [
  1129. "Arrow",
  1130. {
  1131. location:1,
  1132. width:11,
  1133. length:11
  1134. }
  1135. ],
  1136. ],
  1137. });';
  1138. $html .= '});</script>'.PHP_EOL;
  1139. return $html;
  1140. }
  1141. }