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