1<?php 2/** 3 * Redissue Syntax Plugin: Insert a link to redmine's issue 4 * 5 * @author Algorys 6 */ 7 8if (!defined('DOKU_INC')) die(); 9require 'redmine/redmine.php'; 10 11class syntax_plugin_redissue extends DokuWiki_Syntax_Plugin { 12 public $isBootstrap = False; 13 14 public function getType() { 15 return 'substition'; 16 } 17 18 public function getPType() { 19 return 'normal'; 20 } 21 // Keep syntax inside plugin 22 function getAllowedTypes() { 23 return array('container', 'baseonly', 'substition','protected','disabled','formatting','paragraphs'); 24 } 25 26 public function getSort() { 27 return 198; 28 } 29 30 function connectTo($mode) { 31 $this->Lexer->addSpecialPattern('<redissue[^>]*/>', $mode,'plugin_redissue'); 32 $this->Lexer->addEntryPattern('<redissue[^>/]*>(?=.*</redissue>)', $mode,'plugin_redissue'); 33 } 34 function postConnect() { 35 $this->Lexer->addExitPattern('</redissue>', 'plugin_redissue'); 36 } 37 38 function getServerFromJson($server) { 39 $json_file = file_get_contents(__DIR__.'/server.json'); 40 $json_data = json_decode($json_file, true); 41 if(isset($json_data[$server])) { 42 return $json_data[$server]; 43 } else { 44 return null; 45 } 46 } 47 48 function handle($match, $state, $pos, Doku_Handler $handler) { 49 switch($state){ 50 case DOKU_LEXER_SPECIAL : 51 $data = array( 52 'state'=>$state, 53 'id'=> 0, 54 'text'=>$this->getLang('redissue.text.default') 55 ); 56 // Redmine Server 57 preg_match("/server *= *(['\"])(.*?)\\1/", $match, $server); 58 if (count($server) != 0) { 59 $server_data = $this->getServerFromJson($server[2]); 60 if( ! is_null($server_data)){ 61 $data['server_url'] = $server_data['url']; 62 $data['server_token'] = $server_data['api_token']; 63 } 64 } 65 if (!isset($data['server_token'])) { 66 $data['server_token'] = $this->getConf('redissue.API'); 67 } 68 if (!isset($data['server_url'])) { 69 $data['server_url'] = $this->getConf('redissue.url'); 70 } 71 72 // Issue Id 73 preg_match("/id *= *(['\"])#(\\d+)\\1/", $match, $id); 74 if ((count($id) != 0) && ($id[2] != 0)) { 75 $data['id'] = $id[2]; 76 } else { 77 $data['id'] = 0; 78 } 79 80 // Title 81 preg_match("/title *= *(['\"])(.*?)\\1/", $match, $title); 82 if( count($title) != 0 ) { 83 $data['title'] = $title[2]; 84 } 85 // Short 86 $data['short'] = $this->getConf('redissue.short'); 87 preg_match("/short *= *(['\"])([0-1])\\1/", $match, $over_short); 88 if( $over_short ){ 89 $data['short'] = $over_short[2]; 90 } 91 // Text Access 92 preg_match("/text *= *(['\"])(.*?)\\1/", $match, $text); 93 if( count($text) != 0 ) { 94 $data['text'] = $text[2]; 95 } 96 // Project Id 97 preg_match("/project *= *(['\"])(.*?)\\1/", $match, $project); 98 if( count($project) != 0 ) { 99 $data['project_id'] = $project[2]; 100 } 101 // Tracker Id 102 preg_match("/tracker *= *(['\"])(.*?)\\1/", $match, $tracker); 103 if( count($tracker) != 0 ) { 104 $data['tracker_id'] = $tracker[2]; 105 } 106 // Limit 107 preg_match("/limit *= *(['\"])(\\d+)\\1/", $match, $limit); 108 if( count($limit) != 0 ) { 109 $data['limit'] = $limit[2]; 110 } else { 111 $data['limit'] = 25; 112 } 113 // Sort 114 preg_match("/sort *= *(['\"])(.*?)\\1/", $match, $sort); 115 if( count($sort) != 0 ) { 116 $data['sort'] = $sort[2]; 117 } else { 118 $data['sort'] = ''; 119 } 120 121 return $data; 122 case DOKU_LEXER_UNMATCHED : 123 return array('state'=>$state, 'text'=>$match); 124 default: 125 return array('state'=>$state, 'bytepos_end' => $pos + strlen($match)); 126 } 127 } 128 129 // Dokuwiki Renderer 130 function render($mode, Doku_Renderer $renderer, $data) { 131 if($mode != 'xhtml') return false; 132 133 if($data['error']) { 134 $renderer->doc .= $data['text']; 135 return true; 136 } 137 $renderer->info['cache'] = false; 138 switch($data['state']) { 139 case DOKU_LEXER_SPECIAL: 140 $this->renderRedissue($renderer, $data); 141 break; 142 case DOKU_LEXER_EXIT: 143 $renderer->doc .= '</div></div>'; 144 case DOKU_LEXER_ENTER: 145 case DOKU_LEXER_UNMATCHED: 146 $renderer->doc .= $renderer->_xmlEntities($data['text']); 147 break; 148 } 149 return true; 150 } 151 152 function renderRedissue($renderer, $data) { 153 $this->detectBootstrap(); 154 $redmine = new DokuwikiRedmine(); 155 if(empty($data['server_token'])){ 156 $this->renderIssueLink($renderer, $data); 157 } else { 158 $url = $data['server_url']; 159 $view = $this->getConf('redissue.view'); 160 $wiki_user = $_SERVER['REMOTE_USER']; 161 $redmine->connect($url, $data['server_token'], $view, $wiki_user); 162 if(array_key_exists('project_id', $data) && array_key_exists('tracker_id', $data)) { 163 $issues = $redmine->client->issue->all([ 164 'project_id' => $data['project_id'], 165 'tracker_id' => $data['tracker_id'], 166 'limit' => $data['limit'], 167 'sort' => $data['sort'] 168 ]); 169 if(isset($issues['issues'])) { 170 for ($i = 0; $i < count($issues['issues']); $i++) { 171 $data['id'] = $issues['issues'][$i]['id']; 172 $this->displayIssue($renderer, $data, $redmine); 173 } 174 } else { 175 $renderer->doc .= '<p style="color: red;">REDISSUE plugin: "project" ID or "tracker" ID is invalid ! Redissue try to display single issue instead !</p>'; 176 $this->displayIssue($renderer, $data, $redmine); 177 } 178 } else { 179 $this->displayIssue($renderer, $data, $redmine); 180 } 181 } 182 } 183 184 function detectBootstrap(){ 185 global $conf; 186 $bootstrap_themes = $this->getConf('redissue.bootstrap-themes'); 187 $bootstrap_exploded = explode(',', $bootstrap_themes); 188 foreach ($bootstrap_exploded as $key => $value) { 189 if (strcmp($conf['template'], $value) == 0){ 190 $this->isBootstrap = True; 191 } 192 } 193 } 194 195 function renderIssueLink($renderer, $data) { 196 // Check if issue or not 197 if ($data['id'] == 0): 198 $cur_title = $data['text']; 199 $collapse = ''; 200 else: 201 if (isset($data['title'])) { 202 $cur_title = '[#'.$data['id'].'] ' . $data['title']; 203 } else { 204 $cur_title = '[#'.$data['id'].'] ' . $data['text']; 205 } 206 $collapse = 'collapse'; 207 endif; 208 // Render Link 209 if ($this->isBootstrap){ 210 $renderer->doc .= '<a title="'.$this->getLang('redissue.link.issue').'" href="' . $this->getIssueUrl($data['id'], $data) . '"><img src="' . $this->getImg() . '" class="redissue"/></a>'; 211 $renderer->doc .= '<a class="btn btn-primary redissue" role="button" data-toggle="'.$collapse.'" href="#collapse-'.$data['id'].'" aria-expanded="false" aria-controls="collapse-'.$data['id'].'">'; 212 $renderer->doc .= $cur_title; 213 $renderer->doc .= '</a> '; 214 $renderer->doc .= '<div class="collapse" id="collapse-'.$data['id'].'">'; 215 } else { 216 $renderer->doc .= '<a title="'.$this->getLang('redissue.link.issue').'" href="' . $this->getIssueUrl($data['id'], $data) . '"><img src="' . $this->getImg() . '" class="redissue"/>'; 217 $renderer->doc .= $cur_title; 218 $renderer->doc .= '</a> '; 219 } 220 } 221 222 function getIssueUrl($id, $data) { 223 return $data['server_url'].'/issues/'.$id; 224 } 225 226 function getImg() { 227 // If empty (False) get the second part 228 return $this->getConf('redissue.img') ?: 'lib/plugins/redissue/images/redmine.png' ; 229 } 230 231 function displayIssue($renderer, $data, $redmine) { 232 $issue = $redmine->getIssue($data['id']); 233 $url = $data['server_url']; 234 235 if($issue == 'Syntax error') { 236 $renderer->doc .= '<p style="color: red;">REDISSUE plugin: Server exist in JSON config but seems not valid ! Please check your <b>url</b> or your <b>API Key</b> !</p>'; 237 // If rights 238 } elseif (isset($issue['issue'])) { 239 // ISSUE INFOS 240 $project = $issue['issue']['project']; 241 $project_identifier = $redmine->getProjectIdentifier($project['name']); 242 $tracker = $issue['issue']['tracker']; 243 $status = $issue['issue']['status']['name']; 244 $author = $issue['issue']['author']; 245 $author_id = $redmine->getIdByUsername($author['name']); 246 $assigned = $issue['issue']['assigned_to']; 247 $assigned_id = $redmine->getIdByUsername($assigned['name']); 248 $subject = $issue['issue']['subject']; 249 $description = $issue['issue']['description']; 250 $done_ratio = $issue['issue']['done_ratio']; 251 // RENDER ISSUE LINK 252 $isClosed = $redmine->getIsClosedValue($issue['issue']['status']['id']); 253 $renderer->doc .= '<p>'; 254 $data['text'] = $issue['issue']['subject']; 255 $this->renderIssueLink($renderer, $data); 256 257 // GENERAL RENDERER 258 $priority = $issue['issue']['priority']; 259 $color_prio = $redmine->getPriorityColor($priority['id']); 260 $dates_times = $redmine->getDatesTimesIssue($issue); 261 if(!$isClosed){ 262 if($this->isBootstrap){ 263 $renderer->doc .= ' <span class="label label-success">' . $status . '</span>'; 264 }else{ 265 $renderer->doc .= ' <span class="badge-prio color-'.$color_prio.'">'.$priority['name'].'</span>'; 266 $renderer->doc .= ' <span class="badge-prio tracker">'. $tracker['name'].'</span>'; 267 $renderer->doc .= ' <span class="badge-prio open">' . $status . '</span>'; 268 } 269 } else { 270 if($this->isBootstrap){ 271 $renderer->doc .= ' <span class="label label-default">' . $status . '</span>'; 272 }else{ 273 $renderer->doc .= ' <span class="badge-prio closed">' . $status . '</span>'; 274 } 275 } 276 277 if($this->isBootstrap) { 278 $renderer->doc .= ' <span class="label label-'.$color_prio.'">'.$priority['name'].'</span>'; 279 $renderer->doc .= ' <span class="label label-primary">'. $tracker['name'].'</span>'; 280 $renderer->doc .= '<div class="well">'; 281 $renderer->doc .= '<div class="issue-info"><dl class="dl-horizontal">'; 282 $renderer->doc .= '<dt><icon class="glyphicon glyphicon-info-sign"> </icon>Projet :</dt>'; 283 $renderer->doc .= '<dd><a href="'.$url.'/projects/'.$project_identifier.'">'.$project['name'].'</a></dd>'; 284 $renderer->doc .= '<dt>'.$this->getLang('redissue.author').' :</dt>'; 285 $renderer->doc .= '<dd><a href="'.$url.'/users/'.$author_id.'">'.$author['name'].'</a></dd>'; 286 $renderer->doc .= '<dt>'.$this->getLang('redissue.assigned').' :</dt>'; 287 $renderer->doc .= '<dd><a href="'.$url.'/users/'.$assigned_id.'">'.$assigned['name'].'</a></dd>'; 288 $renderer->doc .= '<dt>'.$this->getLang('redissue.created').' :</dt>'; 289 $renderer->doc .= '<dd>'.$dates_times['created']['date'].' ('.$dates_times['created']['time'].')</dd>'; 290 $renderer->doc .= '<dt>'.$this->getLang('redissue.updated').' :</dt>'; 291 $renderer->doc .= '<dd>'.$dates_times['updated']['date'].' ('.$dates_times['updated']['time'].')</dd>'; 292 if ($dates_times['closed']){ 293 $renderer->doc .= '<dt>'.$this->getLang('redissue.closed').' :</dt>'; 294 $renderer->doc .= '<dd>'.$dates_times['closed']['date'].' ('.$dates_times['closed']['time'].')</dd>'; 295 } 296 $renderer->doc .= '</dl></div>'; // ./ Issue-info 297 $renderer->doc .= '<h4>'.$this->getLang('redissue.desc').'</h4><p>'.$description.'</p>'; 298 $renderer->doc .= '<div class="progress">'; 299 $renderer->doc .= '<span class="progress-bar" role="progressbar" aria-valuenow="70" aria-valuemin="0" aria-valuemax="100" style="width:'.$done_ratio.'%">'; 300 $renderer->doc .= '<span class="doku">'.$done_ratio.'% Complete</span>'; 301 $renderer->doc .= '</span></div>'; // ./progress 302 $renderer->doc .= '</div>'; // ./ well 303 $renderer->doc .= '</div>'; 304 } else { 305 $renderer->doc .= '<div '; 306 if($data['short'] > 0) { 307 $renderer->doc .= 'style="display:none;"'; 308 } 309 $renderer->doc .= 'class="issue-doku border-'.$color_prio.'">'; 310 $renderer->doc .= '<div>'; 311 $renderer->doc .= '<span><b>'.$this->getLang('redissue.project').' : </b>'; 312 $renderer->doc .= '<a href="'.$url.'/projects/'.$project_identifier.'"> '.$project['name'].'</a></span>'; 313 $renderer->doc .= '<span><b> '.$this->getLang('redissue.author').' : </b>'; 314 $renderer->doc .= '<a href="'.$url.'/users/'.$author_id.'">'.$author['name'].'</a></span>'; 315 $renderer->doc .= '<br>'; 316 $renderer->doc .= '<span><b> '.$this->getLang('redissue.assigned').' :</b>'; 317 $renderer->doc .= '<a href="'.$url.'/users/'.$assigned_id.'"> '.$assigned['name'].' </a></span><br>'; 318 $renderer->doc .= '<span><b> '.$this->getLang('redissue.created').' : </b>'; 319 $renderer->doc .= ''.$dates_times['created']['date'].' ('.$dates_times['created']['time'].')</span>'; 320 $renderer->doc .= '<span><b> '.$this->getLang('redissue.updated').' : </b>'; 321 $renderer->doc .= ''.$dates_times['updated']['date'].' ('.$dates_times['updated']['time'].')</span>'; 322 if ($dates_times['closed']){ 323 $renderer->doc .= '<span><b> '.$this->getLang('redissue.closed').' : </b>'; 324 $renderer->doc .= ''.$dates_times['closed']['date'].' ('.$dates_times['closed']['time'].')</span>'; 325 } 326 $renderer->doc .= '</div>'; // ./ Issue-info 327 $renderer->doc .= '<div class="issue-desc">'; 328 $renderer->doc .= '<b>'.$this->getLang('redissue.desc').' :</b>'; 329 $renderer->doc .= '<p>'.$description.'</p>'; 330 $renderer->doc .= '</div>'; 331 //$renderer->doc .= '<div class="progress">'; 332 $renderer->doc .= $done_ratio.'% Complete '; 333 $renderer->doc .= '<progress max="100" value="'.$done_ratio.'"></progress>'; 334 //$renderer->doc .= '</div>'; // ./progress 335 $renderer->doc .= '</div>'; 336 } 337 $renderer->doc .= '</p>'; 338 } else { 339 // If the user has no Rights 340 $this->renderIssueLink($renderer, $data); 341 } 342 } 343 344} 345