Browse Source

Add setting 'allow_career_diagram' see BT#12861

Allow careers to have a diagram based in a CSV file.

Requires DB change:

alter table extra_field_values modify column value longtext null;
jmontoyaa 7 years ago
parent
commit
5fac7f1ff0

+ 95 - 0
main/admin/career_diagram.php

@@ -0,0 +1,95 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use ChamiloSession as Session;
+
+/**
+ *  @package chamilo.admin
+ */
+
+/*
+ *
+ * Requires extra_field_values.value to be longtext to save diagram:
+ *
+update extra_field_values set created_at = null where created_at = '0000-00-00 00:00:00';
+update extra_field_values set updated_at = null where updated_at = '0000-00-00 00:00:00';
+
+UPDATE extra_field_values SET created_at = NULL WHERE CAST(created_at AS CHAR(20)) = '0000-00-00 00:00:00';
+UPDATE extra_field_values SET updated_at = NULL WHERE CAST(updated_at AS CHAR(20)) = '0000-00-00 00:00:00';
+
+alter table extra_field_values modify column value longtext null;
+
+*/
+
+$cidReset = true;
+require_once __DIR__.'/../inc/global.inc.php';
+
+if (api_get_configuration_value('allow_career_diagram') == false) {
+    api_not_allowed(true);
+}
+
+$this_section = SECTION_PLATFORM_ADMIN;
+
+api_protect_admin_script();
+
+$htmlHeadXtra[] = api_get_js('jsplumb2.js');
+
+$careerId = isset($_GET['id']) ? $_GET['id'] : 0;
+if (empty($careerId)) {
+    api_not_allowed(true);
+}
+
+// setting breadcrumbs
+$interbreadcrumb[] = array(
+    'url' => '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();
+}

+ 7 - 0
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 = '<a href="'.api_get_path(WEB_CODE_PATH).'admin/career_diagram.php?id=\'+options.rowId+\'">'.get_lang('Diagram').'</a>';
+}
+
 //With this function we can add actions to the jgrid (edit, delete, etc)
 $action_links = 'function action_formatter(cellvalue, options, rowObject) {
     return \'<a href="?action=edit&id=\'+options.rowId+\'">'.Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_SMALL).'</a>'.
+    $diagramLink.
     '&nbsp;<a onclick="javascript:if(!confirm('."\'".addslashes(api_htmlentities(get_lang("ConfirmYourChoice"), ENT_QUOTES))."\'".')) return false;"  href="?sec_token='.$token.'&action=copy&id=\'+options.rowId+\'">'.Display::return_icon('copy.png', get_lang('Copy'), '', ICON_SIZE_SMALL).'</a>'.
     '&nbsp;<a onclick="javascript:if(!confirm('."\'".addslashes(api_htmlentities(get_lang("ConfirmYourChoice"), ENT_QUOTES))."\'".')) return false;"  href="?sec_token='.$token.'&action=delete&id=\'+options.rowId+\'">'.Display::return_icon('delete.png', get_lang('Delete'), '', ICON_SIZE_SMALL).'</a>'.
     '\';

+ 283 - 8
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 = '<style> 
+                .window .panel-title {
+                    font-size: 12px;
+                }
+                .window  {
+                    font-size: 12px;
+                }
+            </style>';
+            /** @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;
 

+ 2 - 1
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);

+ 147 - 0
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 .= '</div>'.PHP_EOL;
+                }
+
+                $left =  ($defaultSpace).'px';
+                if ($group == 0) {
+                    $left = 0;
+                }
+                $html .= PHP_EOL.'<div id="'.$groupJsId.'" style="padding:15px;border-style:solid;float:left; margin-left:'.$left.'; width:'.$width.'%">';
+            }
+
+            if ($debug) {
+                echo ('->>>>>>>'.$vertex->getAttribute('graphviz.label')).' - '.$vertex->getGroup().PHP_EOL;
+            }
+
+            $group = $vertex->getGroup();
+            $content = $vertex->getAttribute('Notes');
+            $content .= '<div class="pull-right">['.$id.']</div>';
+            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.'<div id="'.$windowId.'" class="window" style="float:left; width:100%; "  >';
+            $html .= Display::panel(
+                $content,
+                $vertex->getAttribute('graphviz.label'),
+                null,
+                $color,
+                null
+                //$windowId
+            );
+            $html .= '</div>';
+        }
+        $html .= '</div>'.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 = '<script> jsPlumb.ready(function() { ';
+        $html .= 'jsPlumb.connect({
+                        source:"'.$source.'",
+                        target:"'.$target.'",
+                        endpoint:[ "Rectangle", { width:1, height:1 }],                                        
+                        connector: ["Flowchart"],                                        
+                        anchor: ["'.$anchor.'"],
+                        overlays: [
+                            [ "Arrow", { location:0.9 } ],
+                        ],
+                      });';
+        $html .= '});</script>'.PHP_EOL;
+
+        return $html;
+    }
 }

+ 6 - 2
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) ? '<div class="panel-heading"><h3 class="panel-title">'.$title.'</h3>'.$extra.'</div>' : '';
         $footer = !empty($footer) ? '<div class="panel-footer ">'.$footer.'</div>' : '';
         $styles = ['primary', 'success', 'info', 'warning', 'danger'];
         $style = !in_array($style, $styles) ? 'default' : $style;
 
+        if (!empty($id)) {
+            $id = " id = $id ";
+        }
         return '
-            <div class="panel panel-'.$style.'">
+            <div '.$id.' class="panel panel-'.$style.'">
                 '.$title.'
                 '.self::contentPanel($content).'
                 '.$footer.'

+ 5 - 1
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'
         );
     }
 

+ 5 - 1
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;
+

+ 1 - 0
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