1<?php
2/**
3 * Redproject Syntax Plugin: Display Roadmap and other things
4 *
5 * @author Algorys
6 */
7
8if (!defined('DOKU_INC')) die();
9require 'vendor/php-redmine-api/lib/autoload.php';
10
11
12class syntax_plugin_redproject extends DokuWiki_Syntax_Plugin {
13    const RI_IMPERSONATE = 4;
14
15    public function getType() {
16        return 'container';
17    }
18
19    /**
20     * @return string Paragraph type
21     */
22    public function getPType() {
23        return 'normal';
24    }
25    // Keep syntax inside plugin
26    function getAllowedTypes() {
27        return array('container', 'baseonly', 'substition','protected','disabled','formatting','paragraphs');
28    }
29
30    public function getSort() {
31        return 198;
32    }
33
34    function connectTo($mode) {
35        $this->Lexer->addSpecialPattern('<redproject[^>]*/>', $mode,'plugin_redproject');
36        $this->Lexer->addEntryPattern('<redproject[^>]*>(?=.*</redproject>)', $mode,'plugin_redproject');
37    }
38    function postConnect() {
39        $this->Lexer->addExitPattern('</redproject>', 'plugin_redproject');
40    }
41
42    function getServerFromJson($server) {
43        $json_file = file_get_contents(__DIR__.'/server.json');
44        $json_data = json_decode($json_file, true);
45        if(isset($json_data[$server])) {
46            return $json_data[$server];
47        } else {
48            return null;
49        }
50    }
51
52    function getPercent($opIssue, $totalIssue) {
53        $p = $opIssue / $totalIssue;
54        $progress = $p * 100;
55        return round($progress, 1);
56    }
57
58    // Do the regexp
59    function handle($match, $state, $pos, Doku_Handler $handler) {
60        switch($state){
61            case DOKU_LEXER_SPECIAL :
62            case DOKU_LEXER_ENTER :
63                $data = array(
64                        'state'=>$state,
65                        'proj'=> '',
66                    );
67                preg_match("/server *= *(['\"])(.*?)\\1/", $match, $server);
68                if (count($server) != 0) {
69                    $server_data = $this->getServerFromJson($server[2]);
70                    if( ! is_null($server_data)){
71                        $data['server_url'] = $server_data['url'];
72                        $data['server_token'] = $server_data['api_token'];
73                    }
74                }
75                if (!isset($data['server_token'])) {
76                    $data['server_token'] = $this->getConf('redproject.API');
77                }
78                if (!isset($data['server_url'])) {
79                    $data['server_url'] = $this->getConf('redproject.url');
80                }
81
82                // Looking for id
83                preg_match("/proj *= *(['\"])(.*?)\\1/", $match, $proj);
84                if( count($proj) != 0 ) {
85                    $data['proj'] = $proj[2];
86                } else {
87                    return array(
88                            'state'=>$state,
89                            'error'=>true,
90                            'text'=>'##ERROR &lt;redproject&gt;: project name required##'
91                        );
92                }
93
94                return $data;
95            case DOKU_LEXER_UNMATCHED :
96                return array('state'=>$state, 'text'=>$match);
97            default:
98                return array('state'=>$state, 'bytepos_end' => $pos + strlen($match));
99        }
100    }
101
102    // Main render_link
103    function _render_project($renderer, $data) {
104        $client = new Redmine\Client($data['server_url'], $data['server_token']);
105        // Get Id user of the Wiki if Impersonate
106        $view = $this->getConf('redproject.view');
107        if ($view == self::RI_IMPERSONATE) {
108            $redUser = $_SERVER['REMOTE_USER'];
109            // Attempt to collect information with this user
110            $client->setImpersonateUser($redUser);
111        }
112    	// Get Project Info
113        $proj = $client->api('project')->show($data['proj']);
114        if($proj){
115            $projId = $proj['project']['id'];
116            $projIdent = $proj['project']['identifier'];
117            $projName = $proj['project']['name'];
118            $projParent = $proj['project']['parent'];
119            if ( ! empty($projParent)) {
120                $nameParent = $projParent['name'];
121                $parentId = $client->api('project')->getIdByName($nameParent);
122                $parent = $client->api('project')->show($parentId);
123                $parentIdent = $parent['project']['identifier'];
124            }
125            $projHome = $proj['project']['homepage'];
126            $projDesc = $proj['project']['description'];
127            // RENDERER PROJECT INFO
128            // Title
129            $renderer->doc .= '<h2 class="title">'.$this->getLang('title').'</h2>';
130            if($projHome) {
131               $renderer->doc .= '<div class="title">';
132               $renderer->doc .= '<a href="'.$projHome.'"><div class="circle">HOME</div></a>';
133               $renderer->doc .= '<div class="title-droite">';
134               $renderer->doc .= '<span class="info-title">'.$projName.'</span>';
135               $renderer->doc .= '<div class="see-it">';
136               $renderer->doc .= '<a href="'.$data['server_url'].'/projects/'.$projIdent.'">See it in redmine</a>';
137               $renderer->doc .= '</div>';// /.see-it
138               $renderer->doc .= '</div>'; // /.title-droite
139               $renderer->doc .= '</div>'; // /.title
140            } else {
141               $renderer->doc .= '<div class="title">';
142               $renderer->doc .= '<a href="'.$projHome.'" title="Add Homepage"><div class="circle">+</div></a>';
143               $renderer->doc .= '<div class="title-droite">';
144               $renderer->doc .= '<span class="info-title">'.$projName.'</span>';
145               $renderer->doc .= '<div class="see-it">';
146               $renderer->doc .= '<a href="'.$data['server_url'].'/projects/'.$projIdent.'">See it in redmine</a>';
147               $renderer->doc .= '</div>';// /.see-it
148               $renderer->doc .= '</div>'; // /.title-droite
149               $renderer->doc .= '</div>'; // /.title
150
151            }
152            // DESCRIPTION
153            if ($projDesc == ''){
154                $renderer->doc .= '<div class="desc"><h4>'.$this->getLang('desctitle').'</h4> <p>'.$this->getLang('description').'</p></div>';
155            } else {
156                $renderer->doc .= '<div class="desc"><h4>'.$this->getLang('desctitle').'</h4> <p class="desc"> ' . $projDesc . '</p></div>';
157            }
158            // VERSIONS
159            $versions = $client->api('version')->all($data['proj']);
160            // Parsing Version
161            if($versions) {
162                $renderer->doc .= '<div class="version"><h3>'.$this->getLang('vertitle').'</h3>';
163                $renderer->doc .= '<div class="panel-group" id="version-accordion-nb" role="tablist">';
164                for($i = 0; $i < count($versions['versions']); $i++) {
165                    // Begin Accordion
166                    $renderer->doc .= '<div class="panel panel-primary descver">';
167                    $foundVersion = $versions['versions'][$i];
168                    $versionId = $foundVersion['id'];
169                    $renderer->doc .= '<div class="version panel-heading">';
170                    $renderer->doc .= '<h4 class="panel-title">';
171                    $renderer->doc .= '<a class="version" data-toggle="collapse" data-parent="#version-accordion-nb" href="#collapse-version-nb-'.$versionId.'">';
172                    $renderer->doc .= '<span class="version">Version ' . $foundVersion['name'] . '</span> ';
173                    $renderer->doc .= '</a>';
174                    $statusClass = (($foundVersion['status'] == 'open') ? 'statusop' : 'statuscl');
175                    $renderer->doc .= '<span class="'.$statusClass.'"> ' . $foundVersion['status'] . ' </span>';
176                    $renderer->doc .= '</h4>'; // /.panel-title
177                    $renderer->doc .= '</div>'; // /.panel-heading
178                    $renderer->doc .= '<div id="collapse-version-nb-'.$versionId.'" class="panel-collapse collapse">';
179                    // PANEL BODY
180                    $renderer->doc .= '<div class="panel-body">';
181                    // Time Entries
182                    $createdOn = DateTime::createFromFormat(DateTime::ISO8601, $foundVersion['created_on']);
183                    $updatedOn = DateTime::createFromFormat(DateTime::ISO8601, $foundVersion['updated_on']);
184                    $renderer->doc .= '<p><b>Description :</b> '.$foundVersion['description'].'</p>';
185                    $renderer->doc .= '<p><a href="'.$data['server_url'].'/versions/'.$versionId.'">See this version in redmine</a></p>';
186                    $renderer->doc .= '<p><b>'.$this->getLang('createdon').'</b>'.$createdOn->format(DateTime::RFC850).'</p>';
187                    $renderer->doc .= '<p><b>'.$this->getLang('updatedon').'</b>'.$updatedOn->format(DateTime::RFC850).'</p>';
188                    // Issues of Versions
189                    $issueTotal = $client->api('issue')->all(array(
190                      'project_id' => $projId,
191                      'status_id' => '*',
192                      'fixed_version_id' => $foundVersion['id'],
193                      'limit' => 1
194                      ));
195                    // Total issues & open
196                    $issueOpen = $client->api('issue')->all(array(
197                      'project_id' => $projId,
198                      'status_id' => 'open',
199                      'fixed_version_id' => $foundVersion['id'],
200                      'limit' => 1
201                       ));
202                    // Get percent version
203                    $diffIssue = $issueTotal['total_count'] - $issueOpen['total_count'];
204                    $progress = $this->getPercent($diffIssue,$issueTotal['total_count']);
205                    // renderer Progressbar
206                    $renderer->doc .= '<span class="col-md-3">';
207                    $renderer->doc .= '<a href="'.$data['server_url'].'/projects/'.$projIdent.'/issues">'.$issueTotal['total_count'].' issues ('.$diffIssue.' closed - '.$issueOpen['total_count'].' open)</a>';
208                    $renderer->doc .= '</span>'; // /.col-md-3
209                    $renderer->doc .= '<span class="col-md-6">';
210                    $renderer->doc .= '<div class="progress">';
211                    $renderer->doc .= '<span class="progress-bar" role="progressbar" aria-valuenow="70"
212  aria-valuemin="0" aria-valuemax="100" style="width:'.$progress.'%">';
213                    $renderer->doc .= '<span class="doku">'.$progress.'% Complete</span>';
214                    $renderer->doc .= '</span></div>'; // ./progress
215                    $renderer->doc .= '</span>'; // ./col-md-6
216                    $renderer->doc .= '</div>'; // /.panel-body
217                    $renderer->doc .= '</div>'; // /#collapse-version-nb-'.$versionId.' .panel-collapse
218                    $renderer->doc .= '</div>'; // /.panel .panel-default
219                    $renderer->doc .= '<br>';
220                }
221                $renderer->doc .= '</div>'; // /.panel-group
222            } else {
223                $renderer->doc .= '<div class="version"><h3>'.$this->getLang('vertitle').'</h3>';
224                $renderer->doc .= $nbVersion . ' versions';
225                $renderer->doc .= 'div class="descver"><p>' . $this->getLang('noversion') . '</p></div>';
226            }
227            $renderer->doc .= '</div>';
228
229            // DETAILS
230            // Get Number of Version
231            for($v = 0; $v < count($versions['versions']); $v++) {
232                $nbVersion = $v + 1;
233            }
234            // Get number of Issues
235            $issueTotal = $client->api('issue')->all(array(
236              'project_id' => $projId,
237              'status_id' => '*',
238              'limit' => 1
239              ));
240            $issueOpen = $client->api('issue')->all(array(
241              'project_id' => $projId,
242              'status_id' => 'open',
243              'limit' => 1
244              ));
245            // Initialize Array
246            $usersByRole = array();
247            $members = $client->api('membership')->all($projId);
248            // Found each Members
249            for($m = 0; $m < count($members['memberships']); $m++) {
250               // $z++;
251                $memberFound = $members['memberships'][$m];
252                $currentUser = $memberFound['user'];
253                for($r = 0; $r <count($memberFound['roles']); $r++) {
254                    $currentRole = $memberFound['roles'][$r];
255                    $roleId = $currentRole['id'];
256                    // If doesn't exist in usersByRole, create it
257                    if(!$usersByRole[$roleId]) {
258                        $currentRole['members'] = array($currentUser);
259                        $usersByRole[$roleId] = $currentRole;
260                    }
261                    // Else Push to array
262                    else {
263                        array_push($usersByRole[$roleId]['members'], $currentUser);
264                    }
265                }
266            }
267            // Renderer Details
268            $renderer->doc .= '<div class="details">';
269            $renderer->doc .= '<h3>'.$this->getlang('hdetail').'</h3>';
270            // Stats
271            $renderer->doc .= '<div class="stats">';
272            if($projParent == ''){
273                $renderer->doc .= '<p>'.$this->getLang('mainproj').'</p>';
274            } else {
275                $renderer->doc .= '<p>'.$this->getLang('subproject').' <a href="'.$data['server_url'].'/projects/'.$parentIdent.'">'.$nameParent.'</a></p>';
276            }
277            $renderer->doc .= '<p>'.$this->getLang('tversion').'<span class="label label-info">'.$nbVersion.'</span>'.$this->getLang('vversion').'</p>';
278            $renderer->doc .= '<p><span class="label label-success">'. $issueTotal['total_count'].'</span>'.$this->getLang('issues').'<span class="label label-warning">'.$issueOpen['total_count'].'</span>'.$this->getLang('open').'</p>';
279            $renderer->doc .= '<p><span class="label label-info">'.$m.'</span>'.$this->getLang('membdetail').'</p>';
280            $renderer->doc .= '</div>'; // /.stats
281            $renderer->doc .= '</div>'; // /.details
282            // MEMBERSHIPS & ROLES
283            $langMembers = $this->getLang('membres');
284            $renderer->doc .= '<h3 class="member">'. $langMembers . '</h3>';
285            // Display new array usersByRole
286            $renderer->doc .= '<div class="member">';
287            foreach($usersByRole as $role => $currentRole) {
288                $renderer->doc .= '<p class="member">'.$currentRole['name'].' : ';
289                // Define a total to render commas
290                $total = count($currentRole['members']);
291                foreach($currentRole['members'] as $who => $currentUser) {
292                    $userId = $currentUser['id'];
293                    $mailCurrentUser = $client->api('user')->show($userId);
294                    $mailUser = $mailCurrentUser['user']['mail'];
295                    $renderer->doc .= ' <a href="mailto:'.$mailUser.'?Subject=Project '.$projName.'"target="_top"><span>'. $currentUser['name'] . '</span></a>' ;
296                    if ($who < $total - 1) {
297                        $renderer->doc .= ',';
298                    }
299                }
300                $renderer->doc .= '</p>';
301            }
302            $renderer->doc .= '</div>';
303        } else {
304            $renderer->doc .= '<h2 class="title">'.$this->getLang('private').'</h2>';
305            $renderer->doc .= '<div class="desc" style="float: none;"><h3>'.$this->getLang('info').'</h3>'.$this->getLang('norights').' </p></div>';
306
307        }
308    }
309    // Dokuwiki Renderer
310    function render($mode, Doku_Renderer $renderer, $data) {
311        $renderer->info['cache'] = false;
312        if($mode != 'xhtml') return false;
313
314        if($data['error']) {
315            $renderer->doc .= $data['text'];
316            return true;
317        }
318        switch($data['state']) {
319            case DOKU_LEXER_SPECIAL :
320                $this->_render_project($renderer, $data);
321                break;
322            case DOKU_LEXER_ENTER :
323                $this->_render_project($renderer, $data);
324                break;
325            case DOKU_LEXER_EXIT:
326            case DOKU_LEXER_UNMATCHED :
327                $renderer->doc .= $renderer->_xmlEntities($data['text']);
328                break;
329        }
330        return true;
331    }
332}
333