diff --git a/main/admin/career_diagram.php b/main/admin/career_diagram.php new file mode 100644 index 0000000000..38cd9ae959 --- /dev/null +++ b/main/admin/career_diagram.php @@ -0,0 +1,95 @@ + 'index.php', + 'name' => get_lang('PlatformAdmin'), +); +$interbreadcrumb[] = array( + 'url' => 'career_dashboard.php', + 'name' => get_lang('CareersAndPromotions'), +); + +$interbreadcrumb[] = array( + 'url' => 'careers.php', + 'name' => get_lang('Careers'), +); + +$action = isset($_GET['action']) ? $_GET['action'] : null; + +$check = Security::check_token('request'); +$token = Security::get_token(); + +if ($action == 'add') { + $interbreadcrumb[] = array('url' => 'careers.php', 'name' => get_lang('Careers')); + $tool_name = get_lang('Add'); +} elseif ($action == 'edit') { + $interbreadcrumb[] = array('url' => 'careers.php', 'name' => get_lang('Careers')); + $interbreadcrumb[] = array('url' => '#', 'name' => get_lang('Edit')); + $tool_name = get_lang('Edit'); +} else { + $tool_name = get_lang('Careers'); +} + +$career = new Career(); +$careerInfo = $career->get($careerId); +if (empty($careerInfo)) { + api_not_allowed(true); +} + +$extraFieldValue = new ExtraFieldValue('career'); +$item = $extraFieldValue->get_values_by_handler_and_field_variable( + $careerId, + 'career_diagram', + false, + false, + false +); + +if (!empty($item) && isset($item['value']) && !empty($item['value'])) { + $graph = unserialize($item['value']); + $html = Display::page_header($careerInfo['name']); + $html .= Career::renderDiagram($graph); + $tpl = new Template(''); + $tpl->assign('content', $html); + $tpl->display_one_col_template(); +} diff --git a/main/admin/careers.php b/main/admin/careers.php index c3aa5e97b8..3fb89cfd81 100755 --- a/main/admin/careers.php +++ b/main/admin/careers.php @@ -81,9 +81,16 @@ $extra_params['autowidth'] = 'true'; //height auto $extra_params['height'] = 'auto'; +$diagramLink = ''; +$allow = api_get_configuration_value('allow_career_diagram'); +if ($allow) { + $diagramLink = ''.get_lang('Diagram').''; +} + //With this function we can add actions to the jgrid (edit, delete, etc) $action_links = 'function action_formatter(cellvalue, options, rowObject) { return \''.Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_SMALL).''. + $diagramLink. ' '.Display::return_icon('copy.png', get_lang('Copy'), '', ICON_SIZE_SMALL).''. ' '.Display::return_icon('delete.png', get_lang('Delete'), '', ICON_SIZE_SMALL).''. '\'; diff --git a/main/cron/import_csv.php b/main/cron/import_csv.php index 95e32a62f3..88817210f7 100755 --- a/main/cron/import_csv.php +++ b/main/cron/import_csv.php @@ -4,6 +4,13 @@ use Chamilo\CourseBundle\Entity\CCalendarEvent; use Chamilo\CourseBundle\Entity\CItemProperty; use Chamilo\PluginBundle\Entity\StudentFollowUp\CarePost; +use Fhaculty\Graph\Graph; +use Graphp\GraphViz\GraphViz; +use Monolog\Logger; +use Monolog\Handler\StreamHandler; +use Monolog\Handler\NativeMailerHandler; +use Monolog\Handler\RotatingFileHandler; +use Monolog\Handler\BufferHandler; if (PHP_SAPI != 'cli') { die('Run this script through the command line or comment this line in the code'); @@ -33,6 +40,8 @@ class ImportCsv 'course' => 'external_course_id', 'user' => 'external_user_id', 'calendar_event' => 'external_calendar_event_id', + 'career' => 'external_career_id', + 'career_diagram' => 'career_diagram', ); public $defaultAdminId = 1; public $defaultSessionVisibility = 1; @@ -137,10 +146,13 @@ class ImportCsv $method = 'importUnsubsessionsExtidStatic'; } + if ($method == 'importCareersdiagram') { + $method = 'importCareersDiagram'; + } + if ($method == 'importSubsessionsextidStatic') { $method = 'importSubscribeUserToCourseSessionExtStatic'; } - if (method_exists($this, $method)) { if (( $method == 'importSubscribeStatic' || @@ -186,6 +198,8 @@ class ImportCsv 'courseinsert-static', 'unsubscribe-static', 'care', + 'careers', + 'careersdiagram' ); foreach ($sections as $section) { @@ -291,6 +305,25 @@ class ImportCsv 'display_text' => 'External calendar event id', ) ); + + $extraField = new ExtraField('career'); + $extraField->save( + array( + 'visible_to_self' => 1, + 'field_type' => ExtraField::FIELD_TYPE_TEXT, + 'variable' => $this->extraFieldIdNameList['career'], + 'display_text' => 'External career id', + ) + ); + + $extraField->save( + array( + 'visible_to_self' => 1, + 'field_type' => ExtraField::FIELD_TYPE_TEXTAREA, + 'variable' => $this->extraFieldIdNameList['career_diagram'], + 'display_text' => 'Career diagram', + ) + ); } /** @@ -2192,6 +2225,255 @@ class ImportCsv return $date; } + /** + * @param $file + * @param bool $moveFile + * @param array $teacherBackup + * @param array $groupBackup + * @return bool + */ + private function importCareers( + $file, + $moveFile = false, + &$teacherBackup = array(), + &$groupBackup = array() + ) { + $data = Import::csv_reader($file); + + if (!empty($data)) { + $this->logger->addInfo(count($data)." records found."); + $extraFieldValue = new ExtraFieldValue('career'); + $extraFieldName = $this->extraFieldIdNameList['career']; + $externalEventId = null; + + $extraField = new ExtraField('career'); + $extraFieldInfo = $extraField->get_handler_field_info_by_field_variable( + $extraFieldName + ); + + if (empty($extraFieldInfo)) { + return false; + } + + foreach ($data as $row) { + foreach ($row as $key => $value) { + $key = (string)trim($key); + // Remove utf8 bom + $key = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $key); + $row[$key] = $value; + } + + $itemId = $row['CareerId']; + $item = $extraFieldValue->get_item_id_from_field_variable_and_field_value( + $extraFieldName, + $itemId, + false, + false, + false + ); + + $career = new Career(); + if (empty($item)) { + $params = [ + 'status' => 1, + 'name' => $row['CareerName'] + ]; + $careerId = $career->save($params); + if ($careerId) { + $params = [ + 'item_id' => $careerId, + 'extra_'.$extraFieldName => $itemId, + ]; + $extraFieldValue->saveFieldValues($params); + } + } else { + if (isset($item['item_id'])) { + $params = [ + 'id' => $item['item_id'], + 'name' => $row['CareerName'] + ]; + $career->update($params); + } + } + } + } + } + + /** + * @param $file + * @param bool $moveFile + * @param array $teacherBackup + * @param array $groupBackup + */ + private function importCareersDiagram( + $file, + $moveFile = false, + &$teacherBackup = array(), + &$groupBackup = array() + ) { + $data = Import::csv_reader($file); + + $extraFieldValue = new ExtraFieldValue('career'); + $extraFieldName = $this->extraFieldIdNameList['career']; + $externalEventId = null; + + $extraField = new ExtraField('career'); + $extraFieldInfo = $extraField->get_handler_field_info_by_field_variable( + $extraFieldName + ); + + $careerDiagramExtraFieldName = $this->extraFieldIdNameList['career_diagram']; + $extraFieldDiagramInfo = $extraField->get_handler_field_info_by_field_variable( + $careerDiagramExtraFieldName + ); + + if (empty($extraFieldInfo) || empty($extraFieldDiagramInfo)) { + return false; + } + + if (!empty($data)) { + $this->logger->addInfo(count($data)." records found."); + $values = []; + foreach ($data as $row) { + foreach ($row as $key => $value) { + $key = (string) trim($key); + // Remove utf8 bom + $key = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $key); + $row[$key] = $value; + } + $values[$row['Column']][] = $row; + } + + $careerList = []; + $careerNameList = []; + ksort($values); + $careerChamiloIdList = []; + // 1. First create all items + foreach ($values as $column => $rowList) { + foreach ($rowList as $row) { + $careerId = $row['CareerId']; + + $item = $extraFieldValue->get_item_id_from_field_variable_and_field_value( + $extraFieldName, + $careerId, + false, + false, + false + ); + + $chamiloCareerName = ''; + if (empty($item)) { + $this->logger->addInfo("Career not found: $careerId"); + continue; + } else { + if (isset($item['item_id'])) { + $careerChamiloId = $item['item_id']; + $career = new Career(); + $career = $career->find($careerChamiloId); + $chamiloCareerName = $career['name']; + $careerNameList[$careerId] = $chamiloCareerName; + $careerChamiloIdList[$careerId] = $careerChamiloId; + } else { + continue; + } + } + + if (empty($chamiloCareerName)) { + $this->logger->addInfo("Career not found: $careerId"); + continue; + } + + if (isset($careerList[$careerId])) { + $graph = $careerList[$careerId]; + } else { + $graph = new Graph($careerId); + $graph->setAttribute('graphviz.graph.rankdir', 'LR'); + $careerList[$careerId] = $graph; + } + + $currentCourseId = (int)$row['CourseId']; + $name = $row['CourseName']; + $hasColor = $row['HasColor']; + $notes = $row['Notes']; + + if ($graph->hasVertex($currentCourseId)) { + // Avoid double insertion + continue; + } else { + $current = $graph->createVertex($currentCourseId); + $current->setAttribute('graphviz.label', $name); + $current->setAttribute('HasColor', $hasColor); + $current->setAttribute('Notes', $notes); + //$current->setAttribute('graphviz.color', 'blue'); + $current->setAttribute('graphviz.shape', 'box'); + $current->setGroup($column); + } + } + } + + // 2. Create connections + // $column start with 1 (depending in Column row) + foreach ($values as $column => $rowList) { + foreach ($rowList as $row) { + $careerId = $row['CareerId']; + if (isset($careerList[$careerId])) { + $graph = $careerList[$careerId]; + } else { + continue; + } + + $currentCourseId = (int) $row['CourseId']; + if ($graph->hasVertex($currentCourseId)) { + $current = $graph->getVertex($currentCourseId); + } else { + continue; + } + + if (isset($row['DependedOn']) && !empty($row['DependedOn'])) { + $parentList = explode(',', $row['DependedOn']); + foreach ($parentList as $parentId) { + $parentId = (int) $parentId; + echo $parentId.PHP_EOL; + if ($graph->hasVertex($parentId)) { + $parent = $graph->getVertex($parentId); + /*$parent->setAttribute('graphviz.color', 'red'); + $parent->setAttribute('graphviz.label', $name); + $parent->setAttribute('graphviz.shape', 'square');*/ + $parent->createEdgeTo($current); + } + } + } + } + } + + // 2. Transform the graph into a jsplumb graph + $html = ''; + /** @var Graph $graph */ + foreach ($careerList as $id => $graph) { + if ($id != 631) { + //continue; + } + + if (isset($careerChamiloIdList[$id])) { + $params = [ + 'item_id' => $careerChamiloIdList[$id], + 'extra_'.$careerDiagramExtraFieldName => serialize($graph), + 'extra_'.$extraFieldName => $id, + ]; + $extraFieldValue->saveFieldValues($params); + } + } + } + } + + /** * @param string $file * @param bool $moveFile @@ -2451,15 +2733,8 @@ class ImportCsv Database::query($sql); } } - } -use Monolog\Logger; -use Monolog\Handler\StreamHandler; -use Monolog\Handler\NativeMailerHandler; -use Monolog\Handler\RotatingFileHandler; -use Monolog\Handler\BufferHandler; - $logger = new Logger('cron'); $emails = isset($_configuration['cron_notification_mails']) ? $_configuration['cron_notification_mails'] : null; diff --git a/main/inc/ajax/sequence.ajax.php b/main/inc/ajax/sequence.ajax.php index 4a8d8cd60a..e3ad693fed 100644 --- a/main/inc/ajax/sequence.ajax.php +++ b/main/inc/ajax/sequence.ajax.php @@ -5,6 +5,7 @@ use Chamilo\CoreBundle\Entity\Sequence; use Chamilo\CoreBundle\Entity\SequenceResource; use Fhaculty\Graph\Graph; use Fhaculty\Graph\Vertex; +use Graphp\GraphViz\GraphViz; /** * Responses to AJAX calls @@ -37,7 +38,7 @@ switch ($action) { if ($sequence->hasGraph()) { $graph = $sequence->getUnSerializeGraph(); $graph->setAttribute('graphviz.node.fontname', 'arial'); - $graphviz = new \Graphp\GraphViz\GraphViz(); + $graphviz = new GraphViz(); $graphImage = ''; try { $graphImage = $graphviz->createImageHtml($graph); diff --git a/main/inc/lib/career.lib.php b/main/inc/lib/career.lib.php index b870b4d399..b84e86e8eb 100755 --- a/main/inc/lib/career.lib.php +++ b/main/inc/lib/career.lib.php @@ -275,4 +275,151 @@ class Career extends Model return parent::update($params); } + + /** + * @param \Fhaculty\Graph\Graph $graph + * + * @return string + */ + public static function renderDiagram($graph) + { + if (!($graph instanceof \Fhaculty\Graph\Graph)) { + return ''; + } + + $debug = false; + $maxColumn = 0; + foreach ($graph->getVertices() as $vertex) { + $groupId = (int) $vertex->getGroup(); + if ($groupId > $maxColumn) { + $maxColumn = $groupId; + } + } + + $width = 80 / $maxColumn; + //var_dump($maxColumn); + //$width = 100; + //$groupWidth = $width + 30; + $defaultSpace = 40; + $group = 0; + /** @var \Fhaculty\Graph\Vertex $vertex */ + $counter = 0; + $html = ''; + foreach ($graph->getVertices() as $vertex) { + $id = $vertex->getId(); + $windowId = "window_$id"; + $groupId = $vertex->getGroup(); + $groupJsId = "group_$groupId"; + + if ($group != $vertex->getGroup()) { + if ($group > 0) { + $counter = 0; + $html .= ''.PHP_EOL; + } + + $left = ($defaultSpace).'px'; + if ($group == 0) { + $left = 0; + } + $html .= PHP_EOL.'
'; + } + + if ($debug) { + echo ('->>>>>>>'.$vertex->getAttribute('graphviz.label')).' - '.$vertex->getGroup().PHP_EOL; + } + + $group = $vertex->getGroup(); + $content = $vertex->getAttribute('Notes'); + $content .= '
['.$id.']
'; + if ($debug) { + echo ('entering vertices: ').PHP_EOL; + } + + /** @var \Fhaculty\Graph\Vertex $vertexTo */ + foreach ($vertex->getVerticesEdgeTo() as $vertexTo) { + $childId = $vertexTo->getId(); + if ($id == $childId) { + continue; + } + + $childId = "window_$childId"; + $childGroupId = $vertexTo->getGroup(); + $childJsGroupId = "group_$childGroupId"; + if ($debug) { + echo ($vertexTo->getAttribute('graphviz.label')).PHP_EOL; + } + + if (($vertexTo->getGroup() - $groupId) == 1) { + $content .= self::createConnection($windowId, $childId, ['Left', 'Right']); + } else { + if ($childGroupId > $groupId) { + $content .= self::createConnection( + $groupJsId, + $childJsGroupId + ); + } else { + $anchor = ['Left', 'Right']; + if ($childGroupId == 1) { + $anchor = ['Right', 'Left']; + } + $content .= self::createConnection( + $childJsGroupId, + $groupJsId, + $anchor + ); + } + } + } + + $counter++; + $color = ''; + if ($vertex->getAttribute('HasColor') == 1) { + $color = 'danger'; + } + + $html .= PHP_EOL.'
'; + $html .= Display::panel( + $content, + $vertex->getAttribute('graphviz.label'), + null, + $color, + null + //$windowId + ); + $html .= '
'; + } + $html .= '
'.PHP_EOL; + + return $html; + } + + /** + * @param string $source + * @param string $target + * @param array $anchor + * @return string + */ + public static function createConnection($source, $target, $anchor = []) + { + if (empty($anchor)) { + // Default + $anchor = ['Bottom', 'Right']; + } + + $anchor = implode('","', $anchor); + $html = ''.PHP_EOL; + + return $html; + } } diff --git a/main/inc/lib/display.lib.php b/main/inc/lib/display.lib.php index 99048795f1..d624704a44 100755 --- a/main/inc/lib/display.lib.php +++ b/main/inc/lib/display.lib.php @@ -2276,18 +2276,22 @@ class Display * @param string $footer * @param string $style primary|success|info|warning|danger * @param string $extra + * @param string $id * * @return string */ - public static function panel($content, $title = '', $footer = '', $style = '', $extra = '') + public static function panel($content, $title = '', $footer = '', $style = '', $extra = '', $id = '') { $title = !empty($title) ? '

'.$title.'

'.$extra.'
' : ''; $footer = !empty($footer) ? '' : ''; $styles = ['primary', 'success', 'info', 'warning', 'danger']; $style = !in_array($style, $styles) ? 'default' : $style; + if (!empty($id)) { + $id = " id = $id "; + } return ' -
+
'.$title.' '.self::contentPanel($content).' '.$footer.' diff --git a/main/inc/lib/extra_field.lib.php b/main/inc/lib/extra_field.lib.php index bdecb580be..40f882a054 100755 --- a/main/inc/lib/extra_field.lib.php +++ b/main/inc/lib/extra_field.lib.php @@ -129,6 +129,9 @@ class ExtraField extends Model case 'work': $this->extraFieldType = EntityExtraField::WORK_FIELD_TYPE; break; + case 'career': + $this->extraFieldType = EntityExtraField::CAREER_FIELD_TYPE; + break; } $this->pageUrl = 'extra_fields.php?type='.$this->type; @@ -158,7 +161,8 @@ class ExtraField extends Model 'calendar_event', 'lp_item', 'skill', - 'work' + 'work', + 'career' ); } diff --git a/main/install/configuration.dist.php b/main/install/configuration.dist.php index 37e5dc550d..e6424b6603 100755 --- a/main/install/configuration.dist.php +++ b/main/install/configuration.dist.php @@ -451,4 +451,8 @@ $_configuration['agenda_legend'] = [ // HTTP headers security section ends here // Add answered_at field in table survey_invitation -//$_configuration['survey_answered_at_field'] = 'false'; +//$_configuration['survey_answered_at_field'] = false; + +// Allow career diagram +//$_configuration['allow_career_diagram'] = false; + diff --git a/src/Chamilo/CoreBundle/Entity/ExtraField.php b/src/Chamilo/CoreBundle/Entity/ExtraField.php index 297a6c902d..123d7b4216 100644 --- a/src/Chamilo/CoreBundle/Entity/ExtraField.php +++ b/src/Chamilo/CoreBundle/Entity/ExtraField.php @@ -26,6 +26,7 @@ class ExtraField extends BaseAttribute const LP_ITEM_FIELD_TYPE = 7; const SKILL_FIELD_TYPE = 8; const WORK_FIELD_TYPE = 9; + const CAREER_FIELD_TYPE = 10; /** * @var integer