1<?php
2/**
3 * DokuWiki Plugin youtrack (Helper Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Dominic Pöllath <dominic.poellath@ils-gmbh.net>
7 * @author  Anika Henke <anika@zopa.com>
8 */
9
10// must be run within Dokuwiki
11if(!defined('DOKU_INC')) die();
12
13class helper_plugin_youtrack extends DokuWiki_Plugin {
14
15    protected $cookie = false;
16
17    /**
18     * Return info about supported methods in this Helper Plugin
19     *
20     * @return array of public methods
21     */
22    public function getMethods() {
23        return array(
24            array(
25                'name'   => 'request',
26                'desc'   => 'Send request to REST API',
27                'params' => array(
28                    'method'            => 'string',
29                    'endpoint'          => 'string',
30                    'params (optional)' => 'array'
31                ),
32                'return' => array('Response' => 'mixed')
33            ),
34            array(
35                'name'   => 'getBaseUrl',
36                'desc'   => 'Get YouTrack base URL',
37                'return' => array('URL' => 'string')
38            ),
39            array(
40                'name'   => 'login',
41                'desc'   => 'Login to YouTrack',
42                'return' => array('If logged in or not' => 'bool')
43            ),
44            array(
45                'name'   => 'logout',
46                'desc'   => 'Logout of YouTrack by deleting cookie',
47            ),
48            array(
49                'name'   => 'getIssue',
50                'desc'   => 'Get issue by ID',
51                'params' => array(
52                    'id' => 'string',
53                ),
54                'return' => array('Issue data' => 'SimpleXMLElement')
55            ),
56            array(
57                'name'   => 'getIssues',
58                'desc'   => 'Get issues by filter',
59                'params' => array(
60                    'filter' => 'string',
61                ),
62                'return' => array('Issues data' => 'SimpleXMLElement')
63            ),
64            array(
65                'name'   => 'getIssueUrl',
66                'desc'   => 'Get issues by filter',
67                'params' => array(
68                    'id' => 'string',
69                ),
70                'return' => array('URL' => 'string')
71            ),
72            array(
73                'name'   => 'renderIssueTable',
74                'desc'   => 'Render table of issues',
75                'params' => array(
76                    'R'      => 'Doku_Renderer',
77                    'issues' => 'string',
78                    'cols'   => 'string'
79                ),
80            ),
81        );
82    }
83
84    /**
85     * Send request to REST API
86     *
87     * @param string $method    HTTP methods: POST, PUT, GET etc
88     * @param string $endpoint  API endpoint
89     * @param array  $params    Request params: array("param" => "value") ==> ?param=value
90     * @return SimpleXMLElement Response
91     */
92    function request($method, $endpoint, $params = false) {
93        if (!extension_loaded('curl')) {
94            msg('You need to have curl installed and enabled to use the YouTrack plugin', -1);
95            return false;
96        }
97
98        $url = $this->getBaseUrl().$endpoint;
99
100        $curl = curl_init();
101        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 1);
102        curl_setopt($curl, CURLOPT_TIMEOUT, 1);
103        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
104
105        switch ($method) {
106            case "POST":
107                curl_setopt($curl, CURLOPT_POST, 1);
108                if ($params) {
109                    curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
110                }
111                break;
112            case "PUT":
113                curl_setopt($curl, CURLOPT_PUT, 1);
114                break;
115            default:
116                if ($params) {
117                    $url = sprintf("%s?%s", $url, http_build_query($params, '', '&'));
118                }
119        }
120
121        curl_setopt($curl, CURLOPT_URL, $url);
122
123        // Optional Authentication:
124        // curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
125        // curl_setopt($curl, CURLOPT_USERPWD, "username:password");
126        // curl_setopt($curl, CURLOPT_COOKIEJAR,  $ckfile);
127        if ($this->cookie) {
128            curl_setopt($curl, CURLOPT_COOKIEFILE, $this->cookie);
129            curl_setopt($curl, CURLOPT_COOKIEJAR, $this->cookie);
130        }
131
132        $content = curl_exec($curl);
133        curl_close($curl);
134        return $content ? $content : null;
135    }
136
137
138    /**
139     * Get YouTrack base URL (without ending slash)
140     *
141     * @return string URL
142     */
143    function getBaseUrl() {
144        $url = $this->getConf('url');
145        if (empty($url)) {
146            msg('YouTrack URL is not defined.', -1);
147        }
148        return substr($url, -1) === '/' ? substr($url, 0, strlen($url)-1) : $url;
149    }
150
151
152    /**
153     * Login to YouTrack
154     *
155     * @return bool If logged in or not
156     */
157    function login() {
158        $user = $this->getConf('user');
159        $password = $this->getConf('password');
160        $url = $this->getBaseUrl();
161
162        if (empty($user) || empty($password) || empty($url)) {
163            $this->cookie = false;
164            return false;
165        }
166
167        $this->cookie = tempnam(sys_get_temp_dir(), "CURLCOOKIE");
168
169        $content = $this->request(
170            "POST",
171            "/rest/user/login",
172            array(
173                "login" => $user,
174                "password" => $password
175            )
176        );
177
178        if ($content && simplexml_load_string($content) != "ok") {
179            msg('Login data not correct, or REST login is not enabled.', -1);
180            return false;
181        }
182        return true;
183    }
184
185    /**
186     * Logout of YouTrack by deleting cookie
187     */
188    function logout() {
189        if ($this->cookie) {
190            unlink($this->cookie) or die("Can't unlink $this->cookie");
191        }
192    }
193
194    /**
195     * Get issue by ID
196     *
197     * @param string $id ID of issue
198     * @return SimpleXMLElement Issue data
199     */
200    function getIssue($id) {
201        $this->login();
202        $xml = $this->request("GET", "/rest/issue/$id");
203        $this->logout();
204        return simplexml_load_string($xml);
205    }
206
207    /**
208     * Get issues by filter
209     *
210     * @param string $filter Filter in YouTrack query language
211     * @return SimpleXMLElement Issues data
212     */
213    function getIssues($filter) {
214        $this->login();
215        $xml = $this->request(
216            "GET",
217            "/rest/issue/",
218            array(
219                'filter' => $filter,
220                'max' => 100 // TODO: set max via config?
221            )
222        );
223        $this->logout();
224        return simplexml_load_string($xml);
225    }
226
227    /**
228     * Get link to issue
229     *
230     * @param string $id ID of issue
231     * @return string
232     */
233    function getIssueUrl($id) {
234        if (empty($id)) {
235            return '';
236        }
237        return $this->getBaseUrl().'/issue/'.$id;
238    }
239
240    /**
241     * Render table of issues
242     *
243     * @param Doku_Renderer $R      Renderer
244     * @param array         $issues Issues with their relevant values
245     * @param array         $cols   Columns to show
246     */
247    function renderIssueTable(Doku_Renderer &$R, $issues, $cols) {
248        $R->table_open(count($cols));
249            $R->tablethead_open();
250                $R->tablerow_open();
251                    foreach($cols as $col) {
252                        $R->tableheader_open();
253                        $R->cdata($col);
254                        $R->tableheader_close();
255                    }
256                $R->tablerow_close();
257            $R->tablethead_close();
258            if (method_exists($R, 'tabletbody_open')) {
259                $R->tabletbody_open();
260            }
261
262                foreach ($issues as $issue) {
263                    $R->tablerow_open();
264                        foreach($cols as $col) {
265                            $R->tablecell_open();
266                            if ($col == 'ID') {
267                                $R->externallink($this->getIssueUrl($issue[$col]), $issue[$col]);
268                            } else {
269                                $R->cdata($issue[$col]);
270                            }
271                            $R->tablecell_close();
272                        }
273                    $R->tablerow_close();
274                }
275
276            if (method_exists($R, 'tabletbody_close')) {
277                $R->tabletbody_close();
278            }
279        $R->table_close();
280    }
281
282}
283// vim:ts=4:sw=4:et:
284