false)) as $user) $users[$user->login] = $user; $response['rows'] = array(); foreach(IssueReport::staticQuery($query,$data) as $row){ //$row = $issueReport->toArray(true); $row['state'] = IssueReport::states($row['state']); $row['relativefrom'] = str_replace(ROOT_URL,'',$row['from']); $row['date'] = date('d-m-Y',$row['created']); $row['hour'] = date('H:i',$row['created']); $row['osIcon'] = 'fas fa-question-circle'; $row['browserIcon'] = 'fas fa-question-circle'; $row['comments'] = $row['comments']==1 ? false : $row['comments']-1; $row['excerpt'] = isset($row['comment']) ? truncate(strip_tags($row['comment']),150) : 'Aucun commentaire'; $row['excerpt'] = preg_replace('/https?:\/\/[^\s]*\?module=issue&page=sheet\.report&id=([0-9]*)/is','#$1',$row['excerpt']); $row['belongUser'] = $myUser->login == $row['creator'] || $myUser->can('issue','configure'); $row['fullName'] = isset($users[$row['creator']]) ? $users[$row['creator']]->fullName() : $row['creator']; if(strlen($row['os'])>=3 && substr(strtolower($row['os']),0,3) == 'win') $row['osIcon'] = 'fab fa-windows text-primary'; if(strlen($row['os'])>=5 && substr(strtolower($row['os']),0,5) == 'linux') $row['osIcon'] = 'fab fa-linux text-danger'; if(strlen($row['os'])>=3 && substr(strtolower($row['os']),0,3) == 'mac') $row['osIcon'] = 'fab fa-apple text-secondary'; switch($row['browser']){ case 'firefox': $row['browserIcon'] = 'fab fa-firefox text-warning'; break; case 'ie': $row['browserIcon'] = 'fab fa-internet-explorer text-danger'; break; case 'edge': $row['browserIcon'] = 'fab fa-edge text-primary'; break; case 'chrome': $row['browserIcon'] = 'fab fa-chrome text-success'; break; } $row['tags'] = array(); foreach(IssueReportTag::loadAll(array('report'=>$row['id'])) as $tag){ if(!isset($allTags[$tag->tag])) continue; $row['tags'][] = $allTags[$tag->tag]; } $response['rows'][] = $row; } }); Action::register('issue_autocomplete',function(&$response){ global $myUser,$_; if(!$myUser->connected()) throw new Exception("Vous devez être connecté",401); require_once(__DIR__.SLASH.'IssueReport.class.php'); require_once(__DIR__.SLASH.'IssueEvent.class.php'); $query = IssueReport::staticQuery('SELECT ir.id as id,ie.content as content FROM {{table}} ir LEFT JOIN '.IssueEvent::tableName().' ie ON ie.id=( SELECT id FROM '.IssueEvent::tableName().' ie2 WHERE ir.id=ie2.issue AND ie2.type=? ORDER BY id LIMIT 1) WHERE ie.content LIKE ?',array("comment",'%'.$_['keyword'].'%')); $response['rows'] = array(); foreach ($query->fetchAll() as $row) { $response['rows'][] = array( 'id' => $row['id'], 'name' => $row['id'].' - '.strip_tags($row['content']), 'slug' => $row['id'] ); } }); //Gestion avatar drag & drop Action::register('issue_attachments',function(&$response){ File::handle_component(array( 'namespace' => 'issue', //stockés dans file/summary/*.* 'access' => 'issue', // crud sur summary, 'size' => '1000000000', // taille max 'storage' => 'issue/attachments/{{data.id}}/*' //chemin complet vers le fichier stocké ),$response); }); Action::register('issue_add_document',function(&$response){ global $myUser,$_; if(!$myUser->connected()) throw new Exception("Vous devez être connecté",401); require_once(__DIR__.SLASH.'IssueReport.class.php'); require_once(__DIR__.SLASH.'IssueEvent.class.php'); $report = IssueReport::provide(); $report->save(); foreach ($_['files'] as $file) { $name = (get_OS() === 'WIN') ? utf8_decode($file['name']) : $file['name']; $row = File::move(File::temp().$file['path'],'issue'.SLASH.'screens'.SLASH.$report->id.SLASH.$name); $row['url'] = 'action.php?action=issue_download_document&event='.$event->id.'&path='.base64_encode($file['name']); $row['oldPath'] = $file['path']; $response['files'][] = $row; } $response['id'] = $report->id; }); //Téléchargement des documents Action::register('issue_download_document',function(&$response){ global $myUser,$_; if(!$myUser->connected()) throw new Exception("Vous devez être connecté",401); require_once(__DIR__.SLASH.'IssueEvent.class.php'); $event = IssueEvent::getById($_['event']); $path = str_replace(array('..','/','\\'),'',base64_decode($_['path'])); $path = $event->dir().SLASH.$path; File::downloadFile($path); }); Action::register('issue_issuereport_meta_save',function(&$response){ global $myUser,$_,$conf; User::check_access('issue','edit'); require_once(__DIR__.SLASH.'IssueReport.class.php'); require_once(__DIR__.SLASH.'IssueEvent.class.php'); require_once(__DIR__.SLASH.'IssueReportTag.class.php'); if(!array_key_exists($_['state'], IssueReport::states())) throw new Exception("L'état du ticket est invalide"); $item = IssueReport::provide(); $oldItem = clone $item; if($item->creator != $myUser->login && !$myUser->can('issue','configure')) throw new Exception("Permissions insuffisantes", 403); //Maj des tags if(empty($_['tags'])) throw new Exception("Le ticket doit avoir au moins un tag"); $existingTags = IssueReportTag::loadAll(array('report'=>$item->id)); $tags = explode(',',$_['tags']); $tagsAction = false; $similarTags = array(); foreach ($existingTags as $existingTag) { if(in_array($existingTag->tag, $tags)){ $similarTags[] = $existingTag->tag; continue; } IssueReportTag::deleteById($existingTag->id); $tagsAction = array('action'=>'deleted','tag'=>IssueReportTag::tags($existingTag->tag)); } foreach ($tags as $tag) { if(in_array($tag, $similarTags)) continue; $newTag = new IssueReportTag(); $newTag->tag = $tag; $newTag->report = $item->id; $newTag->save(); $tagsAction = array('action'=>'added','tag'=>IssueReportTag::tags($tag)); } if($tagsAction != false){ $event = new IssueEvent(); $event->type = IssueEvent::TYPE_TAG; $event->issue = $item->id; $event->content = json_encode($tagsAction); $event->save(); } if(!$myUser->can('issue','configure')) throw new Exception("Permissions insuffisantes", 403); $item->state = $_['state']; if(!empty($_['assign'])){ $item->assign = stripslashes($_['assign']); $item->addFollower($item->assign); } $item->addFollower($myUser->login); $item->save(); //Si l'assignation a changée on envoi une notification if($oldItem->assign != $item->assign){ $event = new IssueEvent(); $event->type = IssueEvent::TYPE_ASSIGNATION; $event->issue = $item->id; $event->content = json_encode(array( 'assigned' => $item->assign )); $event->save(); if($item->assign != $myUser->login){ Plugin::callHook("emit_notification", array(array( 'label' => '['.PROGRAM_NAME.' ] Le Ticket #'.$item->id.' vous a été assigné', 'html' => "Le ticket #".$item->id." vous a été assigné par ".(!empty($myUser->fullName())?$myUser->fullName():$myUser->login)." le ".date('d-m-Y à H:i').".
Bonne chance :).", 'type' => "issue", 'meta' => array('link' => ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$item->id), 'recipients' => array($item->assign) ))); Plugin::callHook("emit_notification", array(array( 'label' => '['.PROGRAM_NAME.'] Le Ticket #'.$item->id.' a été assigné à '.User::byLogin($item->assign)->fullName(), 'html' => "Le ticket #".$item->id." a été assigné à ".User::byLogin($item->assign)->fullName()." par ".(!empty($myUser->fullName())?$myUser->fullName():$myUser->login)." le ".date('d-m-Y à H:i').".", 'type' => "issue", 'meta' => array('link' => ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$item->id), 'recipients' => array_diff( $item->followers(), array($myUser->login), array($item->assign) ) ))); } } //Si l'etat a changé on envois une notification if($oldItem->state != $item->state){ $infos = array( 'meta' => array('link' => ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$item->id), 'recipients' => array_diff( $item->followers(), array($myUser->login) ) , 'type' => "notice" ); switch ($item->state) { case 'closed': $infos['label'] = "Le Ticket #".$item->id." est résolu"; $infos['html'] = "Le ticket d'erreur #".$item->id." a été marqué comme résolu par ".(!empty($myUser->fullName())?$myUser->fullName():$myUser->login).' le '.date('d-m-Y à H:i').'.
Il est possible que la résolution de cette erreur ne soit mise en production que dans quelques jours.'; break; case 'open': $infos['label'] = "Le Ticket #".$item->id." a été ré-ouvert"; $infos['html'] = "Le ticket d'erreur #".$item->id." a été ré-ouvert par ".(!empty($myUser->fullName())?$myUser->fullName():$myUser->login).' le '.date('d-m-Y à H:i').'.
Il est possible que la résolution de cette erreur ne soit mise en production que dans quelques jours.'; break; case 'canceled': $infos['label'] = "Le Ticket #".$item->id." a été annulé"; $infos['html'] = "Le ticket d'erreur #".$item->id." a été annulé ".(!empty($myUser->fullName())?$myUser->fullName():$myUser->login).' le '.date('d-m-Y à H:i').'.
Il est possible que ce ticket soit un doublon ou que son contenu soit innaproprié.'; break; default: # code... break; } $event = new IssueEvent(); $event->type = IssueEvent::TYPE_STATE; $event->issue = $item->id; $event->content = json_encode(array( 'old' => $oldItem->state, 'new' => $item->state )); $event->save(); Plugin::callHook("emit_notification", array($infos)); } }); //Ajout ou modification d'élément issuereport Action::register('issue_issuereport_save',function(&$response){ global $myUser,$_,$conf; require_once(__DIR__.SLASH.'IssueReport.class.php'); require_once(__DIR__.SLASH.'IssueEvent.class.php'); require_once(__DIR__.SLASH.'IssueReportTag.class.php'); if(!$myUser->connected()) throw new Exception("Vous devez être connecté pour laisser un rapport d'erreur",401); $item = IssueReport::provide(); if(!$myUser->can('issue','edit') && is_numeric($item->id) && $item->id!=0) throw new Exception("Permissions insuffisantes",403); $tags = array_filter(explode(',',$_['tags'])); if(count($tags)==0) throw new Exception("Merci de sélectionner au moins une catégorie"); if(!isset($_['issue-comment']) || empty($_['issue-comment'])) throw new Exception("Merci de commenter votre ticket"); $item->browser = $_['browser']; $item->browserVersion = $_['browserVersion']; $item->online = $_['online']; $item->os = $_['os']; $item->state = 'open'; $item->from = $_['from']; $item->width = $_['width']; $item->height = $_['height']; $item->history = $_['history']; $item->ip = ip(); $item->addFollower($myUser->login); $item->save(); $firstEvent = new IssueEvent(); $firstEvent->created = $item->created; $firstEvent->updated = $item->updated; $firstEvent->creator = $item->creator; $firstEvent->updater = $item->updater; $firstEvent->type = IssueEvent::TYPE_COMMENT; $firstEvent->content = preg_replace('//', '', wysiwyg_filter(html_entity_decode($_['issue-comment']))); $firstEvent->issue = $item->id; $firstEvent->save(); if (preg_match_all('//', wysiwyg_filter(html_entity_decode($_['issue-comment'])), $matches)) { foreach ($matches[0] as $index => $imgTag) { if(preg_match('/src="data:image.*base64,.*"/', $imgTag, $imgSrc)) { list($type, $data) = explode(';', $imgSrc[0]); $ext = explode('/', $type)[1]; $dataEncode = explode(',', $data); $stream = base64_decode($dataEncode[1]); $attachmentFolder = $firstEvent->dir(); if(!file_exists($attachmentFolder)) mkdir($attachmentFolder,0755,true); $screenPath = $firstEvent->dir().SLASH.'image'.$index.'.'.$ext; file_put_contents($screenPath, $stream); } } } $response['item'] = $item->toArray(); IssueReportTag::delete(array('report'=>$item->id)); foreach ($tags as $tag) { $reportTag = new IssueReportTag(); $reportTag->tag = $tag; $reportTag->report = $item->id; $reportTag->save(); } if(!empty($_['screenshot'])){ $screenshot = str_replace('data:image/png;base64,', '', $_['screenshot']); $screenshot = str_replace(' ', '+', $screenshot); $stream = base64_decode($screenshot); $attachmentFolder = $firstEvent->dir(); if(!file_exists($attachmentFolder)) mkdir($attachmentFolder,0755,true); $screenPath = $attachmentFolder.SLASH.'screenshot.jpg'; file_put_contents($screenPath, $stream); } //Ajout fichiers joints if(!empty($_['document'])) File::save_component('document', 'issue/attachments/'.$firstEvent->id.'/{{label}}'); $data = $item->toArray(); $data['comment'] = ''.$item->creator.': '.$firstEvent->content; $data['url'] = ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$data['id']; $data['title'] = 'Le Ticket #'.$item->id.' a été ouvert par '.$item->creator; if(!empty($conf->get('issue_report_mails'))){ $path = __DIR__.SLASH.'mail.template.php'; if(!file_exists($path)) return; $stream = file_get_contents($path); $recipients = array(); foreach (explode(',', $conf->get('issue_report_mails')) as $recipient) { if(is_numeric($recipient)){ foreach(UserFirmRank::loadAll(array('rank'=>$recipient)) as $ufr) $recipients[] = $ufr->user; continue; } $recipients[] = $recipient; } // Émission d'une notification pour les devs Plugin::callHook("emit_notification", array(array( 'label' => 'Ticket #'.$item->id.' ouvert', 'html' => template($stream,$data), 'type' => "issue", 'meta' => array('link' => $data['url']), 'recipients' => $recipients ))); } // Émission d'une notification pour l'auteur du ticket Plugin::callHook("emit_notification", array(array( 'label' => "Votre Ticket #".$item->id." a été envoyé", 'html' => "Le ticket #".$item->id." a été créé et sera pris en compte par nos techniciens, vous pouvez consulter son avancement en cliquant ici", 'type' => "notice", 'meta' => array('link' => $data['url']), 'recipients' => array($item->creator) ))); Log::put('Déclaration d\'un rapport d\'erreur #'.$item->toText(), 'Issue'); }); Action::register('issue_screenshot_download',function(&$response){ global $myUser,$_; if(!$myUser->connected()) throw new Exception("Connexion requise",401); try { require_once(__DIR__.SLASH.'IssueEvent.class.php'); $event = IssueEvent::getById($_['id']); File::downloadFile($event->dir().SLASH.'screenshot.jpg'); } catch(Exception $e) { File::downloadFile(__ROOT__.'img/default-image-muted.png'); } }); //Suppression d'élement issuereport Action::register('issue_issuereport_delete',function(&$response){ global $myUser,$_; require_once(__DIR__.SLASH.'IssueReport.class.php'); require_once(__DIR__.SLASH.'IssueEvent.class.php'); $issue = IssueReport::provide(); if($issue->creator != $myUser->login && !$myUser->can('issue','configure')) throw new Exception("Permissions insuffisantes", 403); $issue->remove(); }); //Etre notifié du sujet Action::register('issue_follow_toggle',function(&$response){ global $myUser,$_; require_once(__DIR__.SLASH.'IssueReport.class.php'); require_once(__DIR__.SLASH.'IssueEvent.class.php'); User::check_access('issue','read'); $issue = IssueReport::provide(); $followers = $issue->followers(); $key = array_search($myUser->login, $followers); if($_['state']){ if($key===false) $followers[] = $myUser->login; }else{ if($issue->creator == $myUser->login) throw new Exception("Vous ne pouvez pas arrêter de suivre un ticket que vous avez créé."); if($key!==false) unset($followers[$key]); } $issue->followers($followers); $issue->save(); }); //Sauvegarde des configurations de issue Action::register('issue_setting_save',function(&$response){ global $myUser,$_,$conf; User::check_access('issue','configure'); foreach(Configuration::setting('issue') as $key=>$value){ if(!is_array($value)) continue; $allowed[] = $key; } foreach ($_['fields'] as $key => $value) if(in_array($key, $allowed)) $conf->put($key,$value); }); /** EVENT **/ //Récuperation d'une liste de issueevent Action::register('issue_issue_event_search',function(&$response){ global $myUser,$_; User::check_access('issue','read'); require_once(__DIR__.SLASH.'IssueEvent.class.php'); require_once(__DIR__.SLASH.'IssueReport.class.php'); $events = IssueEvent::loadAll(array('issue'=>$_['issue']),array('id ASC')); foreach($events as $i=>$event){ $row = $event->toArray(); $creator = User::byLogin($event->creator); $row['edit_rights'] = ($myUser->login == $event->creator || $myUser->superadmin); $row['creator'] = $creator->toArray(); $row['avatar'] = $creator->getAvatar(); $row['fullName'] = $creator->fullName(); $row['created'] = date('d/m/Y H:i', $event->created); $row['createdRelative'] = relative_time($event->created, null, 6, true); switch($event->type){ case 'comment': $row['classes'] = $i == 0 ? 'issue-first-comment' : ''; $row['files'] = array(); foreach (glob($event->dir().SLASH.'*') as $key => $value){ $file = array(); $file['extension'] = getExt($value); $file['label'] = mt_basename($value); $file['labelExcerpt'] = truncate($file['label'],35); $file['type'] = in_array($file['extension'], array('jpg','png','jpeg','bmp','gif','svg')) ? 'image' : 'file'; $file['url'] = 'action.php?action=issue_download_document&event='.$event->id.'&path='.base64_encode(basename($value)); $file['icon'] = getExtIcon($file['extension']); $row['files'][] = $file; } if($creator->login == $myUser->login || $myUser->can('issue','configure')) $row['classes'] .=' editable'; $row['comment'] = empty($event->content) ? 'Pas de commentaire': $event->content; $row['comment'] = preg_replace('/https?:\/\/[^\s]*\?module=issue&page=sheet\.report&id=([0-9]*)/is','#$1',$row['comment']); $row['hasfiles'] = count($row['files']) > 0 ? true : false; break; case IssueEvent::TYPE_STATE: $infos = json_decode($row['content'],true); $row['oldstate'] = IssueReport::states($infos['old']); $row['state'] = IssueReport::states($infos['new']); break; case IssueEvent::TYPE_TAG: $infos = json_decode($row['content'],true); $row['action'] = $infos['action'] == 'added' ? 'ajouté' :'supprimé'; $row['tag'] = $infos['tag']; break; case IssueEvent::TYPE_ASSIGNATION: $infos = json_decode($row['content'],true); $assigned = User::byLogin($infos['assigned']); $row['assigned'] = array( 'fullName' => $assigned->fullName(), 'avatar' => $assigned->getAvatar() ); break; } $response['rows'][] = $row; } }); //Ajout ou modification d'élément issueevent Action::register('issue_issue_event_save',function(&$response){ global $myUser,$_; require_once(__DIR__.SLASH.'IssueReport.class.php'); require_once(__DIR__.SLASH.'IssueEvent.class.php'); $item = IssueEvent::provide('id',1); $isNew = empty($item->id); if($myUser->login != $item->creator && !$myUser->can('issue','edit')) throw new Exception("Permissions insuffisantes", 403); if(!isset($_['content']) || empty($_['content'])) throw new Exception("Vous devez spécifier un commentaire"); $item->type = IssueEvent::TYPE_COMMENT; $item->content = wysiwyg_filter(html_entity_decode($_['content'])); $item->issue = $_['issue']; $mentionned = array(); if($item->id == 0){ preg_match_all('|data-mention-value="([^"]*)"|isU', $item->content, $matches,PREG_SET_ORDER); foreach ($matches as $match) { $mentionned[] = $match[1]; } } $item->save(); if(!empty($_['event-files'])) File::save_component('event-files', 'issue/attachments/'.$item->id.'/{{label}}'); $issue = IssueReport::getById($item->issue); $issue->addFollower($myUser->login); $issue->save(); $path = __DIR__.SLASH.'mail.template.php'; if(!file_exists($path)) return; $stream = file_get_contents($path); $data = $item->toArray(); $data['comment'] = ''.$item->creator.': '.$item->content; $data['url'] = ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$data['issue']; $data['title'] = 'Le Ticket #'.$item->issue.' a été commenté par '.$item->creator; $recipients = array_diff($issue->followers(), array($myUser->login)); // Émission d'une notification pour les acteurs du rapport Plugin::callHook("emit_notification", array(array( 'label' => 'Ticket #'.$item->issue.': '.($isNew?'Nouveau commentaire':'Édition d\'un commentaire') , 'html' => template($stream,$data), 'type' => "issue", 'meta' => array('link' => $data['url']), 'recipients' => $recipients ))); // Émission d'une notification pour les acteurs spécifiquement mentionnés if(count($mentionned)){ $data['comment'] = 'Vous avez été mentionné dans le commentaire suivant :
'.$data['comment']; $data['url'] = ROOT_URL.'/index.php?module=issue&page=sheet.report&id='.$data['issue'].'#issue-comment-'.$item->id; $data['title'] = 'Vous avez été mentionné par '.$item->creator.' dans le ticket #'.$item->issue.''; Plugin::callHook("emit_notification", array(array( 'label' => 'Vous avez été mentionné par '.$item->creator.' dans le ticket #'.$item->issue.':' , 'html' => template($stream,$data), 'type' => "issue", 'meta' => array('link' => $data['url']), 'recipients' => $mentionned ))); } }); //Récuperation ou edition d'élément issueevent Action::register('issue_issue_event_edit',function(&$response){ global $myUser,$_; User::check_access('issue','edit'); require_once(__DIR__.SLASH.'IssueEvent.class.php'); require_once(__DIR__.SLASH.'IssueReport.class.php'); $event = IssueEvent::getById($_['id'],1); if($event->type != 'comment') throw new Exception("Vous ne pouvez pas éditer cet événement"); if($myUser->login != $event->creator && !$myUser->superadmin) throw new Exception("Permissions insuffisantes", 403); $response = $event->toArray(true); }); //Suppression d'élement issueevent Action::register('issue_issue_event_delete',function(&$response){ global $myUser,$_; require_once(__DIR__.SLASH.'IssueReport.class.php'); require_once(__DIR__.SLASH.'IssueEvent.class.php'); $event = IssueEvent::getById($_['id'],1); if($event->type != 'comment') throw new Exception("Vous ne pouvez pas supprimer cet événement"); $issue = $event->join('issue'); if($event->creator != $myUser->login && !$myUser->can('issue','configure')) throw new Exception("Permissions insuffisantes", 403); $event->remove(); if(IssueEvent::rowCount(array('issue'=>$issue->id)) == 0){ if($issue->creator != $myUser->login && !$myUser->can('issue','configure')) throw new Exception("Permissions insuffisantes", 403); $issue->remove(false); $response['redirect'] = 'index.php?module=issue&page=list.issue'; } }); ?>