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">&nbsp;</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