login;
}
$tags = array_filter(explode(',',$_['tags']));
if(count($tags)!=0){
$query .= ' AND t.tag IN ("'.implode('","',$tags).'")';
}
//Recherche avancée
if(isset($_['filters']['advanced'])) filter_secure_query($_['filters']['advanced'],array('{{table}}.id','from','comment','{{table}}.creator','{{table}}.created','assign','browser','ip','state'),$query,$data);
$query .= ' ORDER BY {{table}}.id desc ';
//Pagination
$response['pagination'] = IssueReport::paginate(20,(!empty($_['page'])?$_['page']:0),$query,$data);
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['belongUser'] = $myUser->login == $row['creator'] || $myUser->can('issue','configure');
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;
}
});
break;
case 'issue_autocomplete':
Action::write(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="comment" ORDER BY id LIMIT 1) WHERE ie.content LIKE ?',array('%'.$_['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']
);
}
});
break;
case 'issue_add_document':
Action::write(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;
});
break;
//Téléchargement des documents
case 'issue_download_document':
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);
break;
case 'issue_issuereport_meta_save':
Action::write(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(!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.' - '.PROGRAM_UID.'] 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.' - '.PROGRAM_UID.'] 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));
}
});
break;
//Ajout ou modification d'élément issuereport
case 'issue_issuereport_save':
Action::write(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 des fichiers joints
if(!empty($_['document_temporary'])){
$files = json_decode($_['document_temporary'],true);
$attachmentFolderRelative = $firstEvent->dir(true);
$attachmentFolder = $firstEvent->dir();
if(!file_exists($attachmentFolder)) mkdir($attachmentFolder,0755,true);
foreach($files as $file){
$from = (get_OS() === 'WIN') ? File::temp().utf8_decode($file['path']) : File::temp().$file['path'];
$to = (get_OS() === 'WIN') ? utf8_decode($file['name']) : $file['name'];
File::move($from, $attachmentFolderRelative.SLASH.$to);
}
}
$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');
});
break;
case 'issue_screenshot_download':
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.png');
}
break;
//Suppression d'élement issuereport
case 'issue_issuereport_delete':
Action::write(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();
});
break;
//Etre notifié du sujet
case 'issue_follow_toggle':
Action::write(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();
});
break;
//Sauvegarde des configurations de issue
case 'issue_setting_save':
Action::write(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);
});
break;
/** EVENT **/
//Récuperation d'une liste de issueevent
case 'issue_issue_event_search':
Action::write(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['creator'] = $creator->toArray();
$row['avatar'] = $creator->getAvatar();
$row['fullName'] = $creator->fullName();
$row['createdRelative'] = relative_time($event->created,null,10,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['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;
}
});
break;
//Ajout ou modification d'élément issueevent
case 'issue_issue_event_save':
Action::write(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();
$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
)));
}
});
break;
//Récuperation ou edition d'élément issueevent
case 'issue_issue_event_edit':
Action::write(function(&$response){
global $myUser,$_;
require_once(__DIR__.SLASH.'IssueEvent.class.php');
require_once(__DIR__.SLASH.'IssueReport.class.php');
$event = IssueEvent::getById($_['id'],1);
if($myUser->login != $event->creator && !$myUser->can('issue','edit')) throw new Exception("Permissions insuffisantes", 403);
$response = $event->toArray(true);
});
break;
//Suppression d'élement issueevent
case 'issue_issue_event_delete':
Action::write(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';
}
});
break;
}
?>