action.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. <?php
  2. global $_,$conf;
  3. switch($_['action']){
  4. /** ISSUEREPORT **/
  5. //Récuperation d'une liste de issuereport
  6. case 'issue_issuereport_search':
  7. Action::write(function(&$response){
  8. global $myUser,$_;
  9. User::check_access('issue','read');
  10. require_once(__DIR__.SLASH.'IssueReport.class.php');
  11. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  12. require_once(__DIR__.SLASH.'IssueReportTag.class.php');
  13. $allTags = IssueReportTag::tags();
  14. $query = 'SELECT DISTINCT {{table}}.id, {{table}}.*,(SELECT ie.content FROM '.IssueEvent::tableName().' ie WHERE ie.type=? AND ie.issue={{table}}.id ORDER BY id ASC LIMIT 1) as comment,(SELECT COUNT(ie2.id) FROM '.IssueEvent::tableName().' ie2 WHERE ie2.type=? AND ie2.issue={{table}}.id) as comments FROM {{table}} LEFT JOIN '.IssueReportTag::tableName().' t ON t.report={{table}}.id WHERE 1';
  15. $data = array(IssueEvent::TYPE_COMMENT,IssueEvent::TYPE_COMMENT);
  16. //Recherche simple
  17. if(!empty($_['filters']['keyword'])){
  18. $query .= ' AND {{table}}.label LIKE ?';
  19. $data[] = '%'.$_['filters']['keyword'].'%';
  20. }
  21. $tags = array_filter(explode(',',$_['tags']));
  22. if(count($tags)!=0){
  23. $query .= ' AND t.tag IN ("'.implode('","',$tags).'")';
  24. }
  25. //Recherche avancée
  26. if(isset($_['filters']['advanced'])) filter_secure_query($_['filters']['advanced'],array('from','comment','{{table}}.creator','{{table}}.created','browser','ip','state'),$query,$data);
  27. $query .= ' ORDER BY {{table}}.id desc ';
  28. //Pagination
  29. $response['pagination'] = IssueReport::paginate(20,(!empty($_['page'])?$_['page']:0),$query,$data);
  30. foreach(IssueReport::staticQuery($query,$data) as $row){
  31. //$row = $issueReport->toArray(true);
  32. $row['state'] = IssueReport::states($row['state']);
  33. $row['relativefrom'] = str_replace(ROOT_URL,'',$row['from']);
  34. $row['date'] = date('d-m-Y',$row['created']);
  35. $row['hour'] = date('H:i',$row['created']);
  36. $row['osIcon'] = 'fas fa-question-circle';
  37. $row['browserIcon'] = 'fas fa-question-circle';
  38. $row['comments'] = $row['comments']==1 ? false: $row['comments'];
  39. $row['excerpt'] = isset($row['comment']) ? truncate(strip_tags($row['comment']),150) : 'Aucun commentaire';
  40. if(strlen($row['os'])>=3 && substr(strtolower($row['os']),0,3) == 'win') $row['osIcon'] = 'fab fa-windows text-primary';
  41. if(strlen($row['os'])>=5 && substr(strtolower($row['os']),0,5) == 'linux') $row['osIcon'] = 'fab fa-linux text-danger';
  42. if(strlen($row['os'])>=3 && substr(strtolower($row['os']),0,3) == 'mac') $row['osIcon'] = 'fab fa-apple text-secondary';
  43. switch($row['browser']){
  44. case 'firefox': $row['browserIcon'] = 'fab fa-firefox text-warning'; break;
  45. case 'ie': $row['browserIcon'] = 'fab fa-internet-explorer text-danger'; break;
  46. case 'edge': $row['browserIcon'] = 'fab fa-edge text-primary'; break;
  47. case 'chrome': $row['browserIcon'] = 'fab fa-chrome text-success'; break;
  48. }
  49. $row['tags'] = array();
  50. foreach(IssueReportTag::loadAll(array('report'=>$row['id'])) as $tag){
  51. if(!isset($allTags[$tag->tag])) continue;
  52. $row['tags'][] = $allTags[$tag->tag];
  53. }
  54. $response['rows'][] = $row;
  55. }
  56. });
  57. break;
  58. case 'issue_add_document':
  59. Action::write(function(&$response){
  60. global $myUser,$_;
  61. if(!$myUser->connected()) throw new Exception("Vous devez être connecté",401);
  62. require_once(__DIR__.SLASH.'IssueReport.class.php');
  63. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  64. $report = IssueReport::provide();
  65. $report->save();
  66. foreach ($_['files'] as $file) {
  67. $name = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? utf8_decode($file['name']) : $file['name'];
  68. $row = File::move(File::temp().$file['path'],'issue'.SLASH.'screens'.SLASH.$report->id.SLASH.$name);
  69. $row['url'] = 'action.php?action=issue_download_document&event='.$event->id.'&path='.base64_encode($file['name']);
  70. $row['oldPath'] = $file['path'];
  71. $response['files'][] = $row;
  72. }
  73. $response['id'] = $report->id;
  74. });
  75. break;
  76. //Téléchargement des documents
  77. case 'issue_download_document':
  78. global $myUser,$_;
  79. if(!$myUser->connected()) throw new Exception("Vous devez être connecté",401);
  80. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  81. $event = IssueEvent::getById($_['event']);
  82. $path = str_replace(array('..','/','\\'),'',base64_decode($_['path']));
  83. $path = $event->dir().SLASH.$path;
  84. File::downloadFile($path);
  85. break;
  86. case 'issue_issuereport_meta_save':
  87. Action::write(function(&$response){
  88. global $myUser,$_,$conf;
  89. require_once(__DIR__.SLASH.'IssueReport.class.php');
  90. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  91. require_once(__DIR__.SLASH.'IssueReportTag.class.php');
  92. if(!array_key_exists($_['state'], IssueReport::states())) throw new Exception("L'état du ticket est invalide");
  93. User::check_access('issue','edit');
  94. $item = IssueReport::provide();
  95. $oldItem = clone $item;
  96. $item->state = $_['state'];
  97. if(!empty($_['assign'])) $item->assign = $_['assign'];
  98. $item->save();
  99. //Maj des tags
  100. $existingTags = IssueReportTag::loadAll(array('report'=>$item->id));
  101. $tags = explode(',',$_['tags']);
  102. $tagsAction = false;
  103. $similarTags = array();
  104. foreach ($existingTags as $existingTag) {
  105. if(in_array($existingTag->tag, $tags)){
  106. $similarTags[] = $existingTag->tag;
  107. continue;
  108. }
  109. IssueReportTag::deleteById($existingTag->id);
  110. //var_dump($existingTag->tag);
  111. $tagsAction = array('action'=>'deleted','tag'=>IssueReportTag::tags($existingTag->tag));
  112. }
  113. foreach ($tags as $tag) {
  114. if(in_array($tag, $similarTags)) continue;
  115. $newTag = new IssueReportTag();
  116. $newTag->tag = $tag;
  117. $newTag->report = $item->id;
  118. $newTag->save();
  119. $tagsAction = array('action'=>'added','tag'=>IssueReportTag::tags($tag));
  120. }
  121. if($tagsAction!=false){
  122. $event = new IssueEvent();
  123. $event->type = IssueEvent::TYPE_TAG;
  124. $event->issue = $item->id;
  125. $event->content = json_encode($tagsAction);
  126. $event->save();
  127. }
  128. //Si l'assignation a changée on envoi une notification
  129. if($oldItem->assign != $item->assign){
  130. $event = new IssueEvent();
  131. $event->type = IssueEvent::TYPE_ASSIGNATION;
  132. $event->issue = $item->id;
  133. $event->content = json_encode(array(
  134. 'assigned' => $item->assign
  135. ));
  136. $event->save();
  137. if($item->assign != $myUser->login){
  138. Plugin::callHook("emit_notification", array(array(
  139. 'label' => '['.PROGRAM_NAME.' - '.PROGRAM_UID.'] Le Ticket #'.$item->id.' vous a été assigné',
  140. 'html' => "Le ticket <a href='".ROOT_URL."/index.php?module=issue&page=sheet.report&id=".$item->id."'>#".$item->id."</a> vous a été assigné par ".(!empty($myUser->fullName())?$myUser->fullName():$myUser->login)." le ".date('d-m-Y à H:i').".
  141. <br>Bonne chance :).",
  142. 'type' => "issue",
  143. 'meta' => array('link' => ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$item->id),
  144. 'recipients' => array($item->assign)
  145. )));
  146. }
  147. }
  148. //Si l'etat a changé on envois une notification
  149. if($oldItem->state != $item->state){
  150. switch ($item->state) {
  151. case 'closed':
  152. $infos = array(
  153. 'label' => "Votre Ticket #".$item->id." est résolu",
  154. 'html' => "Le ticket d'erreur <a href='".ROOT_URL."/index.php?module=issue&page=sheet.report&id=".$item->id."'>#".$item->id."</a> a été marqué comme résolu par ".(!empty($myUser->fullName())?$myUser->fullName():$myUser->login).' le '.date('d-m-Y à H:i').'.
  155. <br>Il est possible que la résolution de cette erreur ne soit mise en production que dans quelques jours.',
  156. 'type' => "notice",
  157. 'meta' => array('link' => ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$item->id),
  158. 'recipients' => array($item->creator)
  159. );
  160. break;
  161. case 'open':
  162. $infos = array(
  163. 'label' => "Votre Ticket #".$item->id." a été ré-ouvert",
  164. 'html' => "Le ticket d'erreur <a href='".ROOT_URL."/index.php?module=issue&page=sheet.report&id=".$item->id."'>#".$item->id."</a> a été ré-ouvert par ".(!empty($myUser->fullName())?$myUser->fullName():$myUser->login).' le '.date('d-m-Y à H:i').'.
  165. <br>Il est possible que la résolution de cette erreur ne soit mise en production que dans quelques jours.',
  166. 'type' => "notice",
  167. 'meta' => array('link' => ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$item->id),
  168. 'recipients' => array($item->creator)
  169. );
  170. break;
  171. case 'canceled':
  172. $infos = array(
  173. 'label' => "Votre Ticket #".$item->id." a été annulé",
  174. 'html' => "Le ticket d'erreur <a href='".ROOT_URL."/index.php?module=issue&page=sheet.report&id=".$item->id."'>#".$item->id."</a> a été annulé ".(!empty($myUser->fullName())?$myUser->fullName():$myUser->login).' le '.date('d-m-Y à H:i').'.
  175. <br>Il est possible que ce ticket soit un doublon ou que son contenu soit innaproprié.',
  176. 'type' => "notice",
  177. 'meta' => array('link' => ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$item->id),
  178. 'recipients' => array($item->creator)
  179. );
  180. break;
  181. default:
  182. # code...
  183. break;
  184. }
  185. $event = new IssueEvent();
  186. $event->type = IssueEvent::TYPE_STATE;
  187. $event->issue = $item->id;
  188. $event->content = json_encode(array(
  189. 'old' => $oldItem->state,
  190. 'new' => $item->state
  191. ));
  192. $event->save();
  193. Plugin::callHook("emit_notification", array($infos));
  194. }
  195. });
  196. break;
  197. //Ajout ou modification d'élément issuereport
  198. case 'issue_issuereport_save':
  199. Action::write(function(&$response){
  200. global $myUser,$_,$conf;
  201. require_once(__DIR__.SLASH.'IssueReport.class.php');
  202. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  203. require_once(__DIR__.SLASH.'IssueReportTag.class.php');
  204. if(!$myUser->connected()) throw new Exception("Vous devez être connecté pour laisser un rapport d'erreur",401);
  205. $item = IssueReport::provide();
  206. if(!$myUser->can('issue','edit') && is_numeric($item->id) && $item->id!=0) throw new Exception("Permissions insuffisantes",403);
  207. $tags = array_filter(explode(',',$_['tags']));
  208. if(count($tags)==0) throw new Exception("Merci de sélectionner au moins une catégorie");
  209. if(!isset($_['issue-comment']) || empty($_['issue-comment'])) throw new Exception("Merci de commenter votre ticket");
  210. $item->browser = $_['browser'];
  211. $item->browserVersion = $_['browserVersion'];
  212. $item->online = $_['online'];
  213. $item->os = $_['os'];
  214. $item->state = 'open';
  215. $item->from = $_['from'];
  216. $item->width = $_['width'];
  217. $item->height = $_['height'];
  218. $item->history = $_['history'];
  219. $item->ip = ip();
  220. $item->save();
  221. $firstEvent = new IssueEvent();
  222. $firstEvent->created = $item->created;
  223. $firstEvent->updated = $item->updated;
  224. $firstEvent->creator = $item->creator;
  225. $firstEvent->updater = $item->updater;
  226. $firstEvent->type = IssueEvent::TYPE_COMMENT;
  227. $firstEvent->content = $_['issue-comment'];
  228. $firstEvent->issue = $item->id;
  229. $firstEvent->save();
  230. $response['item'] = $item->toArray();
  231. IssueReportTag::delete(array('report'=>$item->id));
  232. foreach ($tags as $tag) {
  233. $reportTag = new IssueReportTag();
  234. $reportTag->tag = $tag;
  235. $reportTag->report = $item->id;
  236. $reportTag->save();
  237. }
  238. if(!empty($_['screenshot'])){
  239. $screenshot = str_replace('data:image/png;base64,', '', $_['screenshot']);
  240. $screenshot = str_replace(' ', '+', $screenshot);
  241. $stream = base64_decode($screenshot);
  242. $attachmentFolder = $firstEvent->dir();
  243. if(!file_exists($attachmentFolder)) mkdir($attachmentFolder,0755,true);
  244. $screenPath = $attachmentFolder.SLASH.'screenshot.jpg';
  245. file_put_contents($screenPath, $stream);
  246. }
  247. //Ajout des fichiers joints
  248. if(!empty($_['document_temporary'])){
  249. $files = json_decode($_['document_temporary'],true);
  250. $attachmentFolderRelative = $firstEvent->dir(true);
  251. $attachmentFolder = $firstEvent->dir();
  252. if(!file_exists($attachmentFolder)) mkdir($attachmentFolder,0755,true);
  253. foreach($files as $file){
  254. $from = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? File::temp().utf8_decode($file['path']) : File::temp().$file['path'];
  255. $to = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? utf8_decode($file['name']) : $file['name'];
  256. File::move($from, $attachmentFolderRelative.SLASH.$to);
  257. }
  258. }
  259. $data = $item->toArray();
  260. $data['comment'] = html_entity_decode($firstEvent->content);
  261. $data['url'] = ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$data['id'];
  262. if(!empty($conf->get('issue_report_mails'))){
  263. $path = __DIR__.SLASH.'mail.template.php';
  264. if(!file_exists($path)) return;
  265. $stream = file_get_contents($path);
  266. $recipients = array();
  267. foreach (explode(',', $conf->get('issue_report_mails')) as $recipient) {
  268. if(is_numeric($recipient)){
  269. foreach(UserFirmRank::loadAll(array('rank'=>$recipient)) as $ufr)
  270. $recipients[] = $ufr->user;
  271. continue;
  272. }
  273. $recipients[] = $recipient;
  274. }
  275. // Émission d'une notification pour les devs
  276. Plugin::callHook("emit_notification", array(array(
  277. 'label' => '['.PROGRAM_NAME.' - '.PROGRAM_UID.'] Ticket #'.$item->id.' ouvert',
  278. 'html' => template($stream,$data),
  279. 'type' => "issue",
  280. 'meta' => array('link' => $data['url']),
  281. 'recipients' => $recipients
  282. )));
  283. }
  284. // Émission d'une notification pour l'auteur du ticket
  285. Plugin::callHook("emit_notification", array(array(
  286. 'label' => "Votre Ticket #".$item->id." a été envoyé",
  287. 'html' => "Le ticket <a href='".ROOT_URL."/index.php?module=issue&page=sheet.report&id=".$item->id."'>#".$item->id."</a> a été créé et sera pris en compte par nos techniciens, vous pouvez consulter
  288. son avancement en <a href='".ROOT_URL."/index.php?module=issue&page=sheet.report&id=".$item->id."'>cliquant ici</a>",
  289. 'type' => "notice",
  290. 'meta' => array('link' => $data['url']),
  291. 'recipients' => array($item->creator)
  292. )));
  293. Log::put('Déclaration d\'un rapport d\'erreur #'.$item->toText(), 'Issue');
  294. });
  295. break;
  296. case 'issue_screenshot_download':
  297. global $myUser,$_;
  298. if(!$myUser->connected()) throw new Exception("Vous devez être connecté",401);
  299. try {
  300. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  301. $event = IssueEvent::getById($_['id']);
  302. File::downloadFile($event->dir().SLASH.'screenshot.jpg');
  303. } catch(Exception $e) {
  304. File::downloadFile(__ROOT__.'img/default-image.png');
  305. }
  306. break;
  307. //Suppression d'élement issuereport
  308. case 'issue_issuereport_delete':
  309. Action::write(function(&$response){
  310. global $myUser,$_;
  311. User::check_access('issue','configure');
  312. require_once(__DIR__.SLASH.'IssueReport.class.php');
  313. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  314. $issue = IssueReport::provide();
  315. $issue->remove();
  316. });
  317. break;
  318. //Sauvegarde des configurations de issue
  319. case 'issue_setting_save':
  320. Action::write(function(&$response){
  321. global $myUser,$_,$conf;
  322. User::check_access('issue','configure');
  323. foreach(Configuration::setting('issue') as $key=>$value){
  324. if(!is_array($value)) continue;
  325. $allowed[] = $key;
  326. }
  327. foreach ($_['fields'] as $key => $value)
  328. if(in_array($key, $allowed)) $conf->put($key,$value);
  329. });
  330. break;
  331. /** EVENT **/
  332. //Récuperation d'une liste de issueevent
  333. case 'issue_issue_event_search':
  334. Action::write(function(&$response){
  335. global $myUser,$_;
  336. User::check_access('issue','read');
  337. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  338. require_once(__DIR__.SLASH.'IssueReport.class.php');
  339. $events = IssueEvent::loadAll(array('issue'=>$_['issue']),array('id ASC'));
  340. foreach($events as $i=>$event){
  341. $row = $event->toArray();
  342. $creator = User::byLogin($event->creator);
  343. $row['creator'] = $creator->toArray();
  344. $row['avatar'] = $creator->getAvatar();
  345. $row['fullName'] = $creator->fullName();
  346. $row['createdRelative'] = relative_time($event->created);
  347. switch($event->type){
  348. case 'comment':
  349. $row['classes'] = $i == 0 ? 'issue-first-comment' : '';
  350. $row['files'] = array();
  351. foreach (glob($event->dir().SLASH.'*') as $key => $value){
  352. $file = array();
  353. $file['extension'] = getExt($value);
  354. $file['label'] = mt_basename($value);
  355. $file['labelExcerpt'] = truncate($file['label'],35);
  356. $file['type'] = in_array($file['extension'], array('jpg','png','jpeg','bmp','gif','svg')) ? 'image' : 'file';
  357. $file['url'] = 'action.php?action=issue_download_document&event='.$event->id.'&path='.base64_encode(basename($value));
  358. $file['icon'] = getExtIcon($file['extension']);
  359. $row['files'][] = $file;
  360. }
  361. if($creator->login == $myUser->login || $myUser->can('issue','configure')) $row['classes'] .=' editable';
  362. $row['comment'] = empty($event->content) ? 'Pas de commentaires': html_entity_decode($event->content);
  363. $row['hasfiles'] = count($row['files']) > 0 ? true : false;
  364. break;
  365. case IssueEvent::TYPE_STATE:
  366. $infos = json_decode($row['content'],true);
  367. $row['oldstate'] = IssueReport::states($infos['old']);
  368. $row['state'] = IssueReport::states($infos['new']);
  369. break;
  370. case IssueEvent::TYPE_TAG:
  371. $infos = json_decode($row['content'],true);
  372. $row['action'] = $infos['action'] == 'added' ? 'ajouté' :'supprimé';
  373. $row['tag'] = $infos['tag'];
  374. break;
  375. case IssueEvent::TYPE_ASSIGNATION:
  376. $infos = json_decode($row['content'],true);
  377. $assigned = User::byLogin($infos['assigned']);
  378. $row['assigned'] = array(
  379. 'fullName' => $assigned->fullName(),
  380. 'avatar' => $assigned->getAvatar()
  381. );
  382. break;
  383. }
  384. $response['rows'][] = $row;
  385. }
  386. });
  387. break;
  388. //Ajout ou modification d'élément issueevent
  389. case 'issue_issue_event_save':
  390. Action::write(function(&$response){
  391. global $myUser,$_;
  392. User::check_access('issue','edit');
  393. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  394. $item = IssueEvent::provide();
  395. $item->type = IssueEvent::TYPE_COMMENT;
  396. $item->content = $_['content'];
  397. $item->issue = $_['issue'];
  398. $item->save();
  399. });
  400. break;
  401. //Récuperation ou edition d'élément issueevent
  402. case 'issue_issue_event_edit':
  403. Action::write(function(&$response){
  404. global $myUser,$_;
  405. User::check_access('issue','edit');
  406. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  407. require_once(__DIR__.SLASH.'IssueReport.class.php');
  408. $response = IssueEvent::getById($_['id'],1);
  409. });
  410. break;
  411. //Suppression d'élement issueevent
  412. case 'issue_issue_event_delete':
  413. Action::write(function(&$response){
  414. global $myUser,$_;
  415. User::check_access('issue','delete');
  416. require_once(__DIR__.SLASH.'IssueReport.class.php');
  417. require_once(__DIR__.SLASH.'IssueEvent.class.php');
  418. $event = IssueEvent::getById($_['id'],1);
  419. $issue = $event->join('issue');
  420. if($issue->creator != $myUser->login && !$myUser->can('issue','configure')) throw new Exception("Permission refusée");
  421. $event->remove();
  422. if(IssueEvent::rowCount(array('issue'=>$issue->id)) == 0){
  423. $issue->remove(false);
  424. $response['redirect'] = 'setting.php?section=global.report';
  425. }
  426. });
  427. break;
  428. }
  429. ?>