1 | 1 | simandl | <?php |
2 | | | // WebSVN - Subversion repository viewing via the web using PHP |
3 | | | // Copyright (C) 2004-2006 Tim Armes |
4 | | | // |
5 | | | // This program is free software; you can redistribute it and/or modify |
6 | | | // it under the terms of the GNU General Public License as published by |
7 | | | // the Free Software Foundation; either version 2 of the License, or |
8 | | | // (at your option) any later version. |
9 | | | // |
10 | | | // This program is distributed in the hope that it will be useful, |
11 | | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | | // GNU General Public License for more details. |
14 | | | // |
15 | | | // You should have received a copy of the GNU General Public License |
16 | | | // along with this program; if not, write to the Free Software |
17 | | | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
18 | | | // |
19 | | | // -- |
20 | | | // |
21 | | | // bugtraq.php |
22 | | | // |
23 | | | // Functions for accessing the bugtraq properties and replacing issue IDs |
24 | | | // with URLs. |
25 | | | // |
26 | | | // For more information about bugtraq, see |
27 | | | // http://svn.collab.net/repos/tortoisesvn/trunk/doc/issuetrackers.txt |
28 | | | |
29 | | | class Bugtraq { |
30 | | | // {{{ Properties |
31 | | | |
32 | | | var $msgstring; |
33 | | | var $urlstring; |
34 | | | var $logregex; |
35 | | | var $append; |
36 | | | |
37 | | | var $firstPart; |
38 | | | var $firstPartLen; |
39 | | | var $lastPart; |
40 | | | var $lastPartLen; |
41 | | | |
42 | | | var $propsfound = false; |
43 | | | |
44 | | | // }}} |
45 | | | |
46 | | | // {{{ __construct($rep, $svnrep, $path) |
47 | | | |
48 | | | function Bugtraq($rep, $svnrep, $path) { |
49 | | | global $config; |
50 | | | |
51 | | | if ($rep->getBugtraq()) { |
52 | | | $pos = strrpos($path, "/"); |
53 | | | $parent = substr($path, 0, $pos + 1); |
54 | | | $this->append = true; |
55 | | | |
56 | | | $enoughdata = false; |
57 | | | while(!$enoughdata && (strpos($parent, "/") !== false)) { |
58 | | | if (empty($this->msgstring)) $this->msgstring = $svnrep->getProperty($parent, 'bugtraq:message'); |
59 | | | if (empty($this->logregex)) $this->logregex = $svnrep->getProperty($parent, 'bugtraq:logregex'); |
60 | | | if (empty($this->urlstring)) $this->urlstring = $svnrep->getProperty($parent, 'bugtraq:url'); |
61 | 3 | simandl | if ($svnrep->getProperty($parent, 'bugtraq:append') == 'false') $this->append = false; |
62 | 1 | simandl | |
63 | | | $parent = substr($parent, 0, -1); // Remove the trailing slash |
64 | | | $pos = strrpos($parent, "/"); // Find the last trailing slash |
65 | | | $parent = substr($parent, 0, $pos + 1); // Find the previous parent directory |
66 | | | $enoughdata = ((!empty($this->msgstring) || !empty($this->logregex)) && !empty($this->urlstring)); |
67 | | | } |
68 | | | |
69 | | | $this->msgstring = trim(@$this->msgstring); |
70 | | | $this->urlstring = trim(@$this->urlstring); |
71 | | | |
72 | | | if ($enoughdata && !empty($this->msgstring)) { |
73 | | | $this->initPartInfo(); |
74 | | | } |
75 | | | |
76 | | | if ($enoughdata) { |
77 | | | $this->propsfound = true; |
78 | | | } |
79 | | | } |
80 | | | } |
81 | | | |
82 | | | // }}} |
83 | | | |
84 | | | // {{{ initPartInfo() |
85 | | | |
86 | | | function initPartInfo() { |
87 | | | if (($bugidpos = strpos($this->msgstring, "%BUGID%")) !== false && strpos($this->urlstring, "%BUGID%") !== false) { |
88 | | | // Get the textual parts of the message string for comparison purposes |
89 | | | $this->firstPart = substr($this->msgstring, 0, $bugidpos); |
90 | | | $this->firstPartLen = strlen($this->firstPart); |
91 | | | $this->lastPart = substr($this->msgstring, $bugidpos + 7); |
92 | | | $this->lastPartLen = strlen($this->lastPart); |
93 | | | } |
94 | | | } |
95 | | | |
96 | | | // }}} |
97 | | | |
98 | | | // {{{ replaceIDs($message) |
99 | | | |
100 | | | function replaceIDs($message) { |
101 | | | if ($this->propsfound) { |
102 | | | // First we search for the message string |
103 | | | |
104 | | | $logmsg = ""; |
105 | | | $message = rtrim($message); |
106 | | | |
107 | | | if ($this->append) { |
108 | | | // Just compare the last line |
109 | | | if (($offset = strrpos($message, "\n")) !== false) { |
110 | | | $logmsg = substr($message, 0, $offset + 1); |
111 | | | $bugLine = substr($message, $offset + 1); |
112 | | | } else { |
113 | | | $bugLine = $message; |
114 | | | } |
115 | | | } else { |
116 | | | if (($offset = strpos($message, "\n")) !== false) { |
117 | | | $bugLine = substr($message, 0, $offset); |
118 | | | $logmsg = substr($message, $offset); |
119 | | | } else { |
120 | | | $bugLine = $message; |
121 | | | } |
122 | | | } |
123 | | | |
124 | | | // Make sure that our line really is an issue tracker message |
125 | | | |
126 | | | if (isset($this->firstPart) && isset($this->lastPart) && ((strncmp($bugLine, $this->firstPart, $this->firstPartLen) == 0)) && strcmp(substr($bugLine, -$this->lastPartLen, $this->lastPartLen), $this->lastPart) == 0) { |
127 | | | // Get the issues list |
128 | | | if ($this->lastPartLen > 0) { |
129 | | | $issues = substr($bugLine, $this->firstPartLen, -$this->lastPartLen); |
130 | | | } else { |
131 | | | $issues = substr($bugLine, $this->firstPartLen); |
132 | | | } |
133 | | | |
134 | | | // Add each reference to the first part of the line |
135 | | | $line = $this->firstPart; |
136 | | | while ($pos = strpos($issues, ",")) { |
137 | | | $issue = trim(substr($issues, 0, $pos)); |
138 | | | $issues = substr($issues, $pos + 1); |
139 | | | |
140 | | | $line .= "<a href=\"".str_replace("%BUGID%", $issue, $this->urlstring)."\">$issue</a>, "; |
141 | | | } |
142 | | | $line .= "<a href=\"".str_replace("%BUGID%", trim($issues), $this->urlstring)."\">".trim($issues)."</a>".$this->lastPart; |
143 | | | |
144 | | | if ($this->append) { |
145 | | | $message = $logmsg.$line; |
146 | | | } else { |
147 | | | $message = $line.$logmsg; |
148 | | | } |
149 | | | } |
150 | | | |
151 | | | // Now replace all other instances of bug IDs that match the regex |
152 | | | |
153 | | | if ($this->logregex) { |
154 | | | $message = rtrim($message); |
155 | | | $line = ""; |
156 | | | $allissues = ""; |
157 | | | |
158 | 3 | simandl | $lines = explode("\n", $this->logregex); |
159 | 1 | simandl | $regex_all = "~".$lines[0]."~"; |
160 | | | $regex_single = @$lines[1]; |
161 | | | |
162 | | | if (empty($regex_single)) { |
163 | | | // If the property only contains one line, then the pattern is only designed |
164 | | | // to find one issue number at a time. e.g. [Ii]ssue #?(\d+). In this case |
165 | | | // we need to replace the matched issue ID with the link. |
166 | | | |
167 | | | if ($numMatches = preg_match_all($regex_all, $message, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { |
168 | | | $addedOffset = 0; |
169 | | | for ($match = 0; $match < $numMatches; $match++) { |
170 | | | $issue = $matches[$match][1][0]; |
171 | | | $issueOffset = $matches[$match][1][1]; |
172 | | | |
173 | | | $issueLink = "<a href=\"".str_replace("%BUGID%", $issue, $this->urlstring)."\">".$issue."</a>"; |
174 | | | $message = substr_replace($message, $issueLink, $issueOffset + $addedOffset, strlen($issue)); |
175 | | | $addedOffset += strlen($issueLink) - strlen($issue); |
176 | | | } |
177 | | | } |
178 | | | } else { |
179 | | | // It the property contains two lines, then the first is a pattern for extracting |
180 | | | // multiple issue numbers, and the second is a pattern extracting each issue |
181 | | | // number from the multiple match. e.g. [Ii]ssue #?(\d+)(,? ?#?(\d+))+ and (\d+) |
182 | | | |
183 | | | while (preg_match($regex_all, $message, $matches, PREG_OFFSET_CAPTURE)) { |
184 | | | $completeMatch = $matches[0][0]; |
185 | | | $completeMatchOffset = $matches[0][1]; |
186 | | | |
187 | | | $replacement = $completeMatch; |
188 | | | |
189 | | | if ($numMatches = preg_match_all("~".$regex_single."~", $replacement, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { |
190 | | | $addedOffset = 0; |
191 | | | for ($match = 0; $match < $numMatches; $match++) { |
192 | | | $issue = $matches[$match][1][0]; |
193 | | | $issueOffset = $matches[$match][1][1]; |
194 | | | |
195 | | | $issueLink = "<a href=\"".str_replace("%BUGID%", $issue, $this->urlstring)."\">".$issue."</a>"; |
196 | | | $replacement = substr_replace($replacement, $issueLink, $issueOffset + $addedOffset, strlen($issue)); |
197 | | | $addedOffset += strlen($issueLink) - strlen($issue); |
198 | | | } |
199 | | | } |
200 | | | |
201 | | | $message = substr_replace($message, $replacement, $completeMatchOffset, strlen($completeMatch)); |
202 | | | } |
203 | | | } |
204 | | | } |
205 | | | } |
206 | | | |
207 | | | return $message; |
208 | | | } |
209 | | | |
210 | | | // }}} |
211 | | | |
212 | | | } |
213 | | | |
214 | | | // The BugtraqTestable class is a derived class that is used to test the matching |
215 | | | // abilities of the Bugtraq class. In particular, it allows for the initialisation of the |
216 | | | // class without the need for a repository. |
217 | | | |
218 | | | class BugtraqTestable extends Bugtraq { |
219 | | | // {{{ __construct() |
220 | | | |
221 | | | function BugtraqTestable() { |
222 | | | // This constructor serves to assure that the parent constructor is not |
223 | | | // called. |
224 | | | } |
225 | | | |
226 | | | // }}} |
227 | | | |
228 | | | // {{{ setUpVars($message, $url, $regex, $append) |
229 | | | |
230 | | | function setUpVars($message, $url, $regex, $append) { |
231 | | | $this->msgstring = $message; |
232 | | | $this->urlstring = $url; |
233 | | | $this->logregex = $regex; |
234 | | | $this->append = $append; |
235 | | | $this->propsfound = true; |
236 | | | |
237 | | | $this->initPartInfo(); |
238 | | | } |
239 | | | |
240 | | | // }}} |
241 | | | |
242 | | | // {{{ setMessage($message) |
243 | | | |
244 | | | function setMessage($message) { |
245 | | | $this->msgstring = $message; |
246 | | | } |
247 | | | |
248 | | | // }}} |
249 | | | |
250 | | | // {{{ setUrl($url) |
251 | | | |
252 | | | function setUrl($url) { |
253 | | | $this->urlstring = $url; |
254 | | | } |
255 | | | |
256 | | | // }}} |
257 | | | |
258 | | | // {{{ setRegex($regex) |
259 | | | |
260 | | | function setRegEx($regex) { |
261 | | | $this->logregex = $regex; |
262 | | | } |
263 | | | |
264 | | | // }}} |
265 | | | |
266 | | | // {{{ setAppend($append) |
267 | | | |
268 | | | function setAppend($append) { |
269 | | | $this->append = $append; |
270 | | | } |
271 | | | |
272 | | | // }}} |
273 | | | |
274 | | | // {{{ printVars() |
275 | | | |
276 | | | function printVars() { |
277 | | | echo "msgstring = ".$this->msgstring."\n"; |
278 | | | echo "urlstring = ".$this->urlstring."\n"; |
279 | | | echo "logregex = ".$this->logregex."\n"; |
280 | | | echo "append = ".$this->append."\n"; |
281 | | | |
282 | | | echo "firstPart = ".$this->firstPart."\n"; |
283 | | | echo "firstPartLen = ".$this->firstPartLen."\n"; |
284 | | | echo "lastPart = ".$this->lastPart."\n"; |
285 | | | echo "lastPartLen = ".$this->lastPartLen."\n"; |
286 | | | } |
287 | | | |
288 | | | // }}} |
289 | | | } |
290 | | | |
291 | | | // {{{ test_bugtraq() |
292 | | | |
293 | | | function test_bugtraq() { |
294 | | | $tester = new BugtraqTestable; |
295 | | | |
296 | | | $tester->setUpVars("BugID: %BUGID%", |
297 | | | "http://bugtracker/?id=%BUGID%", |
298 | | | "[Ii]ssue #?(\d+)", |
299 | | | true |
300 | | | ); |
301 | | | |
302 | | | //$tester->printVars(); |
303 | | | |
304 | | | $res = $tester->replaceIDs("BugID: 789\n". |
305 | | | "This is a test message that refers to issue #123 and\n". |
306 | | | "issue #456.\n". |
307 | | | "BugID: 789" |
308 | | | ); |
309 | | | |
310 | | | echo nl2br($res)."<p>"; |
311 | | | |
312 | | | $res = $tester->replaceIDs("BugID: 789, 101112\n". |
313 | | | "This is a test message that refers to issue #123 and\n". |
314 | | | "issue #456.\n". |
315 | | | "BugID: 789, 101112" |
316 | | | ); |
317 | | | |
318 | | | echo nl2br($res)."<p>"; |
319 | | | |
320 | | | $tester->setAppend(false); |
321 | | | |
322 | | | $res = $tester->replaceIDs("BugID: 789\n". |
323 | | | "This is a test message that refers to issue #123 and\n". |
324 | | | "issue #456.\n". |
325 | | | "BugID: 789" |
326 | | | ); |
327 | | | |
328 | | | echo nl2br($res)."<p>"; |
329 | | | |
330 | | | $res = $tester->replaceIDs("BugID: 789, 101112\n". |
331 | | | "This is a test message that refers to issue #123 and\n". |
332 | | | "issue #456.\n". |
333 | | | "BugID: 789, 101112" |
334 | | | ); |
335 | | | |
336 | | | echo nl2br($res)."<p>"; |
337 | | | |
338 | | | $tester->setUpVars("BugID: %BUGID%", |
339 | | | "http://bugtracker/?id=%BUGID%", |
340 | | | "[Ii]ssues?:?(\s*(,|and)?\s*#\d+)+\n(\d+)", |
341 | | | true |
342 | | | ); |
343 | | | |
344 | | | $res = $tester->replaceIDs("BugID: 789, 101112\n". |
345 | | | "This is a test message that refers to issue #123 and\n". |
346 | | | "issues #456, #654 and #321.\n". |
347 | | | "BugID: 789, 101112" |
348 | | | ); |
349 | | | |
350 | | | echo nl2br($res)."<p>"; |
351 | | | |
352 | | | $tester->setUpVars("Test: %BUGID%", |
353 | | | "http://bugtracker/?id=%BUGID%", |
354 | | | "\s*[Cc]ases*\s*[IDs]*\s*[#: ]+((\d+[ ,:;#]*)+)\n(\d+)", |
355 | | | true |
356 | | | ); |
357 | | | |
358 | | | $res = $tester->replaceIDs("Cosmetic change\n". |
359 | | | "CaseIDs: 48" |
360 | | | ); |
361 | | | |
362 | | | echo nl2br($res)."<p>"; |
363 | | | } |
364 | | | |
365 | | | // }}} |