Rev Author Line No. Line
4988 kaklik 1 <?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 __construct($rep, $svnrep, $path) {
49 global $config;
50  
51 if ($rep->isBugtraqEnabled()) {
52 $enoughdata = false;
53 if (($properties = $rep->getBugtraqProperties()) !== null) {
54 $this->msgstring = $properties['bugtraq:message'];
55 $this->logregex = $properties['bugtraq:logregex'];
56 $this->urlstring = $properties['bugtraq:url'];
57 $this->append = $properties['bugtraq:append'];
58 $enoughdata = true;
59 } else {
60 $pos = strrpos($path, '/');
61 $parent = substr($path, 0, $pos + 1);
62 $this->append = true;
63  
64 while (!$enoughdata && (strpos($parent, '/') !== false)) {
65 $properties = $svnrep->getProperties($parent);
66 if (empty($this->msgstring) && in_array('bugtraq:message', $properties)) $this->msgstring = $svnrep->getProperty($parent, 'bugtraq:message');
67 if (empty($this->logregex) && in_array('bugtraq:logregex', $properties)) $this->logregex = $svnrep->getProperty($parent, 'bugtraq:logregex');
68 if (empty($this->urlstring) && in_array('bugtraq:url', $properties)) $this->urlstring = $svnrep->getProperty($parent, 'bugtraq:url');
69 if (in_array('bugtraq:append', $properties) && $svnrep->getProperty($parent, 'bugtraq:append') == 'false') $this->append = false;
70  
71 $parent = substr($parent, 0, -1); // Remove the trailing slash
72 $pos = strrpos($parent, '/'); // Find the last trailing slash
73 $parent = substr($parent, 0, $pos + 1); // Find the previous parent directory
74 $enoughdata = ((!empty($this->msgstring) || !empty($this->logregex)) && !empty($this->urlstring));
75 }
76 }
77  
78 $this->msgstring = trim(@$this->msgstring);
79 $this->urlstring = trim(@$this->urlstring);
80  
81 if ($enoughdata && !empty($this->msgstring)) {
82 $this->initPartInfo();
83 }
84  
85 if ($enoughdata) {
86 $this->propsfound = true;
87 }
88 }
89 }
90  
91 // }}}
92  
93 // {{{ initPartInfo()
94  
95 function initPartInfo() {
96 if (($bugidpos = strpos($this->msgstring, '%BUGID%')) !== false && strpos($this->urlstring, '%BUGID%') !== false) {
97 // Get the textual parts of the message string for comparison purposes
98 $this->firstPart = substr($this->msgstring, 0, $bugidpos);
99 $this->firstPartLen = strlen($this->firstPart);
100 $this->lastPart = substr($this->msgstring, $bugidpos + 7);
101 $this->lastPartLen = strlen($this->lastPart);
102 }
103 }
104  
105 // }}}
106  
107 // {{{ replaceIDs($message)
108  
109 function replaceIDs($message) {
110 if (!$this->propsfound) return $message;
111  
112 // First we search for the message string
113 $logmsg = '';
114 $message = rtrim($message);
115  
116 if ($this->append) {
117 // Just compare the last line
118 if (($offset = strrpos($message, "\n")) !== false) {
119 $logmsg = substr($message, 0, $offset + 1);
120 $bugLine = substr($message, $offset + 1);
121 } else {
122 $bugLine = $message;
123 }
124 } else {
125 if (($offset = strpos($message, "\n")) !== false) {
126 $bugLine = substr($message, 0, $offset);
127 $logmsg = substr($message, $offset);
128 } else {
129 $bugLine = $message;
130 }
131 }
132  
133 // Make sure that our line really is an issue tracker message
134 if (isset($this->firstPart) && isset($this->lastPart) && ((strncmp($bugLine, $this->firstPart, $this->firstPartLen) == 0)) && strcmp(substr($bugLine, -$this->lastPartLen, $this->lastPartLen), $this->lastPart) == 0) {
135 // Get the issues list
136 if ($this->lastPartLen > 0) {
137 $issues = substr($bugLine, $this->firstPartLen, -$this->lastPartLen);
138 } else {
139 $issues = substr($bugLine, $this->firstPartLen);
140 }
141  
142 // Add each reference to the first part of the line
143 $line = $this->firstPart;
144 while ($pos = strpos($issues, ',')) {
145 $issue = trim(substr($issues, 0, $pos));
146 $issues = substr($issues, $pos + 1);
147  
148 $line .= '<a href="'.str_replace('%BUGID%', $issue, $this->urlstring).'">'.$issue.'</a>, ';
149 }
150 $line .= '<a href="'.str_replace('%BUGID%', trim($issues), $this->urlstring).'">'.trim($issues).'</a>'.$this->lastPart;
151  
152 if ($this->append) {
153 $message = $logmsg.$line;
154 } else {
155 $message = $line.$logmsg;
156 }
157 }
158  
159 // Now replace all other instances of bug IDs that match the regex
160 if ($this->logregex) {
161 $message = rtrim($message);
162 $line = '';
163 $allissues = '';
164  
165 $lines = explode("\n", $this->logregex);
166 $regex_all = '~'.$lines[0].'~';
167 $regex_single = @$lines[1];
168  
169 if (empty($regex_single)) {
170 // If the property only contains one line, then the pattern is only designed
171 // to find one issue number at a time. e.g. [Ii]ssue #?(\d+). In this case
172 // we need to replace the matched issue ID with the link.
173  
174 if ($numMatches = preg_match_all($regex_all, $message, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
175 $addedOffset = 0;
176 for ($match = 0; $match < $numMatches; $match++) {
177 $issue = $matches[$match][1][0];
178 $issueOffset = $matches[$match][1][1];
179  
180 $issueLink = '<a href="'.str_replace('%BUGID%', $issue, $this->urlstring).'">'.$issue.'</a>';
181 $message = substr_replace($message, $issueLink, $issueOffset + $addedOffset, strlen($issue));
182 $addedOffset += strlen($issueLink) - strlen($issue);
183 }
184 }
185 } else {
186 // It the property contains two lines, then the first is a pattern for extracting
187 // multiple issue numbers, and the second is a pattern extracting each issue
188 // number from the multiple match. e.g. [Ii]ssue #?(\d+)(,? ?#?(\d+))+ and (\d+)
189  
190 while (preg_match($regex_all, $message, $matches, PREG_OFFSET_CAPTURE)) {
191 $completeMatch = $matches[0][0];
192 $completeMatchOffset = $matches[0][1];
193  
194 $replacement = $completeMatch;
195  
196 if ($numMatches = preg_match_all('~'.$regex_single.'~', $replacement, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
197 $addedOffset = 0;
198 for ($match = 0; $match < $numMatches; $match++) {
199 $issue = $matches[$match][1][0];
200 $issueOffset = $matches[$match][1][1];
201  
202 $issueLink = '<a href="'.str_replace('%BUGID%', $issue, $this->urlstring).'">'.$issue.'</a>';
203 $replacement = substr_replace($replacement, $issueLink, $issueOffset + $addedOffset, strlen($issue));
204 $addedOffset += strlen($issueLink) - strlen($issue);
205 }
206 }
207  
208 $message = substr_replace($message, $replacement, $completeMatchOffset, strlen($completeMatch));
209 }
210 }
211 }
212  
213 return $message;
214 }
215  
216 // }}}
217  
218 }
219  
220 // The BugtraqTestable class is a derived class that is used to test the matching
221 // abilities of the Bugtraq class. In particular, it allows for the initialisation of the
222 // class without the need for a repository.
223  
224 class BugtraqTestable extends Bugtraq {
225 // {{{ __construct()
226  
227 function __construct() {
228 // This constructor serves to assure that the parent constructor is not
229 // called.
230 }
231  
232 // }}}
233  
234 // {{{ setUpVars($message, $url, $regex, $append)
235  
236 function setUpVars($message, $url, $regex, $append) {
237 $this->msgstring = $message;
238 $this->urlstring = $url;
239 $this->logregex = $regex;
240 $this->append = $append;
241 $this->propsfound = true;
242  
243 $this->initPartInfo();
244 }
245  
246 // }}}
247  
248 // {{{ setMessage($message)
249  
250 function setMessage($message) {
251 $this->msgstring = $message;
252 }
253  
254 // }}}
255  
256 // {{{ setUrl($url)
257  
258 function setUrl($url) {
259 $this->urlstring = $url;
260 }
261  
262 // }}}
263  
264 // {{{ setRegex($regex)
265  
266 function setRegEx($regex) {
267 $this->logregex = $regex;
268 }
269  
270 // }}}
271  
272 // {{{ setAppend($append)
273  
274 function setAppend($append) {
275 $this->append = $append;
276 }
277  
278 // }}}
279  
280 // {{{ printVars()
281  
282 function printVars() {
283 echo 'msgstring = '.$this->msgstring."\n";
284 echo 'urlstring = '.$this->urlstring."\n";
285 echo 'logregex = '.$this->logregex."\n";
286 echo 'append = '.$this->append."\n";
287  
288 echo 'firstPart = '.$this->firstPart."\n";
289 echo 'firstPartLen = '.$this->firstPartLen."\n";
290 echo 'lastPart = '.$this->lastPart."\n";
291 echo 'lastPartLen = '.$this->lastPartLen."\n";
292 }
293  
294 // }}}
295 }
296  
297 // {{{ test_bugtraq()
298  
299 function test_bugtraq() {
300 $tester = new BugtraqTestable;
301  
302 $tester->setUpVars('BugID: %BUGID%',
303 'http://bugtracker/?id=%BUGID%',
304 '[Ii]ssue #?(\d+)',
305 true
306 );
307  
308 //$tester->printVars();
309  
310 $res = $tester->replaceIDs('BugID: 789'."\n".
311 'This is a test message that refers to issue #123 and'."\n".
312 'issue #456.'."\n".
313 'BugID: 789'
314 );
315  
316 echo nl2br($res).'<p>';
317  
318 $res = $tester->replaceIDs('BugID: 789, 101112'."\n".
319 'This is a test message that refers to issue #123 and'."\n".
320 'issue #456.'."\n".
321 'BugID: 789, 101112'
322 );
323  
324 echo nl2br($res).'<p>';
325  
326 $tester->setAppend(false);
327  
328 $res = $tester->replaceIDs('BugID: 789'."\n".
329 'This is a test message that refers to issue #123 and'."\n".
330 'issue #456.'."\n".
331 'BugID: 789'
332 );
333  
334 echo nl2br($res).'<p>';
335  
336 $res = $tester->replaceIDs('BugID: 789, 101112'."\n".
337 'This is a test message that refers to issue #123 and'."\n".
338 'issue #456.'."\n".
339 'BugID: 789, 101112'
340 );
341  
342 echo nl2br($res).'<p>';
343  
344 $tester->setUpVars('BugID: %BUGID%',
345 'http://bugtracker/?id=%BUGID%',
346 '[Ii]ssues?:?(\s*(,|and)?\s*#\d+)+\n(\d+)',
347 true
348 );
349  
350 $res = $tester->replaceIDs('BugID: 789, 101112'."\n".
351 'This is a test message that refers to issue #123 and'."\n".
352 'issues #456, #654 and #321.'."\n".
353 'BugID: 789, 101112'
354 );
355  
356 echo nl2br($res).'<p>';
357  
358 $tester->setUpVars('Test: %BUGID%',
359 'http://bugtracker/?id=%BUGID%',
360 '\s*[Cc]ases*\s*[IDs]*\s*[#: ]+((\d+[ ,:;#]*)+)\n(\d+)',
361 true
362 );
363  
364 $res = $tester->replaceIDs('Cosmetic change'."\n".
365 'CaseIDs: 48'
366 );
367  
368 echo nl2br($res).'<p>';
369 }
370  
371 // }}}