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