Compare commits

...

115 Commits

Author SHA1 Message Date
Adrien Marquès 177752fdc4 npm packages update 2018-06-09 15:26:17 +02:00
Adrien Marquès 782d2084ca fix http vs. https for CAS service (redirection) 2018-06-09 15:08:46 +02:00
Adrien Marquès 047efa100c update package (vue-template-compiler) 2018-06-09 14:58:04 +02:00
Adrien Marquès 8bd98484aa make login HOST generic 2018-06-09 12:22:20 +02:00
Adrien Marquès 47c90d8737 add dedicated page 'fiche' for teachers only (not 'cas_admin') + router management + add logout btn 2018-05-17 15:16:30 +02:00
xdrm-brackets 1bfd97009e fix [modules.json] format + fix 'teacher/pdf' to return data even if no formation is found (+remove dumb data from pdf) 2018-05-14 12:03:11 +02:00
xdrm-brackets cfb9e0df23 remove page 'fiche' 2018-05-14 11:56:11 +02:00
Unknown 8f909821b6 add version delete/update restrictions 2018-05-14 01:03:18 +02:00
Unknown 24b0bb3798 Implement non-admin user pdf download 2018-05-14 00:57:57 +02:00
Unknown 9dea19b9b1 Fix version update 2018-05-13 23:41:32 +02:00
xdrm-brackets edbd2da4eb Merge branch 'master' of https://git.xdrm.io/ptut/vhost 2018-05-12 16:48:13 +02:00
xdrm-brackets 799116d1a4 remove debug 2018-05-12 16:48:07 +02:00
xdrm-brackets f62674cb0b fix export extension 2018-05-12 16:46:23 +02:00
xdrm-brackets 50f99f6a44 fix export extension 2018-05-12 16:45:47 +02:00
xdrm-brackets c52893ffd6 added global department's version export 2018-05-12 16:42:49 +02:00
xdrm-brackets 56eb72ac55 department/export now uses the download system 2018-05-12 16:42:28 +02:00
xdrm-brackets 9093ce2d38 department.create/delete(unimplemented server-side)/switch 2018-05-12 16:17:27 +02:00
Unknown a745daa506 Add fiche download auth check 2018-05-10 15:43:02 +02:00
Unknown e9579c8956 Implemented Excel export of version 2018-05-10 15:09:40 +02:00
Unknown 6af33306af Merge branch 'verions-new' 2018-05-10 12:37:51 +02:00
Unknown b33f1a67cb Implemented Department creation 2018-05-10 12:37:27 +02:00
xdrm-brackets fa7d9ee6f2 Merge branch 'master' of https://git.xdrm.io/ptut/vhost 2018-05-09 17:51:47 +02:00
xdrm-brackets dd52ed39bc fail on trying to rename a version with the empty string 2018-05-09 17:45:52 +02:00
xdrm-brackets 78c3d1c02d now better version management all-in-header 2018-05-09 17:43:34 +02:00
xdrm-brackets 1225f69d9d [api.cas] on login select version with 'default=1' first then if nothing, take the first element. Same for [api.version.DELETE] to update the list 2018-05-09 17:43:20 +02:00
xdrm-brackets 058eb5c275 Merge branch 'verions-new' of https://git.xdrm.io/ptut/vhost into verions-new 2018-05-09 15:57:54 +02:00
Unknown 79bdb1c33c Implemented Department creation 2018-05-09 17:44:40 +02:00
xdrm-brackets 35a6b2a931 extremely ugly edit mode for versions 2018-05-09 15:57:47 +02:00
xdrm-brackets 13e545e5f9 update version id + currentDatabase when switching department 2018-05-09 15:12:26 +02:00
xdrm-brackets 3dfa1cc525 minfix 2018-05-09 12:38:09 +02:00
xdrm-brackets 73bc584036 update version model + casController/switch version format in session 2018-05-09 12:36:12 +02:00
xdrm-brackets 63fdcc012d Merge branch 'master' into verions-new 2018-05-09 11:44:02 +02:00
Unknown d2dfd2e962 Implement Formation CRUD 2018-05-08 19:23:46 +02:00
xdrm-brackets d803925b2b add ordering in 'vue.ue.manage' by professor name OR volume count | formation list length 2018-05-08 15:58:11 +02:00
xdrm-brackets bc5d2fff19 alphabetical professor order 2018-05-08 15:48:45 +02:00
xdrm-brackets 6d57e0aa5b display default creation formations on UI for 'POST api.ue.*' 2018-05-08 14:50:39 +02:00
xdrm-brackets 465fde053b fix api.ue.cours + api.ue.tp to return 'formations' id list 2018-05-08 14:48:48 +02:00
xdrm-brackets 4e1ca634ff fixed [ue.manage] list style + added ordering basis (TODO: order algorithm) 2018-05-08 14:46:50 +02:00
xdrm-brackets ca9ca6e290 [POST cours|td|tp] adds the default formation if exists + given formations as arguments - also it returns as output the added formation id list 2018-05-08 14:45:26 +02:00
Unknown c6b46e1b88 New version managing API 2018-05-07 18:14:17 +02:00
xdrm-brackets 367d3c5988 [ue.manage][scss.container.list] fuckin' layout pseudo~fix~~~ 2018-04-17 18:13:57 +02:00
xdrm-brackets 9e96d7a84e Merge branch 'master' of https://git.xdrm.io/ptut/vhost 2018-04-17 17:38:38 +02:00
xdrm-brackets f14b6d4bbf [scss.container.list] fixed layout + added togglable arrows for filtering (ordering actually) + [ue.manage] added back button 2018-04-17 17:38:32 +02:00
Guillaume FAUVET 21d67f3d2a {upd} statistiques are responsive 2018-04-07 12:40:40 +02:00
Unknown 32da5131b4 [repo.ue] Fixed request stacker + fixed UE request 2018-04-01 13:10:36 +02:00
Guillaume FAUVET f65513a241 Merge branch 'page-home' 2018-03-31 13:40:51 +02:00
Guillaume FAUVET 1c2de0b5df [page.home] {upd} ajout de commentaires 2018-03-31 13:36:32 +02:00
Guillaume FAUVET c40d45e7c9 [page.home] {correction} maj des noms des heures + ajout de légendes extra 2018-03-31 13:33:08 +02:00
Guillaume FAUVET 1e93e712c3 [master] {correction} affiche les messages d'erreur de connexion plus longtemps 2018-03-31 13:11:59 +02:00
Guillaume FAUVET 52f3a3ac60 Merge page-home to master 2018-03-31 13:06:02 +02:00
xdrm-brackets dc8420a70c [teacher.view] let EHTD in EDIT MODE 2018-03-30 19:18:43 +02:00
xdrm-brackets ad2e393e8c [data.ue] implemented 'formation' inclusive filter and vue template updated in [ue.view] + managed auto-unblur in template 'beforeMount' 2018-03-30 18:32:13 +02:00
xdrm-brackets 1ead035f0e [data.ue] added formation list to search bar + fixed proccess time (algo exit first) 2018-03-30 18:07:04 +02:00
xdrm-brackets 9bd58eb460 [teacher.view] create card with same design as others [data.teacher] created handler 'ic_reset' + added button 'CANCEL' to reset form & untoggle 2018-03-30 17:32:12 +02:00
xdrm-brackets 365e1eb567 [ue.view] create card with same design as others [data.ue] created handler 'ic_reset' + added button 'CANCEL' to reset form & untoggle 2018-03-30 17:29:39 +02:00
xdrm-brackets 7b8c01b38a renamed 'Cours' -> 'CM' [teacher.view] [ue.manage] 2018-03-30 17:19:50 +02:00
xdrm-brackets 7a1872a65a [ue.view] errors more explicit : missing teacher, wrong volume 2018-03-30 17:16:56 +02:00
xdrm-brackets 07ab742071 [scss.global] added tooltip arrow + transformed before-icons to raw icons [ue.view] replaced before-icons with raw icons 2018-03-30 17:03:35 +02:00
xdrm-brackets e8ed5ab97d [teacher.view] fixed 'sous-service'/'sur-service' float with N decimal numbers -> now only 2 2018-03-30 16:54:03 +02:00
xdrm-brackets 0f136299e9 [teacher.view] added 'sous-service'/'sur-service' + EHTD + EDIT MODE valid + info header 2018-03-29 22:01:40 +02:00
Unknown a79710f3fe [repo.ue]Fix additionnals data computation 2018-03-29 23:51:15 +02:00
xdrm-brackets 5ea9d901a9 [ue.view] fix for EDIT MODE 2018-03-29 21:36:57 +02:00
xdrm-brackets d8cc6b27ac [data.ue] fixed manage cannot find 'code' from URI when playing with history back/forth so now uses VueRouter 2018-03-29 21:29:29 +02:00
xdrm-brackets 2d270f5689 [ue.view] updated 'required' colors + 'CM', 'TD', 'TP' with group count + when errors in bold red + error incicators 2018-03-29 21:21:37 +02:00
xdrm-brackets 53e671457d Merge branch 'master' of https://git.xdrm.io/ptut/vhost 2018-03-29 20:36:41 +02:00
Unknown 848a1b7970 [repo.ue] Added some data
+ number of CM TD TP
+ modulo of the number of hours of CM TD TP
+ number of prof assigned to CM TD TP
2018-03-29 22:24:37 +02:00
xdrm-brackets e64667a955 [scss.container.card] added 'info' (mini-header for little informations) + added icon to update 'required'/'optional' in right top corner + [vue.ue.view] moved 'required' with pin before name and 'enabled' with line-through [scss.global] 2018-03-29 19:30:37 +02:00
Guillaume FAUVET eaa2bc65e0 [page.home] {fix} affichage sur réponse de l'API 2018-03-28 11:32:59 +02:00
xdrm-brackets 1997af49f4 [scss.container.card] bigger '+' create toggle 2018-03-26 13:34:03 +02:00
xdrm-brackets 8b5a385252 merge conflict 2018-03-26 07:27:20 -04:00
xdrm-brackets f62af0693e [webpack.teacher] updated fields names + pdf download works [scss.container.card] added PDF download icon 2018-03-26 13:24:19 +02:00
xdrm-brackets 72e38d59b1 [webpack.teacher] updated fields names + pdf download works [scss.container.card] added PDF download icon 2018-03-26 13:23:32 +02:00
xdrm-brackets a4e56c8574 merge 2018-03-22 06:13:09 -04:00
Guillaume FAUVET 6e375a8e14 [home > graphique svg] 2018-03-22 10:54:46 +01:00
xdrm-brackets 20a4f86d5c [module.professor.pdf] Added extension '.pdf' 2018-03-21 15:24:26 +01:00
xdrm-brackets 8b33f401af [module.professor.pdf] Added extension '.pdf' 2018-03-21 15:07:50 +01:00
xdrm-brackets be1e5b0a4d [api.core.Request] if DOWNLOAD -> create TMP dir in PUBLIC 2018-03-21 15:02:12 +01:00
xdrm-brackets 6273a58046 [module.professor.pdf] Generate PDF link 2018-03-21 14:54:42 +01:00
xdrm-brackets 2c0681dd7c [module.professor.pdf] Generate PDF (not tested but working) ONLY WORKS WITH 'prof_id' not ALL 2018-03-21 14:43:47 +01:00
xdrm-brackets e153b7ecb4 [repo.cours] getForProfessor [repo.td] getForProfessor [repo.tp] getForProfessor 2018-03-21 14:42:52 +01:00
xdrm-brackets daddb8ebcf [config.modules] added 'cas' permissions for 'department.version' and 'professor' 2018-03-21 00:59:51 +01:00
xdrm-brackets ec6a89233f [module.cas] GET minmod 2018-03-21 00:50:19 +01:00
xdrm-brackets 81db5a02c8 [module.cas] GET works with URL0=0 with redirection | URL0=1 with pop-up management 2018-03-21 00:41:44 +01:00
xdrm-brackets 46ba44d042 [module.cas] PUT is so slow.. timeout (CAS server not responding in consistent time) 2018-03-20 23:51:39 +01:00
xdrm-brackets 100b09c695 [config.modules] added 'cas' permissions [module.cas] added PUT to really logout (from CAS server) but do not work (except from document.location) 2018-03-20 23:35:16 +01:00
xdrm-brackets 7db2a41027 [repo.meta] fixed 'create_prof' return type to 'bool' [repo.professor] removed getLinkedDepartments 2018-03-20 18:49:00 +01:00
xdrm-brackets 12b1a29ed0 [repo.meta] created meta database repo (create_prof, delete_prof, prof_exists, link_exists, link, unlink, get_prof_departments) [repo.professor] used [repo.meta] to synchronize meta database (fully tested, even if meta not already synchronized) [module.casController] now uses [repo.meta] [module.department] now uses [repo.meta] 2018-03-20 18:44:43 +01:00
xdrm-brackets e562bec77f [repo.professor] fixed 'CurrentDepartementId' to 'CurrentDepartmentId' 2018-03-20 17:26:23 +01:00
xdrm-brackets ad67e62680 [webpack.login] login successful redirects to URL page (allows direct access) 2018-03-20 12:09:26 +01:00
xdrm-brackets c8c1bf2ede [webpack.common] added gstore field 'HOST' that contains 'https://ptut.xdrm.io' or 'http://ptut.com:8080' 2018-03-20 12:08:53 +01:00
xdrm-brackets e32ce89837 [webpack.login] added custom error code (-5) 'popup_interrupted' 2018-03-20 11:51:20 +01:00
xdrm-brackets e301a7eb2e [api.department] added GET (id or ALL) 2018-03-20 11:47:54 +01:00
xdrm-brackets c5adb6d660 [api.core.auth-system-default] fixed 'CurrentDepartmentId' reset type check 2018-03-20 10:54:12 +01:00
xdrm-brackets b06b8666bc [renamed] 'AvailableDepartment' -> 'AvailableDepartments' [renamed] 'CurrentDepartementId' -> 'CurrentDepartmentId' 2018-03-20 10:49:53 +01:00
xdrm-brackets 35b0cb36fa [repo.professor] fix CREATE sync with meta database 2018-03-20 10:49:12 +01:00
xdrm-brackets 67e0f9eba2 [webpack.login] re-activate button after fail attempt + debug message as raw-html 2018-03-20 10:25:30 +01:00
xdrm-brackets 0486110f2b [webpack.login] minfix 2018-03-20 10:22:55 +01:00
xdrm-brackets 2dedde4fa6 [webpack.data.login] send custom error code '-2' not NULL anymore (from previous commit) 2018-03-20 10:20:40 +01:00
xdrm-brackets e67195871f [module.cas] dispatch custom error codes [webpack.login] display error codes 2018-03-20 10:19:32 +01:00
xdrm-brackets 14c71d88f1 [repo.ue] removed debug (PDO::ERRMODE_EXCEPTION) 2018-03-19 23:39:53 +01:00
xdrm-brackets 38600994c0 [repo.ue] UPDATE manage 'new_code' [module.ue] PUT manages optional argument 'new_code' [webpack.data.ue] display input for 'code' and send 'new_code' to API if set 2018-03-19 23:32:09 +01:00
xdrm-brackets 228bdddd2d [composer.mpdf] added PDF lib [public_html.pdf.php] PDF basics + layout 2018-03-19 23:15:19 +01:00
xdrm-brackets bef4dad5f7 [api.core.authsystemdefault] removed CAS management [module.cas] implemented good CAS management with database + error management (edge cases) 2018-03-19 19:06:00 +01:00
xdrm-brackets 60ceb9d2a8 [module.department] renamed 'getLinkedDepartments' (plural) 2018-03-19 19:05:19 +01:00
xdrm-brackets b47341f792 [repo.professor] renamed 'getLinkedDepartment' -> 'getLinkedDepartments' (plural) + Error management (edge cases) + update -> updates meta database + delete -> updates meta database 2018-03-19 19:04:57 +01:00
xdrm-brackets 959df5cfde [module.cas] fix1 [webpack.page.login] fix [webpack.data.login] fix 2018-03-19 18:27:33 +01:00
xdrm-brackets 39635cb5fa [webpack.ue.manage] CREATE css minmod 2018-03-18 17:36:23 +01:00
xdrm-brackets dc647f567f [webpack.ue.manage] CREATE new Cours|TD|TP implemented (with real-time feedback (push into VueJS), then updates/remove works) 2018-03-18 17:17:31 +01:00
xdrm-brackets 861fb49846 [webpack.ue.manage] minmod 2018-03-18 16:31:03 +01:00
xdrm-brackets d43c2244a0 [backup.dir] removed + .gitignore 2018-03-18 11:15:30 +01:00
xdrm-brackets 70ff0edeb9 [api.core.AuthSystemDefault] added $_SESSION['VERSION'] [router.controller.js] added 'session.version' in javascript '_SERVER' variable [module.version] minmod [webpack.header] implemented VERSION: fetch|create|switch 2018-03-17 19:07:44 +01:00
xdrm-brackets 4280bbb5f6 [webpack.lib.onblur] js lib to call 'callbacks' when unblur (for example <div> that shows only on click, but now it allows us to hide them when clicking outside this element) [webpack.data.common] added lib in 'window.unblur' [webpack.teacher.view] used the lib for 'filter' list that shows when clicking on filter name [webpack.header] used for 'department' and 'version' popup lists 2018-03-17 18:26:09 +01:00
xdrm-brackets e221452295 [module.department.save] => [module.department.version] refactor + POST split into POST & PUT + used API 'error' return field BIGUPDATE 2018-03-17 14:34:16 +01:00
xdrm-brackets 466d197246 [module.professor] -Removed 'GET professor/stats' 2018-03-17 13:18:37 +01:00
Unknown d8e74ada8a Merge branch 'db-save' 2018-03-16 20:40:14 +01:00
68 changed files with 5030 additions and 10225 deletions

4
.gitignore vendored
View File

@ -4,4 +4,6 @@
/public_html/css
/public_html/js
/node_modules
/backup/*
/backup
/tmp
/public_html/tmp

View File

View File

@ -23,52 +23,14 @@
/* (1) Init session variables
---------------------------------------------------------*/
if( !isset($_SESSION['CAS']) || !is_array($_SESSION['CAS']) ) $_SESSION['CAS'] = [];
if( !isset($_SESSION['AUTH']) || !is_array($_SESSION['AUTH']) ) $_SESSION['AUTH'] = [];
if( !isset($_SESSION['AvailableDepartment']) || !is_array($_SESSION['AvailableDepartment']) ) $_SESSION['AvailableDepartment'] = [];
if( !isset($_SESSION['CAS']) || !is_array($_SESSION['CAS']) ) $_SESSION['CAS'] = [];
if( !isset($_SESSION['AUTH']) || !is_array($_SESSION['AUTH']) ) $_SESSION['AUTH'] = [];
if( !isset($_SESSION['AvailableDepartments']) || !is_array($_SESSION['AvailableDepartments']) ) $_SESSION['AvailableDepartments'] = [];
if( !isset($_SESSION['VERSION']) || !is_array($_SESSION['VERSION']) ) $_SESSION['VERSION'] = [];
if( !isset($_SESSION['CurrentDepartmentId']) || !is_int($_SESSION['CurrentDepartmentId']) ) $_SESSION['CurrentDepartmentId'] = null;
/* (2) Check CAS
---------------------------------------------------------*/
if( (!isset($_SESSION["isLogged"]) || !$_SESSION["isLogged"]) && isset($_SESSION['CAS']['login']) && isset($_SESSION['CAS']['ticket']) ){
/* (1) If the user is not logged we try to retrive the list of the linked department*/
/** @var professor $prof_repo */
$prof_repo = Repo::getRepo('professor');
/* (2) Get professor with this login */
$deps = $prof_repo->getLinkedDepartment($_SESSION['CAS']['login']);
if(is_array($deps)){
$_SESSION["AvailableDepartment"] = $deps;
$_SESSION['CurrentDatabase'] = $deps[0]["dbName"];
$_SESSION['CurrentDepartementId'] = $deps[0]["idDep"];
Repo::switchDatabase($_SESSION['CurrentDatabase']);
$by_login = $prof_repo->getByLogin($_SESSION['CAS']['login']);
/* (3) If found -> store useful information */
if( is_array($by_login) && isset($by_login['idProfesseur']) && isset($by_login['admin']) ){
//security
session_regenerate_id();
$_SESSION['CAS']['admin'] = (bool) $by_login['admin'];
$_SESSION['CAS']['id'] = (int) $by_login['idProfesseur'];
$_SESSION["isLogged"] = true;
/* (4) If no login found -> remove CAS auth */
}else
$_SESSION['CAS'] = [];
}else{
$_SESSION['CAS'] = [];
}
}
/* (3) Process AUTH
/* (2) Process AUTH
---------------------------------------------------------*/
/* (1) cas_admin | cas_user */
if( isset($_SESSION['CAS']['admin']) && is_bool($_SESSION['CAS']['admin']) ){
@ -86,7 +48,7 @@
$_SESSION['AUTH'] = \array_diff($_SESSION['AUTH'], ['cas_admin']);
}
/* (2) Other permissions */
/* (3) Other permissions */
// TODO
}

View File

@ -614,39 +614,44 @@ use \error\core\Error;
/* (3) Gestion du téléchargement différé (AJAX)
---------------------------------------------------------*/
/* (1) On génère les noms de fichiers utiles */
/* (1) Create if not exists public_html/tmp */
if( !is_dir(__PUBLIC__.'/tmp') ){
mkdir(__PUBLIC__.'/tmp');
chmod(__PUBLIC__.'/tmp', 0775);
}
/* (2) On génère les noms de fichiers utiles */
$target_fname = '/tmp/download_'.uniqid().'.php'; // cible
$buffer_fname = __ROOT__.'/tmp/content_'.uniqid().'.php'; // buffer
/* (2) On écrit le BODY dans un fichier buffer */
/* (3) On écrit le BODY dans un fichier buffer */
$buffer_file = fopen($buffer_fname, 'w');
fwrite($buffer_file, $returned['body']);
fclose($buffer_file);
/* (3) On crée le fichier cible */
/* (4) On crée le fichier cible */
$target_fnameroot = __PUBLIC__.$target_fname;
$taret_file = fopen($target_fnameroot, 'w');
fwrite($taret_file, '<?php'.PHP_EOL);
/* (4) Script qui écrira les headers */
/* (5) Script qui écrira les headers */
foreach($returned['headers'] as $header=>$value)
fwrite($taret_file, "header(\"$header: $value\");".PHP_EOL);
fwrite($taret_file, 'header(\''.$header.': '.$value.'\');'.PHP_EOL);
/* (5) Script qui écrira le contenu du buffer */
/* (6) Script qui écrira le contenu du buffer */
chmod($buffer_fname, 0775);
fwrite($taret_file, "readfile('$buffer_fname');".PHP_EOL);
/* (6) Script qui supprimera les fichiers: buffer+target */
/* (7) Script qui supprimera les fichiers: buffer+target */
fwrite($taret_file, "unlink('$buffer_fname');".PHP_EOL);
fwrite($taret_file, "unlink(__FILE__);".PHP_EOL);
fwrite($taret_file, '?>'.PHP_EOL);
/* (7) On ferme le fichier cible */
/* (8) On ferme le fichier cible */
fclose($taret_file);
chmod($target_fnameroot, 0775);
/* (8) On envoie la réponse contenant le lien du fichier cible */
/* (9) On envoie la réponse contenant le lien du fichier cible */
$response = new Response($this->error);
$response->append('link', $target_fname);

View File

@ -11,6 +11,7 @@ namespace api\module;
use database\core\Repo;
use database\repo\professor;
use database\repo\meta;
use error\core\Error;
use error\core\Err;
@ -19,10 +20,15 @@ class casController{
/* (1) Authentication callback
*
* @return professors<array> The professor(s) data
* @popup_mode<boolean> Whether to manage the popup
* @GET[ticket]<String> CAS callback @ticket
*
* @return headers|body<array> The download content
*
---------------------------------------------------------*/
public static function get($args){
$popup_mode = false;
extract($args);
// login: https://sso.univ-pau.fr/cas/login?service=http://ptut.com:8080/api/v/1.0/cas
@ -45,11 +51,32 @@ class casController{
// Launch PopUp
// window.pop = window.open('https://sso.univ-pau.fr/cas/login?service=http://ptut.com:8080/api/v/1.0/cas', '_blank', 'location=no,height=1024,width=1024,scrollbars=yes,status=no');
/* (0) Global DOWNLOAD data
/* (0) Initialize
---------------------------------------------------------*/
$headers = ['Content-Type' => 'text/html; charset=UTF-8' ];
$body_start = "Veuillez patienter...<br>Vous allez être redirigés<script type='text/javascript'>window.opener.cas_callback(";
$body_end = ");window.close();</script>";
/* (1) Global DOWNLOAD data */
$headers = ['Content-Type' => 'text/html; charset=UTF-8' ];
/* (2) If @popup_mode */
if( $popup_mode !== 0 ){
$body_start = "Veuillez patienter...<br>Vous allez être redirigés<script type='text/javascript'>( typeof window.opener.cas_callback === 'function' ) && window.opener.cas_callback(";
$body_end = "); window.close();</script>";
/* (3) Else -> redirection */
}else{
$homepage = ($_SERVER['SERVER_NAME'] == 'ptut.com' ) ? 'http' : 'https';
$homepage .= '://'.$_SERVER['HTTP_HOST'].'/home';
$body_start = "Veuillez patienter...<br>Vous allez être redirigés<script type='text/javascript'>console.log(";
$body_end = "); document.location = '$homepage'; </script>";
}
/* (4) Reset SESSION */
// $_SESSION['CAS'] = [];
/* (1) Check if already connected
@ -66,49 +93,111 @@ class casController{
/* (2) Fail if no ticket */
if( !isset($_GET['ticket']) || !is_string($_GET['ticket']) || strlen($_GET['ticket']) < 1 )
return [ 'headers' => $headers, 'body' => $body_start.'null'.$body_end ];
return [ 'headers' => $headers, 'body' => $body_start.'-1'.$body_end ];
/* (2) Check ticket (validate)
---------------------------------------------------------*/
/* (1) Build useful variables */
$service = ($_SERVER['SERVER_NAME'] == 'ptut.com' ) ? 'http' : 'https';
$service .= '://'.$_SERVER['HTTP_HOST'].'/api/v/1.0/cas';
$service = ($_SERVER['SERVER_PORT'] == 80) ? 'http' : 'https';
$service .= '://'.$_SERVER['HTTP_HOST'].'/api/v/1.0/cas/'.$popup_mode;
$ticket = urlencode($_GET['ticket']);
$validate_url = "https://sso.univ-pau.fr/cas/serviceValidate?ticket=$ticket&service=$service";
/* (2) Configure & Prepare CURL */
$ch = curl_init();
$ch = \curl_init();
curl_setopt($ch, CURLOPT_URL, $validate_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
\curl_setopt($ch, CURLOPT_URL, $validate_url);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
/* (3) Execute CURL & Close it */
$output = curl_exec($ch);
curl_close($ch);
$output = \curl_exec($ch);
\curl_close($ch);
/* (4) Fail if not validated */
if( strpos($output, 'user') === false )
return [ 'headers' => $headers, 'body' => $body_start.'null'.$body_end ];
return [ 'headers' => $headers, 'body' => $body_start.'-2'.$body_end ];
/* (5) Extract cas_login */
$cas_login = trim(strip_tags($output));
/* (6) Check empty */
if( strlen($cas_login) < 1 )
return [ 'headers' => $headers, 'body' => $body_start.'null'.$body_end ];
return [ 'headers' => $headers, 'body' => $body_start.'-2'.$body_end ];
/* (3) Store data in session
/* (3) Meta database: check if @cas_login referenced
---------------------------------------------------------*/
$_SESSION['CAS'] = [
'login' => $cas_login,
'ticket' => $ticket
/** @var professor $prof_repo */
$prof_repo = Repo::getRepo('professor');
/** @var meta $meta_repo */
$meta_repo = Repo::getRepo('meta');
/* (1) Get the list of linked departments for this @cas_login */
$departments = $meta_repo->get_prof_departments($cas_login);
/* (2) Failure: if no department for @cas_login */
if( count($departments) === 0 )
return [ 'headers' => $headers, 'body' => $body_start.'-3'.$body_end ];
/* (3) Set departments data */
$_SESSION['AvailableDepartments'] = $departments;
/* (4) Choose first department by default */
$_SESSION['CurrentDepartmentId'] = $departments[0]['idDep'];
$_SESSION['VERSION'] = [
'list' => $departments[0]['versions'],
'current' => null
];
/* (5) select version with default = 1 */
foreach($_SESSION['VERSION']['list'] as $v){
if( $v['default'] == 1 ){
$_SESSION['VERSION']['current'] = intval($v['iddatabase']);
$_SESSION['CurrentDatabase'] = $v['dbName'];
break;
}
}
/* (2) Success CAS login */
/* (6) if no default -> select first */
if( !is_int($_SESSION['VERSION']) ){
$_SESSION['VERSION']['current'] = intval($_SESSION['VERSION']['list'][0]['iddatabase']);
$_SESSION['CurrentDatabase'] = $_SESSION['VERSION']['list'][0]['dbName'];
}
/* (7) Use this department's database */
Repo::switchDatabase($_SESSION['CurrentDatabase']);
/* (4) Fetch @cas_login professor data
---------------------------------------------------------*/
/* (1) Try to fetch professor */
$by_login = $prof_repo->getByLogin($cas_login);
/* (2) If not found -> reset SESSION */
if( !is_array($by_login) || !isset($by_login['idProfesseur']) || !isset($by_login['admin']) )
return [ 'headers' => $headers, 'body' => $body_start.'-4'.$body_end ];
/* (5) Store data in session
---------------------------------------------------------*/
/* (1) Security */
\session_regenerate_id();
/* (2) Store CAS user data in SESSION */
$_SESSION['CAS'] = [
'login' => $cas_login,
'ticket' => $ticket,
'id' => (int) $by_login['idProfesseur'],
'admin' => (bool) $by_login['admin']
];
/* (3) Success CAS login */
return [
'headers' => $headers,
'body' => $body_start."'".$_SESSION['CAS']['login']."'".$body_end
@ -118,8 +207,48 @@ class casController{
/* (2) Logout from CAS server
*
* @return logged_out<bool> Whether you have been logged out
*
---------------------------------------------------------*/
public function put(){
/* (1) Call logout script
---------------------------------------------------------*/
/* (1) Build useful variables */
$logout_url = "https://sso.univ-pau.fr/cas/logout";
/* (2) Configure & Prepare CURL */
$ch = \curl_init();
\curl_setopt($ch, CURLOPT_URL, $logout_url);
\curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
/* (3) Execute CURL & Close it */
$output = \curl_exec($ch);
\curl_close($ch);
/* (4) Error if no output */
if( strlen($output) < 1 )
return ['logged_out' => false, 'redirect_url' => $logout_url];
/* (4) Destroy session */
\session_destroy();
/* (5) Return if logged out */
return ['logged_out' => true, 'redirect_url' => $logout_url];
}
/* (3) Logout (not from CAS server)
*
* @return logged_out<bool> Whether you have been logged out
*
---------------------------------------------------------*/
public function delete(){
\session_destroy();
@ -130,4 +259,4 @@ class casController{
}
}
}

View File

@ -0,0 +1,182 @@
<?php
/**
* Created by PhpStorm.
* User: lucas
* Date: 10/05/18
* Time: 13:03
*/
namespace api\module\department;
use database\core\Repo;
use database\repo\cours;
use database\repo\formation;
use database\repo\td;
use database\repo\tp;
use database\repo\ue;
use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\Border;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Borders;
class exportController
{
public function get($args){
/** @var ue $ueRepo */
$ueRepo = Repo::getRepo("ue");
/** @var cours $cmRepo */
$cmRepo = Repo::getRepo("cours");
/** @var td $tdRepo */
$tdRepo = Repo::getRepo("td");
/** @var tp $tpRepo */
$tpRepo = Repo::getRepo("tp");
/** @var formation $formationRepo */
$formationRepo = Repo::getRepo("formation");
$ues = $ueRepo->exportUE();
$formations = [];
//cache formations labels
foreach ($formationRepo->get(null) as $form){
$formations[$form["idForm"]] = $form["labelForm"];
}
$formatFormations = function(String $formationsId) use ($formations) : String{
$returned = "";
foreach (json_decode($formationsId,true) as $form){
$returned .= $formations[$form]."+";
}
return rtrim($returned,"+");
};
$spreadsheet = new Spreadsheet();
$excel = $spreadsheet->getActiveSheet();
//set up base document
$arrayHeaders = ["Code","Intitulé","Statut","V.H.","Cours","NbGr","Enseignant","TD","NbGr","Enseignant","TP","NbGr","Enseignant","Equ. TD","Total"];
$excel->setCellValue("B2","CODIFICATION DES UE");
$i = 1;
foreach ($arrayHeaders as $header){
$excel->setCellValueByColumnAndRow($i,4,$header);
$i++;
}
$excel->freezePane("O5");
//set up state variables
$currentFormation = null;
$currentLine = 5;
foreach ($ues as $ue){
if($currentFormation != $ue["labelFormation"]){
$currentFormation = $ue["labelFormation"];
$excel->setCellValue("A$currentLine",$ue["labelFormation"]);
$excel->getStyle("A$currentLine")->getFont()->setBold(true)->setSize(11);
$excel->getStyle("A$currentLine:O$currentLine")->getBorders()->getTop()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_DOUBLE);
$currentLine += 2;
}
//get all groups data
$cm = $cmRepo->getGroups($ue["code"]);
$td = $tdRepo->getGroups($ue["code"]);
$tp = $tpRepo->getGroups($ue["code"]);
//set UE header
$nbrGroupeCM = count($cm);
$nbrGroupeTD = count($td);
$nbrGroupeTP = count($tp);
$volumeUE = $ue["volumeCours"] + $ue["volumeTD"] + $ue["volumeTP"];
$equiTDUE = $nbrGroupeCM*1.5*$ue["volumeCours"] + $nbrGroupeTD*$ue["volumeTD"] + $nbrGroupeTP*$ue["volumeTP"];
$excel->setCellValue("A$currentLine",$ue["code"]);
$excel->setCellValue("B$currentLine",$ue["label"]);
$excel->setCellValue("C$currentLine",$ue["required"] == 1? "OBL" : "OPT");
$excel->setCellValue("D$currentLine","$volumeUE");
$excel->setCellValue("E$currentLine","{$ue["volumeCours"]}");
$excel->setCellValue("F$currentLine","$nbrGroupeCM");
$excel->setCellValue("H$currentLine","{$ue["volumeTD"]}");
$excel->setCellValue("I$currentLine","$nbrGroupeTD");
$excel->setCellValue("K$currentLine","{$ue["volumeTP"]}");
$excel->setCellValue("L$currentLine","$nbrGroupeTP");
$excel->setCellValue("O$currentLine","$equiTDUE");
$excel->getStyle("A$currentLine:O$currentLine")->getFont()->setBold( true )->setSize(9);
$currentLine++;
$nbrLine = max($nbrGroupeCM,$nbrGroupeTD,$nbrGroupeTP)-1;
foreach (range(0,$nbrLine) as $n){
$excel->setCellValue("A$currentLine",$ue["code"]);
$excel->setCellValue("B$currentLine",$ue["label"]);
$equiTD = 0;
if(isset($cm[$n])){
$excel->setCellValue("E$currentLine","{$cm[$n]["volume"]}");
$excel->setCellValue("F$currentLine",$formatFormations($cm[$n]["formations"]));
$excel->setCellValue("G$currentLine",$cm[$n]["lastName"]." ".$cm[$n]["firstName"]);
$equiTD += 1.5*$cm[$n]["volume"];
}
if(isset($td[$n])){
$excel->setCellValue("H$currentLine","{$td[$n]["volume"]}");
$excel->setCellValue("I$currentLine",$formatFormations($td[$n]["formations"]));
$excel->setCellValue("J$currentLine",$td[$n]["lastName"]." ".$td[$n]["firstName"]);
$equiTD += $td[$n]["volume"];
}
if(isset($tp[$n])){
$excel->setCellValue("K$currentLine","{$tp[$n]["volume"]}");
$excel->setCellValue("L$currentLine",$formatFormations($tp[$n]["formations"]));
$excel->setCellValue("M$currentLine",$tp[$n]["lastName"]." ".$tp[$n]["firstName"]);
$equiTD += $tp[$n]["volume"];
}
$excel->setCellValue("N$currentLine","$equiTD");
$excel->getStyle("A$currentLine:O$currentLine")->getFont()->setSize(8);
$currentLine++;
}
$currentLine++;
}
//resize all columns
foreach(range('A','O') as $columnID) {
$excel->getColumnDimension($columnID)->setAutoSize(true);
}
//set content type headers
// header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
//reating writer
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xls($spreadsheet);
//as phpSpreadSheet do not support output on the buffer, we have to write in a temp file then read it
//create temporary file;
$file = tmpfile();
//get URI
$metaDatas = stream_get_meta_data($file);
$tmpFilename = $metaDatas['uri'];
//close file pointer
fclose($file);
//write data
$writer->save("$tmpFilename");
//get file content
return [
'headers' => [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Content-Disposition' => 'attachment; filename=archive.xls'
],
'body' => file_get_contents($tmpFilename)
];
}
}

View File

@ -1,140 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: lucas
* Date: 15/03/18
* Time: 15:50
*/
namespace api\module\department;
use database\core\Repo;
use database\repo\department;
use Ifsnop\Mysqldump\Mysqldump;
class saveController
{
private $backupPath = "";
private $originDBName = "";
private function initDir(string $dbName){
//match preview_DATABASENAME_sha1 in order to determine if we init the directory or not
$matches = [];
$reg = preg_match("/preview_(\w*)_\w*/",$dbName,$matches);
//if the dbname match we store the original database name
if($reg == 1){
$dbName = $matches[1];
}
$this->originDBName = $dbName;
if(!is_dir(__BACKUP__."/$dbName/")){
mkdir(__BACKUP__."/$dbName/");
}
$this->backupPath =__BACKUP__."/$dbName/";
}
private function scandir(string $path) : array {
//scan the directory
$arr = scandir($path);
//strip the useless "." and ".."
unset($arr[0],$arr[1]);
//make the arry start at 0 again
$arr = array_values($arr);
return $arr;
}
public function get($args){
$this->initDir($_SESSION["CurrentDatabase"]);
//strip extensions
$backupNames = array_map(function($e){
return pathinfo($e, PATHINFO_FILENAME);
}, $this->scandir($this->backupPath));
return ["data" => $backupNames];
}
public function delete($args){
$this->initDir($_SESSION["CurrentDatabase"]);
$backupName = "";
extract($args);
return ["success" => unlink($this->backupPath.$backupName.".sql")];
}
public function post($args){
$this->initDir($_SESSION["CurrentDatabase"]);
$backupName = "";
$apply = false;
extract($args);
//if the backup name is empty we create it
if($backupName == ""){
try {
$conf = Repo::getDBConfig();
$dump = new Mysqldump("mysql:host={$conf["host"]};dbname={$conf["dbname"]}", $conf["username"], $conf["password"],
[
"compress" => Mysqldump::GZIP
]);
$date = date("Y-W-d");
$dump->start($this->backupPath.$date.".sql");
return ["success" => true,"backupName" => $date];
} catch (\Exception $e) {
return ["success" => false];
}
}else{
//read the backup
ob_start();
readgzfile($this->backupPath.$backupName.".sql");
$sql = ob_get_clean();
/** @var department $depRepo */
$depRepo = Repo::getRepo("department");
if($sql == "") return ["success" => false];
if($apply){
$depRepo->restore($this->originDBName,$sql);
return ["success" => true];
}else{
if($backupName == "origin"){
$_SESSION['CurrentDatabase'] = $this->originDBName;
return ["success" => true];
}
$previewDBName = $depRepo->previewExists($this->originDBName,$backupName);
if($previewDBName == null){
$previewDBName = $depRepo->createPreview($this->originDBName,$backupName);
$depRepo->restore($previewDBName,$sql);
}
$_SESSION['CurrentDatabase'] = $previewDBName;
}
return ["success" => true, "currentDatabase" => $_SESSION['CurrentDatabase']];
}
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* Created by PhpStorm.
* User: lucas
* Date: 07/05/18
* Time: 17:59
*/
namespace api\module\department\version;
use database\core\Repo;
class switchController
{
public function get($args){
$version = null;
extract($args);
$versionData = Repo::getRepo("meta")->getVersionById($version);
$_SESSION['CurrentDatabase'] = $versionData["dbName"];
$_SESSION['VERSION']['current'] = intval( $version );
return ["success" => true];
}
}

View File

@ -0,0 +1,158 @@
<?php
/**
* Created by PhpStorm.
* User: lucas
* Date: 15/03/18
* Time: 15:50
*/
namespace api\module\department;
use database\core\Repo;
use database\repo\meta;
use error\core\Error;
use error\core\Err;
use database\repo\department;
use Ifsnop\Mysqldump\Mysqldump;
class versionController{
/* (4) List available versions for this department
*
* @return versions<array> Version list
*
---------------------------------------------------------*/
public function get($args){
//search for the current department in the session and return its versions
/** @var meta $depRepo */
$depRepo = Repo::getRepo("meta");
//if no department found, return an empty array
return ['versions' => $depRepo->getAllVersions($_SESSION['CurrentDepartmentId']) ];
}
/* (5) Remove an existing version for this department
*
* @version<int> Version name (typically snapshot date)
*
* @return deleted<bool> Whether the version has been deleted
*
---------------------------------------------------------*/
public function delete($args){
$version = null;
extract($args);
/* (1) Try to delete */
$deleted = Repo::getRepo("meta")->deleteVersion($version);
if( !$deleted )
return ['error' => new Error(Err::ModuleError)];
/* (2) Update version list */
$_SESSION['VERSION']['list'] = Repo::getRepo("meta")->getAllVersions($_SESSION['CurrentDepartmentId']);
/* (3) Update current */
$_SESSION['VERSION']['current'] = null;
// select version with default = 1
foreach($_SESSION['VERSION']['list'] as $v){
if( $v['default'] == 1 ){
$_SESSION['VERSION']['current'] = intval($v['iddatabase']);
$_SESSION['CurrentDatabase'] = $v['dbName'];
break;
}
}
// if no default -> select first
if( !is_int($_SESSION['VERSION']) ){
$_SESSION['VERSION']['current'] = intval($_SESSION['VERSION']['list'][0]['iddatabase']);
$_SESSION['CurrentDatabase'] = $_SESSION['VERSION']['list'][0]['dbName'];
}
return ['deleted' => true];
}
/* (6) Creates a new version (snapshot of database) from now
*
* @return created_id<String> The created version id (date)
*
---------------------------------------------------------*/
public function post($args){
$label = null;
extract($args);
/* (2) Try to create the snapshot */
try{
/* (2.1) Get database configuration */
$conf = Repo::getDBConfig();
/* (2.2) Try to dump the database */
/** @var Mysqldump*/
$dump = new Mysqldump(
'mysql:host='.$conf['host'].';dbname='.$_SESSION['CurrentDatabase'],
$conf['username'],
$conf['password']
);
//create temporary file;
$file = tmpfile();
//get URI
$metaDatas = stream_get_meta_data($file);
$tmpFilename = $metaDatas['uri'];
//close file pointer
fclose($file);
//fill the file with sql dump
$dump->start($tmpFilename);
/** @var department $depRepo */
$depRepo = Repo::getRepo("department");
$dbName = $depRepo->createVersionDatabase($_SESSION['CurrentDepartmentId'],file_get_contents($tmpFilename));
/** @var meta $metaRep */
$metaRep = Repo::getRepo("meta");
$versionId = $metaRep->createVersion($label, $dbName,$_SESSION['CurrentDepartmentId'] ,false);
/* (2.5) Return status */
return ['created_id' => $versionId ];
/* (3) On error -> dispatch error */
}catch(\Exception $e){
return ['error' => new Error(Err::RepoError)];
}
}
/* (6) Modify a version and use it
*
*
* @return bool success
*
---------------------------------------------------------*/
public function put($args){
$label = null;
$version = null;
$default = null;
extract($args);
/** @var meta $metaRepo */
$metaRepo = Repo::getRepo("meta");
/* (4) Return status */
return [ 'updated' => $metaRepo->updateVersion($version,$label,$default) ];
}
}

View File

@ -10,36 +10,155 @@ namespace api\module;
use database\core\Repo;
use database\repo\database;
use database\repo\department;
use database\repo\meta;
use database\repo\professor;
class departmentController
{
public function put($args){
$department = 0;
/* (1) Get 1 | all departments
*
* @id_dep<int> [OPT] Department id
*
* @return departments<array> Matching departments
*
---------------------------------------------------------*/
public function get($args){
$id_dep = null;
extract($args);
/** @var professor $prof_repo */
$prof_repo = Repo::getRepo('professor');
/** @var meta $meta_repo */
$meta_repo = Repo::getRepo('meta');
$deps = $prof_repo->getLinkedDepartment($_SESSION['CAS']['login']);
/* (1) Get the list of linked departments for this @cas_login */
$departments = $meta_repo->get_prof_departments($_SESSION['CAS']['login']);
if(is_array($deps)){
foreach ($deps as $dep){
if($dep["idDep"] == $department){
$_SESSION["AvailableDepartment"] = $deps;
$_SESSION['CurrentDatabase'] = $dep["dbName"];
$_SESSION['CurrentDepartementId'] = $dep["idDep"];
/* (2) If no @id_dep -> return all */
if( is_null($id_dep) )
return ['departments' => $departments];
return ["success" => true];
}
/* (3) If @id_dep -> Only add elements that are @id_dep */
$filtered = [];
foreach($departments as $dep){
if( $dep['idDep'] == $id_dep ){
$filtered[] = $dep;
break;
}
}
return ["success" => false];
/* (4) Return filtered departments */
return ['departments' => $filtered];
}
public function put($args){
$department = 0;
extract($args);
/** @var meta $meta_repo */
$meta_repo = Repo::getRepo('meta');
$deps = $meta_repo->get_prof_departments($_SESSION['CAS']['login']);
if( count($deps) > 0 ){
foreach($deps as $dep){
if( $dep['idDep'] == $department ){
$_SESSION['AvailableDepartments'] = $deps;
$_SESSION['CurrentDepartmentId'] = $dep['idDep'];
$_SESSION['VERSION'] = [
'list' => $dep['versions'],
'current' => null
];
// select version with default = 1
foreach($_SESSION['VERSION']['list'] as $v){
if( $v['default'] == 1 ){
$_SESSION['VERSION']['current'] = intval($v['iddatabase']);
$_SESSION['CurrentDatabase'] = $v['dbName'];
break;
}
}
// if no default -> select first
if( !is_int($_SESSION['VERSION']) ){
$_SESSION['VERSION']['current'] = intval($_SESSION['VERSION']['list'][0]['iddatabase']);
$_SESSION['CurrentDatabase'] = $_SESSION['VERSION']['list'][0]['dbName'];
}
return ['switched' => true];
}
}
}
return ['switched' => false];
}
public function post($args){
$name = null;
extract($args);
/** @var meta $metaRepo */
$metaRepo = Repo::getRepo("meta");
/** @var professor $profRep */
$profRep = Repo::getRepo("professor");
/** @var database $dbRepo */
$dbRepo = Repo::getRepo("database");
//create the department in the meta database
$depId = $metaRepo->createDepartment($name);
//link the current user to that department
$metaRepo->link($_SESSION['CAS']['login'],$depId);
//create the database and init the structure
$dbName = $dbRepo->init($depId);
//link the new database to the department
$metaRepo->createVersion("Version 1",$dbName,$depId,true);
//get the current user data from current department
$user = $profRep->get($_SESSION['CAS']['id'])[0];
//switch our connexion to that database
Repo::switchDatabase($dbName);
//create the default admin in the new database (categories are common to all department)
$profRep->create($user["lastName"],
$user["firstName"],
$user["Categorie_idCategorie"],
$user["hoursToDo"],
$user["abreviation"],
true,
$user["casLogin"]);
//update user session
$departments = $metaRepo->get_prof_departments($user["casLogin"]);
$_SESSION['AvailableDepartments'] = $departments;
//we are good now
return [];
}
}

View File

@ -14,7 +14,7 @@ use database\core\Repo;
class formationController
{
public static function get($args){
public function get($args){
$form_id = null;
extract($args);
@ -24,4 +24,37 @@ class formationController
return ['formations' => $repo->get($form_id)];
}
public function post($args){
$label = null;
$isInternal = null;
extract($args);
/** @var \database\repo\formation $repo */
$repo = Repo::getRepo("formation");
return ['idFormation' => $repo->create($label,$isInternal)];
}
public function put($args){
$idFormation = null;
$label = null;
$isInternal = null;
extract($args);
/** @var \database\repo\formation $repo */
$repo = Repo::getRepo("formation");
return ['success' => $repo->update($idFormation,$label,$isInternal)];
}
public function delete($args){
$idFormation = null;
extract($args);
/** @var \database\repo\formation $repo */
$repo = Repo::getRepo("formation");
return ['success' => $repo->delete($idFormation)];
}
}

View File

@ -0,0 +1,437 @@
<?php
namespace api\module\professor;
use database\core\Repo;
use database\repo\prof;
use database\repo\cours;
use database\repo\td;
use database\repo\tp;
use database\repo\formation;
use error\core\Error;
use error\core\Err;
class pdfController{
/* (1) Get professor PDF fiche matching a specific ID
*
* @prof_id<array> Professor ID
*
* @return download<File> The PDF fiche
---------------------------------------------------------*/
public static function get($args){
$prof_id = -1;
extract($args);
if(!$_SESSION["CAS"]["admin"] && $_SESSION["CAS"]["id"] != $prof_id){
return [
'headers' => ["Content-Type" => "text/html"],
'body' => "Unauthorized access"
];
}
/* (0) Initialize
---------------------------------------------------------*/
/* (1) Initialize data structure */
$data = [
'prof' => [],
'cours' => [],
'td' => [],
'tp' => [],
'form' => []
];
/* (2) Get repositories */
/** @var professor $prof_repo */
$prof_repo = Repo::getRepo('professor');
/** @var formation $form_repo */
$form_repo = Repo::getRepo('formation');
/** @var cours $cours_repo */
$cours_repo = Repo::getRepo('cours');
/** @var td $td_repo */
$td_repo = Repo::getRepo('td');
/** @var tp $tp_repo */
$tp_repo = Repo::getRepo('tp');
/* (1) Get professor data
---------------------------------------------------------*/
/* (1) Request */
$fetched = $prof_repo->getWithVH($prof_id);
/* (2) Error */
if( count($fetched) < 1 )
return ['error' => new Error(Err::NoMatchFound)];
/* (3) Extract data */
$data['prof'] = $fetched[0];
/* (2) Get each Cours
---------------------------------------------------------*/
/* (1) Request */
$fetched = $cours_repo->getForProfessor($prof_id);
/* (2) Store data if no error */
if( !is_null($fetched) )
$data['cours'] = $fetched;
/* (3) For each Cours -> parse formation list */
foreach($data['cours'] as $kcours=>$cours)
$data['cours'][$kcours]['formations'] = json_decode($cours['formations']);
/* (3) get each TD
---------------------------------------------------------*/
/* (1) Request */
$fetched = $td_repo->getForProfessor($prof_id);
/* (2) Store data if no error */
if( !is_null($fetched) )
$data['td'] = $fetched;
/* (3) For each TD -> parse formation list */
foreach($data['td'] as $ktd=>$td)
$data['td'][$ktd]['formations'] = json_decode($td['formations']);
/* (4) get each TP
---------------------------------------------------------*/
/* (1) Request */
$fetched = $tp_repo->getForProfessor($prof_id);
/* (2) Store data if no error */
if( !is_null($fetched) )
$data['tp'] = $fetched;
/* (3) For each TP -> parse formation list */
foreach($data['tp'] as $ktp=>$tp)
$data['tp'][$ktp]['formations'] = json_decode($tp['formations']);
/* (5) Get formations (id => label)
---------------------------------------------------------*/
/* (1) Request */
$fetched = $form_repo->get(null);
/* (2) Error: no formation found */
if( !is_array($fetched) )
return ['error' => new Error(Err::RepoError)];
/* (3) Reference formations by key = idForm */
foreach($fetched as $form)
$data['form'][intval($form['idForm'])] = $form;
/* (6) Render PDF
---------------------------------------------------------*/
/* (1) Generate body */
$body = self::generate_pdf_body($data);
/* (2) Set headers */
$fullname = $data['prof']['firstName'].' '.$data['prof']['lastName'];
$date = date('d-m-Y');
$title = "Fiche enseignant - $fullname ($date)";
$headers = [
// 'Content-Description' => 'File Transfer',
'Content-Transfer-Encoding' => 'binary',
'Content-Disposition' => 'attachment; filename="'.$title.'"',
'Cache-Control' => 'public, must-revalidate, max-age=0',
'Pragma' => 'public',
'X-Generator' => 'mPDF',
'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT',
'Last-Modified' => gmdate('D, d M Y H:i:s').' GMT',
'Content-Type' => 'application/pdf'
];
/* (3) Generate download */
return [
'headers' => $headers,
'body' => $body
];
}
private static function generate_pdf_body($data){
/* (1) Format data
---------------------------------------------------------*/
/* (1) Get fullname */
$fullname = $data['prof']['firstName'].' '.$data['prof']['lastName'];
/* (2) Get formatted date */
$date = date('d-m-Y');
/* (3) Total horaire */
$total_h = floatval($data['prof']['VHCours'])
+ floatval($data['prof']['VHTd'])
+ floatval($data['prof']['VHTp']);
$equiTD = floatval($data['prof']['equiTD']);
/* (4) Heures manquantes */
// TOTAL_H or equiTD ???
$missing_h = floatval($data['prof']['hoursToDo']) - $equiTD;
$missing_h = $missing_h < 0 ? 0 : $missing_h;
/* (5) Heures sup */
$sup_h = $equiTD > floatval($data['prof']['hoursToDo']) ? $equiTD - floatval($data['prof']['hoursToDo']) : 0;
/* (2) Initialize PDF
---------------------------------------------------------*/
/* (1) Get Font default directory */
$defaultConfig = (new \Mpdf\Config\ConfigVariables())->getDefaults();
$fontDirs = $defaultConfig['fontDir'];
/* (2) Get Font data */
$defaultFontConfig = (new \Mpdf\Config\FontVariables())->getDefaults();
$fontData = $defaultFontConfig['fontdata'];
/* (3) Create PDF file */
$pdf = new \Mpdf\Mpdf([
'mode' => 'utf-8',
'defaultCssFile' => __PUBLIC__.'/css/pdf.css',
'tempDir' => __ROOT__.'/tmp',
'fontDir' => array_merge($fontDirs, [ __PUBLIC__.'/font/' ]),
'fontdata' => $fontData + [
'Fira Sans' => [
'R' => 'FiraSans-Regular.ttf'
]
],
'default_font' => 'Fira Sans'
]);
/* (4) Set PDF title */
$pdf->SetTitle("Fiche enseignant - $fullname ($date)");
/* (5) Store SVG */
$svg_warning = '<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" height="10" width="10" viewBox="0 0 1792 1792">
<path d="m 1024,1375 v -190 q 0,-14 -9.5,-23.5 Q 1005,1152 992,1152 H 800 q -13,0 -22.5,9.5 -9.5,9.5 -9.5,23.5 v 190 q 0,14 9.5,23.5 9.5,9.5 22.5,9.5 h 192 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-23.5 z m -2,-374 18,-459 q 0,-12 -10,-19 -13,-11 -24,-11 H 786 q -11,0 -24,11 -10,7 -10,21 l 17,457 q 0,10 10,16.5 10,6.5 24,6.5 h 185 q 14,0 23.5,-6.5 9.5,-6.5 10.5,-16.5 z m -14,-934 768,1408 q 35,63 -2,126 -17,29 -46.5,46 -29.5,17 -63.5,17 H 128 Q 94,1664 64.5,1647 35,1630 18,1601 -19,1538 16,1475 L 784,67 q 17,-31 47,-49 30,-18 65,-18 35,0 65,18 30,18 47,49 z" style="fill: #ea4b35" />
</svg>';
/* (3) Set Header|Footer
---------------------------------------------------------*/
/* (1) Set Header content (Left, Right) */
$header_content = [
'C' => [ 'color' => '#aaa', 'content' => "Fiche enseignant $fullname" ],
'line' => 1
];
/* (2) Apply Header */
$pdf->setHeader([
'odd' => $header_content,
'even' => $header_content
]);
/* (1) Set Footer content (Left, Right) */
$footer_content = [
'L' => [ 'color' => '#aaa', 'content' => "Généré le $date" ],
'R' => [ 'color' => '#aaa', 'content' => 'Page {PAGENO} sur {nbpg}' ],
'line' => 1
];
/* (2) Apply Footer */
$pdf->setFooter([
'odd' => $footer_content,
'even' => $footer_content
]);
/* (4) Page 1 - Récapitulatif
---------------------------------------------------------*/
/* (1) Initialize page */
$pdf->AddPage();
/* (2) Write content */
$pdf->WriteHTML('<br>
<h3>1. Récapitulatif</h3>
<hr>
<article>
<p>
La liste des informations récapitulative est basée sur les données de tous les enseignants enregistrés à ce jour.
</p>
<blockquote>
Il est à noter qu\'elle contient les données générées au '.$date.'. Elle peut ne plus être à jour.
</blockquote>
<br><br><br>
<table>
<thead>
<tr>
<td class="color ac">Donnée</td>
<td class="color ac">Valeur</td>
</tr>
</thead>
<tbody>
<tr>
<td class="ar">Heures à faire</td>
<td>'.$data['prof']['hoursToDo'].' h</td>
</tr>
<tr>
<td class="ar">Equivalents TD</td>
<td>'.$data['prof']['equiTD'].' h</td>
</tr>
<tr>
<td class="ar">Heures programmées</td>
<td>'.$total_h.' h</td>
</tr>
<tr>
<td class="ar">Sous service</td>
<td>'.( $missing_h>0 ? "$svg_warning $missing_h" : $missing_h).' h</td>
</tr>
<tr>
<td class="ar">Sur service</td>
<td>'.$sup_h.' h</td>
</tr>
</tbody>
</table>
</article>
');
/* (5) Page 2 - Enseignements
---------------------------------------------------------*/
/* (1) Initialize page */
$pdf->AddPage();
/* (2) Write content */
$pdf->WriteHTML('<br>
<h3>2. Enseignements</h3>
<hr>
<article>
<p>
La liste des enseignements générée dans le tableau ci-dessous est uniquement à titre informatif et n\'est pas contractuelle.
</p>
<blockquote>
Il est à noter qu\'elle contient les données générées au '.$date.'. Elle peut ne plus être à jour.
</blockquote>
<br><br><br>
<table>
<thead>
<tr>
<th colspan="5" class="ac">Liste des enseignements programmés</th>
</tr>
<tr>
<td class="color ac">Code UE</td>
<td class="color ac">Nom UE</td>
<td class="color ac">Prestation</td>
<td class="color ac">volume horaire</td>
<td class="color ac">formations</td>
</tr>
</thead>
<tbody>');
/* (3) List Cours */
foreach($data['cours'] as $kc=>$c){
$pdf->WriteHTML('<tr>
<td>'.$c['code'].'</td>
<td>'.$c['label'].'</td>
<td>Cours</td>
<td>'.$c['volume'].' heures</td>
<td><ul>');
// print formations
foreach($c['formations'] as $f)
if( isset($data['form'][$f]) )
$pdf->WriteHTML('<li>'.$data['form'][$f]['labelForm'].'</li>');
$pdf->WriteHTML('</ul></td></tr>');
}
/* (4) List TD */
foreach($data['td'] as $kc=>$c){
$pdf->WriteHTML('<tr>
<td>'.$c['code'].'</td>
<td>'.$c['label'].'</td>
<td>TD</td>
<td>'.$c['volume'].' heures</td>
<td><ul>');
// print formations
foreach($c['formations'] as $f)
if( isset($data['form'][$f]) )
$pdf->WriteHTML('<li>'.$data['form'][$f]['labelForm'].'</li>');
$pdf->WriteHTML('</ul></td></tr>');
}
/* (5) List TP */
foreach($data['tp'] as $kc=>$c){
$pdf->WriteHTML('<tr>
<td>'.$c['code'].'</td>
<td>'.$c['label'].'</td>
<td>TP</td>
<td>'.$c['volume'].' heures</td>
<td><ul>');
// print formations
foreach($c['formations'] as $f)
if( isset($data['form'][$f]) )
$pdf->WriteHTML('<li>'.$data['form'][$f]['labelForm'].'</li>');
$pdf->WriteHTML('</ul></td></tr>');
}
/* (6) End HTML */
$pdf->WriteHTML('</tr>
</tbody>
</table>
</article>');
/* (N) Print out PDF file
---------------------------------------------------------*/
return $pdf->Output("Fiche enseignant - $fullname ($date)", 'S');
}
}

View File

@ -1,29 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: lucas
* Date: 27/02/18
* Time: 16:19
*/
namespace api\module\professor;
use database\core\Repo;
use database\repo\professor;
class statsController{
public static function get($args){
$idProf = 0;
extract($args);
//get data from the database
/** @var professor $profRepo */
$profRepo = Repo::getRepo("professor");
$VH = $profRepo->getWithVH($idProf);
return ["data" => $VH];
}
}

View File

@ -25,10 +25,31 @@ class coursController{
$formations = [];
extract($args);
/* Get the cours repo */
/* Get the repos */
/** @var cours $cours_repo */
$cours_repo = Repo::getRepo('cours');
/** @var ue $ue_repo */
$ue_repo = Repo::getRepo('ue');
/* (1) Fetch default formation from UE
---------------------------------------------------------*/
/* (1) Try to fetch the cours' UE */
$fetched_ue = $ue_repo->get($code);
/* (2) Manage error */
if( !is_array($fetched_ue) || count($fetched_ue) < 1 )
return ['error' => new Error(Err::RepoError)];
$defaultForm = intval($fetched_ue[0]['idForm']);
/* (3) Add to formation list if got a valid default formation */
if( is_int($defaultForm) && $defaultForm >= 0 && !in_array($defaultForm, $formations) )
$formations[] = $defaultForm;
/* (2) Create the cours
---------------------------------------------------------*/
/* (1) Try to create cours */
$created_id = $cours_repo->create($code, $idProf, $volume, $formations);
@ -36,7 +57,7 @@ class coursController{
if( is_null($created_id) || !is_int($created_id) )
return ['error' => new Error(Err::RepoError)];
return ['created_id' => $created_id];
return ['created_id' => $created_id, 'formations' => $formations];
}

View File

@ -25,10 +25,31 @@ class tdController{
$formations = [];
extract($args);
/* Get the td repo */
/* Get the repos */
/** @var td $td_repo */
$td_repo = Repo::getRepo('td');
/** @var ue $ue_repo */
$ue_repo = Repo::getRepo('ue');
/* (1) Fetch default formation from UE
---------------------------------------------------------*/
/* (1) Try to fetch the TD' UE */
$fetched_ue = $ue_repo->get($code);
/* (2) Manage error */
if( !is_array($fetched_ue) || count($fetched_ue) < 1 )
return ['error' => new Error(Err::RepoError)];
$defaultForm = intval($fetched_ue[0]['idForm']);
/* (3) Add to formation list if got a valid default formation */
if( is_int($defaultForm) && $defaultForm >= 0 && !in_array($defaultForm, $formations) )
$formations[] = $defaultForm;
/* (2) Create the TD
---------------------------------------------------------*/
/* (1) Try to create td */
$created_id = $td_repo->create($code, $idProf, $volume, $formations);
@ -36,7 +57,7 @@ class tdController{
if( is_null($created_id) || !is_int($created_id) )
return ['error' => new Error(Err::RepoError)];
return ['created_id' => $created_id];
return ['created_id' => $created_id, 'formations' => $formations];
}

View File

@ -25,10 +25,31 @@ class tpController{
$formations = [];
extract($args);
/* Get the tp repo */
/* Get the repos */
/** @var tp $tp_repo */
$tp_repo = Repo::getRepo('tp');
/** @var ue $ue_repo */
$ue_repo = Repo::getRepo('ue');
/* (1) Fetch default formation from UE
---------------------------------------------------------*/
/* (1) Try to fetch the TP' UE */
$fetched_ue = $ue_repo->get($code);
/* (2) Manage error */
if( !is_array($fetched_ue) || count($fetched_ue) < 1 )
return ['error' => new Error(Err::RepoError)];
$defaultForm = intval($fetched_ue[0]['idForm']);
/* (3) Add to formation list if got a valid default formation */
if( is_int($defaultForm) && $defaultForm >= 0 && !in_array($defaultForm, $formations) )
$formations[] = $defaultForm;
/* (2) Create the TP
---------------------------------------------------------*/
/* (1) Try to create tp */
$created_id = $tp_repo->create($code, $idProf, $volume, $formations);
@ -36,7 +57,7 @@ class tpController{
if( is_null($created_id) || !is_int($created_id) )
return ['error' => new Error(Err::RepoError)];
return ['created_id' => $created_id];
return ['created_id' => $created_id, 'formations' => $formations];
}

View File

@ -130,6 +130,7 @@ class ueController{
/* (4) Edits an existing UE
*
* @code<String> The code of the UE
* @new_code<String> [OPT] The new code of the UE
* @label<String> [OPT] The UE label (name)
* @required<bool> [OPT] If the UE is required
* @volumeCours<float> [OPT] The UE required volume of COURSES
@ -143,6 +144,7 @@ class ueController{
---------------------------------------------------------*/
public static function put($args){
$code = "";
$new_code = "";
$label = "";
$required = false;
$volumeCours = 0;
@ -156,9 +158,29 @@ class ueController{
/** @var ue $ue_repo */
$ue_repo = Repo::getRepo('ue');
/* (1) Try to update */
/* (1) Check for @new_code to be unique (not already used)
---------------------------------------------------------*/
if( !is_null($new_code) ){
/* (1) Check @new_code */
$exists = $ue_repo->get($new_code);
/* (2) If found -> already exists */
if( count($exists) > 0 )
return ['error' => new Error(Err::AlreadyExists)];
}
/* (2) Try to update
---------------------------------------------------------*/
return ['updated' => $ue_repo->update(
$code,
$new_code,
$label,
$required,
$volumeCours,

View File

@ -49,7 +49,11 @@ class PDOWrapper extends \PDO
if($this->stacking){
return new PDOStatementWrapper($statement, $this);
}else{
return parent::prepare($statement, $options);
$st = parent::prepare($statement, $options);
if($st === false){
throw new \PDOException("There is an error in your SQL statement");
}
return $st;
}
}
@ -154,23 +158,19 @@ class PDOWrapper extends \PDO
//find the given pattern in the request, then call our function and replace the matched string by the return value of our function
$finalStatement .= rtrim(preg_replace_callback("/(:[a-z_\-0-9]*)/is",function($matches) use (&$i,&$tempParametes){
//get next number
$i++;
//delete the ':' at the beginning of the string
$tempKey = ltrim($matches[0],':');
//copy the parameter with the modified index
$tempParametes[$tempKey.$i] = $tempParametes[$tempKey];
$tempParametes[":$i"] = $tempParametes[$matches[0]];
//delete the old index
unset($tempParametes[$tempKey]);
unset($tempParametes[$matches[0]]);
//return the modified string for replacement
return $matches[0].$i;
return ":".$i++;
},$statement),';').';';
$finalExecute = array_merge($finalExecute,$tempParametes);
$finalExecute += $tempParametes;
}
//disable stacking
@ -184,6 +184,7 @@ class PDOWrapper extends \PDO
$req = $this->prepare($finalStatement);
$success = $req->execute($finalExecute);
//as we execute multiple query that we don't fetch, we have to close the cursor if we want to do other requests later
$req->closeCursor();
$this->commit();

View File

@ -78,7 +78,7 @@
}
public static function switchDatabase(string $dbName){
return static::$driver->pdo()->prepare("USE $dbName")->execute();
return static::$driver->pdo()->exec("USE $dbName;SET autocommit=1;");
}
public static function getDBConfig() : array{

View File

@ -201,7 +201,7 @@ class cours extends Repo_i {
}
/* (x) Get groups for a specific UE
/* (6) Get groups for a specific UE
*
* @code<String> UE code
*
@ -246,4 +246,49 @@ class cours extends Repo_i {
}
/* (7) Get groups for a specific Professor
*
* @prof_id<int> Professor ID
*
* @return groups<array> The list of groups for this Professor
* NULL on error
*
---------------------------------------------------------*/
public function getForProfessor(int $prof_id) : ?array{
/* (1) Prepare statement */
$st = $this->pdo->prepare("SELECT
c.UE_code code,
ue.label label,
c.idCours,
IFNULL(lform.flist, '[]') formations,
c.volume
FROM Cours c
LEFT JOIN ( SELECT gc.Cours_idCours idCours, CONCAT('[', GROUP_CONCAT(DISTINCT gc.Formation_idFormation),']') flist
FROM GroupeCours gc
GROUP BY gc.Cours_idCours
ORDER BY gc.Formation_idFormation DESC
) lform ON c.idCours = lform.idCours
JOIN UE ue ON ue.code = c.UE_code
WHERE c.Professeur_idProfesseur = :prof_id;");
/* (2) Check statement */
if( is_bool($st) )
return NULL;
/* (3) Execute statement */
$success = $st->execute([':prof_id' => $prof_id]);
/* (4) Check error */
if( !$success )
return NULL;
/* (5) Dispatch fetch result */
return $st->fetchAll();
}
}

View File

@ -0,0 +1,266 @@
<?php
/**
* Created by PhpStorm.
* User: lucas
* Date: 09/05/18
* Time: 16:55
*/
namespace database\repo;
use database\core\Repo;
use database\core\Repo_i;
class database extends Repo_i {
/**
* @param int $depId
* @return String the name of the department database
* @throws \Exception
*/
public function init(int $depId) : String{
/** @var department $metaRep */
$metaRep = Repo::getRepo("department");
$SQL = "
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';
-- -----------------------------------------------------
-- Table `Categorie`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `Categorie` (
`idCategorie` INT(11) NOT NULL,
`labelCategorie` VARCHAR(100) NOT NULL,
PRIMARY KEY (`idCategorie`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;
-- -----------------------------------------------------
-- Table `Professeur`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `Professeur` (
`idProfesseur` INT(11) NOT NULL AUTO_INCREMENT,
`casLogin` VARCHAR(50) NULL DEFAULT NULL,
`lastName` VARCHAR(50) NULL DEFAULT NULL,
`firstName` VARCHAR(50) NULL DEFAULT NULL,
`abreviation` VARCHAR(10) NULL DEFAULT NULL COMMENT 'Abreviation used in the excel document',
`admin` TINYINT(4) NOT NULL DEFAULT 0,
`hoursToDo` INT(11) NOT NULL DEFAULT 0,
`Categorie_idCategorie` INT(11) NOT NULL,
PRIMARY KEY (`idProfesseur`),
INDEX `fk_Professeur_Categorie1_idx` (`Categorie_idCategorie` ASC),
CONSTRAINT `fk_Professeur_Categorie1`
FOREIGN KEY (`Categorie_idCategorie`)
REFERENCES `Categorie` (`idCategorie`)
ON DELETE NO ACTION
ON UPDATE CASCADE)
ENGINE = InnoDB
AUTO_INCREMENT = 34
DEFAULT CHARACTER SET = latin1;
-- -----------------------------------------------------
-- Table `Formation`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `Formation` (
`idFormation` INT(11) NOT NULL AUTO_INCREMENT,
`labelFormation` VARCHAR(50) NULL DEFAULT NULL,
`isInternal` TINYINT(4) NULL DEFAULT 1,
PRIMARY KEY (`idFormation`))
ENGINE = InnoDB
AUTO_INCREMENT = 20
DEFAULT CHARACTER SET = latin1;
-- -----------------------------------------------------
-- Table `UE`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `UE` (
`code` VARCHAR(20) NOT NULL,
`label` VARCHAR(100) NOT NULL,
`required` TINYINT(4) NOT NULL DEFAULT 1,
`volumeCours` FLOAT NOT NULL DEFAULT 0,
`volumeTP` FLOAT NOT NULL DEFAULT 0,
`volumeTD` FLOAT NOT NULL,
`disabled` TINYINT(4) NOT NULL DEFAULT 0,
`Formation_idFormation` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`code`),
INDEX `fk_UE_Formation1_idx` (`Formation_idFormation` ASC),
CONSTRAINT `fk_UE_Formation1`
FOREIGN KEY (`Formation_idFormation`)
REFERENCES `Formation` (`idFormation`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1
COMMENT = 'Table contenant le code et le label des UE';
-- -----------------------------------------------------
-- Table `Cours`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `Cours` (
`idCours` INT(11) NOT NULL AUTO_INCREMENT,
`UE_code` VARCHAR(20) NOT NULL,
`Professeur_idProfesseur` INT(11) NULL DEFAULT NULL,
`volume` FLOAT NOT NULL DEFAULT 0,
PRIMARY KEY (`idCours`),
INDEX `fk_Cours_UE_idx` (`UE_code` ASC),
INDEX `fk_Cours_Professeur1_idx` (`Professeur_idProfesseur` ASC),
CONSTRAINT `fk_Cours_Professeur1`
FOREIGN KEY (`Professeur_idProfesseur`)
REFERENCES `Professeur` (`idProfesseur`)
ON DELETE SET NULL
ON UPDATE SET NULL,
CONSTRAINT `fk_Cours_UE`
FOREIGN KEY (`UE_code`)
REFERENCES `UE` (`code`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
AUTO_INCREMENT = 88
DEFAULT CHARACTER SET = latin1;
-- -----------------------------------------------------
-- Table `GroupeCours`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `GroupeCours` (
`Formation_idFormation` INT(11) NOT NULL,
`Cours_idCours` INT(11) NOT NULL,
PRIMARY KEY (`Formation_idFormation`, `Cours_idCours`),
INDEX `fk_Formation_has_Cours_Cours1_idx` (`Cours_idCours` ASC),
INDEX `fk_Formation_has_Cours_Formation1_idx` (`Formation_idFormation` ASC),
CONSTRAINT `fk_Formation_has_Cours_Cours1`
FOREIGN KEY (`Cours_idCours`)
REFERENCES `Cours` (`idCours`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `fk_Formation_has_Cours_Formation1`
FOREIGN KEY (`Formation_idFormation`)
REFERENCES `Formation` (`idFormation`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;
-- -----------------------------------------------------
-- Table `TD`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `TD` (
`idTD` INT(11) NOT NULL AUTO_INCREMENT,
`UE_code` VARCHAR(20) NOT NULL,
`Professeur_idProfesseur` INT(11) NULL DEFAULT NULL,
`volume` FLOAT NOT NULL DEFAULT 0,
PRIMARY KEY (`idTD`),
INDEX `fk_TD_UE1_idx` (`UE_code` ASC),
INDEX `fk_TD_Professeur1_idx` (`Professeur_idProfesseur` ASC),
CONSTRAINT `fk_TD_Professeur1`
FOREIGN KEY (`Professeur_idProfesseur`)
REFERENCES `Professeur` (`idProfesseur`)
ON DELETE SET NULL
ON UPDATE SET NULL,
CONSTRAINT `fk_TD_UE1`
FOREIGN KEY (`UE_code`)
REFERENCES `UE` (`code`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
AUTO_INCREMENT = 107
DEFAULT CHARACTER SET = latin1;
-- -----------------------------------------------------
-- Table `GroupeTD`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `GroupeTD` (
`Formation_idFormation` INT(11) NOT NULL,
`TD_idTD` INT(11) NOT NULL,
PRIMARY KEY (`Formation_idFormation`, `TD_idTD`),
INDEX `fk_Formation_has_TD_TD1_idx` (`TD_idTD` ASC),
INDEX `fk_Formation_has_TD_Formation1_idx` (`Formation_idFormation` ASC),
CONSTRAINT `fk_Formation_has_TD_Formation1`
FOREIGN KEY (`Formation_idFormation`)
REFERENCES `Formation` (`idFormation`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `fk_Formation_has_TD_TD1`
FOREIGN KEY (`TD_idTD`)
REFERENCES `TD` (`idTD`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;
-- -----------------------------------------------------
-- Table `TP`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `TP` (
`idTP` INT(11) NOT NULL AUTO_INCREMENT,
`UE_code` VARCHAR(20) NOT NULL,
`Professeur_idProfesseur` INT(11) NULL DEFAULT NULL,
`volume` FLOAT NOT NULL DEFAULT 0,
PRIMARY KEY (`idTP`),
INDEX `fk_TP_UE1_idx` (`UE_code` ASC),
INDEX `fk_TP_Professeur1_idx` (`Professeur_idProfesseur` ASC),
CONSTRAINT `fk_TP_Professeur1`
FOREIGN KEY (`Professeur_idProfesseur`)
REFERENCES `Professeur` (`idProfesseur`)
ON DELETE SET NULL
ON UPDATE SET NULL,
CONSTRAINT `fk_TP_UE1`
FOREIGN KEY (`UE_code`)
REFERENCES `UE` (`code`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
AUTO_INCREMENT = 164
DEFAULT CHARACTER SET = latin1;
-- -----------------------------------------------------
-- Table `GroupeTP`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `GroupeTP` (
`Formation_idFormation` INT(11) NOT NULL,
`TP_idTP` INT(11) NOT NULL,
PRIMARY KEY (`Formation_idFormation`, `TP_idTP`),
INDEX `fk_Formation_has_TP_TP1_idx` (`TP_idTP` ASC),
INDEX `fk_Formation_has_TP_Formation1_idx` (`Formation_idFormation` ASC),
CONSTRAINT `fk_Formation_has_TP_Formation1`
FOREIGN KEY (`Formation_idFormation`)
REFERENCES `Formation` (`idFormation`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `fk_Formation_has_TP_TP1`
FOREIGN KEY (`TP_idTP`)
REFERENCES `TP` (`idTP`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;
SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
LOCK TABLES `Categorie` WRITE;
/*!40000 ALTER TABLE `Categorie` DISABLE KEYS */;
INSERT INTO `Categorie` VALUES (1,'Professeurs et Maîtres de Conférences'),(2,'ATERs'),(3,'CDDs enseignement'),(4,'Doctorants Vacataires'),(5,'Permanents UPPA'),(6,'Vacataires extérieurs');
/*!40000 ALTER TABLE `Categorie` ENABLE KEYS */;
UNLOCK TABLES;
";
return $metaRep->createVersionDatabase($depId,$SQL);
}
}

View File

@ -167,36 +167,13 @@ class department extends Repo_i
}
public function restore(string $dbName, string $SQL) : bool{
public function createVersionDatabase(String $depId, String $SQL) : String{
$dbName = uniqid($depId)."";
//get the list of command to execute
$this->pdo->exec("DROP DATABASE $dbName;");
$this->pdo->exec("CREATE DATABASE $dbName;");
$this->pdo->exec("USE $dbName;");
$this->pdo->exec("CREATE DATABASE $dbName; USE $dbName;".$SQL."USE {$_SESSION["CurrentDatabase"]};SET autocommit=1;");
$this->pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT,1);
return $this->pdo->exec($SQL);
}
private static function generatePreviewDBName(string $bdName, string $backupName) : string{
return "preview_{$bdName}_".sha1($bdName.$backupName);
}
public function previewExists(string $dbName, string $backupname) : ?string {
$previewDBName = static::generatePreviewDBName($dbName,$backupname);
$st = $this->pdo->prepare("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = :dbName");
$st->execute(["dbName" => $previewDBName]);
if($st->fetch() === false) return null;
else return $previewDBName;
}
public function createPreview(string $dbName, string $backupname) : string {
$previewDBName = static::generatePreviewDBName($dbName,$backupname);
$this->pdo->exec("CREATE OR REPLACE DATABASE $previewDBName");
return $previewDBName;
}
return $dbName;
}
}

View File

@ -42,6 +42,26 @@ class formation extends Repo_i {
}
public function update(int $idFormation, ?String $label, ?bool $isInternal) : bool{
$req = "";
$execute = [];
if($label != null){
$req .= "labelFormation=:label,";
$execute["label"] = $label;
}
if($isInternal != null){
$req .= "isInternal=:isInternal,";
$execute["isInternal"] = $isInternal?1:0;
}
$req = rtrim($req,",");
$execute["idFormation"] = $idFormation;
$st = $this->pdo->prepare("UPDATE `Formation` SET $req WHERE idFormation=:idFormation");
return $st->execute($execute);
}
/* (2) Check if a formation exists (by its label)

View File

@ -0,0 +1,351 @@
<?php
namespace database\repo;
use database\core\Repo_i;
class meta extends Repo_i {
/* (1) Creates a new professor
*
* @casLogin<String> The professor's cas login
* @firstName<String> [OPT] The professor's firstName
* @lastName<String> [OPT] The professor's lastName
*
* @return created<bool> Whether the professor has been created
*
---------------------------------------------------------*/
public function create_prof(String $casLogin, ?String $firstName, ?String $lastName) : bool{
/* (1) Create user in meta database
---------------------------------------------------------*/
/* (1) Try to insert user */
$st = $this->pdo->prepare("INSERT INTO
meta_vhost.casUser(casLogin, firstName, lastName)
VALUES(:cas_login, :first_name, :last_name)");
/* (2) Manage statement error */
if( is_bool($st) )
return FALSE;
/* (3) Try to execute */
$success = $st->execute([
':cas_login' => $casLogin,
':first_name' => $firstName,
':last_name' => $lastName
]);
/* (4) Dispatch execution error */
return $success;
}
/* (2) Check if a professor exists (by its casLogin)
*
* @casLogin<String> The professor's cas login
*
* @return exists<bool> Whether the professor exists
*
---------------------------------------------------------*/
public function prof_exists(String $casLogin) : bool{
/* (1) Prepare Statement */
$st = $this->pdo->prepare("SELECT casLogin
FROM meta_vhost.casUser
WHERE casLogin = :cas_login;");
/* (2) Statement eror */
if( is_bool($st) )
return FALSE;
/* (3) Bind params and execute */
$success = $st->execute([ ':cas_login' => $casLogin ]);
/* (4) Manage execution error */
if( !$success )
return FALSE;
/* (7) Return That it exists */
return is_array( $st->fetch() );
}
/* (3) Get available departments for a CAS login
*
* @casLogin<String> The professor's CAS username
*
* @return departments<array> The list of available departments (empty on error)
*
---------------------------------------------------------*/
public function get_prof_departments(String $casLogin) : array{
/* (1) Prepare Statement */
$st = $this->pdo->prepare("SELECT d2.iddepartement idDep, d2.label labelDep
FROM meta_vhost.casUser
JOIN meta_vhost.linkedDep D ON casUser.casLogin = D.casUser_casLogin
JOIN meta_vhost.departement d2 ON D.departement_iddepartement = d2.iddepartement
WHERE casLogin = :cas_login");
/* (2) Check if statement error */
if( is_bool($st) )
return [];
/* (3) Bind params and execute sstatement */
$success = $st->execute([ ':cas_login' => $casLogin ]);
/* (4) Manage error */
if( !$success )
return [];
/* (5) Get data */
$fetched = $st->fetchAll();
/* (6) Return [] on no result */
if( $fetched === false )
return [];
/* (7) Add all possible databases to the department */
foreach ($fetched as &$dep){
$st = $this->pdo->prepare("SELECT *
FROM meta_vhost.`databases`
WHERE departement_iddepartement = :idDep
ORDER BY meta_vhost.`databases`.`default` DESC ");
$st->execute(["idDep" => $dep["idDep"]]);
$dep["versions"] = $st->fetchAll();
}
/* (8) Return data */
return $fetched;
}
/* (4) Deletes a professor
*
* @casLogin<String> The professor's CAS username
*
* @return deleted<bool> Whether the professor have been deleeted successfully
*
---------------------------------------------------------*/
public function delete_prof(String $casLogin) : bool{
/* (1) Prepare statement */
$st = $this->pdo->prepare('DELETE FROM meta_vhost.casUser
WHERE casLogin = :cas_login');
/* (2) Manage statement error */
if( is_bool($st) )
return FALSE;
/* (3) Try to execute */
$success = $st->execute([ ':cas_login' => $casLogin ]);
/* (4) Dispatch execution error */
return $success;
}
/* (5) Check if a link exists
*
* @casLogin<String> The professor's cas login
* @idDep<int> The department id
*
* @return exists<bool> Whether the professor exists
*
---------------------------------------------------------*/
public function link_exists(String $casLogin, int $idDep) : bool{
/* (1) Prepare Statement */
$st = $this->pdo->prepare("SELECT *
FROM meta_vhost.linkedDep
WHERE departement_iddepartement = :id_dep
AND casUser_casLogin = :cas_login");
/* (2) Statement eror */
if( is_bool($st) )
return FALSE;
/* (3) Bind params and execute */
$success = $st->execute([
':id_dep' => $idDep,
':cas_login' => $casLogin
]);
/* (4) Manage execution error */
if( !$success )
return FALSE;
/* (7) Return That it exists */
return is_array( $st->fetch() );
}
/* (6) Associates a professor to a department
*
* @casLogin<String> The professor's CAS username
* @idDep<int> The department id
*
* @return linked<bool> Whether the link have been created
*
---------------------------------------------------------*/
public function link(String $casLogin, int $idDep) : bool{
/* (1) Prepare statement */
$st = $this->pdo->prepare("INSERT INTO meta_vhost.linkedDep(departement_iddepartement, casUser_casLogin)
VALUES(:id_dep, :cas_login);");
/* (2) Manage statement error */
if( is_bool($st) )
return FALSE;
/* (3) Try to execute request */
$success = $st->execute([
':id_dep' => $idDep,
':cas_login' => $casLogin
]);
/* (4) Dispatch execution error */
return $success;
}
/* (7) Remove a department from a professor
*
* @casLogin<String> The professor's CAS username
* @idDep<int> The department id
*
* @return unlinked<bool> Whether the link have been removed
*
---------------------------------------------------------*/
public function unlink(String $casLogin, int $idDep) : bool{
/* (1) Prepare statement */
$st = $this->pdo->prepare("DELETE FROM meta_vhost.linkedDep
WHERE departement_iddepartement = :id_dep
AND casUser_casLogin = :cas_login;");
/* (2) Manage statement error */
if( is_bool($st) )
return FALSE;
/* (3) Try to execute request */
$success = $st->execute([
':id_dep' => $idDep,
':cas_login' => $casLogin
]);
/* (4) Dispatch execution error */
return $success;
}
public function deleteVersion(int $version) : bool{
//get version data
$st = $this->pdo->prepare("SELECT * FROM meta_vhost.`databases` WHERE iddatabase = :id");
$st->execute(["id" => $version]);
$versionData = $st->fetch();
if( !is_array($versionData) || $versionData["default"] == 1){
return false;
}
//delete database
$versionDatabase = $versionData['dbName'];
$this->pdo->exec("DROP DATABASE $versionDatabase;");
//remove from meta
$st = $this->pdo->prepare("DELETE FROM meta_vhost.`databases` WHERE iddatabase = :id");
return $st->execute(["id" => $version]);
}
public function createVersion(String $label, String $dbName, int $depId, bool $isDefault = false) : int{
$st = $this->pdo->prepare("INSERT INTO meta_vhost.`databases`(label, dbName, `default`, departement_iddepartement)
VALUE (:label,:dbName,:default,:depId)");
$st->execute([
"label" => $label,
"dbName" => $dbName,
"default" => $isDefault? 1 : 0,
"depId" => $depId
]);
return $this->pdo->lastInsertId();
}
public function getVersionById(int $id) : array{
$st = $this->pdo->prepare("SELECT * FROM meta_vhost.`databases` WHERE iddatabase = :idVersion");
$st->execute(["idVersion" => $id]);
$fetched = $st->fetch();
return $fetched == false ? [] : $fetched;
}
public function getAllVersions(int $idDep) : array {
$st = $this->pdo->prepare("SELECT * FROM meta_vhost.`databases` WHERE departement_iddepartement = :idDep");
$st->execute(["idDep" => $idDep]);
return $st->fetchAll();
}
public function updateVersion(int $version, ?String $label, ?bool $default) : bool{
//you can't un-default a version, you have to choose another version to be the default one
if($default == false){
return false;
}
$set = "";
$execute = [];
if($label != null){
$set .= "label=:label,";
$execute["label"] = $label;
}
if($default != null){
$set .= "`default`=:default,";
$execute["default"] = $default?1:0;
//if default = true, set previous default version to non-default
if($default == true){
$req = $this->pdo->prepare("UPDATE meta_vhost.`databases` SET `default`=0 WHERE departement_iddepartement=:idDep");
$req->execute(["idDep" => $_SESSION['CurrentDepartmentId']]);
}
}
$set = rtrim($set,",");
$execute["idVersion"] = $version;
$st = $this->pdo->prepare("UPDATE meta_vhost.`databases` SET $set WHERE iddatabase=:idVersion");
return $st->execute($execute);
}
public function createDepartment(String $name) : int{
$st = $this->pdo->prepare("INSERT INTO meta_vhost.departement(label) VALUE (:label)");
$st->execute(["label" => $name]);
return $this->pdo->lastInsertId();
}
}

View File

@ -10,6 +10,7 @@ namespace database\repo;
use database\core\Repo_i;
use database\core\Repo;
class professor extends Repo_i {
@ -29,12 +30,19 @@ class professor extends Repo_i {
---------------------------------------------------------*/
public function create(string $lastName, string $firstName, int $category, int $hoursToDo = 0, ?string $initials = null , bool $isAdmin = false , ?string $casLogin = null ) : ?int{
/* (1) Create professor into local database
---------------------------------------------------------*/
/* (1) Prepare Statement */
$st = $this->pdo->prepare("INSERT INTO
Professeur(`casLogin`, `lastName`, `firstName`, `abreviation`, `admin`, `hoursToDo`, `Categorie_idCategorie`)
VALUE (:casLogin, :lastName, :firstName, :abrev, :is_admin, :hoursToDo, :cat);");
/* (2) Bind params and execute */
/* (2) Manage statement error */
if( is_bool($st) )
return NULL;
/* (3) Bind params and execute */
$success = $st->execute([
':casLogin' => $casLogin,
':lastName' => $lastName,
@ -45,30 +53,44 @@ class professor extends Repo_i {
':cat' => $category
]);
$profId = $this->pdo->lastInsertId();
/* (3) synchroize the meta database */
if(!is_null($casLogin)){
$st = $this->pdo->prepare("INSERT IGNORE INTO meta_vhost.casUser(casLogin, firstName, lastName) VALUE (:casLogin,:firstName,:lastName)");
$st->execute([
"casLogin" => $casLogin,
"firstName" => $firstName,
"lastName" => $lastName
]);
$st = $this->pdo->prepare("INSERT INTO meta_vhost.linkedDep(departement_iddepartement, casUser_casLogin) VALUE (:idDep,:casLogin)");
$st->execute([
"idDep" => $_SESSION['CurrentDepartementId'],
"casLogin" => $casLogin
]);
}
/* (3) Manage error */
/* (4) If execution error -> dispatch */
if( !$success )
return NULL;
/* (4) Return inserted ID */
return $profId;
/* (5) Store id */
$id_prof = $this->pdo->lastInsertId();
/* (6) Exit now if no meta database to udpate */
if( is_null($casLogin) )
return $id_prof;
/* (2) Synchronize meta database
---------------------------------------------------------*/
/** @var meta $meta_repo */
$meta_repo = Repo::getRepo('meta');
/* (1) If user does not exist */
if( !$meta_repo->prof_exists($casLogin) ){
/* (2) Try to create -> dispatch error */
if( !$meta_repo->create_prof($casLogin, $firstName, $lastName) )
return NULL;
}
/* (3) If link already exists -> done */
if( $meta_repo->link_exists($casLogin, $_SESSION['CurrentDepartmentId']) )
return $id_prof;
/* (4) Else: try to link prof to dep -> dispatch error */
if( !$meta_repo->link($casLogin, $_SESSION['CurrentDepartmentId']) )
return NULL;
/* (5) If reached here -> Success */
return $id_prof;
}
@ -115,42 +137,11 @@ class professor extends Repo_i {
}
public function getLinkedDepartment(string $casLogin) : ?array{
/* (1) Prepare Statement */
$st = $this->pdo->prepare("SELECT d2.iddepartement idDep, d2.label labelDep, d2.databaseName dbName
FROM meta_vhost.casUser
JOIN meta_vhost.linkedDep D ON casUser.casLogin = D.casUser_casLogin
JOIN meta_vhost.departement d2 ON D.departement_iddepartement = d2.iddepartement
WHERE casLogin = :caslogin");
/* (2) Check if statement error */
if( is_bool($st) )
return NULL;
/* (3) Bind params and execute statement */
$success = $st->execute([ ':caslogin' => $casLogin ]);
/* (4) Manage error */
if( !$success )
return NULL;
/* (5) Get data */
$fetched = $st->fetchAll();
/* (6) Return NULL on no result */
if( $fetched === false )
return NULL;
/* (7) Return data */
return $fetched;
}
/* (3) Check if a professor exists (by its names)
/* (3) Updates a professor's data
*
* @idProf<int> The professor's UID
* @lastName<String> [OPT] The professor's new lastName
@ -166,6 +157,29 @@ class professor extends Repo_i {
---------------------------------------------------------*/
public function update(int $id, ?String $lastName, ?String $firstName, ?int $category, ?int $hoursToDo, ?String $initials, ?bool $isAdmin, ?String $casLogin) : bool{
/* (1) Check professor data
---------------------------------------------------------*/
/* (1) Try to fetch professor's data */
$prof = $this->get($id);
/* (2) Error: no professor found */
if( count($prof) === 0 )
return FALSE;
/* (3) Take first matching professor */
$prof = $prof[0];
/* (4) Extract old CAS login */
$oldCasLogin = $prof['casLogin'];
/* (2) Update current database
---------------------------------------------------------*/
/* (1) Build request */
$build_rq = [];
$bind_param = [ ':idProfesseur' => $id ];
@ -191,29 +205,76 @@ class professor extends Repo_i {
/* (5) Return execution success */
$success = $st->execute($bind_param);
$prof = $this->get($id);
if($success && !is_null($prof[0]["casLogin"])){
//try to get the user
$st = $this->pdo->prepare("SELECT * FROM meta_vhost.casUser WHERE casLogin = :casLogin");
$st->execute([
"casLogin" => $prof[0]["casLogin"]
]);
/* (6) Manage execution error */
if( !$success )
return FALSE;
/* (7) If do not have to update 'meta' database -> success */
if( is_null($casLogin) )
return TRUE;
/** @var meta $meta_repo */
$meta_repo = Repo::getRepo('meta');
/* (3) Synchronize meta database -> remove old value
---------------------------------------------------------*/
if( !is_null($oldCasLogin) && strlen($oldCasLogin) > 0 ){
/* Proccess only if professor exists */
if( $meta_repo->prof_exists($oldCasLogin) ){
/* (1) If link exists -> remove it */
if( $meta_repo->link_exists($oldCasLogin, $_SESSION['CurrentDepartmentId']) ){
/* (2) Try to unlink-> ignore error */
$meta_repo->unlink($oldCasLogin, $_SESSION['CurrentDepartmentId']);
}
/* (3) If has no more department -> remove professor */
if( count( $meta_repo->get_prof_departments($oldCasLogin) ) == 0 ){
/* (4) Try to remove professor -> dispatch error */
if( !$meta_repo->delete_prof($oldCasLogin) )
return FALSE;
}
//is the user does not already exists, we create it
if(!is_array($st->fetch())){
$st = $this->pdo->prepare("INSERT INTO meta_vhost.casUser(casLogin, firstName, lastName)
VALUE(:casLogin,:firstName,:lastName)");
}else{
$st = $this->pdo->prepare("UPDATE meta_vhost.casUser SET casLogin = :casLogin,firstName = :firstName, lastName = :lastName ");
}
$st->execute([
"firstName" => $prof[0]["firstName"],
"lastName" => $prof[0]["lastName"],
"casLogin" => $prof[0]["casLogin"]
]);
}
return $success;
/* (4) Synchronize meta database -> create new
---------------------------------------------------------*/
if( !is_null($casLogin) && strlen($casLogin) > 0 ){
/* (1) If user does not exist */
if( !$meta_repo->prof_exists($casLogin) ){
/* (2) Try to create -> dispatch error */
if( !$meta_repo->create_prof($casLogin, $prof['firstName'], $prof['lastName']) )
return FALSE;
}
/* (3) If link already exists -> done */
if( $meta_repo->link_exists($casLogin, $_SESSION['CurrentDepartmentId']) )
return TRUE;
/* (4) Else: try to link prof to dep -> dispatch error */
if( !$meta_repo->link($casLogin, $_SESSION['CurrentDepartmentId']) )
return FALSE;
}
/* (5) If reached here -> Success */
return TRUE;
}
@ -260,7 +321,7 @@ class professor extends Repo_i {
$parm = is_null($prof_id) ? [] : [':id' => $prof_id];
/* (2) Prepare Statement */
$st = $this->pdo->prepare("SELECT * FROM `Professeur`$cond ORDER BY abreviation ASC");
$st = $this->pdo->prepare("SELECT * FROM `Professeur`$cond ORDER BY firstName, lastName ASC");
/* (3) Bind params and execute statement */
if( is_bool($st) ) return [];
@ -372,7 +433,8 @@ class professor extends Repo_i {
AND VHTp.idProf = Prof.idProfesseur
AND VHTd.idProf = Prof.idProfesseur
GROUP BY
Prof.idProfesseur;");
Prof.idProfesseur
ORDER BY Prof.firstName, Prof.lastName ASC;");
/* (3) Bind params and execute statement */
if( is_bool($st) ) return [];
@ -431,25 +493,77 @@ class professor extends Repo_i {
---------------------------------------------------------*/
public function delete(int $id) : bool{
//we have to store the professor to synchronize the meta database later
/** @var meta $meta_repo */
$meta_repo = Repo::getRepo('meta');
/* (1) Try to fetch professor data
---------------------------------------------------------*/
/* (1) Try to fetch */
$prof = $this->get($id);
/* (2) Error: if not found */
if( count($prof) === 0 )
return false;
/* (3) Take first matching professor */
$prof = $prof[0];
/* (4) Extract @casLogin */
$casLogin = $prof['casLogin'];
/* (2) Remove professor from current database
---------------------------------------------------------*/
/* (1) Prepare statement */
$st = $this->pdo->prepare("DELETE FROM `Professeur` WHERE `idProfesseur` = :id");
/* (2) Return the execution status */
/* (2) Manage statement error */
if( is_bool($st) )
return false;
/* (3) Return the execution status */
$success = $st->execute([ ':id' => $id ]);
if($success){
//delete the association
$st = $this->pdo->prepare("DELETE FROM meta_vhost.linkedDep WHERE casUser_casLogin = :casLogin AND departement_iddepartement = :idDep");
$st->execute([
"casLogin" => $prof[0]["casLogin"],
"idDep" => $_SESSION['CurrentDepartementId']
]);
/* (4) Error: on execution error */
if( !$success )
return false;
/* (5) Success if no @casLogin to update in meta */
if( !$meta_repo->prof_exists($casLogin) )
return TRUE;
/* (3) Synchronize meta database
---------------------------------------------------------*/
/* Proccess only if professor exists */
if( $meta_repo->prof_exists($casLogin) ){
/* (1) If link exists -> remove it */
if( $meta_repo->link_exists($casLogin, $_SESSION['CurrentDepartmentId']) ){
/* (2) Try to unlink-> ignore error */
$meta_repo->unlink($casLogin, $_SESSION['CurrentDepartmentId']);
}
/* (3) If has no more department -> remove professor */
if( count( $meta_repo->get_prof_departments($casLogin) ) == 0 ){
/* (4) Try to remove professor -> dispatch error */
if( !$meta_repo->delete_prof($casLogin) )
return FALSE;
}
}
return $success;
return TRUE;
}

View File

@ -246,4 +246,49 @@ class td extends Repo_i {
}
/* (7) Get groups for a specific Professor
*
* @prof_id<int> Professor ID
*
* @return groups<array> The list of groups for this Professor
* NULL on error
*
---------------------------------------------------------*/
public function getForProfessor(int $prof_id) : ?array{
/* (1) Prepare statement */
$st = $this->pdo->prepare("SELECT
td.UE_code code,
ue.label label,
td.idTD,
IFNULL(lform.flist, '[]') formations,
td.volume
FROM TD td
LEFT JOIN ( SELECT gtd.TD_idTD idTD, CONCAT('[', GROUP_CONCAT(DISTINCT gtd.Formation_idFormation),']') flist
FROM GroupeTD gtd
GROUP BY gtd.TD_idTD
ORDER BY gtd.Formation_idFormation DESC
) lform ON td.idTD = lform.idTD
JOIN UE ue ON ue.code = td.UE_code
WHERE td.Professeur_idProfesseur = :prof_id;");
/* (2) Check statement */
if( is_bool($st) )
return NULL;
/* (3) Execute statement */
$success = $st->execute([':prof_id' => $prof_id]);
/* (4) Check error */
if( !$success )
return NULL;
/* (5) Dispatch fetch result */
return $st->fetchAll();
}
}

View File

@ -246,4 +246,49 @@ class tp extends Repo_i {
}
/* (7) Get groups for a specific Professor
*
* @prof_id<int> Professor ID
*
* @return groups<array> The list of groups for this Professor
* NULL on error
*
---------------------------------------------------------*/
public function getForProfessor(int $prof_id) : ?array{
/* (1) Prepare statement */
$st = $this->pdo->prepare("SELECT
tp.UE_code code,
ue.label label,
tp.idTP,
IFNULL(lform.flist, '[]') formations,
tp.volume
FROM TP tp
LEFT JOIN ( SELECT gtp.TP_idTP idTP, CONCAT('[', GROUP_CONCAT(DISTINCT gtp.Formation_idFormation),']') flist
FROM GroupeTP gtp
GROUP BY gtp.TP_idTP
ORDER BY gtp.Formation_idFormation DESC
) lform ON tp.idTP = lform.idTP
JOIN UE ue ON ue.code = tp.UE_code
WHERE tp.Professeur_idProfesseur = :prof_id;");
/* (2) Check statement */
if( is_bool($st) )
return NULL;
/* (3) Execute statement */
$success = $st->execute([':prof_id' => $prof_id]);
/* (4) Check error */
if( !$success )
return NULL;
/* (5) Dispatch fetch result */
return $st->fetchAll();
}
}

View File

@ -63,6 +63,7 @@ class ue extends Repo_i {
/* (2) Updates an UE data
*
* @code<String> The UE code
* @new_code<String> [OPT] The new UE code
* @label<String> [OPT] The UE's new label
* @required<bool> [OPT] Whether the UE is required
* @volumeCours<float> [OPT] The UE's new volume of COURSES
@ -74,12 +75,13 @@ class ue extends Repo_i {
* @return updated<bool> Whether the update have been successful
*
---------------------------------------------------------*/
public function update(string $code, ?String $label, ?bool $required, ?float $volumeCours, ?float $volumeTD, ?float $volumeTP, ?bool $disabled, ?int $defaultFormation) : bool{
public function update(string $code, ?String $new_code, ?String $label, ?bool $required, ?float $volumeCours, ?float $volumeTD, ?float $volumeTP, ?bool $disabled, ?int $defaultFormation) : bool{
/* (1) Build request */
$build_rq = [];
$bind_param = [ ':code' => $code ];
if( !is_null($new_code) ){ $build_rq[] = '`code` = :new_code'; $bind_param[':new_code'] = $new_code; }
if( !is_null($label) ){ $build_rq[] = '`label` = :label'; $bind_param[':label'] = $label; }
if( !is_null($required) ){ $build_rq[] = '`required` = :required'; $bind_param[':required'] = $required?1:0; }
if( !is_null($volumeCours) ){ $build_rq[] = '`volumeCours` = :volumeCours'; $bind_param[':volumeCours'] = $volumeCours; }
@ -178,37 +180,56 @@ class ue extends Repo_i {
$parm = is_null($code) ? [] : [':code' => $code];
/* (2) Prepare Statement */
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$st = $this->pdo->prepare("SELECT
ue.code,
ue.label,
ue.disabled,
ue.required,
ue.volumeCours,
ue.volumeTD,
ue.volumeTP,
IFNULL(ue.Formation_idFormation, -1) idForm,
fdef.labelFormation labelForm,
IFNULL(formlist.formations, '[]') formations
FROM UE ue
LEFT JOIN Formation fdef ON ue.Formation_idFormation = fdef.idFormation
LEFT JOIN (
SELECT ue2.code code, CONCAT('[',GROUP_CONCAT(DISTINCT Formation.idFormation), ']') formations
FROM UE ue2
LEFT JOIN Cours C ON ue2.code = C.UE_code
LEFT JOIN TD T ON ue2.code = T.UE_code
LEFT JOIN TP T2 ON ue2.code = T2.UE_code
LEFT JOIN GroupeCours C2 ON C.idCours = C2.Cours_idCours
LEFT JOIN GroupeTD TD2 ON T.idTD = TD2.TD_idTD
LEFT JOIN GroupeTP TP2 ON T2.idTP = TP2.TP_idTP
JOIN Formation ON C2.Formation_idFormation = Formation.idFormation
OR TD2.Formation_idFormation = Formation.idFormation
OR TP2.Formation_idFormation = Formation.idFormation
GROUP BY `ue2`.`code`
) formlist ON formlist.code = ue.code
GROUP BY `ue`.`code`
$cond
ORDER BY `ue`.`label` ASC");
$st = $this->pdo->prepare("
SELECT
ue.code,
ue.label,
ue.disabled,
ue.required,
ue.volumeCours,
ue.volumeTD,
ue.volumeTP,
IFNULL(ue.Formation_idFormation, -1) idForm,
fdef.labelFormation labelForm,
IFNULL(formlist.formations, '[]') formations,
IFNULL(formlist.nbrCours,0) nbrCours,
IFNULL(formlist.nbrTD,0) nbrTD,
IFNULL(formlist.nbrTP,0) nbrTP,
IFNULL(formlist.modCours,0) modCours,
IFNULL(formlist.modTD,0) modTD,
IFNULL(formlist.modTP,0) modTP,
IFNULL(formlist.nbrProfCours,0) nbrProfCours,
IFNULL(formlist.nbrProfTD,0) nbrProfTD,
IFNULL(formlist.nbrProfTP,0) nbrProfTP
FROM UE ue
LEFT JOIN Formation fdef ON ue.Formation_idFormation = fdef.idFormation
LEFT JOIN (
SELECT ue2.code code, CONCAT('[',GROUP_CONCAT(DISTINCT Formation.idFormation), ']') formations,
count(DISTINCT C.idCours) nbrCours, count(DISTINCT T.idTD) nbrTD, count(DISTINCT T2.idTP) nbrTP,
MOD(volC.vol,ue2.volumeCours) modCours,
MOD(volTD.vol,ue2.volumeTD) modTD,
MOD(volTP.vol,ue2.volumeTP) modTP,
count(DISTINCT C.Professeur_idProfesseur,C.idCours) nbrProfCours,
count(DISTINCT T.Professeur_idProfesseur,T.idTD) nbrProfTD,
count(DISTINCT T2.Professeur_idProfesseur,T2.idTP) nbrProfTP
FROM UE ue2
LEFT JOIN (SELECT sum(CO.volume) vol, CO.UE_code FROM Cours CO GROUP BY CO.UE_code) volC ON ue2.code = volC.UE_code
LEFT JOIN (SELECT sum(TD2.volume) vol, TD2.UE_code FROM TD TD2 GROUP BY TD2.UE_code) volTD ON ue2.code = volTD.UE_code
LEFT JOIN (SELECT sum(TP2.volume) vol, TP2.UE_code FROM TP TP2 GROUP BY TP2.UE_code) volTP ON ue2.code = volTP.UE_code
LEFT JOIN Cours C ON ue2.code = C.UE_code
LEFT JOIN TD T ON ue2.code = T.UE_code
LEFT JOIN TP T2 ON ue2.code = T2.UE_code
LEFT JOIN GroupeCours C2 ON C.idCours = C2.Cours_idCours
LEFT JOIN GroupeTD TD2 ON T.idTD = TD2.TD_idTD
LEFT JOIN GroupeTP TP2 ON T2.idTP = TP2.TP_idTP
JOIN Formation ON C2.Formation_idFormation = Formation.idFormation
OR TD2.Formation_idFormation = Formation.idFormation
OR TP2.Formation_idFormation = Formation.idFormation
GROUP BY `ue2`.`code`
) formlist ON formlist.code = ue.code
GROUP BY `ue`.`code`
$cond
ORDER BY `ue`.`label` ASC");
/* (3) Bind params and execute statement */
if( is_bool($st) ) return [];
@ -269,4 +290,13 @@ class ue extends Repo_i {
return $fetched;
}
public function exportUE() : array{
$st = $this->pdo->prepare("SELECT * FROM UE
LEFT JOIN Formation
ON UE.Formation_idFormation = Formation.idFormation
ORDER BY Formation_idFormation IS NULL DESC, Formation_idFormation ASC");
$st->execute();
return $st->fetchAll();
}
}

View File

@ -30,8 +30,9 @@
'session' => [
'name' => $_SESSION['CAS']['login'],
'connected' => isset($_SESSION['AUTH']) ? count($_SESSION['AUTH']) > 0 : false,
'departments' => array_map(function($d){ return [ 'id' => $d['idDep'], 'label' => $d['labelDep']]; }, $_SESSION['AvailableDepartment']),
'department_id' => $_SESSION['CurrentDepartementId']
'departments' => array_map(function($d){ return [ 'id' => $d['idDep'], 'label' => $d['labelDep']]; }, $_SESSION['AvailableDepartments']),
'department_id' => $_SESSION['CurrentDepartmentId'],
'version' => $_SESSION['VERSION']
]
])."\n";

View File

@ -33,19 +33,43 @@
}
/* (2) Only teacher */
if( !in_array('cas_admin', $_SESSION['AUTH']) ){
// redirection
if( $this->pagename != "fiche" ){
header('Location: /fiche/');
die();
}
// deconnection
if( $this->uri == 'logout' || $this->uri == 'logout/' ){
\session_destroy();
header('Location: /');
die();
}
// load page
include __PUBLIC__."/page/fiche.php";
die();
}
/* (1) Build page file name */
/* (3) Build page file name */
$page_fname = __PUBLIC__."/page/".$this->pagename.".php";
/* (2) If page does not exist -> 404 */
/* (4) If page does not exist -> 404 */
if( !file_exists($page_fname) )
include __PUBLIC__.'/page/404.php';
/* (3) Set URI arguments */
/* (5) Set URI arguments */
$_GET['uri'] = explode('/', $this->uri);
/* (4) Load page */
/* (6) Load page */
include __PUBLIC__."/page/".$this->pagename.".php";
}

View File

@ -27,6 +27,9 @@
"require": {
"phpoffice/phpspreadsheet": "^1.1",
"phpstan/phpstan": "^0.9.2",
"ifsnop/mysqldump-php": "2.*"
"ifsnop/mysqldump-php": "2.*",
"setasign/fpdf": "1.8.1",
"mpdf/mpdf": "^7.0",
"php" : ">= 7.1"
}
}

252
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "28e31e7da9313d9726a26a4f36bf06a3",
"content-hash": "d300af5ed297b7cdfbfda9d2604a8f95",
"packages": [
{
"name": "ifsnop/mysqldump-php",
@ -109,6 +109,73 @@
],
"time": "2018-01-21T13:54:22+00:00"
},
{
"name": "mpdf/mpdf",
"version": "v7.0.3",
"source": {
"type": "git",
"url": "https://github.com/mpdf/mpdf.git",
"reference": "5681a0cae1eea197143d5d27f06e19b0523cd8d6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mpdf/mpdf/zipball/5681a0cae1eea197143d5d27f06e19b0523cd8d6",
"reference": "5681a0cae1eea197143d5d27f06e19b0523cd8d6",
"shasum": ""
},
"require": {
"ext-gd": "*",
"ext-mbstring": "*",
"paragonie/random_compat": "^1.4|^2.0",
"php": "^5.6 || ~7.0.0 || ~7.1.0 || ~7.2.0",
"psr/log": "^1.0",
"setasign/fpdi": "1.6.*"
},
"require-dev": {
"mockery/mockery": "^0.9.5",
"phpunit/phpunit": "^5.0",
"squizlabs/php_codesniffer": "^2.7.0",
"tracy/tracy": "^2.4"
},
"suggest": {
"ext-bcmath": "Needed for generation of some types of barcodes",
"ext-xml": "Needed mainly for SVG manipulation",
"ext-zlib": "Needed for compression of embedded resources, such as fonts"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-development": "7.0-dev"
}
},
"autoload": {
"psr-4": {
"Mpdf\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0"
],
"authors": [
{
"name": "Matěj Humpál",
"role": "Developer, maintainer"
},
{
"name": "Ian Back",
"role": "Developer (retired)"
}
],
"description": "A PHP class to generate PDF files from HTML with Unicode/UTF-8 and CJK support",
"homepage": "https://mpdf.github.io",
"keywords": [
"pdf",
"php",
"utf-8"
],
"time": "2018-01-03T07:32:36+00:00"
},
{
"name": "nette/bootstrap",
"version": "v2.4.5",
@ -673,6 +740,54 @@
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
"time": "2018-02-05T13:05:30+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v2.0.11",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8",
"reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8",
"shasum": ""
},
"require": {
"php": ">=5.2.0"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"autoload": {
"files": [
"lib/random.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"pseudorandom",
"random"
],
"time": "2017-09-27T21:40:39+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.2.0",
@ -867,6 +982,53 @@
"description": "PHPStan - PHP Static Analysis Tool",
"time": "2018-01-28T13:22:19+00:00"
},
{
"name": "psr/log",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2016-10-10T12:19:37+00:00"
},
{
"name": "psr/simple-cache",
"version": "1.0.1",
@ -915,6 +1077,94 @@
],
"time": "2017-10-23T01:57:42+00:00"
},
{
"name": "setasign/fpdf",
"version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/Setasign/FPDF.git",
"reference": "2c68c9e6c034ac3187d25968790139a73184cdb1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Setasign/FPDF/zipball/2c68c9e6c034ac3187d25968790139a73184cdb1",
"reference": "2c68c9e6c034ac3187d25968790139a73184cdb1",
"shasum": ""
},
"type": "library",
"autoload": {
"classmap": [
"fpdf.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"no usage restriction"
],
"authors": [
{
"name": "Olivier Plathey",
"email": "oliver@fpdf.org",
"homepage": "http://fpdf.org/"
}
],
"description": "FPDF is a PHP class which allows to generate PDF files with pure PHP. F from FPDF stands for Free: you may use it for any kind of usage and modify it to suit your needs.",
"homepage": "http://www.fpdf.org",
"keywords": [
"fpdf",
"pdf"
],
"time": "2016-01-01T17:47:15+00:00"
},
{
"name": "setasign/fpdi",
"version": "1.6.2",
"source": {
"type": "git",
"url": "https://github.com/Setasign/FPDI.git",
"reference": "a6ad58897a6d97cc2d2cd2adaeda343b25a368ea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Setasign/FPDI/zipball/a6ad58897a6d97cc2d2cd2adaeda343b25a368ea",
"reference": "a6ad58897a6d97cc2d2cd2adaeda343b25a368ea",
"shasum": ""
},
"suggest": {
"setasign/fpdf": "FPDI will extend this class but as it is also possible to use \"tecnickcom/tcpdf\" as an alternative there's no fixed dependency configured.",
"setasign/fpdi-fpdf": "Use this package to automatically evaluate dependencies to FPDF.",
"setasign/fpdi-tcpdf": "Use this package to automatically evaluate dependencies to TCPDF."
},
"type": "library",
"autoload": {
"classmap": [
"filters/",
"fpdi.php",
"fpdf_tpl.php",
"fpdi_pdf_parser.php",
"pdf_context.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Slabon",
"email": "jan.slabon@setasign.com",
"homepage": "https://www.setasign.com"
}
],
"description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.",
"homepage": "https://www.setasign.com/fpdi",
"keywords": [
"fpdf",
"fpdi",
"pdf"
],
"time": "2017-05-11T14:25:49+00:00"
},
{
"name": "symfony/console",
"version": "v4.0.6",

BIN
composer.phar Normal file

Binary file not shown.

View File

@ -26,25 +26,21 @@
"GET": {
"des": "Authenticatation callback (used by third-party OAuth)",
"per": [],
"par": {},
"par": {
"URL0": { "des": "Whether to manage a popup", "typ": "id", "opt": true, "ren": "popup_mode", "def": 0 }
},
"opt": { "download": true }
},
"POST": {
"des": "Login if not already authenticated",
"per": [],
"par": {}
},
"PUT": {
"des": "Check if authenticated",
"per": [],
"des": "Real logout to change login",
"per": [["cas_user"]],
"par": {}
},
"DELETE": {
"des": "Logout",
"per": [],
"per": [["cas_user"]],
"par": {}
}
@ -67,7 +63,7 @@
"excel":{
"POST": {
"des": "Import data from an Excel file",
"per": [],
"per": [["cas_admin"]],
"par": {
}
}
@ -75,17 +71,40 @@
"department":{
"GET": {
"des": "Get one or all departments",
"per": [["cas_user"]],
"par": {
"URL0": { "des": "Optional department id.", "typ": "id", "ren": "id_dep", "opt": true }
},
"out": {
"departments": { "des": "department list", "typ": "array" }
}
},
"POST": {
"des": "Create a new Department",
"per": [["cas_user"]],
"par": {
"name": { "des": "Name of the department", "typ": "text"}
}
},
"PUT":{
"des": "Switch the user on another department database",
"per": [],
"per": [["cas_user"]],
"par": {
"URL0": {"des": "Department id", "typ": "id", "ren": "department" }
},
"out": {
"switched": { "des": "Whether the department has been switched", "typ": "bool" }
}
},
"errors":{
"GET": {
"des": "Get the list of incoherence of the department",
"per": [],
"per": [["cas_admin"]],
"par": {
}
}
@ -93,31 +112,70 @@
"stats":{
"GET": {
"des": "Get the statistics about the department",
"per": [],
"per": [["cas_admin"]],
"par": {
}
}
},
"save":{
"export":{
"GET": {
"des": "Get the list of the saves of the department database",
"per": [],
"par": {
"des": "Export the data of the current department and version to a Excel file",
"per": [["cas_admin"]],
"par": {},
"opt": { "download": true }
}
},
"version":{
"switch":{
"GET": {
"des": "Switch database version for the current session",
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "The version id", "typ": "id", "ren": "version" }
},
"out": {
"sucess": { "des": "success of the operation", "typ": "bool" }
}
}
},
"GET": {
"des": "Get the list of the versions of the department",
"per": [["cas_admin"]],
"par": {},
"out": {
"versions": { "des": "List of available versions", "typ": "array" }
}
},
"POST": {
"des": "Create a backup if the name is empty, execute the backup if the name is set",
"per": [],
"per": [["cas_admin"]],
"par": {
"backupName" : {"des": "Backup name", "typ": "varchar(1,10,alphanumeric)", "opt" : true},
"apply" : {"des": "Should we apply the backup on production or just preview it (true : apply, false : preview)", "typ": "boolean", "opt" : true}
"label": { "des": "Label of the version", "typ": "text" }
},
"out": {
"created_id": { "des": "The id of the created version", "typ": "varchar(10,10,alphanumeric)" }
}
},
"PUT": {
"des": "Update a version and switch to this version",
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "id of the version", "typ": "id", "ren": "version" },
"label": { "des": "Label de la version", "typ": "text", "opt": true },
"default": { "des": "Whether this version should be used on login", "typ": "bool", "opt": true }
},
"out": {
"updated": { "des": "Whether the version has been switched|applied", "typ": "bool" }
}
},
"DELETE": {
"des": "Delete a backup",
"per": [],
"per": [["cas_admin"]],
"par": {
"backupName": {"des": "Backup name", "typ": "varchar(1,10,alphanumeric)"}
"URL0": { "des": "The version id", "typ": "id", "ren": "version" }
},
"out": {
"deleted": { "des": "Whether the version has been deleted", "typ": "bool" }
}
}
}
@ -127,7 +185,7 @@
"POST": {
"des": "Creates a new professor",
"per": [],
"per": [["cas_admin"]],
"par": {
"firstName": { "des": "Professor last name.", "typ": "varchar(2,30,alphanumeric)" },
"lastName": { "des": "Professor first name.", "typ": "varchar(2,30,alphanumeric)" },
@ -144,7 +202,7 @@
"GET": {
"des": "Get one or all professors",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Whether to return VH", "typ": "id", "ren": "with_vh" },
"URL1": { "des": "Optional professor UID.", "typ": "id", "ren": "prof_id", "opt": true }
@ -156,7 +214,7 @@
"DELETE": {
"des": "Deletes an existing professor",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Professor UID.", "typ": "id", "ren": "prof_id" }
},
@ -167,7 +225,7 @@
"PUT": {
"des": "Edits an existing professor",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Optional professor UID.", "typ": "id", "ren": "prof_id" },
"firstName": { "des": "Professor last name.", "typ": "varchar(2,30,alphanumeric)", "opt": true },
@ -184,21 +242,10 @@
}
},
"stats": {
"GET":{
"des": "Get statistics of the professor",
"per": [],
"par":{
"URL0": {"des" : "Id of the professor", "typ": "id", "ren": "idProf", "opt" : false}
}
}
},
"filter": {
"POST": {
"des": "Get matching professors (only ids) for a given filter",
"per": [],
"per": [["cas_admin"]],
"par": {
"categories": { "des": "Optional category ID list", "typ": "array<id>", "opt": true },
"formations": { "des": "Optional formation ID list", "typ": "array<id>", "opt": true },
@ -208,6 +255,19 @@
"matches": { "des": "Matching professor UID(s)", "typ": "array<id>" }
}
}
},
"pdf": {
"GET": {
"des": "Get a professor's fiche",
"per": [["cas_admin"], ["cas_user"]],
"par": {
"URL0": { "des": "Optional professor UID.", "typ": "id", "ren": "prof_id" }
},
"opt": { "download": true }
}
}
@ -217,7 +277,7 @@
"POST": {
"des": "Creates a new UE",
"per": [],
"per": [["cas_admin"]],
"par": {
"code": { "des": "UE code.", "typ": "varchar(4,20,alphanumeric)" },
"label": { "des": "UE label", "typ": "varchar(4,30,alphanumeric)" },
@ -235,7 +295,7 @@
"GET": {
"des": "Get one or all UE",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Optional UE code.", "typ": "varchar(4,20,alphanumeric)", "ren": "code", "opt": true }
},
@ -247,7 +307,7 @@
"DELETE": {
"des": "Deletes an existing UE",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "UE code.", "typ": "varchar(4,20,alphanumeric)", "ren": "code" }
},
@ -258,9 +318,10 @@
"PUT": {
"des": "Edits an existing UE",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "UE code.", "typ": "varchar(4,20,alphanumeric)", "ren": "code" },
"new_code": { "des": "UE new code", "typ": "varchar(4,20,alphanumeric)", "opt": true },
"label": { "des": "UE label", "typ": "varchar(4,30,alphanumeric)", "opt": true },
"required": { "des": "If UE is required", "typ": "bool", "opt": true },
"volumeCours": { "des": "Number of course hours for UE", "typ": "float", "opt": true },
@ -278,21 +339,22 @@
"POST": {
"des" : "Creates a new Cours for an UE",
"per": [],
"per": [["cas_admin"]],
"par": {
"code": { "des": "Code of the UE", "typ": "varchar(4,20,alphanumeric)" },
"idProf": { "des": "Id of the professor", "typ": "id", "opt": true },
"volume": { "des": "Number of hours for Cours", "typ": "id", "opt": true, "def": 0 },
"formations": { "des": "List of formations (ids)", "typ": "array<id>", "opt": true, "def": [] }
},
"output": {
"created_id" : { "des": "The id of the created Cours", "typ": "id" }
"out": {
"created_id" : { "des": "The id of the created Cours", "typ": "id" },
"formations" : { "des": "The ids of the linked formations", "typ": "array<id>" }
}
},
"GET": {
"des" : "Get all cours data about a given UE",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Code of the UE", "typ": "varchar(4,20,alphanumeric)", "ren": "code" }
}
@ -300,7 +362,7 @@
"PUT": {
"des" : "Updates an existing Cours",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Id of the Cours", "typ": "id", "ren": "idCours" },
"idProf": { "des": "Id of the professor (-1 to unset)", "typ": "int", "opt": true },
@ -308,18 +370,18 @@
"add_form": { "des": "Id of formations to add", "typ": "array<id>", "opt": true, "def": [] },
"rem_form": { "des": "Id of formations to remove", "typ": "array<id>", "opt": true, "def": [] }
},
"output": {
"out": {
"updated" : { "des": "Whether it has been updated", "typ": "bool" }
}
},
"DELETE": {
"des" : "Deletes an existing Cours",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Id of the Cours", "typ": "id", "ren": "idCours" }
},
"output": {
"out": {
"deleted" : { "des": "Whether it has been deleted", "typ": "bool" }
}
}
@ -330,21 +392,22 @@
"POST": {
"des" : "Creates a new TD for an UE",
"per": [],
"per": [["cas_admin"]],
"par": {
"code": { "des": "Code of the UE", "typ": "varchar(4,20,alphanumeric)" },
"idProf": { "des": "Id of the professor", "typ": "id", "opt": true },
"volume": { "des": "Number of hours for TD", "typ": "id", "opt": true, "def": 0 },
"formations": { "des": "List of formations (ids)", "typ": "array<id>", "opt": true, "def": [] }
},
"output": {
"created_id" : { "des": "The id of the created TD", "typ": "id" }
"out": {
"created_id" : { "des": "The id of the created TD", "typ": "id" },
"formations" : { "des": "The ids of the linked formations", "typ": "array<id>" }
}
},
"GET": {
"des" : "Get all TD data about a given UE",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Code of the UE", "typ": "varchar(4,20,alphanumeric)", "ren": "code" }
}
@ -352,7 +415,7 @@
"PUT": {
"des" : "Updates an existing TD",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Id of the TD", "typ": "id", "ren": "idTD" },
"idProf": { "des": "Id of the professor (-1 to unset)", "typ": "int", "opt": true },
@ -360,18 +423,18 @@
"add_form": { "des": "Id of formations to add", "typ": "array<id>", "opt": true, "def": [] },
"rem_form": { "des": "Id of formations to remove", "typ": "array<id>", "opt": true, "def": [] }
},
"output": {
"out": {
"updated" : { "des": "Whether it has been updated", "typ": "bool" }
}
},
"DELETE": {
"des" : "Deletes an existing TD",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Id of the TD", "typ": "id", "ren": "idTD" }
},
"output": {
"out": {
"deleted" : { "des": "Whether it has been deleted", "typ": "bool" }
}
}
@ -382,21 +445,22 @@
"POST": {
"des" : "Creates a new TP for an UE",
"per": [],
"per": [["cas_admin"]],
"par": {
"code": { "des": "Code of the UE", "typ": "varchar(4,20,alphanumeric)" },
"idProf": { "des": "Id of the professor", "typ": "id", "opt": true },
"volume": { "des": "Number of hours for TP", "typ": "id", "opt": true, "def": 0 },
"formations": { "des": "List of formations (ids)", "typ": "array<id>", "opt": true, "def": [] }
},
"output": {
"created_id" : { "des": "The id of the created TP", "typ": "id" }
"out": {
"created_id" : { "des": "The id of the created TP", "typ": "id" },
"formations" : { "des": "The ids of the linked formations", "typ": "array<id>" }
}
},
"GET": {
"des" : "Get all TP data about a given UE",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Code of the UE", "typ": "varchar(4,20,alphanumeric)", "ren": "code" }
}
@ -404,7 +468,7 @@
"PUT": {
"des" : "Updates an existing TP",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Id of the TP", "typ": "id", "ren": "idTP" },
"idProf": { "des": "Id of the professor (-1 to unset)", "typ": "int", "opt": true },
@ -412,18 +476,18 @@
"add_form": { "des": "Id of formations to add", "typ": "array<id>", "opt": true, "def": [] },
"rem_form": { "des": "Id of formations to remove", "typ": "array<id>", "opt": true, "def": [] }
},
"output": {
"out": {
"updated" : { "des": "Whether it has been updated", "typ": "bool" }
}
},
"DELETE": {
"des" : "Deletes an existing TP",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Id of the TP", "typ": "id", "ren": "idTP" }
},
"output": {
"out": {
"deleted" : { "des": "Whether it has been deleted", "typ": "bool" }
}
}
@ -435,17 +499,44 @@
"formation": {
"GET":{
"des": "Get all data about a formation",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0":{"des" : "Id of the formation", "typ": "id", "ren": "form_id", "opt" : true }
}
},
"POST":{
"des": "Create a new formation",
"per": [["cas_admin"]],
"par": {
"label":{"des" : "name of the formation", "typ": "text" },
"isInternal":{"des" : "is this formation internal to the department", "typ": "bool" }
}
},
"PUT":{
"des": "Update a formation",
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Id of the formation", "typ": "id", "ren": "idFormation" },
"label":{"des" : "name of the formation", "typ": "text", "opt":true },
"isInternal":{"des" : "is this formation internal to the department", "typ": "bool", "opt":true }
}
},
"DELETE":{
"des": "Delete a formation",
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Id of the formation", "typ": "id", "ren": "idFormation" }
}
}
},
"category": {
"GET": {
"des" : "Get all data about a given category | all",
"per": [],
"per": [["cas_admin"]],
"par": {
"URL0": { "des": "Id of the category", "typ": "id", "ren": "cat_id", "opt": true }
}

9419
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -37,7 +37,7 @@
"node-sass": "^4.7.2",
"sass-loader": "^6.0.6",
"vue-loader": "^13.0.5",
"vue-template-compiler": "^2.5.9",
"vue-template-compiler": "^2.5.16",
"webpack": "^3.8.1",
"webpack-dev-server": "^2.9.5"
}

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="32px"
id="Layer_1"
style="enable-background:new 0 0 32 32;"
version="1.1"
viewBox="0 0 32 32"
width="32px"
xml:space="preserve"
sodipodi:docname="back.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"><metadata
id="metadata9"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs7" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1015"
id="namedview5"
showgrid="false"
inkscape:zoom="7.375"
inkscape:cx="16"
inkscape:cy="16"
inkscape:window-x="0"
inkscape:window-y="29"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><path
d="M 12,6 C 11.469,6 11.005141,6.1930781 10.619141,6.5800781 L 2.6621094,14.537109 C 2.3341094,14.865109 2,15.271 2,16 c 0,0.729 0.2794844,1.080266 0.6464844,1.447266 l 7.9726566,7.972656 C 11.005141,25.806922 11.469,26 12,26 c 1.188,0 2,-1.016 2,-2 0,-0.516 -0.186078,-0.986859 -0.580078,-1.380859 L 8.8007812,18 H 28 c 1.104,0 2,-0.896 2,-2 0,-1.104 -0.896,-2 -2,-2 H 8.8007812 L 13.419922,9.3808594 C 13.813922,8.9868594 14,8.516 14,8 14,7.016 13.187,6 12,6 Z"
id="fill-edit"
inkscape:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
enable-background="new 0 0 500 500"
height="500px"
id="Layer_1"
version="1.1"
viewBox="0 0 500 500"
width="500px"
xml:space="preserve"
sodipodi:docname="pin-disabled.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"><metadata
id="metadata9"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs7" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1015"
id="namedview5"
showgrid="false"
inkscape:zoom="0.6675088"
inkscape:cx="186.10652"
inkscape:cy="69.51775"
inkscape:window-x="0"
inkscape:window-y="29"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1"
inkscape:snap-page="true" /><path
style="clip-rule:evenodd;fill:#010101;fill-rule:evenodd"
d="M 463.1543 1 L 349.9375 114.2168 L 349.9375 77.375 L 363.56836 77.375 C 376.10336 77.375 386.2832 67.198109 386.2832 54.662109 C 386.2832 42.126109 376.10336 31.949219 363.56836 31.949219 L 136.43164 31.949219 C 123.89564 31.949219 113.71875 42.126109 113.71875 54.662109 C 113.71875 67.199109 123.89564 77.375 136.43164 77.375 L 150.06055 77.375 L 150.06055 232.73828 L 82.828125 300.60742 C 79.465125 303.87442 77.376953 308.50466 77.376953 313.59766 C 77.376953 323.58866 85.459234 331.77148 95.365234 331.77148 L 132.38281 331.77148 L 1 463.1543 L 36.845703 499 L 499 36.845703 L 463.1543 1 z M 213.6582 77.375 C 223.6532 77.375 231.83008 85.552828 231.83008 95.548828 L 231.83008 213.65625 C 231.83008 223.65225 223.6542 231.82812 213.6582 231.82812 C 203.6642 231.82812 195.48633 223.65225 195.48633 213.65625 L 195.48633 95.548828 C 195.48633 85.552828 203.6642 77.375 213.6582 77.375 z M 352.55469 235.29102 L 222.74414 365.10156 L 222.74414 445.33984 C 222.74414 457.87584 232.92103 468.05078 245.45703 468.05078 C 257.99403 468.05078 268.17188 457.87584 268.17188 445.33984 L 268.17188 331.77148 L 404.63281 331.77148 C 414.53181 331.77148 422.62305 323.58866 422.62305 313.59766 C 422.62305 308.60466 420.61847 304.06211 417.35547 300.78711 L 352.55469 235.29102 z "
id="fill-edit" /></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -32,13 +32,14 @@
inkscape:window-height="1015"
id="namedview5"
showgrid="false"
inkscape:zoom="0.472"
inkscape:cx="14.830508"
inkscape:cy="250"
inkscape:zoom="0.6675088"
inkscape:cx="186.10652"
inkscape:cy="69.51775"
inkscape:window-x="0"
inkscape:window-y="29"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><path
inkscape:current-layer="Layer_1"
inkscape:snap-page="true" /><path
d="m 150.061,232.739 -67.232,67.868 c -3.363,3.267 -5.453,7.898 -5.453,12.991 0,9.991 8.083,18.173 17.989,18.173 H 222.744 V 445.34 c 0,12.536 10.177,22.711 22.713,22.711 12.537,0 22.715,-10.175 22.715,-22.711 V 331.771 h 136.46 c 9.899,0 17.992,-8.182 17.992,-18.173 0,-4.993 -2.006,-9.536 -5.269,-12.811 L 349.938,232.644 V 77.375 h 13.631 c 12.535,0 22.715,-10.177 22.715,-22.713 0,-12.536 -10.18,-22.713 -22.715,-22.713 H 136.432 c -12.536,0 -22.713,10.177 -22.713,22.713 0,12.537 10.177,22.713 22.713,22.713 h 13.629 V 232.739 Z M 231.83,95.548 v 118.109 c 0,9.996 -8.176,18.172 -18.172,18.172 -9.994,0 -18.171,-8.176 -18.171,-18.172 V 95.548 c 0,-9.996 8.177,-18.173 18.171,-18.173 9.995,0 18.172,8.177 18.172,18.173 z"
id="fill-edit"
inkscape:connector-curvature="0"

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="32"
viewBox="0 0 32 32"
width="32"
version="1.1"
id="svg6"
sodipodi:docname="switch.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1022"
id="namedview8"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="4.9166667"
inkscape:cx="24"
inkscape:cy="29.898306"
inkscape:window-x="0"
inkscape:window-y="29"
inkscape:window-maximized="1"
inkscape:current-layer="svg6" />
<path
d="M 23.384615,24.615384 V 18.461538 H 8.615385 v 6.153846 L 0,15.999999 8.615385,7.3846151 v 6.1538459 h 14.76923 V 7.3846151 L 32,15.999999 Z"
id="fill-edit"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccc"
style="stroke-width:1.23076928" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
enable-background="new 0 0 24 24"
height="24px"
id="Layer_1"
version="1.1"
viewBox="0 0 24 24"
width="24px"
xml:space="preserve"
sodipodi:docname="time.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"><metadata
id="metadata13"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs11" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1015"
id="namedview9"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-x="0"
inkscape:window-y="29"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><path
d="m 12,2.0344069 c -5.5018378,0 -9.9631017,4.4620943 -9.9631017,9.9655931 0,5.503499 4.4612639,9.965593 9.9631017,9.965593 5.502668,0 9.963102,-4.462094 9.963102,-9.965593 0,-5.5034988 -4.460434,-9.9655931 -9.963102,-9.9655931 z m 3.664016,13.8762581 -0.290663,0.290663 c -0.241665,0.241666 -0.649424,0.253292 -0.903547,0.02408 l -3.753707,-3.28449 C 10.460316,12.71254 10.266817,12.244987 10.283427,11.902835 l 0.347965,-5.9245446 c 0.01827,-0.3429825 0.313086,-0.6220191 0.656068,-0.6220191 h 0.40942 c 0.342982,0 0.636967,0.2790366 0.653577,0.6211886 l 0.28485,4.9279861 c 0.01744,0.342982 0.217582,0.830466 0.444299,1.08708 l 2.610985,3.012931 c 0.226718,0.256614 0.215921,0.663542 -0.02657,0.905208 z"
id="fill-edit"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill-rule:evenodd;stroke-width:0.83046609" /></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

View File

@ -7,42 +7,29 @@
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Gestion des enseignants</title>
<title>Espace enseignant</title>
<!-- Icon -->
<link rel='shortcut icon' href='/favicon.ico'>
<!-- Icon -->
<link rel='shortcut icon' href='/favicon.ico'>
<!-- CSS dependencies -->
<link href="https://fonts.googleapis.com/css?family=Fira+Sans" rel="stylesheet">
<link rel='stylesheet' type='text/css' href='/css/font-loader.css'>
<link rel='stylesheet' type='text/css' href='/css/global.css'>
<link rel='stylesheet' type='text/css' href='/css/pop-up.css'>
<link rel='stylesheet' type='text/css' href='/css/layout.css'>
<link rel='stylesheet' type='text/css' href='/css/menu.css'>
<link rel='stylesheet' type='text/css' href='/css/header.css'>
<link rel='stylesheet' type='text/css' href='/css/container.css'>
<!-- CSS dependencies -->
<link href="https://fonts.googleapis.com/css?family=Fira+Sans" rel="stylesheet">
<link rel='stylesheet' type='text/css' href='/css/layout.css'>
<!-- JS dependencies -->
<script type='text/javascript' src='/js/_SERVER.js'></script>
<body>
</head>
<body class='loading'>
<div id="WRAPPER" class='login'>
<div id='main-vue'></div>
<!-- POPUP WINDOW -->
<div id='POPUP'>
<div class='header'></div>
<div class='body'></div>
<div class='footer'></div>
<div id='LOGIN_REDIRECT'>
<div class='icon'></div>
<div class='title'><b>P</b><i>lateforme</i> <b>A</b><i>ssistée de</i> <b>T</b><i>raitement</i> <b>A</b><i>dministratif des</i> <b>T</b><i>aches d'</i><b>E</b><i>nseignement</i></div>
<a href="/api/v/1.0/professor/pdf/<?php echo $_SESSION['CAS']["id"]; ?>"><button style='font-weight: normal; margin-bottom: 1em;'>Télécharger ma fiche</button></a>
<a href="/fiche/logout"><button style='font-weight: normal'>Me déconnecter</button></a>
</div>
<div id='POPUP-BG'></div>
<!-- Main loop -->
<script type='text/javascript' src='/js/bundle@fiche.js' onload="document.body.className=''"></script>
</div>
</body>
</html>

View File

@ -7,23 +7,24 @@
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>PTUT web title</title>
<title>Connexion</title>
<!-- Icon -->
<link rel='shortcut icon' href='/favicon.ico'>
<!-- Icon -->
<link rel='shortcut icon' href='/favicon.ico'>
<!-- CSS dependencies -->
<link href="https://fonts.googleapis.com/css?family=Fira+Sans" rel="stylesheet">
<link rel='stylesheet' type='text/css' href='/css/font-loader.css'>
<link rel='stylesheet' type='text/css' href='/css/global.css'>
<link rel='stylesheet' type='text/css' href='/css/pop-up.css'>
<link rel='stylesheet' type='text/css' href='/css/layout.css'>
<link rel='stylesheet' type='text/css' href='/css/menu.css'>
<link rel='stylesheet' type='text/css' href='/css/header.css'>
<link rel='stylesheet' type='text/css' href='/css/container.css'>
<!-- CSS dependencies -->
<link href="https://fonts.googleapis.com/css?family=Fira+Sans" rel="stylesheet">
<link rel='stylesheet' type='text/css' href='/css/font-loader.css'>
<link rel='stylesheet' type='text/css' href='/css/global.css'>
<link rel='stylesheet' type='text/css' href='/css/pop-up.css'>
<link rel='stylesheet' type='text/css' href='/css/layout.css'>
<link rel='stylesheet' type='text/css' href='/css/menu.css'>
<link rel='stylesheet' type='text/css' href='/css/header.css'>
<link rel='stylesheet' type='text/css' href='/css/container.css'>
<link rel='stylesheet' type='text/css' href='/css/container/svg.css'>
<!-- JS dependencies -->
<script type='text/javascript' src='/js/_SERVER.js'></script>
<!-- JS dependencies -->
<script type='text/javascript' src='/js/_SERVER.js'></script>
</head>
<body class='loading'>
@ -45,4 +46,5 @@
<!-- Main loop -->
<script type='text/javascript' src='/js/bundle@home.js' onload="document.body.className=''"></script>
</body>
</html>

View File

@ -72,18 +72,6 @@ module.exports = [ {
module: mod_common,
devtool: (process.env.NODE_ENV==='development') ? '#eval-source-map' : false
}, {
name: "fiche",
entry: './webpack/page/fiche.js',
output: {
path: path.resolve(__dirname, './public_html/js/bundle'),
publicPath: '/js/bundle/',
filename: 'fiche@0.js'
},
module: mod_common,
devtool: (process.env.NODE_ENV==='development') ? '#eval-source-map' : false
}, {
name: "settings",

View File

@ -1,6 +1,43 @@
<template>
<div id='CONTAINER' class='list'>
<svg xmlns="http://www.w3.org/2000/svg" :viewBox="gstore.viewBox">
<template v-if="gstore.dimensions">
<path
:d="'m ' + (gstore.dimensions.padding + gstore.dimensions.text.size) + ',' + (gstore.dimensions.padding) + ' ' +
'0,' + (gstore.dimensions.axis.height + gstore.dimensions.padding) + ' ' +
(gstore.dimensions.axis.width) + ',0'"></path>
<text
class="precision"
:x="gstore.dimensions.padding + gstore.dimensions.text.size"
:y="gstore.dimensions.text.alignV + gstore.dimensions.axis.height + gstore.dimensions.padding">0</text>
<template v-for="i in gstore.dimensions.axis.precision">
<path
class="precision"
:d="'m ' + (gstore.dimensions.padding + gstore.dimensions.text.size + (i*gstore.dimensions.axis.width/gstore.dimensions.axis.precision)) + ',' + (gstore.dimensions.padding) + ' ' +
'0,' + (gstore.dimensions.axis.height+gstore.dimensions.padding) + ' '"></path>
<text
class="precision"
:x="gstore.dimensions.padding + gstore.dimensions.text.size + (i*gstore.dimensions.axis.width/gstore.dimensions.axis.precision)"
:y="gstore.dimensions.text.alignV + gstore.dimensions.axis.height + gstore.dimensions.padding">{{ i*gstore.maxValue/gstore.dimensions.axis.precision }}</text>
</template>
<template v-for="(value, key, i) in gstore.stats">
<text
:x="gstore.dimensions.text.size + gstore.dimensions.padding - gstore.dimensions.text.alignH"
:y="i*(gstore.dimensions.bin.width + gstore.dimensions.bin.spacing) + gstore.dimensions.padding + gstore.dimensions.text.alignV"
>{{ key }}</text>
<rect
:class="gstore.colors[i] + ' hiding'"
:x="gstore.dimensions.text.size + gstore.dimensions.padding + 1"
:y="i*(gstore.dimensions.bin.width+gstore.dimensions.bin.spacing) + gstore.dimensions.padding"
:height="gstore.dimensions.bin.width"
:width="gstore.dimensions.bin.margin + (gstore.dimensions.axis.width * value)/gstore.maxValue"
:data-info="value">
<title>{{ (gstore.titles && gstore.titles[key]) ? value + ' ' + gstore.titles[key] : value }}</title>
</rect>
</template>
</template>
</svg>
<section>bla</section>
<section>bla</section>
@ -30,7 +67,17 @@
export default {
name: 'CONTAINER_PAGE1',
data(){
return { gstore: gstore.get }
return {
gstore: gstore.get
}
}
}
window.onload = function() {
let allRect = document.getElementsByTagName('rect');
for (let rect of allRect) {
rect.classList.remove('hiding');
}
}

View File

@ -4,12 +4,12 @@
<div class='card filter'>
<div v-for='(filter_grp, gname) in gstore.filters' :title='gname'>
<div v-for='(filter_grp, gname) in gstore.filters' :title='gname' data-unblur-filter>
<div class='fold-toggle' :data-show='gstore.filters[gname][0].visible?1:0' @click='gstore.show_fgroup(gname)' :data-count='gstore.filters[gname][0].active.length'>{{ gname }}</div>
<div class='fold-toggle' :data-show='gstore.filters[gname][0].visible?1:0' @click='gstore.show_fgroup(gname)' :data-count='gstore.filters[gname][0].active.length' data-unblur-filter>{{ gname }}</div>
<div class='fold'>
<span v-for='(data, i) in filter_grp' v-if='i > 0' :class="data.active == true ? 'active' : ''" @click='gstore.toggle_filter(gname, i); gstore.filter_handler(gname);' :title='data.code'>{{ data.name }}</span>
<div class='fold' data-unblur-filter>
<span v-for='(data, i) in filter_grp' v-if='i > 0' :class="data.active == true ? 'active' : ''" @click='gstore.toggle_filter(gname, i); gstore.filter_handler(gname);' :title='data.code' data-unblur-filter>{{ data.name }}</span>
</div>
</div>
@ -33,6 +33,10 @@
</h1>
<div class='table'>
<div title='équivalents TD'>
<span>0</span>
<span>HETD</span>
</div>
<div>
<span><input type='text' placeholder='???' v-model='gstore.create_h'></span>
<span>heures à faire</span>
@ -43,6 +47,7 @@
<div class='footer'>
<button class='valid' @click='gstore.ic_handler()'>Créer l'enseignant</button>
<button class='neutral' @click='gstore.ic_reset(); gstore.create_card=false'>Annuler</button>
</div>
</section>
@ -83,29 +88,32 @@
<div class='table'>
<div>
<!-- if VIEW MODE -->
<span v-show='gstore.edit_i!=pi'>{{prof.hoursToDo}}</span>
<!-- if EDIT MODE -->
<span v-show='gstore.edit_i==pi'><input type='text' placeholder='???' v-model='gstore.edit_h'></span>
<!-- endif -->
<span>heures à faire</span>
</div>
<div title='équivalents TD'>
<span>{{ prof.equiTD }}</span>
<span>heures prévues</span>
<span>HETD</span>
</div>
<!-- if EDIT MODE -->
<div v-show='gstore.edit_i==pi'>
<span><input type='text' placeholder='???' v-model='gstore.edit_h'></span>
<span>heures à faire</span>
</div>
<!-- endif -->
<div title='heures de décalage' v-show='gstore.edit_i!=pi'>
<span :data-error='prof.equiTD < prof.hoursToDo?1:0' :data-success='prof.equiTD < prof.hoursToDo?0:1'>{{ Math.floor( (prof.equiTD < prof.hoursToDo ? prof.hoursToDo-prof.equiTD : prof.equiTD-prof.hoursToDo)*100 )/100 }}</span>
<span>{{ prof.equiTD < prof.hoursToDo ? 'sous-service' : 'sur-service' }}</span>
</div>
</div>
<!-- if VIEW MODE -->
<div v-show='gstore.edit_i!=pi' class='sub' title='total des heures prévues'><strong>{{ prof.VHCours + prof.VHTd + prof.VHTp }}h</strong> réelles prévues</div>
<div v-show='gstore.edit_i!=pi' class='pdfdl' title='Télécharger la fiche' @click='gstore.id_handler(prof.idProfesseur)'>fiche</div>
<!-- if EDIT MODE -->
<div v-show='gstore.edit_i==pi' :class="gstore.edit_err.length > 0 ? 'sub warning' : 'sub'" :data-valid='gstore.edit_err_valid?1:0'>{{ gstore.edit_err }}</div>
<!-- endif -->
<div class='footer'>
<!-- if VIEW MODE -->
<span v-show='gstore.edit_i!=pi' :class="(prof.VHCours == 0) ? 'course' : 'course active'">{{ prof.VHCours }}h <span>Cours</span></span>
<span v-show='gstore.edit_i!=pi' :class="(prof.VHCours == 0) ? 'course' : 'course active'">{{ prof.VHCours }}h <span>CM</span></span>
<hr v-show='gstore.edit_i!=pi'>
<span v-show='gstore.edit_i!=pi' :class="(prof.VHTd == 0) ? 'td' : 'td active'" >{{ prof.VHTd }}h <span>TD</span></span>
<hr v-show='gstore.edit_i!=pi'>
@ -115,6 +123,12 @@
<button v-show='gstore.edit_i==pi' class='grey' @click='gstore.ie_toggle(-1)'>Annuler</button>
<!-- endif -->
</div>
<div class='info'>
<strong>{{ prof.hoursToDo }}h</strong> à faire, <strong>{{ prof.VHCours + prof.VHTd + prof.VHTp }}h</strong> présentielles
</div>
</section>
</div>
@ -133,7 +147,22 @@
name: 'CONTAINER_VIEW',
data(){
return { gstore: gstore.get }
},
beforeMount(){
// set onblur to hide filter
window.onblur.link('teacher.filter', (e) => {
// ignore [data-unblur-filter] elements
if( e.target.getAttribute('data-unblur-filter') !== null )
return;
// else: hide
gstore.get.show_fgroup(-1);
});
}
}
</script>
</script>

View File

@ -4,30 +4,66 @@
<div class='list container' data-anim-incoming='1' :data-anim-bounce='gstore.nav_anim.out?1:0'>
<!-- <button @click='gstore.nav_out($router)'>Retour</button> -->
<section class='filter'>
<button class='back reflow search' @click='$router.back()'>Retour</button>
</section>
<!-- FILTERS -->
<section class='filter'>
<div>enseignant</div>
<div class='null'></div>
<div>volume horaire</div>
<div>formations</div>
<div style='flex-basis: 3.2em'></div>
<div :data-filter='gstore.order.current===0?1:0' @click='gstore.order_toggle(0)'>enseignant <span class='arrow' :data-way='gstore.order.way'></span></div>
<div :data-filter='gstore.order.current===1?1:0' @click='gstore.order_toggle(1)'>volume horaire <span class='arrow' :data-way='gstore.order.way'></span></div>
<div :data-filter='gstore.order.current===2?1:0' @click='gstore.order_toggle(2)'>formations <span class='arrow' :data-way='gstore.order.way'></span></div>
</section>
<!-- COURS -->
<section class='cours'
v-for='(c,i) in gstore.manage.cours'
:data-id='c.idCours'>
<div class='icon' @click='gstore.rem(0, i)'></div>
<!-- CREATE -->
<section class='create'
data-anim-incoming='1'
:data-anim-bounce='gstore.nav_anim.out?1:0'>
<div class='icon' @click='gstore.ccreate()'></div>
<select v-model='gstore.ccrea.prof'>
<option value='-1'>Aucun enseignant affecté</option>
<option v-for='p in gstore.manage.prof' :value='p.idProfesseur'>{{ `${p.firstName} ${p.lastName}` }}</option>
</select>
<select v-model='gstore.ccrea.type' class='min'>
<option value='-' disabled>Type</option>
<option value='0'>CM</option>
<option value='1'>TD</option>
<option value='2'>TP</option>
</select>
<input type='text' placeholder='volume' v-model='gstore.ccrea.vol'>
<div style='margin-left: 1em;' :class="gstore.ccrea.err.length > 0 ? (gstore.ccrea.valid ? 'warning valid' : 'warning invalid') : ''" :data-valid='gstore.ccrea.valid?1:0'>{{ gstore.ccrea.err }}</div>
</section>
<section class='filter'></section>
<!-- COURS -->
<section class='bcours'
v-for='(c,i) in gstore.manage.cours'
:data-id='c.idCours'
data-anim-incoming='1'
:data-anim-bounce='gstore.nav_anim.out?1:0'
:data-prof='c.idProf'
:data-vol='c.volume'
:data-form='c.formations.join(`|`)'>
<div class='icon remove' @click='gstore.rem(0, i)'></div>
<select v-model='c.new_prof' @change='gstore.upd_prof(0, i)'>
<option value='-1' v-show='c.idProf!=-1'>Aucun enseignant affecté</option>
<option v-for='p in gstore.manage.prof' :value='p.idProfesseur' v-show='p.idProfesseur!=c.idProf'>{{ `${p.firstName} ${p.lastName}` }}</option>
</select>
<div>Cours</div>
<div>{{ c.volume }}</div>
<div class='cm reflow active'>{{ c.volume }}</div>
<div class='taglist'>
<div v-for='f in c.formations' data-action>
<span class='tag'>{{ gstore.form_by_id(f).labelForm || '???' }}</span>
@ -50,19 +86,22 @@
<!-- TD -->
<section class='td'
<section class='btd'
v-for='(td,i) in gstore.manage.td'
:data-id='td.idTD'
data-anim-incoming='1'
:data-anim-bounce='gstore.nav_anim.out?1:0'>
:data-anim-bounce='gstore.nav_anim.out?1:0'
<div class='icon' @click='gstore.rem(1, i)'></div>
:data-prof='td.idProf'
:data-vol='td.volume'
:data-form='td.formations.join(`|`)'>
<div class='icon remove' @click='gstore.rem(1, i)'></div>
<select v-model='td.new_prof' @change='gstore.upd_prof(1, i)'>
<option value='-1' v-show='td.idProf!=-1'>Aucun enseignant affecté</option>
<option v-for='p in gstore.manage.prof' :value='p.idProfesseur' v-show='p.idProfesseur!=td.idProf'>{{ `${p.firstName} ${p.lastName}` }}</option>
</select>
<div>TD</div>
<div>{{ td.volume }}</div>
<div class='td reflow active'>{{ td.volume }}</div>
<div class='taglist'>
<div v-for='f in td.formations' data-action>
<span class='tag'>{{ gstore.form_by_id(f).labelForm || '???' }}</span>
@ -85,19 +124,22 @@
<!-- TP -->
<section class='tp'
<section class='btp'
v-for='(tp,i) in gstore.manage.tp'
:data-id='tp.idTP'
data-anim-incoming='1'
:data-anim-bounce='gstore.nav_anim.out?1:0'>
:data-anim-bounce='gstore.nav_anim.out?1:0'
<div class='icon' @click='gstore.rem(2, i)'></div>
:data-prof='tp.idProf'
:data-vol='tp.volume'
:data-form='tp.formations.join(`|`)'>
<div class='icon remove' @click='gstore.rem(2, i)'></div>
<select v-model='tp.new_prof' @change='gstore.upd_prof(2, i)'>
<option value='-1' v-show='tp.idProf!=-1'>Aucun enseignant affecté</option>
<option v-for='p in gstore.manage.prof' :value='p.idProfesseur' v-show='p.idProfesseur!=tp.idProf'>{{ `${p.firstName} ${p.lastName}` }}</option>
</select>
<div>TP</div>
<div>{{ tp.volume }}</div>
<div class='tp reflow active'>{{ tp.volume }}</div>
<div class='taglist'>
<div v-for='f in tp.formations' data-action>
<span class='tag'>{{ gstore.form_by_id(f).labelForm || '???' }}</span>
@ -117,6 +159,10 @@
</div>
</section>
</div>

View File

@ -2,6 +2,20 @@
<div id='CONTAINER' class='card'>
<div class='card filter'>
<div v-for='(filter_grp, gname) in gstore.filters' :title='gname' data-unblur-filter>
<div class='fold-toggle' :data-show='gstore.filters[gname][0].visible?1:0' @click='gstore.show_fgroup(gname)' :data-count='gstore.filters[gname][0].active.length' data-unblur-filter>{{ gname }}</div>
<div class='fold' data-unblur-filter>
<span v-for='(data, i) in filter_grp' v-if='i > 0' :class="data.active == true ? 'active' : ''" @click='gstore.toggle_filter(gname, i); gstore.filter_handler(gname);' :title='data.code' data-unblur-filter>{{ data.name }}</span>
</div>
</div>
</div>
<div class='card container' :data-anim-outgoing='gstore.nav_anim.in?1:0'>
<input data-anim='0' class='card instant-search neutral' type='text' @keyup='gstore.is_handler($event)' placeholder='Recherche instantannée' id='ue_view_instant_search'>
@ -15,11 +29,23 @@
<option v-for='form in gstore.formations' :value='form.idForm'>{{ form.labelForm }}</option>
</select>
<h1>
<h1 class='pin'>
<input type='text' placeholder='Libellé' v-model='gstore.create_label'>
<span data-visible='1'>(<input type='text' placeholder='code' v-model='gstore.create_code'>)</span>
</h1>
<div class='table little'>
<div>
<span class='notlast active reflow' data-error='0'>0 CM</span>
</div>
<div>
<span class='notlast active reflow' data-error='0'>0 TD</span>
</div>
<div>
<span class='notlast active reflow' data-error='0'>0 TP</span>
</div>
</div>
<div :class="gstore.create_err.length > 0 ? 'sub warning' : 'sub'" :data-valid='gstore.create_err_valid?1:0'>{{ gstore.create_err }}</div>
<div class='footer'>
@ -32,6 +58,7 @@
<div class='footer'>
<button class='valid' @click='gstore.ic_handler()'>Créer l'UE</button>
<button class='neutral' @click='gstore.ic_reset(); gstore.create_card=false'>Annuler</button>
</div>
</section>
@ -45,9 +72,10 @@
<!-- if VIEW MODE -->
<div class='goo-menu' v-show='gstore.edit_i!=pi'>
<div class='enabled' :data-enabled='ue.code' :data-active='ue.disabled?0:1' title='UE activée' @click="gstore.ia_handler(pi)"></div>
<div class='remove' :data-remove='ue.code' title='Supprimer' @click="gstore.ir_handler(ue.code)"></div>
<div class='edit' :data-edit='ue.code' title='Modifier' @click="gstore.ie_toggle(pi)"></div>
<div class='enabled' :data-enabled='ue.code' :data-active='ue.disabled?0:1' title='UE activée' @click="gstore.ia_handler(pi)"></div>
<div class='required' :data-required='ue.code' :data-active='ue.required?1:0' title='UE obligatoire' @click="gstore.io_handler(pi)"></div>
<div class='remove' :data-remove='ue.code' title='Supprimer' @click="gstore.ir_handler(ue.code)"></div>
<div class='edit' :data-edit='ue.code' title='Modifier' @click="gstore.ie_toggle(pi)"></div>
</div>
<!-- if VIEW MODE -->
@ -61,19 +89,35 @@
<!-- endif -->
<!-- if VIEW MODE -->
<h1 v-show='gstore.edit_i!=pi'>{{ ue.label }}<span :data-visible='1'>({{ ue.code }})</span></h1>
<h1 v-show='gstore.edit_i!=pi' :class='ue.required?`pin`:`pin disabled`' :title='ue.required?`obligatoire`:`optionnelle`'><span :data-strike='ue.disabled?1:0'>{{ ue.label }}</span><span :data-visible='1'>({{ ue.code }})</span></h1>
<!-- if EDIT MODE -->
<h1 v-show='gstore.edit_i==pi' :class="ue.required ? 'warning' : ''">
<h1 v-show='gstore.edit_i==pi' :class='ue.required?`pin`:`pin disabled`'>
<input type='text' placeholder='Libellé' v-model='gstore.edit_label'>
<span :data-visible='1'>({{ ue.code }})</span>
<!-- <span data-visible='1'>(<input type='text' placeholder='code' v-model='gstore.edit_code'>)</span> -->
<!-- <span :data-visible='1'>({{ ue.code }})</span> -->
<span data-visible='1'>(<input type='text' placeholder='code' v-model='gstore.edit_code'>)</span>
</h1>
<!-- endif -->
<div class='table little'>
<div>
<span class='active reflow' :data-error='ue.nbrCours>ue.nbrProfCours || ue.modCours > 0?1:0'>{{ ue.nbrCours }} CM</span>
<span v-show='ue.nbrCours>ue.nbrProfCours' class='notlast user-icon reflow nospace' :data-tooltip='`${ue.nbrCours-ue.nbrProfCours} enseignant(s) manquant(s)`'></span>
<span v-show='ue.modCours > 0' class='notlast time-icon reflow nospace' :data-tooltip='`${ue.volumeCours-ue.modCours} à ajouter | ${ue.modCours} à enlever`'></span>
</div>
<div>
<span class='active reflow' :data-error='ue.nbrTD>ue.nbrProfTD || ue.modTD > 0?1:0'>{{ ue.nbrTD }} TD</span>
<span v-show='ue.nbrTD>ue.nbrProfTD' class='notlast user-icon reflow nospace' :data-tooltip='`${ue.nbrCours-ue.nbrProfCours} enseignant(s) manquant(s)`'></span>
<span v-show='ue.modTD > 0' class='notlast time-icon reflow nospace' :data-tooltip='`${ue.volumeTD-ue.modTD} à ajouter | ${ue.modTD} à enlever`'></span>
</div>
<div>
<span class='active reflow' :data-error='ue.nbrTP>ue.nbrProfTP || ue.modTP > 0?1:0'>{{ ue.nbrTP }} TP</span>
<span v-show='ue.nbrTP>ue.nbrProfTP' class='notlast user-icon reflow nospace' :data-tooltip='`${ue.nbrCours-ue.nbrProfCours} enseignant(s) manquant(s)`'></span>
<span v-show='ue.modTP > 0' class='notlast time-icon reflow nospace' :data-tooltip='`${ue.volumeTP-ue.modTP} à ajouter | ${ue.modTP} à enlever`'></span>
</div>
</div>
<!-- if VIEW MODE -->
<div v-show='gstore.edit_i!=pi' :class="ue.required ? 'sub pin' : 'sub pin neutral'">{{ ue.required ? 'UE obligatoire' : 'UE optionnelle' }}</div>
<div v-show='gstore.edit_i!=pi' class='sub'><strong>{{ ue.volumeCours + ue.volumeTD + ue.volumeTP }}h</strong> totales</div>
<div v-show='gstore.edit_i!=pi' class='taglist'>
<span v-if='ue.formations.length==0' class='tag invalid'>Aucune formation</span>
<span v-for='form_id in ue.formations' :class="!!gstore.form_by_id(form_id).isInternal ? 'tag search' : 'tag'">{{ gstore.form_by_id(form_id).labelForm || '???' }}</span>
@ -83,25 +127,6 @@
<div v-show='gstore.edit_i==pi' :class="gstore.edit_err.length > 0 ? 'sub warning' : 'sub'" :data-valid='gstore.edit_err_valid?1:0'>{{ gstore.edit_err }}</div>
<!-- endif -->
<div class='footer'>
<!-- if VIEW MODE -->
<span v-show='gstore.edit_i!=pi' :class="(ue.volumeCours == 0) ? 'course' : 'course active'">{{ ue.volumeCours }}h <span>Cours</span></span>
<hr v-show='gstore.edit_i!=pi'>
<span v-show='gstore.edit_i!=pi' :class="(ue.volumeTD == 0) ? 'td' : 'td active'">{{ ue.volumeTD }}h <span>TD</span></span>
<hr v-show='gstore.edit_i!=pi'>
<span v-show='gstore.edit_i!=pi' :class="(ue.volumeTP == 0) ? 'tp' : 'tp active'">{{ ue.volumeTP }}h <span>TP</span></span>
<!-- if EDIT MODE -->
<span v-show='gstore.edit_i==pi' class='course'><input type='text' placeholder='???' v-model='gstore.edit_vol.c'><span>Cours</span></span>
<hr v-show='gstore.edit_i==pi'>
<span v-show='gstore.edit_i==pi' class='td'><input type='text' placeholder='???' v-model='gstore.edit_vol.td'><span>TD</span></span>
<hr v-show='gstore.edit_i==pi'>
<span v-show='gstore.edit_i==pi' class='tp'><input type='text' placeholder='???' v-model='gstore.edit_vol.tp'><span>TP</span></span>
<!-- endif -->
</div>
<!-- if VIEW MODE -->
<div class='footer' v-show='gstore.edit_i!=pi'>
@ -114,6 +139,13 @@
</div>
<!-- endif -->
<div class='info'>
<strong class='cm reflow'>{{ ue.volumeCours}}</strong> CM
<strong class='td reflow'>{{ ue.volumeTD}}</strong> TD
<strong class='tp reflow'>{{ ue.volumeTP }}</strong> TP
<strong>{{ ue.volumeCours + ue.volumeTD + ue.volumeTP }}h</strong>
</div>
</section>
</div>
@ -131,7 +163,22 @@
name: 'CONTAINER_VIEW',
data(){
return { gstore: gstore.get }
}
},
beforeMount(){
// set onblur to hide filter
window.onblur.link('ue.filter', (e) => {
// ignore [data-unblur-filter] elements
if( e.target.getAttribute('data-unblur-filter') !== null )
return;
// else: hide
gstore.get.show_fgroup(-1);
});
}
}
</script>s

View File

@ -1,6 +1,7 @@
import {GlobalStore} from '../lib/gstore'
import {APIClient} from '../lib/api-client'
import {PopUp} from '../lib/pop-up'
import {GlobalStore} from '../lib/gstore'
import {APIClient} from '../lib/api-client'
import {PopUp} from '../lib/pop-up'
import {OnBlurManager} from '../lib/onblur'
require('../lib/css-class-override')
window.gstore = new GlobalStore();
@ -8,18 +9,25 @@ window.gstore = new GlobalStore();
/* (1) Global data
---------------------------------------------------------*/
/* (1) Get Full URI */
/* (1) Get URL host */
gstore.add('HOST', document.URL.replace(/^((?:https?:\/\/)?[^\/]+)*(?:.+)$/, '$1'));
/* (2) Get list of URI arguments */
gstore.add('URI', document.URL.replace(/^(?:\/\/|[^\/]+)*/, '').split('/').filter(function(i){ return i.length; }));
/* (2) Get if local version or prod */
/* (3) Get if local version or prod */
gstore.add('is_local', document.URL.replace(/^https?:\/\/([^\/:]+).*$/, '$1') == 'ptut.com');
/* (3) API instance */
window.api = new APIClient(gstore.get.is_local ? 'http://ptut.com:8080/api/v/1.0/' : 'https://ptut.xdrm.io/api/v/1.0/');
/* (4) API instance */
window.api = new APIClient(gstore.get.is_local ? 'http://ptut.com:8080/api/v/1.0/' : `${gstore.get.HOST}/api/v/1.0/`);
/* (4) PopUp instance */
/* (5) PopUp instance */
window.popup = new PopUp();
/* (6) Create class in window */
window.onblur = new OnBlurManager(document.body);
/* (2) Main components
@ -41,10 +49,6 @@ gstore.add('menu_item', {
label: 'Gestion UE',
url: 'ue',
icon: 'ue'
}, fiche: {
label: 'Fiches',
url: 'fiche',
icon: 'fiche'
}, settings: {
label: 'Administration',
url: 'settings',

View File

@ -1 +1,78 @@
gstore.add('blo', 12);
/* (1) Load statistics
---------------------------------------------------------*/
/* (1) Initialize list */
gstore.add('stats', null);
gstore.add('dimensions', null);
/* (2) Get statistics */
api.call('GET department/stats', {}, function(rs) {
// {1} If error -> abort //
if (rs.error !== 0) {
return console.log('No formation found, error: ' + rs.error);
}
let maxValue = null; // plus haute valeur des stats
let maxLabelLength = null; // plus longues chaîne de caractères pour les stats
let data = {}; // ensemble des statistiques à transmettre à VueJS
let map = {
"potentiel" : "Heures potentielles",
"sous_service" : "Heures en sous-services",
"heures_comp" : "Heures comp.",
"heures_vacataire" : "Heures vacataires",
"heures_exterieur" : "Heures à l'extérieurs",
"heures_ue_desactive" : "Heures UE annulées",
"nbr_ue_desactive" : "Nombre d'UE annulées"
};
for (let stat in rs.data) {
// détection de la plus grande valeur statistique
maxValue = rs.data[stat] > maxValue ? rs.data[stat] : maxValue;
// détection du plus grand nom présent
maxLabelLength = map[stat].length > maxLabelLength ? map[stat].length : maxLabelLength;
data[map[stat]] = Math.round(rs.data[stat] * 100) / 100;
}
// légendes à afficher en plus de la valeur
gstore.get.titles = {};
gstore.get.titles[map['heures_ue_desactive']] = `sur ${data[map['nbr_ue_desactive']]} UE annulées`;
// statistiques à ne pas afficher
delete data[map['nbr_ue_desactive']];
gstore.get.stats = data;
gstore.get.dimensions = {
padding: 5,
text: {
size: maxLabelLength * 8.5,
alignH: 5,
alignV: 20,
},
bin: {
count: Object.keys(gstore.get.stats).length,
width: 30,
spacing: 15,
margin: 5
}
};
if (maxValue != null) {
let magnitude = Math.pow(10, maxValue.length-1)/2; // ordre de grandeur du nombre
maxValue = Math.round(
((gstore.get.dimensions.bin.margin/100)+1) // on rajoute la marge afin qu'un "bin" ne dépasse jamais sur la droite
* maxValue / magnitude ) * magnitude;
}
gstore.get.maxValue = maxValue;
gstore.get.dimensions.axis = {
height: (gstore.get.dimensions.bin.count*gstore.get.dimensions.bin.width) + ((gstore.get.dimensions.bin.count)*gstore.get.dimensions.bin.spacing),
width: 500,
precision: 4
};
gstore.get.viewBox = `0 0 ${gstore.get.dimensions.axis.width + gstore.get.dimensions.text.size + 50} ${gstore.get.dimensions.axis.height + 30}`;
});
gstore.add('colors', ["blue", "yellow", "green", "red", "purple", "lightblue", "lightred", "lightyellow", "lightgreen", "lightpurple"]);

View File

@ -1,10 +1,24 @@
gstore.add('popup_opened', false);
gstore.add('popup_click', function(){
/* (1) Open popup */
window.pop = window.open('https://sso.univ-pau.fr/cas/login?service='+window.api.target+'cas', '_blank', 'location=no,height=1024,width=1024,scrollbars=yes,status=no');
/* (1) Do nothing if already opened */
if( gstore.get.popup_opened )
return;
/* (2) Store that popup is opened */
gstore.get.popup_opened = true;
/* (3) Open popup */
window.pop = window.open('https://sso.univ-pau.fr/cas/login?service='+window.api.target+'cas/1', '_blank', 'location=no,height=1024,width=1024,scrollbars=yes,status=no');
/* (4) Clear interval (optional) */
!isNaN(window.popint) && clearInterval(window.popint);
/* (5) Check if popup closed -> abort */
window.popint = setInterval(function(){ window.pop.closed && window.cas_callback(-5); }, 500);
/* (2) If popup closed -> abort */
window.popint = setInterval(function(){ window.pop.closed && window.cas_callback(null); }, 500);
});

View File

@ -281,7 +281,17 @@ gstore.add('create_h', '');
gstore.add('create_err_valid', false);
gstore.add('create_err', '');
/* (4) Define create handler */
/* (4) Define reset handler */
gstore.add('ic_reset', function(){
gstore.add('create_cat', '-');
gstore.add('create_name', '');
gstore.add('create_cas', '');
gstore.add('create_h', '');
gstore.add('create_err_valid', false);
gstore.add('create_err', '');
});
/* (5) Define create handler */
gstore.add('ic_handler', function(prof_id){
/* (4.1) Trim text input */
@ -657,4 +667,38 @@ gstore.add('ia_handler', function(prof_i){
});
});
/* (9) Manage instant download fiche
---------------------------------------------------------*/
/* (1) Define download handler */
gstore.add('id_handler', function(prof_id){
/* (1) Abort if wrong prof_id */
if( prof_id == null || isNaN(prof_id) )
return;
/* (2.1) Find index in gstore */
var gi = gstore.get.professors.map( (data, i) => { return ( data.idProfesseur && data.idProfesseur == prof_id ) ? i : ''; }).join('');
/* (2.2) Exit if not found */
if( isNaN(gi) ) return;
var local = gstore.get.professors[gi];
/* (3.1) Update in database */
api.call(`GET professor/pdf/${local.idProfesseur}`, {}, function(rs){
/* (3.1.1) Abort on error */
if( rs.error !== 0 || rs.link == null )
return console.log('Impossible de télécharger la fiche, erreur '+rs.error);
/* (3.1.2) Success */
document.location = rs.link;
});
});

View File

@ -1,9 +1,14 @@
/* (1) Load formations
---------------------------------------------------------*/
/* (1) Initialize list */
/* (1) Define global filter */
gstore.add('filters', {
formations: [{ visible: false, active: [] }]
});
/* (2) Initialize list */
gstore.add('formations', []);
/* (2) Get Formations */
/* (3) Get Formations */
api.call('GET formation', {}, function(rs){
// {1} If error -> abort //
@ -14,9 +19,17 @@ api.call('GET formation', {}, function(rs){
for( var i = 0 ; i < rs.formations.length ; i++ )
gstore.get.formations.push( rs.formations[i] );
// {3} Format formation list for filters
for( var i = 0 ; i < rs.formations.length ; i++ )
gstore.get.filters.formations.push({
code: rs.formations[i].idForm,
name: rs.formations[i].labelForm,
active: false
});
});
/* (3) Get Formation label */
/* (4) Get Formation label */
gstore.add('form_by_id', function(form_id){
/* (1) Abort if wrong form_id */
@ -37,7 +50,6 @@ gstore.add('form_by_id', function(form_id){
/* (2) Load ues
---------------------------------------------------------*/
/* (1) Initialize list */
@ -59,10 +71,101 @@ api.call('GET ue/', { vh: true }, function(rs){
/* (3) Define filters' callback
---------------------------------------------------------*/
/* (1) Define global callback */
gstore.add('filter_handler', function(){
let filter_ids = [];
// 1. Get each formation ID
for( let form of gstore.get.filters.formations ){
// 1.1. Ignore if 'code' not found
if( form.code === null )
continue;
// 1.2. If active -> add to list
( form.code != null && form.active ) && filter_ids.push(form.code);
}
// 3. For UE element
main_loop: for( let ue of gstore.get.ues ){
// 3.1. Show by default
let element = document.querySelector('section[data-id=\''+ue.code+'\']');
if( !(element instanceof Element) )
continue;
element.remClass('filter-hidden');
// 3.2. If no filter -> let all visible
if( filter_ids.length <= 0 )
continue;
// 3.3. If at least one matching formatiom id -> let visible
for( let fid of filter_ids )
if( ue.formations.indexOf(fid) > -1 )
continue main_loop;
// XXXXX. If did not match -> hide
element.addClass('filter-hidden');
}
});
/* (3) Get Filters
---------------------------------------------------------*/
/* (2) Define filter group show/hide */
gstore.add('show_fgroup', function(gname){
var opened = gstore.get.filters[gname] != null && gstore.get.filters[gname][0].visible;
// {1} hide all by default//
for( var f in gstore.get.filters )
gstore.get.filters[f][0].visible = false;
// {2} If wrong @gname -> abort //
if( gstore.get.filters[gname] == null )
return;
// {3} Show selected filter //
gstore.get.filters[gname][0].visible = !opened;
});
/* (3) Define filter item toggle */
gstore.add('toggle_filter', function(gname, i){
// {1} If wrong @gname -> abort //
if( gstore.get.filters[gname] == null )
return;
// {2} If wrong @i -> abort //
if( gstore.get.filters[gname][i] == null )
return;
// {3} Toggle filter activation //
gstore.get.filters[gname][i].active = !gstore.get.filters[gname][i].active;
// {4} Update active table //
gstore.get.filters[gname][0].active.splice(0);
for( var f = 1 ; f < gstore.get.filters[gname].length ; f++ )
if( gstore.get.filters[gname][f].active )
gstore.get.filters[gname][0].active.push(f);
});
/* (2) Manage Instant Search (IS)
/* (4) Manage Instant Search (IS)
---------------------------------------------------------*/
/* (1) Define global timeout index */
gstore.add('is_to', null);
@ -76,8 +179,7 @@ gstore.add('is_buf', null);
gstore.add('is_handler', function(e){
/* (1) Remove last timeout */
if( gstore.get.is_to != null )
clearTimeout(gstore.get.is_to);
!isNaN(gstore.get.is_to) && clearTimeout(gstore.get.is_to);
/* (2) Store value in buffer */
gstore.get.is_buf = e.target.value.trim().toLowerCase();
@ -86,27 +188,55 @@ gstore.add('is_handler', function(e){
gstore.get.is_to = setTimeout(function(){
// 1. Fetch elements
var local_ptr = gstore.get.ues;
var l = gstore.get.ues.length;
let local_ptr = gstore.get.ues;
let l = gstore.get.ues.length;
let buf = gstore.get.is_buf.replace(/[.?*+^$[\]\\(\){}|-]/g, "\\$&"); // Escape errorful regex characters
// 2. For each element
for( var e = 0 ; e < l ; e++ ){
main_loop: for( let e = 0 ; e < l ; e++ ){
// 2.1. Show by default
var element = document.querySelector('section[data-id=\''+local_ptr[e].code+'\']');
if( !element ) continue;
// 2.1. Set visible by default
let element = document.querySelector('section[data-id=\''+local_ptr[e].code+'\']');
if( !(element instanceof Element) )
continue;
element.remClass('search-hidden');
// 2.2. Extract name components
var code = local_ptr[e].code.trim().toLowerCase();
var label = local_ptr[e].label.trim().toLowerCase();
// 2.2. Empty text -> let visible
if( buf.length == 0 )
continue;
// 2.3. Hide if does not match
var match_offset = gstore.get.is_buf.length == 0 || code.search(gstore.get.is_buf) + label.search(gstore.get.is_buf);
// 2.3. Extract name components
let code = local_ptr[e].code.toLowerCase();
let label = local_ptr[e].label.toLowerCase();
let forms = local_ptr[e].formations;
if( match_offset <= -2 )
element.addClass('search-hidden');
// 2.4-1. Match code -> let visible
if( code.search(buf) >= 0 )
continue;
// 2.4-2. Match label -> let visible
if( label.search(buf) >= 0 )
continue;
// 2.4-3. Check for each formation
for( let f of forms ){
let fetched_form = gstore.get.form_by_id(f);
// if formation not found -> ignore
if( fetched_form == null )
continue;
// if formation LABEL matches -> let visible
if( fetched_form.labelForm.toLowerCase().search(buf) >= 0 )
continue main_loop;
}
// XXXX. Hide all that did not match
element.addClass('search-hidden');
}
@ -133,7 +263,19 @@ gstore.add('create_vol', { c: '', td: '', tp: ''});
gstore.add('create_err_valid', false);
gstore.add('create_err', '');
/* (4) Define create handler */
/* (4) Define reset handler */
gstore.add('ic_reset', function(){
gstore.add('create_form', '-');
gstore.add('create_label', '');
gstore.add('create_code', '');
gstore.add('create_vol', { c: '', td: '', tp: ''});
gstore.add('create_err_valid', false);
gstore.add('create_err', '');
});
/* (5) Define create handler */
gstore.add('ic_handler', function(){
/* (4.1) Trim text input */
@ -329,7 +471,7 @@ gstore.add('edit_i', -1);
/* (2) Initialize inputs */
gstore.add('edit_form', '-');
gstore.add('edit_label', '');
// gstore.add('edit_code', '');
gstore.add('edit_code', '');
gstore.add('edit_vol', { c: '', td: '', tp: ''});
/* (3) Initialize error message */
@ -372,7 +514,7 @@ gstore.add('ie_handler', function(ue_i){
/* (5.3) Trim text input */
gstore.get.edit_label = gstore.get.edit_label.trim();
// gstore.get.edit_code = gstore.get.edit_code.toString().trim().toUpperCase();
gstore.get.edit_code = gstore.get.edit_code.toString().trim().toUpperCase();
gstore.get.edit_vol.c = gstore.get.edit_vol.c.toString().trim();
gstore.get.edit_vol.td = gstore.get.edit_vol.td.toString().trim();
gstore.get.edit_vol.tp = gstore.get.edit_vol.tp.toString().trim();
@ -380,7 +522,7 @@ gstore.add('ie_handler', function(ue_i){
/* (5.4) Store values locally */
var form = gstore.get.edit_form;
var label = gstore.get.edit_label;
// var code = gstore.get.edit_code;
var code = gstore.get.edit_code;
var vco = gstore.get.edit_vol.c;
var vtd = gstore.get.edit_vol.td;
var vtp = gstore.get.edit_vol.tp;
@ -396,21 +538,13 @@ gstore.add('ie_handler', function(ue_i){
errors.push('Le label doit comprendre faire au moins 4 caractères');
/* (5.5.3) Check code */
// if( !/^[A-Z0-9]{4,20}$/.test(code) )
// errors.push('Le code doit comprendre de 4 à 20 lettres/chiffres');
if( !/^[A-Z0-9]{4,20}$/.test(code) )
errors.push('Le code doit comprendre de 4 à 20 lettres/chiffres');
/* (5.5.4) Check volumes */
if( vco === '' || isNaN(vco) || vco < 0 )
errors.push('Le volume horaire de cours doit être un entier positif.');
/* (5.5.5) Check TD */
if( vtd === '' || isNaN(vtd) || vtd < 0 )
errors.push('Le volume horaire de TD doit être un entier positif.');
/* (5.5.6) Check TP */
if( vtp === '' || isNaN(vtp) || vtp < 0 )
errors.push('Le volume horaire de TP doit être un entier positif.');
/* (5.6) Show first error only (for 2s) */
if( errors.length > 0 ){
@ -424,7 +558,7 @@ gstore.add('ie_handler', function(ue_i){
/* (5.7) Création de la requête */
var rq = {};
( label != ue.label ) && ( rq.label = label );
// ( code != ue.code ) && ( rq.code = code );
( code != ue.code ) && ( rq.new_code = code );
( vco != ue.volumeCours ) && ( rq.volumeCours = parseInt(vco) );
( vtd != ue.volumeTD ) && ( rq.volumeTD = parseInt(vtd) );
( vtp != ue.volumeTP ) && ( rq.volumeTP = parseInt(vtp) );
@ -447,12 +581,17 @@ gstore.add('ie_handler', function(ue_i){
/* (5.8.1) Update UE */
api.call(`PUT ue/${ue.code}`, rq, function(rs){
/* (5.8.1.1) Abort on error */
console.log(rq, rs.error != 0, rs.updated != true);
/* (5.8.1.1) Manage 'already exist' error */
if( rs.error == 29 ){
gstore.get.edit_err = 'Le nouveau code est déja utilisé par une autre UE.';
return setTimeout(() => gstore.add('edit_err', ''), 2000);
}
/* (5.8.1.2) Abort on error */
if( rs.error != 0 || rs.updated != true )
return reject(rs.error);
/* (5.8.1.2) Success */
/* (5.8.1.3) Success */
resolve();
});
@ -464,7 +603,7 @@ gstore.add('ie_handler', function(ue_i){
/* (5.9.1) update VueJS element */
gstore.get.ues[ue_i].label = label;
// gstore.get.ues[ue_i].code = code;
gstore.get.ues[ue_i].code = code;
gstore.get.ues[ue_i].idForm = parseInt(form);
gstore.get.ues[ue_i].volumeCours = parseInt(vco);
gstore.get.ues[ue_i].volumeTD = parseInt(vtd);
@ -507,9 +646,9 @@ gstore.add('ie_handler', function(ue_i){
/* (8) Manage instant admin
/* (8) Manage instant toggle
---------------------------------------------------------*/
/* (1) Define admin handler */
/* (1) Define 'disabled' handler */
gstore.add('ia_handler', function(ue_i){
/* (1) Abort if wrong ue_i */
@ -535,6 +674,32 @@ gstore.add('ia_handler', function(ue_i){
});
/* (1) Define 'required' / 'optional handler */
gstore.add('io_handler', function(ue_i){
/* (1) Abort if wrong ue_i */
if( ue_i == null || isNaN(ue_i) || gstore.get.ues[ue_i] == null)
return;
/* (2) Toggle current value */
var local = gstore.get.ues[ue_i];
var is_required = local.required == '1' || local.required === true;
var new_state = !is_required;
/* (3.1) Update in database */
api.call('PUT ue/'+local.code, { required: new_state }, function(rs){
/* (3.1.1) Abort on error */
if( rs.error !== 0 || rs.updated !== true )
return console.log('Impossible de changer le status \'requis\' / \'optionnel\', erreur '+rs.error);
/* (3.1.2) Success */
gstore.get.ues[ue_i].required = new_state ? 1 : 0;
});
});
/* (9) Manage 'manage' sub-page
---------------------------------------------------------*/
@ -913,6 +1078,66 @@ gstore.add('upd_prof', function(type, res_i){
});
});
/* (4) Ordering filters */
gstore.add('order', {
available: ['prof', 'volume', 'forms'],
current: 0,
way: 1 // 1 ASC, -1 DESC
});
gstore.add('order_toggle', function(ord_i){
// 1. Check params types
if( isNaN(ord_i) || gstore.get.order.available[ord_i] == null )
return;
// 2. If new ordering field -> toggle it
if( ord_i !== gstore.get.order.current )
gstore.get.order.current = ord_i;
// 3. If already selected -> toggle way
else
gstore.get.order.way *= -1;
// 4. Get all elements to order
let els = document.querySelectorAll('section[data-prof][data-vol][data-form]');
// 5. Ordering by formations
if( gstore.get.order.current === 2 ){
return els.forEach((el) => {
el.style.order = el.getAttribute('data-form').split('|').length * gstore.get.order.way
});
}
// 6. Ordering by 'volume'
if( gstore.get.order.current === 1 )
return els.forEach((el) => {
el.style.order = parseInt( el.getAttribute('data-vol') ) * gstore.get.order.way
});
// 7. Ordering by 'prof'
els.forEach((el) => {
var profId = parseInt( el.getAttribute('data-prof') );
// outside by default if no prof set
el.style.order = - gstore.get.order.way;
// professors are already sorted
for( pi in gstore.get.manage.prof ) {
if( gstore.get.manage.prof[pi].idProfesseur == profId ){
el.style.order = pi * gstore.get.order.way;
break;
}
}
});
});
@ -996,4 +1221,101 @@ gstore.add('rem', function(type, res_i){
});
});
});
/* (2) Create a Cours|TD|TP */
gstore.add('ccrea', {
type: '-',
prof: -1,
vol: '',
err: '',
valid: false
});
gstore.add('ccreate', function(){
/* (1) Trim text input */
gstore.get.ccrea.vol = gstore.get.ccrea.vol.trim();
/* (2) Store values locally */
var type = gstore.get.ccrea.type;
var prof = gstore.get.ccrea.prof;
var vol = gstore.get.ccrea.vol;
/* (2) Init client-side check */
var errors = [];
/* (2.1) Check type */
if( isNaN(type) || [0,1,2].indexOf(parseInt(type)) <= -1 )
errors.push('Le type de prestation est manquant (Cours, TD, TP)');
/* (2.2) Check prof */
if( isNaN(prof) )
errors.push('L\'enseignant est invalide ou manquant');
/* (2.3) Check volume */
if( vol === '' || isNaN(vol) || vol < 0 )
errors.push('Le volume horaire doit être un entier positif.');
/* (2.4) Show first error only (for 2s) */
if( errors.length > 0 ){
gstore.get.ccrea.valid = false;
gstore.get.ccrea.err = errors[0];
return setTimeout(() => gstore.add('ccrea.err', ''), 2000);
}
/* (3) Extract resource type */
var restyp = ['cours', 'td', 'tp'][type];
var restyp2 = ['Cours', 'TD', 'TP'][type];
/* (4) Create request */
var rq = {
code: gstore.get.router.history.current.path.split('/').pop(),
volume: parseInt(vol)
};
// optional 'idProf'
( prof > -1 ) && ( rq.idProf = parseInt(prof) );
/* (5) Send request */
api.call(`POST ue/${restyp}`, rq, function(rs){
console.log(rs);
/* (5.1) Manage errors */
if( rs.error !== 0 ){
gstore.get.ccrea.valid = false
gstore.get.create_err = `erreur (${rs.error}) Impossible de créer le ${restyp}`;
return setTimeout(() => gstore.add('create_err', ''), 2000);
}
/* (5.2) Show that Cours|TD|TP is created */
gstore.get.ccrea.valid = true;
gstore.get.ccrea.err = `Le ${restyp} a bien été créé, rechargez la page pour le visualiser.`;
setTimeout(() => gstore.add('create_err', ''), 2000);
/* (5.3) Empty fields */
// empty fields
gstore.get.ccrea.prof = -1;
gstore.get.ccrea.type = '-';
gstore.get.ccrea.vol = '';
/* (5.4) Add resource to list (update VueJS) */
var newRes = {};
newRes[`id${restyp2}`] = rs.created_id;
newRes[`idProf`] = prof;
newRes[`volume`] = vol;
newRes[`formations`] = rs.formations;
newRes[`add_form`] = '-';
newRes[`new_prof`] = prof;
gstore.get.manage[restyp].push(newRes);
});
});

81
webpack/lib/onblur.js Normal file
View File

@ -0,0 +1,81 @@
/* OnBlur Manager Class */
export class OnBlurManager{
/* (1) Initialize an OnBlurManager
*
* @root<Element> The root element to work in
*
---------------------------------------------------------*/
constructor(root){
/* (1) Error: invalid @root argument */
if( !(root instanceof Element) )
throw new Error(`[OnBlurManager::new] expected argument to be of type (Element), received (${typeof root})`);
/* (2) Store as attribute */
this.root = root;
/* (3) Initialize @callback list */
this.callback = {};
/* (4) Bind to event */
this.root.addEventListener('click', function(e){
// launch each callback
for( var c of Object.keys(this.callback) )
this.callback[c](e);
}.bind(this) );
}
/* (2) Add a @callback
*
* @index<String> String to name the callback for removing
* @callback<Function> Function to link to callback
*
* @return linked<bool> Whether the callback has been linked
*
---------------------------------------------------------*/
link(index, callback){
/* (1) Fail: invalid @index or @callback arguments */
if( typeof index !== 'string' || !(callback instanceof Function) ){
console.error(`[OnBlurManager::link] expected (String, Function), received (${typeof index}, ${typeof callback})`);
return false;
}
/* (2) Add to list of callbacks */
return ( this.callback[index] = callback ) instanceof Function;
}
/* (3) Unlink a @callback
*
* @index<String> Name of the callback to remove
*
* @return unlinked<bool> Whether the callback has been unlinked
*
---------------------------------------------------------*/
unlink(index, callback){
/* (1) Fail: invalid @index argument */
if( typeof index !== 'string' ){
console.error(`[OnBlurManager::unlink] expected (String), received (${typeof index})`);
return false;
}
/* (2) Remove from list of callbacks */
return ( delete this.callback[index] );
}
}

View File

@ -32,22 +32,56 @@ window.cas_callback = function(cas_login){
setTimeout( function(){ if( window.pop.closed ){
/* (2) Stop interval that checks if window closed */
clearInterval(window.popint);
!isNaN(window.popint) && clearInterval(window.popint);
/* (3) If no login -> error */
if( cas_login === null ){
gstore.get.login_error_text = 'Erreur de connexion. Veuillez réessayer.';
gstore.get.login_class = 'invalid';
setTimeout(function(){ gstore.get.login_class = 'neutral'; }, 1500);
// re-activate button
gstore.add('popup_opened', false);
setTimeout(function(){ gstore.get.login_class = 'neutral'; }, 3000);
/* (4) If error code -> display error */
}else if( !isNaN(cas_login) ){
gstore.get.login_class = 'invalid';
switch(cas_login){
case -1:
gstore.get.login_error_text = 'Erreur de connexion. Veuillez réessayer.<br>(errcode: no_ticket_received)';
break;
case -2:
gstore.get.login_error_text = 'Erreur de connexion. Veuillez réessayer.<br>(errcode: cas_not_authed)';
break;
case -3:
gstore.get.login_error_text = 'Erreur de connexion. Veuillez réessayer.<br>(errcode: no_meta_department)';
break;
case -4:
gstore.get.login_error_text = 'Erreur de connexion. Veuillez réessayer.<br>(errcode: no_matching_professor)';
break;
case -5:
gstore.get.login_error_text = 'Erreur de connexion. Veuillez réessayer.<br>(errcode: popup_interrupt)';
break;
}
// re-activate button
gstore.add('popup_opened', false);
setTimeout(function(){ gstore.get.login_class = 'neutral'; }, 3000);
/* (4) If login -> reload page */
}else{
gstore.get.login_error_text = 'Vous êtes connectés. Vous allez être redirigé.';
gstore.get.login_class = 'valid';
setTimeout(function(){ document.location = '/'; }, 1500);
var redirect_url = `/${gstore.get.URI.join('/')}`;
setTimeout(function(){ document.location = redirect_url; }, 3000);
}

View File

@ -23,6 +23,7 @@ $rd-form-valid-color: '20d696';
$rd-form-neutral-color: 'b8c0c8';
$rd-form-search-color: '1d74e5';
$rd-form-invalid-color: 'ea4b35';
$rd-form-primary-color: '54627c';
// Menu
$menu-bg: #333;

View File

@ -53,8 +53,12 @@
margin: .5em;
color: transparent;
font-weight: bold;
background: #fff url('/asset/svg/plus.svg@#{$rd-form-valid-color}') center center no-repeat;
background-size: 40% auto;
}
}
@ -106,7 +110,7 @@
$btn-size: 1.8em;
$btn-space: 0em;
$nb-btn: 3;
$nb-btn: 4;
$nb-spc: $nb-btn - 1;
$cont-w: $btn-size * $nb-btn + $nb-spc * $btn-space;
@ -134,12 +138,14 @@
& > div.remove[data-remove],
& > div.edit[data-edit],
& > div.enabled[data-enabled],
& > div.required[data-required],
& > div.admin[data-admin]{
background-color: darken(#fff, 12%);
/* (2.1.2) displace all but 1st element */
&.edit[data-edit]{ left: calc( 100% - #{$btn-size * 2 + $btn-space} ); }
&.remove[data-remove]{ left: calc( 100% - #{$btn-size * 3 + $btn-space * 2} ); }
&.required[data-required]{ left: calc( 100% - #{$btn-size * 4 + $btn-space * 3} ); }
}
@ -149,6 +155,7 @@
& > div.remove[data-remove],
& > div.edit[data-edit],
& > div.enabled[data-enabled],
& > div.required[data-required],
& > div.admin[data-admin]{
display: inline-block;
position: absolute;
@ -202,6 +209,16 @@
&[data-active='1']:hover{ background-image: url('/asset/svg/bell.svg@f4a118'); }
}
/* (2.4) REQUIRED switch */
&.required[data-required]{
background-image: url('/asset/svg/pin-disabled.svg@aaaaaa');
z-index: 104;
&:hover{ background-image: url('/asset/svg/pin-disabled.svg@555555'); }
&[data-active='1']{ background-image: url('/asset/svg/pin.svg@f4bd18'); }
&[data-active='1']:hover{ background-image: url('/asset/svg/pin.svg@f4a118'); }
}
}
@ -236,7 +253,7 @@
margin: 0;
padding: 0;
margin-left: -.4em; // emulate no <select>
margin-top: -1em; // replace as if not a select
margin-top: -1em; // replace as if not a select
margin-bottom: -.3em; // fix layout for following elements
// remove border
@ -267,6 +284,8 @@
color: darken($primary-color, 5%);
font-size: 1em;
margin: .4em 0;
// flex
flex-direction: row;
justify-content: flex-start;
@ -355,9 +374,16 @@
align-items: center;
flex-wrap: nowrap;
&.little{
font-size: .7em;
white-space: nowrap;
}
/* (6.1) Column */
& > div{
flex: 1;
display: flex;
position: relative;
height: 2.3em;
@ -372,7 +398,7 @@
// flex properties
flex-direction: row;
justify-content: space-between;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
@ -404,7 +430,7 @@
}
/* (6.2) Column Emphasis */
& > span:last-child{
& > span:last-child:not(.notlast){
display: block;
position: relative;
min-width: 4em;
@ -541,7 +567,62 @@
}
/* (8) Card sub */
& > div.pdfdl{
display: inline-block;
margin-top: 1em;
color: lighten($primary-color, 20%);
transition: color .1s ease-in-out;
cursor: pointer;
&:before{
content: '';
display: inline-block;
position: relative;
top: .2em;
width: 1em;
height: 1em;
margin-right: .5em;
background: url('/asset/svg/fiche.svg@#{$menu-item-inactive}') center center no-repeat;
background-size: contain;
}
&:hover{
color: #333;
&:before{ background-image: url('/asset/svg/fiche.svg@ea4C3a'); }
}
}
/* (9) Infobar */
& > div.info{
display: block;
position: relative;
margin-top: 2.1em;
margin-left: -2.1em;
height: auto;
width: calc( 100% + 2*2.1em - 2*1em);
margin-bottom: -2.1em;
padding: .3em 1em;
border-top: 1px solid #f3f3f3;
background-color: darken(#fafbfd,1%);
font-size: .7em;
color: #bbb;
}
}

View File

@ -22,6 +22,8 @@
/* (1) List element */
& > section{
order: -100000;
flex: 1 1 90%;
display: flex;
@ -42,11 +44,16 @@
flex-wrap: nowrap;
/* (1.1) Element item */
& > div:not(.icon){
flex: 1 1 0;
& > div:not(.icon),
& > select,
& > input{
flex: 0 1 30%;
// fix
&.taglist{ margin: 0; }
&.taglist{
margin: 0;
margin-left: 1.5em;
}
}
/* (1.2) Hover animation */
@ -56,20 +63,21 @@
&:hover{ background-color: darken(#fff,1%); }
&.create:hover{ border-left-color: $form-valid-color; }
&:hover,
&.cours:hover{ border-left-color: $form-search-color; }
&.td:hover{ border-left-color: $form-valid-color; }
&.tp:hover{ border-left-color: $form-invalid-color; }
&.bcours:hover{ border-left-color: $form-search-color; }
&.btd:hover{ border-left-color: $form-valid-color; }
&.btp:hover{ border-left-color: $form-invalid-color; }
}
/* (1.3) Select elements*/
& > select{
display: inline-block;
display: block;
position: relative;
height: 1.8em;
width: auto;
margin: 0;
margin-right: 1em;
@ -91,12 +99,25 @@
-moz-appearance: none;
appearance: none;
justify-self: space-around;
flex: 0 1 20em;
// flex: 0 1 15em;
&.min{ flex: 0 1 5em; }
}
/* (1.4) Icon (remove) */
/* (1.4) Input */
& > input{
display: inline-block;
position: relative;
min-width: 0;
margin: 0;
padding: .22em .5em;
flex: 0 1 5em;
}
/* (1.5) Icon (remove) */
& > div.icon{
display: inline-block;
width: 1.2em;
@ -104,21 +125,28 @@
margin-right: 1em;
background: url('/asset/svg/cross.svg@aaaaaa') center center no-repeat;
background: url('/asset/svg/plus.svg@aaaaaa') center center no-repeat;
background-size: 60% auto;
overflow: hidden;
cursor: pointer;
&:hover{ background-image: url('/asset/svg/cross.svg@#{$rd-form-invalid-color}'); }
&:hover{ background-image: url('/asset/svg/plus.svg@#{$rd-form-valid-color}'); }
&.remove{
background-image: url('/asset/svg/cross.svg@aaaaaa');
&:hover{ background-image: url('/asset/svg/cross.svg@#{$rd-form-invalid-color}'); }
}
&.hidden{ background: transparent; }
}
}
/* (2) Filter */
& > section.filter{
// padding-bottom: 0;
background-color: transparent;
@ -129,25 +157,38 @@
font-weight: bold;
text-shadow: 1px 1px 2px #fff;
& > div:after{
content: '';
display: inline-block;
position: absolute;
width: 1.5em;
height: 1.5em;
& > div{
margin-top: -.25em;
margin-left: .5em;
cursor: default;
background: url('/asset/svg/down_arrow.svg@aaaaaa') center center no-repeat;
background-size: auto 100%;
& > span.arrow{
display: inline-block;
position: relative;
width: 1.5em;
height: 1.5em;
margin-bottom: -.3em;
background: url() center center no-repeat;
background-size: auto 100%;
cursor: pointer;
}
// selected filter
&[data-filter='1']{
color: $primary-color;
& > span.arrow{
&[data-way='-1']{ background-image: url('/asset/svg/up_arrow.svg@555555'); }
&[data-way='1']{ background-image: url('/asset/svg/down_arrow.svg@555555'); }
}
}
&[data-filter='up']{ background-image: url('/asset/svg/up_arrow.svg@aaaaaa'); }
}
& > div.null:after{ content: none; }
}
/* Tags color -> darker */

View File

@ -0,0 +1,126 @@
/* COULEUR POUR LE GRAPH SVG */
$svg-blue: #3366CC;
$svg-red: #DC3912;
$svg-yellow: #FF9900;
$svg-green: #109618;
$svg-purple: #990099;
$svg-lightblue: #0099C6;
$svg-lightred: #DD4477;
$svg-lightyellow: #F1CA3A;
$svg-lightgreen: #AAAA11;
$svg-lightpurple: #994499;
svg {
width: 100%;
height: 30vh;
margin: 10px;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */
path {
fill: none;
fill-rule: evenodd;
stroke: rgb(0, 0, 0);
stroke-width: 1px;
stroke-linecap: butt;
stroke-linejoin: miter;
stroke-opacity: 1;
&.precision {
stroke-width: 0.2px;
}
}
rect.hiding {
width: 0;
}
rect {
transition: width 0.75s;
cursor: pointer;
color: rgb(0, 0, 0);
clip-rule: nonzero;
display: inline;
overflow: visible;
visibility: visible;
opacity: 1;
isolation: auto;
mix-blend-mode: normal;
color-interpolation: sRGB;
color-interpolation-filters: linearRGB;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 1px;
stroke-linecap: butt;
stroke-linejoin: miter;
stroke-miterlimit: 4;
stroke-dasharray: none;
stroke-dashoffset: 0;
stroke-opacity: 1;
color-rendering: auto;
image-rendering: auto;
shape-rendering: auto;
text-rendering: auto;
}
text {
text-anchor: end;
&.precision {
text-anchor: middle;
fill: #666666;
font-size: 13px;
}
}
.blue {
fill: $svg-blue
}
.red {
fill: $svg-red
}
.yellow {
fill: $svg-yellow
}
.green {
fill: $svg-green
}
.purple {
fill: $svg-purple
}
.lightblue {
fill: $svg-lightblue
}
.lightred {
fill: $svg-lightred
}
.lightyellow {
fill: $svg-lightyellow
}
.lightgreen {
fill: $svg-lightgreen
}
.lightpurple {
fill: $svg-lightpurple
}
}

View File

@ -13,6 +13,8 @@
@import 'global/tag';
.error, [data-error='1']{ font-weight: bold; color: $form-invalid-color; }
.success, [data-success='1']{ color: $form-valid-color; }
@ -48,4 +50,104 @@ a{
/* (6) pointer cursor (2 times the class to increase priority) */
.pointer.pointer{
cursor: pointer;
}
/* (7) Mono font */
.mono{
font-family: 'Mono';
font-size: .9em;
}
/* (8) Striked text */
[data-strike='1'],
.strike{
text-decoration: line-through;
opacity: .5;
}
/* (9) Icons (1em wide) */
.user-icon, .time-icon{
display: block;
position: relative;
width: 1em;
height: 1em;
background: url('/asset/svg/teacher.svg@#{$rd-form-neutral-color}') center center no-repeat;
background-size: contain;
&.time-icon{
background-image: url('/asset/svg/time.svg@#{$rd-form-neutral-color}');
}
}
/* (10) Toggling tooltip on hover */
[data-tooltip]{
position: relative;
cursor: pointer;
&:before{
content: attr(data-tooltip);
display: block;
position: absolute;
top: calc( 100% + 1em );
left: 50%;
margin-left: -.1em;
padding: .4em .6em;
border-radius: 3px / 3px;
box-shadow: 0 0 .5em 0 #fff;
font-size: .7em;
color: #ddd;
letter-spacing: 0;
background-color: #444;
transform: translateX(-50%) translateY(-50%) scale(0);
transition: transform .1s ease-in-out;
z-index: 999;
}
&:hover:not([data-tooltip='']):before{
transform: translateX(-50%) translateY(0) scale(1);
}
&:after{
content: '';
display: block;
position: absolute;
top: calc( 100% + .6em );
left: 50%;
width: .4em;
height: .4em;
background-color: #444;
transform: translateX(-50%) rotate(45deg) scale(0);
transition: transform .1s ease-in-out;
z-index: 1000;
}
&:hover:not([data-tooltip='']):after{
transform: translateX(-50%) rotate(45deg) scale(1);
}
}

View File

@ -4,7 +4,13 @@
/* (1) Warning icon */
.warning,
.pin{
.user,
.time,
.pin,
.cm,
.back,
.td,
.tp{
// add icon before
&:before{
@ -21,6 +27,30 @@
background-size: auto 90%;
}
&.nospace:before{
margin-right: 0;
}
&.reflow{
white-space: nowrap;
&:before{
font-size: 1.2em;
background-position: center bottom;
background-size: auto 70%;
}
}
&.big{
&:before{
font-size: 1.5em;
}
}
// icon color variants
&.neutral:before{ background-image: url('/asset/svg/warning.svg@#{$rd-form-neutral-color}'); }
&.valid:before{ background-image: url('/asset/svg/warning.svg@#{$rd-form-valid-color}'); }
@ -28,6 +58,10 @@
// force red text
&.invalid{ color: $form-invalid-color; }
&[data-valid='1']{ color: $form-valid-color; }
&[data-neutral='1']{ color: $form-neutral-color; }
&[data-search='1']{ color: $form-search-color; }
}
@ -36,12 +70,140 @@
// add icon before
&:before{
background-image: url('/asset/svg/pin.svg@#{$rd-form-invalid-color}');
background-image: url('/asset/svg/pin.svg@f4bd18');
}
// icon color variants
&.disabled:before{ background-image: url('/asset/svg/pin-disabled.svg@#{$rd-form-neutral-color}'); }
&.neutral:before{ background-image: url('/asset/svg/pin.svg@#{$rd-form-neutral-color}'); }
&.valid:before{ background-image: url('/asset/svg/pin.svg@#{$rd-form-valid-color}'); }
&.search:before{ background-image: url('/asset/svg/pin.svg@#{$rd-form-search-color}'); }
}
/* (3) CM */
.cm{
// add icon before
&:before{
background-image: url('/asset/svg/course.svg@#{$menu-item-inactive}');
}
&.active:before{ background-image: url('/asset/svg/course.svg@5bb8f0'); }
// icon color variants
&.neutral:before{ background-image: url('/asset/svg/course.svg@#{$rd-form-neutral-color}'); }
&.valid:before{ background-image: url('/asset/svg/course.svg@#{$rd-form-valid-color}'); }
&.search:before{ background-image: url('/asset/svg/course.svg@#{$rd-form-search-color}'); }
}
/* (4) TD */
.td{
// add icon before
&:before{
background-image: url('/asset/svg/td.svg@#{$menu-item-inactive}');
}
&.active:before{ background-image: url('/asset/svg/td.svg@20b565'); }
// icon color variants
&.neutral:before{ background-image: url('/asset/svg/td.svg@#{$rd-form-neutral-color}'); }
&.valid:before{ background-image: url('/asset/svg/td.svg@#{$rd-form-valid-color}'); }
&.search:before{ background-image: url('/asset/svg/td.svg@#{$rd-form-search-color}'); }
}
/* (5) TP */
.tp{
// add icon before
&:before{
background-image: url('/asset/svg/tp.svg@#{$menu-item-inactive}');
}
&.active:before{ background-image: url('/asset/svg/tp.svg@e85456'); }
// icon color variants
&.neutral:before{ background-image: url('/asset/svg/tp.svg@#{$rd-form-neutral-color}'); }
&.valid:before{ background-image: url('/asset/svg/tp.svg@#{$rd-form-valid-color}'); }
&.search:before{ background-image: url('/asset/svg/tp.svg@#{$rd-form-search-color}'); }
}
/* (6) USER */
.user{
// add icon before
&:before{
background-image: url('/asset/svg/teacher.svg@#{$menu-item-inactive}');
}
// icon color variants
&.neutral:before{ background-image: url('/asset/svg/teacher.svg@#{$rd-form-neutral-color}'); }
&.invalid:before{ background-image: url('/asset/svg/teacher.svg@#{$rd-form-invalid-color}'); }
&.valid:before{ background-image: url('/asset/svg/teacher.svg@#{$rd-form-valid-color}'); }
&.search:before{ background-image: url('/asset/svg/teacher.svg@#{$rd-form-search-color}'); }
}
/* (7) TIME */
.time{
// add icon before
&:before{
background-image: url('/asset/svg/time.svg@#{$menu-item-inactive}');
}
// icon color variants
&.neutral:before{ background-image: url('/asset/svg/time.svg@#{$rd-form-neutral-color}'); }
&.invalid:before{ background-image: url('/asset/svg/time.svg@#{$rd-form-invalid-color}'); }
&.valid:before{ background-image: url('/asset/svg/time.svg@#{$rd-form-valid-color}'); }
&.search:before{ background-image: url('/asset/svg/time.svg@#{$rd-form-search-color}'); }
}
/* (8) BACK */
.back{
// add icon before
&:before{
background-image: url('/asset/svg/back.svg@#{$menu-item-inactive}');
}
// icon color variants
&.neutral:before{ background-image: url('/asset/svg/back.svg@#{$rd-form-neutral-color}'); }
&.invalid:before{ background-image: url('/asset/svg/back.svg@#{$rd-form-invalid-color}'); }
&.valid:before{ background-image: url('/asset/svg/back.svg@#{$rd-form-valid-color}'); }
&.search:before{ background-image: url('/asset/svg/back.svg@#{$rd-form-search-color}'); }
// hover
&:hover:before{ background-image: url('/asset/svg/back.svg@ffffff'); }
}

View File

@ -33,6 +33,7 @@
color: #999;
font-size: .8em;
white-space: nowrap;
cursor: default;

View File

@ -22,11 +22,12 @@
z-index: 150;
/* (1) Version management */
/* (1) left-side managers */
& > div.departments,
& > div.versions{
& > div.versions,
& > div.global-export{
/* (1.1) Version status */
/* (1.1) Current status */
& > div.current{
display: block;
position: relative;
@ -34,8 +35,7 @@
margin-left: 1em;
padding: .5em 1em;
padding-left: 2em;
padding-left: .7em;
border: 1px solid #ddd;
border-radius: 3px;
@ -48,26 +48,79 @@
// hover animation
&:hover{ box-shadow: 0 2px 2px darken(#fff,10%); }
// color state
&:before{
content: '';
// current: EXPORT / CREATE / EDIT / REMOVE icons
span.export,
span.create,
span.edit,
span.remove{
display: inline-block;
position: relative;
top: .2em;
width: 1em;
height: 1em;
display: block;
position: absolute;
top: calc( 50% - .7em/2 );
left: calc( 1em/2 + .7em/2 );
width: .7em;
height: .7em;
border-radius: 3px;
border-radius: 50%;
background: center center no-repeat;
background-size: 80% auto;
background-color: $form-invalid-color;
&.export{
background-image: url('/asset/svg/fiche.svg@b8c0c8');
background-size: 100% auto;
&:hover{
background-image: url('/asset/svg/fiche.svg@#{$rd-form-invalid-color}');
}
}
&.create{
background-image: url('/asset/svg/plus.svg@b8c0c8');
&:hover{
background-image: url('/asset/svg/plus.svg@#{$rd-form-valid-color}');
}
}
&.edit{
background-image: url('/asset/svg/a.svg@b8c0c8');
&:hover{
background-image: url('/asset/svg/a.svg@#{$rd-form-search-color}');
}
}
&.remove{
background-image: url('/asset/svg/cross.svg@b8c0c8');
&:hover{
background-image: url('/asset/svg/cross.svg@#{$rd-form-invalid-color}');
}
}
&:last-child{
margin-right: .5em;
}
}
&[data-id='-1']:before{
background-color: $form-valid-color;
&:hover > span.export{
background-image: url('/asset/svg/fiche.svg@#{$rd-form-invalid-color}');
}
overflow: hidden;
// editable input
& > input[type='text']{
display: inline-block;
position: relative;
width: 100%;
max-width: 10em;
margin: 0;
padding: 0;
border: none;
border-radius: 0;
// background-color: #f00;
font-size: inherit;
color: inherit;
}
}
@ -87,6 +140,8 @@
border-top: 0;
border-radius: 0 0 3px 3px;
&>:first-child{ border-top: 1px solid #ddd; }
background-color: #fff;
// box-shadow: 0 2px 2px #ddd;
@ -111,24 +166,29 @@
// hover animation
&:hover{ background-color: darken(#fff, 5%); }
// color state
// switch+create icons
&:before{
content: '';
display: block;
position: absolute;
top: calc( 50% - .7em/2 );
left: calc( 1em/2 + .7em/2 );
width: .7em;
height: .7em;
top: calc( 50% - 1em/2 );
left: calc( .5em/2 + 1em/2 );
width: 1em;
height: 1em;
border-radius: 50%;
background-color: $form-invalid-color;
background: url('/asset/svg/switch.svg@#{$rd-form-primary-color}') center center no-repeat;
background-size: auto 80%;
}
&[data-id='-1']:before{
background-color: $form-valid-color;
// create icon specifications
&[data-id='-1']{
&:before{
background-image: url('/asset/svg/plus.svg@#{$rd-form-valid-color}');
background-size: auto 60%;
}
}
}
@ -136,16 +196,20 @@
}
/* (2) Department | Version layout */
/* (2) Department | Version | Export layout */
& > div.departments > div.current{
margin-right: 0;
padding-left: 1em;
border-radius: 3px 0 0 3px;
&:before{ content: none; }
}
& > div.versions > div.current{
margin-left: 0;
border-radius: 0;
border-left: 0;
}
& > div.global-export > div.current{
margin-left: 0;
border-radius: 0 3px 3px 0;
border-left: 0;

View File

@ -281,5 +281,13 @@ body.body404{
}
.downloadButton{
text-decoration: none;
color: #3f4a5e;
box-shadow: 0px 0px 1px 1px #7f8d9b;
background: whitesmoke;
padding: 10px;
border-radius: 2px;
}

95
webpack/scss/pdf.scss Normal file
View File

@ -0,0 +1,95 @@
/* (1) Reset
---------------------------------------------------------*/
*{
margin: 0;
padding: 0;
}
body{
background-color: #fff;
font-size: 16px;
color: #444;
}
/* (2) Basic layout
---------------------------------------------------------*/
article{
// Titles
h3{
color: #555;
}
// Titles' separators
hr{
display: block;
border-style: solid;
border-width: 1px;
}
// paragraphs
p{
text-indent: 2em;
}
// links
a{
color: #2d92cc;
text-decoration: underline;
}
// quotes
blockquote{
margin-left: 0;
padding-left: 1em;
border-left: 2px solid #eee;
color: #999;
}
// tables
table{
border-collapse: collapse;
color: #555;
tr{
td{
padding: .2em 1em;
border: 1px dotted rgb(221, 221, 221);
background-color: #fff;
}
// each other row is darker
&:nth-child(2n) td{
background-color: #eee;
}
}
// table headers bold revert
thead tr th{
font-weight: normal;
}
thead tr td.color,
thead tr th.color{
background-color: rgb(75, 189, 209);
color: #fff;
}
td.ar{ text-align: right; }
td.ac{ text-align: center; }
td.al{ text-align: left; }
}
}

View File

@ -3,25 +3,45 @@
<div id='HEADER'>
<!-- Department management -->
<div class='departments'>
<div class='departments' data-unblur-department>
<div class='current' @click='d_dialog=!d_dialog'>{{ get_dcurrent().label || 'département à jour' }}</div>
<div class='department-dialog' v-show='d_dialog'>
<span v-for='d in dpts' @click='d_switch(d.id)'>{{ d.label }}</span>
<div class='current' data-unblur-department>
<span class='create' @click='!department.create?(department.newLabel="")+(department.create=true):d_create()'></span>
<span class='remove' @click='d_remove()'></span>
<input v-if='department.create' type='text' placeholder='Nouveau nom' v-model='department.newLabel' size=''>
<span v-if='!department.create' @click='department.dialog=!department.dialog' data-unblur-department>{{ get_dcurrent().label }}</span>
</div>
<div class='department-dialog' v-show='department.dialog' data-unblur-department>
<span v-for='d in department.list' v-show='d.id!=department.current' @click='d_switch(d.id)' data-unblur-department>{{ d.label }}</span>
</div>
</div>
<!-- Version management -->
<div class='versions'>
<div class='versions' data-unblur-version>
<div class='current' @click='v_dialog=!v_dialog' :data-id='get_vcurrent().id'>{{ get_vcurrent().date || 'version à jour' }}</div>
<div class='version-dialog' v-show='v_dialog'>
<span v-for='v in vers' v-show='v.id!=ver_id' @click='v_switch(v.id)' :data-id='v.id'>{{ v.date || 'version à jour' }}</span>
<div class='current' :data-id='get_vcurrent().id' data-unblur-version>
<span class='remove' @click='v_remove()'></span>
<span class='edit' @click='!version.edit?(version.newName="")+(version.edit=true):v_edit()'></span>
<input v-if='version.edit' type='text' :placeholder='get_vcurrent().name' v-model='version.newName' size=''>
<span v-if='!version.edit' @click='version.dialog=!version.dialog' data-unblur-version>{{ get_vcurrent().name }}</span>
</div>
<div class='version-dialog' v-show='version.dialog' data-unblur-version>
<span v-for='v in version.list' @click='v_switch(v.id)' v-show='v.id!=version.current' :data-id='v.id' data-unblur-version> {{ v.name }} </span>
<span @click='v_create()' data-unblur-version data-id='-1'>Créer</span>
</div>
</div>
<!-- <div class='header-title'>{{ gstore.header_title }}</div> -->
<!-- Export all -->
<div class='global-export'>
<div class='current export' @click='global_export()'>
<span class='export'></span>
exporter
</div>
</div>
</div>
@ -36,44 +56,54 @@ export default {
gstore: gstore.get,
is_connected: _SERVER.session.connected,
d_dialog: false,
dep_id: _SERVER.session.department_id,
dpts: _SERVER.session.departments,
department: {
dialog: false,
current: _SERVER.session.department_id,
list: _SERVER.session.departments,
create: false,
newLabel: ''
},
v_dialog: false,
ver_id: -1,
vers: [
{ id: -1, date: null },
{ id: 0, date: '01-02-2017' },
{ id: 1, date: '23-03-2017' }
]
version: {
dialog: false,
current: -1,
list: [],
edit: false,
newName: ''
}
};
},
methods: {
/* (1) Get current department data
---------------------------------------------------------*/
get_dcurrent(){
get_dcurrent(id){
// search in @dpts where id is @dep_id
for( var d in this.dpts )
if( this.dpts[d].id == this.dep_id )
return this.dpts[d];
// use @current, if invalid argument @id
( isNaN(id) ) && ( id = this.department.current );
return { id: null, label: null };
// search in @list where id is @current
for( var d in this.department.list )
if( this.department.list[d].id == id )
return this.department.list[d];
return { id: -2, name: null };
},
/* (2) Get current versoin data
/* (2) Get current version data
---------------------------------------------------------*/
get_vcurrent(){
get_vcurrent(id){
// search in @vers where id is @ver_id
for( var v in this.vers )
if( this.vers[v].id == this.ver_id )
return this.vers[v];
// use @version.current, if invalid argument @id
( isNaN(id) ) && ( id = this.version.current );
return { date: null };
// search in @ist where id is @id
for( var v in this.version.list )
if( this.version.list[v].id == id )
return this.version.list[v];
return { id: -2, name: '-' };
},
@ -82,20 +112,24 @@ export default {
d_switch(id){
// 1. De-activate dialogs
this.d_dialog = false;
this.v_dialog = false;
this.department.dialog = false;
this.version.dialog = false;
// 2. Ask for department change
// 2. Do nothing if no change
if( this.department.current == id )
return;
// 3. Ask for department change
api.call(`PUT department/${id}`, {}, function(rs){
// error -> do nothing
if( rs.error !== 0 || rs.success !== true )
// 1. error -> do nothing
if( rs.error !== 0 || rs.switched !== true )
return;
// 3. Update GUI
this.dep_id = id;
// 2. Update GUI
this.department.current = id;
// 4. Reload page if needed
// 3. Reload page if needed
setTimeout(() => { document.location = ''; }, 200);
}.bind(this));
@ -107,15 +141,366 @@ export default {
v_switch(id){
// 1. De-activate dialogs
this.d_dialog = false;
this.v_dialog = false;
this.department.dialog = false;
this.version.dialog = false;
// 2. Ask for version change
// TODO: api.call()
// 2. Do nothing if no change
if( this.version.current == id )
return;
// 3. Ask for department change
api.call(`GET department/version/switch/${id}`, {}, function(rs){
// 1. error -> do nothing
if( rs.error !== 0 )
return;
// 2. Update GUI
this.version.current = id;
// 3. Reload page if needed
setTimeout(() => { document.location = ''; }, 200);
}.bind(this));
},
/* (5) Create a new empty department
---------------------------------------------------------*/
d_create(){
// 1. De-activate dialogs
this.department.dialog = false;
this.version.dialog = false;
// get current department
var cur = this.get_dcurrent();
if( cur.id < 0 || this.department.newLabel.length < 1 ){
this.department.create = false;
return;
}
var newlabel = this.department.newLabel;
// 2. Popup confirm
(new Promise( (resolve, reject) => {
popup.ask({
title: 'Confirmation de création de département',
content: `Le nouveau département <b>${newlabel}</b> va être créé; il ne contiendra aucune donnée, il permet de gérer plusieurs départements ne partageant pas les mêmes UEs, enseignants, formations, etc<br><br>Voulez-vous créer un nouveau département vide ?`,
action: 'Créer',
type: 'valid'
}, (popup_rs) => { popup_rs && resolve() });
// 3. On popup confirm
})).then( () => {
// Call API to create a new department
api.call(`POST department/`, {name:newlabel}, function(rs){
// 1. error -> popup
if( rs.error !== 0 || !rs.hasOwnProperty('created_id') ){
return popup.ask({
title: 'Erreur ('+rs.error+')',
content: 'La création de département a échoué.',
action: 'OK',
type: 'neutral'
}, () => {});
}
// 3. Update GUI
this.department.list.push( { id: parseInt(rs.created_id), name: newlabel } );
}.bind(this));
});
},
/* (5) Create a new version from now
---------------------------------------------------------*/
v_create(){
// 1. De-activate dialogs
this.department.dialog = false;
this.version.dialog = false;
// 2. Popup confirm
(new Promise( (resolve, reject) => {
popup.ask({
title: 'Confirmation de création de version',
content: `Une sauvegarde (ou version) va être crée à partir de l'état actuel des données de tout le département<br><br>Voulez-vous créer cette sauvegarde ?`,
action: 'Créer',
type: 'valid'
}, (popup_rs) => { popup_rs && resolve() });
// 3. On popup confirm
})).then( () => {
let newVersionName = `${this.get_vcurrent().name}*`;
// Call API to create a new version
api.call(`POST department/version/`, {label:newVersionName}, function(rs){
// 1. error -> popup
if( rs.error !== 0 || !rs.hasOwnProperty('created_id') ){
return popup.ask({
title: 'Erreur ('+rs.error+')',
content: 'La création de version à échoué.',
action: 'OK',
type: 'neutral'
}, () => {});
}
// 3. Update GUI
this.version.list.push( { id: parseInt(rs.created_id), name: newVersionName } );
}.bind(this));
});
},
/* (6) Rename a version
---------------------------------------------------------*/
v_edit(){
// get current version
var cur = this.get_vcurrent();
if( cur.id < 0 || this.version.newName.length < 1 ){
this.version.edit = false;
return;
}
var newname = this.version.newName;
// 2. Popup confirm
(new Promise( (resolve, reject) => {
popup.ask({
title: 'Confirmation de modification de version',
content: `La version <b>${cur.name}</b> va être renommée en <b>${newname}</b><br><br>Voulez-vous valider cette modification ?`,
action: 'Valider',
type: 'search'
}, (popup_rs) => { popup_rs && resolve() });
// 3. On popup confirm
})).then( () => {
// Call API to create a new version
api.call(`PUT department/version/${cur.id}`, {label:newname}, function(rs){
// 1. error -> popup
if( rs.error !== 0 || !rs.hasOwnProperty('updated') ){
return popup.ask({
title: 'Erreur ('+rs.error+')',
content: 'La modification a échoué.',
action: 'OK',
type: 'neutral'
}, () => {});
}
// 3. Update GUI
cur.name = newname;
}.bind(this));
}).finally( () => {
this.version.edit = false;
})
},
/* (7) Remove a department
---------------------------------------------------------*/
d_remove(){
// get current department
var cur = this.get_dcurrent();
if( cur.id < 0 )
return;
// if last department -> forbid
if( this.department.list.length < 2 ){
return popup.ask({
title: 'Dernier départment',
content: `Le département <b>${cur.label}</b> ne peut être supprimé car il est le dernier disponible`,
action: 'OK',
type: 'invalid'
});
}
// 2. Popup confirm
(new Promise( (resolve, reject) => {
popup.ask({
title: 'Confirmation de suppression',
content: `Le département <b>${cur.label}</b> va être supprimé. Toutes les données seront perdues de manière définitive</b><br><br>Voulez-vous supprimer ce département ?`,
action: 'Supprimer',
type: 'invalid'
}, (popup_rs) => { popup_rs && resolve() });
// 3. On popup confirm
})).then( () => {
// Call API to delete the current department
api.call(`DELETE department/${cur.id}`, {}, function(rs){
// 1. error -> popup
if( rs.error !== 0 || !rs.hasOwnProperty('deleted') ){
return popup.ask({
title: 'Erreur ('+rs.error+')',
content: 'La suppression a échoué.',
action: 'OK',
type: 'neutral'
}, () => {});
}
// 3. Reload page
document.location = '';
}.bind(this));
});
},
/* (7) Remove a version
---------------------------------------------------------*/
v_remove(){
// get current version
var cur = this.get_vcurrent();
if( cur.id < 0 )
return;
// if last version -> forbid
if( this.version.list.length < 2 ){
return popup.ask({
title: 'Dernière version',
content: `La version <b>${cur.name}</b> ne peut être supprimée car il ne reste aucune autre version pour ce département`,
action: 'OK',
type: 'invalid'
});
}
// 2. Popup confirm
(new Promise( (resolve, reject) => {
popup.ask({
title: 'Confirmation de suppression',
content: `La version <b>${cur.name}</b> va être supprimée. Toutes les données seront perdues de manière définitive</b><br><br>Voulez-vous supprimer cette version ?`,
action: 'Supprimer',
type: 'invalid'
}, (popup_rs) => { popup_rs && resolve() });
// 3. On popup confirm
})).then( () => {
// Call API to create a new version
api.call(`DELETE department/version/${cur.id}`, {}, function(rs){
// 1. error -> popup
if( rs.error !== 0 || !rs.hasOwnProperty('deleted') ){
return popup.ask({
title: 'Erreur ('+rs.error+')',
content: 'La suppression a échoué.',
action: 'OK',
type: 'neutral'
}, () => {});
}
// 3. Reload page
document.location = '';
}.bind(this));
});
},
/* (x) Exports all data about this department's version
---------------------------------------------------------*/
global_export(){
api.call(`GET department/export`, {}, function(rs){
// 1. error -> popup
if( rs.error !== 0 || !rs.hasOwnProperty('link') ){
return popup.ask({
title: 'Erreur ('+rs.error+')',
content: 'L\'export a échoué.',
action: 'OK',
type: 'neutral'
}, () => {});
}
// 2. Launch download
document.location = rs.link;
}.bind(this));
// 3. Update GUI
this.ver_id = id;
}
},
beforeMount(){
/* (1) Try to fetch versions from API */
api.call('GET department/version', {}, function(rs){
// 1. Manage error
if( rs.error !== 0 || !rs.hasOwnProperty('versions') )
return;
// 2. Init version list
this.version.list = [];
// 3. Store versions
for( var ver of rs.versions ){
// if current version -> set @version.current
if( _SERVER.session.version.current === ver.iddatabase )
this.version.current = ver.iddatabase
// add version to list
this.version.list.push( { id: ver.iddatabase, name: ver.label, new_name: ver.label } );
}
this.version
}.bind(this) );
/* (2) Set onblur to hide department lists */
window.onblur.link('header.department', (e) => {
// only hide not [data-unblur-department] elements
if( e.target.getAttribute('data-unblur-department') === null )
this.department.dialog = false;
// only hide not [data-unblur-version] elements
if( e.target.getAttribute('data-unblur-version') === null )
this.version.dialog = false;
});
}
}
</script>

View File

@ -6,7 +6,7 @@
<div class='icon' @mouseover='gstore.icon.start' @mouseout='gstore.icon.stop'></div>
<div class='title'><b>P</b><i>lateforme</i> <b>A</b><i>ssistée de</i> <b>T</b><i>raitement</i> <b>A</b><i>dministratif des</i> <b>T</b><i>aches d'</i><b>E</b><i>nseignement</i></div>
<button :class='gstore.login_class' @click='gstore.popup_click'>Me connecter</button>
<span :class='gstore.login_class'>{{ gstore.login_error_text }}</span>
<span :class='gstore.login_class' v-html='gstore.login_error_text'></span>
</div>