urgent multi-draft fixes from amalfi
[stet:stet.git] / rtNoAuth / changeshown.html
1 <!-- Copyright (C) 2006   Software Freedom Law Center, Inc.
2 --   Author: Orion Montoya <orion@mdcclv.com>
3 --
4 -- This software gives you freedom; it is licensed to you under version
5 -- 3 of the GNU Affero General Public License, along with the
6 -- additional permission in the following paragraph.
7 --
8 -- This notice constitutes a grant of such permission as is necessary
9 -- to combine or link this software, or a modified version of it, with
10 -- Request Tracker (RT), published by Jesse Vincent and Best Practical
11 -- Solutions, LLC, or a derivative work of RT, and to copy, modify, and
12 -- distribute the resulting work.  RT is licensed under version 2 of
13 -- the GNU General Public License.
14 -- 
15 -- This software is distributed WITHOUT ANY WARRANTY, without even the
16 -- implied warranties of MERCHANTABILITY and FITNESS FOR A PARTICULAR
17 -- PURPOSE.  See the GNU Affero General Public License for further
18 -- details.
19 --  
20 -- You should have received a copy of the GNU Affero General Public
21 -- License, version 3, and the GNU General Public License, version 2,
22 -- along with this software.  If not, see <http://www.gnu.org/licenses/>.
23 -->
24 <html>
25 <head>
26 <title>gpl comment system - query builder</title>
27 <script type="text/javascript" src="/comments/stet.js"></script>
28 <link rel="stylesheet" type="text/css" href="/comments/stet.css"/>
29 </head>
30 <body>
31 <div id="topbar" class="topbar">
32 <span id="statustext" class="statustext">Select what you want to search for</span>
33 % my ($CurrentUser, $resp, $name) = getUser("foo");
34 % if ($name) {
35 <span id="login" class="login">you are <% $name %>: <a href="http://gplv3.fsf.org/logout">logout</a></span></div>
36 % } else {
37 </span><span id="login" class="login">you could <a href="http://gplv3.fsf.org/login_form?came_from=/comments/rt/changeshown.html">login</a></span></div>
38 % }
39 <& header.html, name => $name &>
40 <div id="maintext">
41 <FORM METHOD="GET" ACTION="/comments/rt/changeshown.html" NAME="BuildQuery">
42 <input type=hidden name=SearchId value="new">
43 <input type=hidden name=Query value="">
44 % my $qs;
45 % if (!$ARGS{'came_from'}) {
46 % $qs = $ENV{'HTTP_REFERER'};
47 % } else {
48 % $qs = $ARGS{'came_from'};
49 % }
50 % $qs =~ s/(.*)\?.*/$1/;
51 % $qs =~ s/.*\/(.*)/$1/;
52 % $qs =~ s/(.html|.xml)$//;
53 %# $came_from = $qs;
54 <h3 class="searchheader">Search for comments on <% $qs %></h3>
55 <input type=hidden name="came_from" value="<% $came_from %>">
56 <table>
57 <tr>
58 <td valign=top class="boxcontainer">
59 <p>Aggregator:<input type=radio NAME="AndOr" CHECKED VALUE="AND">AND</input>
60 <input type=radio NAME="AndOr" VALUE="OR">OR</input>
61 </p>
62   <tr><td>
63 <tr><td align=right>
64 <SELECT NAME="AttachmentField">
65 <OPTION VALUE="Content">Comment</OPTION>
66 <OPTION VALUE="Subject">Subject</OPTION>
67 </SELECT>
68 </td><td>
69 <SELECT NAME ="AttachmentOp">
70 <OPTION VALUE="LIKE" SELECTED>contains</OPTION>
71 <OPTION VALUE="NOT LIKE" >does not contain</OPTION>
72 </SELECT>
73 </td><td>
74 <Input Name="ValueOfAttachment" Size=20>
75 </td></tr>
76
77 <tr><td align=right>
78 Type
79 </td><td>
80 <SELECT NAME ="QueueOp">
81 <OPTION VALUE="=" SELECTED>is</OPTION>
82 <OPTION VALUE="!=" >isn&#39;t</OPTION>
83 </SELECT>
84 </td><td>
85 <SELECT NAME ="ValueOfQueue">
86 <OPTION VALUE="">-</OPTION>
87 <OPTION VALUE="Inbox" >Inbox
88 </OPTION>
89 <OPTION VALUE="Issues" >Issues
90 </OPTION>
91 % my $drafterQ = RT::Queue->new($CurrentUser);
92 % $drafterQ->Load('Drafter');
93 % if ($drafterQ->CurrentUserHasRight('SeeQueue')) {
94 <OPTION VALUE="Drafter">Drafters' comments</OPTION>
95 % }
96 </SELECT>
97 </td></tr>
98
99 <tr><td class="label" align="right">
100 <SELECT NAME="ActorField">
101 <OPTION VALUE="Creator">Submitter</OPTION>
102 </SELECT>
103 </td><td>
104 <SELECT NAME ="ActorOp">
105 <OPTION VALUE="=" SELECTED>is</OPTION>
106 <OPTION VALUE="!=" >isn&#39;t</OPTION>
107 </SELECT>
108 </td><td>
109 <Input type="text" NAME="ValueOfActor" size=20>
110 </td></tr>
111
112 <tr><td align=right>
113 <SELECT NAME="DateField">
114 <OPTION VALUE="Created">Created</OPTION>
115 <OPTION VALUE="LastUpdated">Last Updated</OPTION>
116 <OPTION VALUE="Updated">Updated</OPTION>
117 </SELECT>
118 </td><td>
119 <SELECT NAME ="DateOp">
120 <OPTION VALUE="&lt;">Before</OPTION>
121 <OPTION VALUE="=">On</OPTION>
122 <OPTION VALUE="&gt;">After</OPTION>
123 </SELECT>
124 </td><td>
125 <INPUT NAME="ValueOfDate" VALUE="" size=16> 
126 </td></tr>
127
128 <tr><td align=right>
129 <SELECT NAME="LinksField">
130 <OPTION VALUE="HasMember">Is a parent/Issue of</OPTION>
131 <OPTION VALUE="MemberOf">Is a child of/belongs to Issue</OPTION>
132 <OPTION VALUE="RefersTo">Links to external object</OPTION>
133 </SELECT>
134 </td><td>
135 <SELECT NAME ="LinksOp">
136 <OPTION VALUE="LIKE" SELECTED>contains</OPTION>
137 <OPTION VALUE="NOT LIKE" >doesn&#39;t contain</OPTION>
138 </SELECT>
139 </td><td>
140 <INPUT Name="ValueOfLinks" value="" SIZE=5>
141 </td></tr>
142 <tr><td align=right>
143 Id
144 </td><td>
145 <SELECT NAME ="idOp">
146 <OPTION VALUE="&lt;"
147 >less than</OPTION>
148 <OPTION VALUE="="
149 >equal to</OPTION>
150 <OPTION VALUE="&gt;"
151 >greater than</OPTION>
152 <OPTION VALUE="!="
153 >not equal to</OPTION>
154 </SELECT>
155 </td><td>
156 <INPUT Name="ValueOfid" SIZE=5>
157 </td></tr>
158
159
160 <tr><td class=label align="right">
161 Text selected for comment
162 </td>
163 <td>
164 <SELECT NAME ="&#39;CF.NoteSelection&#39;Op">
165 <OPTION VALUE="LIKE"
166 >contains</OPTION>
167 <OPTION VALUE="NOT LIKE"
168 >doesn&#39;t contain</OPTION>
169 <OPTION VALUE="="
170 >is</OPTION>
171 <OPTION VALUE="!="
172 >isn&#39;t</OPTION>
173 </SELECT>
174 </td>
175 <td>
176 <input name="ValueOf&#39;CF.NoteSelection&#39;" size="20">
177 </td></tr>
178
179 <tr><td class=label align="right">
180 Filename commented on
181 </td>
182 <td>
183 <SELECT disabled NAME ="&#39;CF.NoteUrl&#39;Op">
184 <OPTION VALUE="LIKE"
185 >matches</OPTION>
186 <OPTION VALUE="NOT LIKE"
187 >doesn&#39;t contain</OPTION>
188 <OPTION VALUE="="
189 >is</OPTION>
190 <OPTION VALUE="!="
191 >isn&#39;t</OPTION>
192 <OPTION VALUE="&lt;"
193 >less than</OPTION>
194 <OPTION VALUE="&gt;"
195 >greater than</OPTION>
196 </SELECT>
197 </td>
198 <td>
199 <input disabled name="ValueOf&#39;CF.NoteUrl&#39;" size="20" value="gplv3-draft-1">
200 </td></tr>
201
202 <input type="hidden" NAME="&#39;CF.NoteUrl&#39;Op" VALUE="LIKE">
203 <input type="hidden" name="ValueOf&#39;CF.NoteUrl&#39;" value="gplv3-draft-1">
204
205
206
207 <tr><td class=label align="right">
208 Section
209 </td>
210 <td>
211 <SELECT NAME ="&#39;CF.NoteStartNodeId&#39;Op">
212 <OPTION VALUE="LIKE"
213 >is</OPTION>
214 <OPTION VALUE="NOT LIKE"
215 >doesn&#39;t contain</OPTION>
216 <!-- <OPTION VALUE="="
217 >is</OPTION>
218 <OPTION VALUE="!="
219 >isn&#39;t</OPTION>
220 <OPTION VALUE="&lt;"
221 >less than</OPTION>
222 <OPTION VALUE="&gt;"
223 >greater than</OPTION> -->
224 </SELECT>
225 </td>
226 <td>
227 <!-- <input name="ValueOf&#39;CF.NoteStartNodeId&#39;" size="20"> -->
228 <select name="ValueOf&#39;CF.NoteStartNodeId&#39;">
229 <option selected value="">-</option>
230 <option value="gpl3.preamble">Preamble</option>
231 <option value="gpl3.definitions">0. Definitions</option>
232 <option value="gpl3.sourcecode">1. Source Code</option>
233 <option value="basicperms">2. Basic Permissions</option>
234 <option value="gpl3.drm">3. Digital Restrictions Management</option>
235 <option value="verbatimcopying">4. Verbatim Copying</option>
236 <option value="distribmod">5. Distributing Modified Source Versions</option>
237 <option value="nonsource">6. Non-Source Distribution</option>
238 <option value="licensecompat">7. License Compatibility</option>
239 <option value="termination">8. Termination</option>
240 <option value="notacontract">9. Not A Contract</option>
241 <option value="autolicense">10. Automatic Licensing of Downstream Users</option>
242 <option value="licensingpatents">11. Licensing of Patents</option>
243 <option value="gpl3.libertyordeath">12. Liberty or Death for the Program</option>
244 <option value="geolimits">[13. Geographical Limitations]</option>
245 <option value="revisedversions">14. Revised Versions of this License</option>
246 <option value="requestingexceptions">15. Requesting Exceptions</option>
247 <option value="nowarranty0">16. NO WARRANTY</option>
248 <option value="nowarranty1">17. NO WARRANTY (cont.)</option>
249 <option value="nottested">18. [not tested for safety-critical]</option>
250 <option value="howtoapply">How to Apply These Terms to Your New Programs</option>
251 </select></td></tr>
252
253 <tr><td class=label align="right">
254
255 Agreeers 
256 </td>
257 <td>
258 <SELECT NAME ="&#39;CF.Agreeers&#39;Op">
259 <OPTION VALUE="LIKE"
260 >contains</OPTION>
261 <OPTION VALUE="NOT LIKE"
262 >doesn&#39;t contain</OPTION>
263 <OPTION VALUE="="
264 >is</OPTION>
265 <OPTION VALUE="!="
266 >isn&#39;t</OPTION>
267 <OPTION VALUE="&lt;"
268 >less than</OPTION>
269 <OPTION VALUE="&gt;"
270 >greater than</OPTION>
271 </SELECT>
272 </td>
273 <td>
274 <input name="ValueOf&#39;CF.Agreeers&#39;" size="20">
275 </td></tr>
276
277 <tr><td class=label align="right">
278 DiscussionGroup 
279 </td>
280 <td>
281 <SELECT NAME ="&#39;CF.DiscussionGroup&#39;Op">
282 <OPTION VALUE="LIKE"
283 >contains</OPTION>
284 <OPTION VALUE="NOT LIKE"
285 >doesn&#39;t contain</OPTION>
286 <OPTION VALUE="="
287 >is</OPTION>
288 <OPTION VALUE="!="
289 >isn&#39;t</OPTION>
290 <OPTION VALUE="&lt;"
291 >less than</OPTION>
292 <OPTION VALUE="&gt;"
293 >greater than</OPTION>
294 </SELECT>
295 </td>
296 <td>
297 <select name="ValueOf&#39;CF.DiscussionGroup&#39;">
298 <option value="" SELECTED>-</option>
299 <option value="NULL">(no value)</option>
300 <option value="A">A</option>
301 <option value="B">B</option>
302 <option value="C">C</option>
303 <option value="D">D</option>
304 <option value="E">E</option>
305 <option value="F">F</option>
306 </select>
307 </td></tr>
308 <tr>
309 <td class=label align="right">
310 Rows per page:
311 </td><td>
312 <SELECT NAME ="RowsPerPage">
313 <OPTION VALUE="0" >
314 Unlimited
315 </OPTION>
316 <OPTION VALUE="10" >
317 10
318 </OPTION>
319 <OPTION VALUE="30" SELECTED>
320 30
321 </OPTION>
322 <OPTION VALUE="50" >
323 50
324 </OPTION>
325 <OPTION VALUE="100" >
326 100
327 </OPTION>
328 </SELECT>
329 </td>
330 </tr>
331
332
333 </table>
334
335 <TABLE WIDTH=100% CELLSPACING=0 BORDER=0 CELLPADDING=0 >
336 <TR>
337 <TD>
338 &nbsp;
339 </TD>
340 <TD ALIGN=RIGHT VALIGN=CENTER>
341 <B>Find comments</b> <INPUT TYPE=SUBMIT
342 NAME="DoSearch"
343  VALUE='Search'>
344 </TD>
345 </TR>
346 </TABLE>
347
348 </div>
349
350 </FORM>
351 </body>
352 </html>
353 <%INIT>
354 use Tree::Simple;
355
356
357 my $search_hash = {};
358 my $search;
359 my $title = loc("Query Builder");
360
361 # {{{ Clear out unwanted data
362 if ($NewQuery or $ARGS{'Delete'}) {
363     # Wipe all data-carrying variables clear if we want a new
364     # search, or we're deleting an old one..
365     $Query = '';
366     $Format = '';
367     $Description = '';
368     $SearchId = '';
369     $Order = '';
370     $OrderBy = '';
371     $RowsPerPage = '';
372     # ($search hasn't been set yet; no need to clear)
373
374     # ..then wipe the session out..
375     undef $session{'CurrentSearchHash'};
376
377     # ..and the search results.
378     $session{'tickets'}->CleanSlate() if defined $session{'tickets'};
379 }
380 # }}}
381
382 # {{{ Attempt to load what we can from the session, set defaults
383
384 # We don't read or write to the session again until the end
385 $search_hash = $session{'CurrentSearchHash'};
386
387 # These variables are what define a search_hash; this is also
388 # where we give sane defaults.
389 $Query ||= $search_hash->{'Query'};
390 $Format ||= $search_hash->{'Format'};
391 $Description ||= $search_hash->{'Description'};
392 $SearchId ||= $search_hash->{'SearchId'} || 'new';
393 $Order ||= $search_hash->{'Order'} || 'DESC';
394 $OrderBy ||= $search_hash->{'OrderBy'} || 'id';
395 $RowsPerPage = ($search_hash->{'RowsPerPage'} || 30) unless defined ($RowsPerPage);
396 $search ||= $search_hash->{'Object'};
397 # }}}
398
399 my @actions = ();
400 my %queues;
401
402 # Clean unwanted junk from the format
403 $Format = $m->comp('/Elements/ScrubHTML', Content => $Format) if ($Format);
404
405 # {{{ If we're asked to delete the current search, make it go away and reset the search parameters
406 if ( $ARGS{'Delete'} ) {
407     # We set $SearchId to 'new' above already, so peek into the %ARGS
408     if ( $ARGS{'SearchId'} =~ /^(.*?)-(\d+)-SavedSearch-(\d+)$/ ) {
409         my $obj_type  = $1;
410         my $obj_id    = $2;
411         my $search_id = $3;
412         
413         my $container_object;
414         if ( $obj_type eq 'RT::User' && $obj_id == $session{'CurrentUser'}->Id)  {
415             $container_object =    $session{'CurrentUser'}->UserObj;
416         }
417         elsif ($obj_type eq 'RT::Group') {
418             $container_object = RT::Group->new($session{'CurrentUser'});
419             $container_object->Load($obj_id);
420         }
421
422         if ($container_object->id ) { 
423             # We have the object the entry is an attribute on; delete
424             # the entry..
425             $container_object->Attributes->DeleteEntry( Name => 'SavedSearch', id   => $search_id);
426         }
427
428     }
429 }
430 # }}}
431
432 # {{{ If the user wants to copy a search, uncouple from the one that this was based on, but don't erase the $Query or $Format
433 if ( $ARGS{'CopySearch'} ) {
434     $SearchId = 'new';
435     $search = undef;
436     $Description = loc("[_1] copy", $Description);
437 }
438 # }}}
439
440 # {{{ if we're asked to revert the current search, we just want to load it
441 if ( $ARGS{'Revert'} ) {
442     $ARGS{'LoadSavedSearch'} = $SearchId;
443 }
444 # }}}
445
446 # {{{ if we're asked to load a search, load it.
447
448 if ( $ARGS{'LoadSavedSearch'} =~ /^(.*?)-(\d+)-SavedSearch-(\d+)$/ ) {
449     my $obj_type  = $1;
450     my $obj_id    = $2;
451     my $search_id = $3;
452     
453     # We explicitly list out the available types (user and group) and
454     # don't trust user input here
455     if (   ( $obj_type eq 'RT::User' ) && ( $obj_id == $session{'CurrentUser'}->id ) ) {
456         $search = $session{'CurrentUser'}->UserObj->Attributes->WithId($search_id);
457         
458     }
459     elsif ($obj_type eq 'RT::Group')  {
460         my $group = RT::Group->new($session{'CurrentUser'});
461         $group->Load($obj_id);
462         $search = $group->Attributes->WithId($search_id);
463     }
464
465     # We have a $search and now; import the others
466     $SearchId    = $ARGS{'LoadSavedSearch'};
467     $Description = $search->Description;
468     $Format      = $search->SubValue('Format');
469     $Query       = $search->SubValue('Query');
470     $Order       = $search->SubValue('Order');
471     $OrderBy     = $search->SubValue('OrderBy');
472     $RowsPerPage = $search->SubValue('RowsPerPage');
473 }
474
475 # }}}
476
477 # {{{ Parse the query
478 my $tree;
479 ParseQuery( $Query, \$tree, \@actions );
480
481 # if parsing went poorly, send them to the edit page to fix it
482 if ( $actions[0] ) {
483     $m->comp( "Edit.html", Query => $Query, actions => \@actions );
484     $m->abort();
485 }
486
487 my @options;
488 my $optionlist;
489 $Query  = "";
490 %queues = ();
491
492 # Build the optionlist from the tree, so we can do additions and movements based on it
493 $optionlist = build_array( \$Query, $ARGS{clauses}, $tree, \@options, \%queues );
494
495 my $currentkey;
496 $currentkey = $options[$ARGS{clauses}] if defined $ARGS{clauses};
497
498 # {{{ Try to find if we're adding a clause
499 foreach my $arg ( keys %ARGS ) {
500     if ( $arg =~ m/ValueOf(.+)/ && $ARGS{$arg} ne "") {
501         # We're adding a $1 clause
502         my $field = $1;
503         my ($keyword, $op, $value);
504
505         #figure out if it's a grouping
506         if ( $ARGS{ $field . "Field" } ) {
507             $keyword = $ARGS{ $field . "Field" };
508         }
509         else {
510             $keyword = $field;
511         }
512
513         $value = $ARGS{'ValueOf' . $field};
514         $op = $ARGS{ $field . 'Op' };
515         if ( $value eq 'NULL' && $op =~ /=/) {
516             if ($op eq '=') {
517                 $op = "IS";
518             } elsif ($op eq '!=') {
519                 $op = "IS NOT";
520             }
521
522             # This isn't "right", but...
523             # It has to be this way until #5182 is fixed
524             $value = "'NULL'";
525         } else {
526             $value = "'$value'";
527         }
528
529         my $clause = {
530             Key   => $keyword,
531             Op    => $op,
532             Value => $value
533         };
534             
535         my $newnode = Tree::Simple->new($clause);
536         if ($currentkey) {
537             my $newindex = $currentkey->getIndex() + 1;
538             if (!$currentkey->getParent->getParent()->isRoot()) {
539             }
540             $currentkey->insertSibling($newindex, $newnode);
541             $currentkey = $newnode;
542         }
543         else {
544             $tree->getChild(0)->addChild($newnode);
545             $currentkey = $newnode;
546         }
547         $newnode->getParent()->setNodeValue($ARGS{'AndOr'});
548     }
549 }
550 # }}}
551
552 # {{{ Move things around
553 if ( $ARGS{"Up"} ) {
554     if ($currentkey) {
555         my $index = $currentkey->getIndex();
556         if ( $currentkey->getIndex() > 0 ) {
557             my $parent = $currentkey->getParent();
558             $parent->removeChild($index);
559             $parent->insertChild($index - 1, $currentkey);
560             $currentkey = $parent->getChild($index - 1);
561         }
562         else {
563             push( @actions, [ "error: can't move up", -1 ] );
564         }
565     }
566     else {
567         push( @actions, [ "error: nothing to move", -1 ] );
568     }
569 }
570 elsif ( $ARGS{"Down"} ) {
571     if ($currentkey) {
572         my $index = $currentkey->getIndex();
573         my $parent = $currentkey->getParent();
574         if ( $currentkey->getIndex() < ($parent->getChildCount - 1) ) {
575             $parent->removeChild($index);
576             $parent->insertChild($index + 1, $currentkey);
577             $currentkey = $parent->getChild($index + 1);
578         }
579         else {
580             push( @actions, [ "error: can't move down", -1 ] );
581         }
582     }
583     else {
584         push( @actions, [ "error: nothing to move", -1 ] );
585     }
586 }
587 elsif ( $ARGS{"Left"} ) {
588     if ($currentkey) {
589         my $parent = $currentkey->getParent();
590         my $grandparent = $parent->getParent();
591         if (!$grandparent->isRoot) {
592             my $index = $parent->getIndex();
593             $parent->removeChild($currentkey);
594             $grandparent->insertChild($index, $currentkey);
595             if ($parent->isLeaf()) {
596                 $grandparent->removeChild($parent);
597             }
598         }
599         else {
600             push( @actions, [ "error: can't move left", -1 ] );
601         }
602     }
603     else {
604         push( @actions, [ "error: nothing to move", -1 ] );
605     }
606 }
607 elsif ( $ARGS{"Right"} ) {
608     if ($currentkey) {
609         my $parent = $currentkey->getParent();
610         my $index = $currentkey->getIndex();
611         my $newparent;
612         if ($index > 0 ) {
613             my $sibling = $parent->getChild($index - 1);
614             if (ref($sibling->getNodeValue)) {
615                 $parent->removeChild($currentkey);
616                 my $newtree = Tree::Simple->new('AND', $parent);
617                 $newtree->addChild($currentkey);
618             } else {
619                 $parent->removeChild($index);
620                 $sibling->addChild($currentkey);
621             }
622         }
623         else {
624             $parent->removeChild($currentkey);
625             $newparent = Tree::Simple->new('AND', $parent);
626             $newparent->addChild($currentkey);
627         }
628     } else {
629         push( @actions, [ "error: nothing to move", -1 ] );
630     }
631 }
632 elsif ( $ARGS{"DeleteClause"} ) {
633     if ($currentkey) {
634         $currentkey->getParent()->removeChild($currentkey);
635     }
636     else {
637         push( @actions, [ "error: nothing to delete", -1 ] );
638     }
639 }
640 elsif ( $ARGS{"Toggle"} ) {
641     my $ea;
642     if ($currentkey) {
643         my $value = $currentkey->getNodeValue();
644         my $parent = $currentkey->getParent();
645         my $parentvalue = $parent->getNodeValue();
646
647         if ( $parentvalue eq 'AND') {
648             $parent->setNodeValue('OR');
649         }
650         else {
651             $parent->setNodeValue('AND');
652         }
653     }
654     else {
655         push( @actions, [ "error: nothing to toggle", -1 ] );
656     }
657 }
658 elsif ( $ARGS{"Clear"} ) {
659     $tree = Tree::Simple->new(Tree::Simple->ROOT);
660 }
661 # }}}
662
663 # {{{ Rebuild $Query based on the additions / movements
664 $Query   = "";
665 @options = ();
666 %queues  = ();
667 $optionlist = build_array( \$Query, $currentkey, $tree, \@options, \%queues );
668
669 sub build_array {
670     my $Query     = shift;
671     my $currentkey = shift;
672     my $tree = shift;
673     my ($keys, $queues)    = @_;
674     my $i = 0;
675     my $optionlist;
676     my $depth = 0;
677     my %parens;
678
679     $tree->traverse( sub {
680         my ($_tree) = @_;
681
682         return if $_tree->getParent->isRoot();
683
684         push @$keys, $_tree;
685         my $clause = $_tree->getNodeValue();
686         my $str;
687         my $ea = $_tree->getParent()->getNodeValue();
688         if (ref($clause)) {
689             $str .= $ea . " " if $_tree->getIndex() > 0;
690             $str .= $clause->{Key} . " " . $clause->{Op} . " " . $clause->{Value};
691         
692             if ( $clause->{Key} eq "Queue" ) {
693                 $queues->{ $clause->{Value} } = 1;
694             }
695         } else {
696             $str = $ea if $_tree->getIndex() > 0;
697         }
698
699         my $selected;
700         if ($_tree == $currentkey) {
701             $selected = "SELECTED";
702         }
703         else {
704             $selected = "";
705         }
706
707         foreach my $p (keys %parens) {
708             if ($p > $_tree->getDepth) {
709                 $$Query .= ')' x $parens{$p};
710                 $parens{$p}--;
711             }
712         }
713
714         $optionlist .= "<option value=$i $selected>" .
715           ("&nbsp;" x 5 x ($_tree->getDepth() - 1)) . "$str</option>\n";
716         my $parent = $_tree->getParent();
717         if (!($parent->isRoot || $parent->getParent()->isRoot) &&
718             !ref($parent->getNodeValue())) {
719             if ( $_tree->getIndex() == 0) {
720                 $$Query .= '(';
721                 $parens{$_tree->getDepth}++;
722             }
723         }
724         $$Query .= " " . $str . " ";
725
726         if ($_tree->getDepth < $depth) {
727             $$Query .= ')';
728             $parens{$depth}--;
729         }
730
731         $i++;
732     });
733
734     foreach my $p (keys %parens) {
735         $$Query .= ") " x $parens{$p};
736     }
737
738     return $optionlist;
739
740 }
741
742 use Regexp::Common qw /delimited/;
743
744 # States
745 use constant VALUE   => 1;
746 use constant AGGREG  => 2;
747 use constant OP      => 4;
748 use constant PAREN   => 8;
749 use constant KEYWORD => 16;
750
751 sub ParseQuery {
752     my $string = shift;
753     my $tree = shift;
754     my @actions = shift;
755     my $want   = KEYWORD | PAREN;
756     my $last   = undef;
757
758     my $depth = 1;
759
760     # make a tree root
761     $$tree = Tree::Simple->new(Tree::Simple->ROOT);
762     my $root = Tree::Simple->new('AND', $$tree);
763     my $lastnode = $root;
764     my $parentnode = $root;
765
766     # get the FIELDS from Tickets_Overlay
767     my $tickets = new RT::Tickets( $session{'CurrentUser'} );
768     my %FIELDS  = %{ $tickets->FIELDS };
769
770     # Lower Case version of FIELDS, for case insensitivity
771     my %lcfields = map { ( lc($_) => $_ ) } ( keys %FIELDS );
772
773     my @tokens     = qw[VALUE AGGREG OP PAREN KEYWORD];
774     my $re_aggreg  = qr[(?i:AND|OR)];
775     my $re_value   = qr[$RE{delimited}{-delim=>qq{\'\"}}|\d+];
776     my $re_keyword = qr[$RE{delimited}{-delim=>qq{\'\"}}|(?:\{|\}|\w|\.)+];
777     my $re_op      = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)] ;    # long to short
778     my $re_paren = qr'\(|\)';
779
780     # assume that $ea is AND if it's not set
781     my ( $ea, $key, $op, $value ) = ( "AND", "", "", "" );
782
783     # order of matches in the RE is important.. op should come early,
784     # because it has spaces in it.  otherwise "NOT LIKE" might be parsed
785     # as a keyword or value.
786
787     while ( $string =~ /(
788                       $re_aggreg
789                       |$re_op
790                       |$re_keyword
791                       |$re_value
792                       |$re_paren
793                      )/igx
794       )
795     {
796         my $val     = $1;
797         my $current = 0;
798
799         # Highest priority is last
800         $current = OP    if _match( $re_op,    $val );
801         $current = VALUE if _match( $re_value, $val );
802         $current = KEYWORD
803           if _match( $re_keyword, $val ) && ( $want & KEYWORD );
804         $current = AGGREG if _match( $re_aggreg, $val );
805         $current = PAREN  if _match( $re_paren,  $val );
806
807         unless ( $current && $want & $current ) {
808
809             # Error
810             # FIXME: I will only print out the highest $want value
811             my $token = $tokens[ ( ( log $want ) / ( log 2 ) ) ];
812             push @actions, [ "current: $current, want $want, Error near ->$val<- expecting a " . $token . " in '$string'\n", -1 ];
813         }
814
815         # State Machine:
816         my $parentdepth = $depth;
817
818         # Parens are highest priority
819         if ( $current & PAREN ) {
820             if ( $val eq "(" ) {
821                 $depth++;
822                 # make a new node that the clauses can be children of
823                 $parentnode = Tree::Simple->new($ea, $parentnode);
824             }
825             else {
826                 $depth--;
827                 $parentnode = $parentnode->getParent();
828                 $lastnode = $parentnode;
829             }
830
831             $want = KEYWORD | PAREN | AGGREG;
832         }
833         elsif ( $current & AGGREG ) {
834             $ea = $val;
835             $want = KEYWORD | PAREN;
836         }
837         elsif ( $current & KEYWORD ) {
838             $key  = $val;
839             $want = OP;
840         }
841         elsif ( $current & OP ) {
842             $op   = $val;
843             $want = VALUE;
844         }
845         elsif ( $current & VALUE ) {
846             $value = $val;
847
848             # Remove surrounding quotes from $key, $val
849             # (in future, simplify as for($key,$val) { action on $_ })
850             if ( $key =~ /$RE{delimited}{-delim=>qq{\'\"}}/ ) {
851                 substr( $key, 0,  1 ) = "";
852                 substr( $key, -1, 1 ) = "";
853             }
854             if ( $val =~ /$RE{delimited}{-delim=>qq{\'\"}}/ ) {
855                 substr( $val, 0,  1 ) = "";
856                 substr( $val, -1, 1 ) = "";
857             }
858
859             # Unescape escaped characters
860             $key =~ s!\\(.)!$1!g;
861             $val =~ s!\\(.)!$1!g;
862
863             my $class;
864             if ( exists $lcfields{ lc $key } ) {
865                 $key   = $lcfields{ lc $key };
866                 $class = $FIELDS{$key}->[0];
867             }
868             if ( $class ne 'INT' ) {
869                 $val = "'$val'";
870             }
871
872             push @actions, [ "Unknown field: $key", -1 ] unless $class;
873
874             $want = PAREN | AGGREG;
875         }
876         else {
877             push @actions, [ "I'm lost", -1 ];
878         }
879
880         if ( $current & VALUE ) {
881             if ( $key =~ /^CF./ ) {
882                 $key = "'" . $key . "'";
883             }
884             my $clause = {
885                 Key   => $key,
886                 Op    => $op,
887                 Value => $val
888             };
889
890             # explicity add a child to it
891             $lastnode = Tree::Simple->new($clause, $parentnode);
892             $lastnode->getParent()->setNodeValue($ea);
893
894             ( $ea, $key, $op, $value ) = ( "", "", "", "" );
895         }
896
897         $last = $current;
898     }    # while
899
900     push @actions, [ "Incomplete query", -1 ]
901       unless ( ( $want | PAREN ) || ( $want | KEYWORD ) );
902
903     push @actions, [ "Incomplete Query", -1 ]
904       unless ( $last && ( $last | PAREN ) || ( $last || VALUE ) );
905
906     # This will never happen, because the parser will complain
907     push @actions, [ "Mismatched parentheses", -1 ]
908       unless $depth == 1;
909 }
910
911 sub _match {
912
913     # Case insensitive equality
914     my ( $y, $x ) = @_;
915     return 1 if $x =~ /^$y$/i;
916
917     #  return 1 if ((lc $x) eq (lc $y)); # Why isnt this equiv?
918     return 0;
919 }
920
921 sub debug {
922     my $message = shift;
923     $m->print($message . "<br>");
924 }
925
926 # }}}
927
928 # }}}
929
930 # {{{ Deal with format changes
931 my ($AvailableColumns, $CurrentFormat);
932 ($Format, $AvailableColumns, $CurrentFormat) = $m->comp('Elements/BuildFormatString', cfqueues => \%queues, %ARGS, Format => $Format);
933 # }}}
934
935 # {{{ if we're asked to save the current search, save it
936 if ( $ARGS{'Save'} ) {
937
938     if ($search && $search->id) {
939         # This search is based on a previously loaded search -- so
940         # just update the current search object with new values
941         $search->SetSubValues(
942             Format      => $Format,
943             Query       => $Query,
944             Order       => $Order,
945             OrderBy     => $OrderBy,
946             RowsPerPage => $RowsPerPage,
947         );
948         $search->SetDescription( $Description );
949
950     }
951     elsif ( $SearchId eq 'new' && $ARGS{'Owner'} =~ /^(.*?)-(\d+)$/ ) {
952         # We're saving a new search
953         my $obj_type  = $1;
954         my $obj_id    = $2;
955  
956
957         # Find out if we're saving on the user, or a group
958         my $container_object;
959         if ( $obj_type eq 'RT::User' && $obj_id == $session{'CurrentUser'}->Id)  {
960             $container_object = $session{'CurrentUser'}->UserObj;
961         }
962         elsif ($obj_type eq 'RT::Group') {
963             $container_object = RT::Group->new($session{'CurrentUser'});
964             $container_object->Load($obj_id);
965         }
966
967         if ($container_object->id ) { 
968             # If we got one or the other, add the saerch        
969             my ( $search_id, $search_msg ) = $container_object->AddAttribute(
970                                                                              Name        => 'SavedSearch',
971                                                                              Description => $Description,
972                                                                              Content     => {
973                                                                                              Format      => $Format,
974                                                                                              Query       => $Query,
975                                                                                              Order       => $Order,
976                                                                                              OrderBy     => $OrderBy,
977                                                                                              RowsPerPage => $RowsPerPage,
978                                                                                             }
979                                                                             );
980             $search = $session{'CurrentUser'}->UserObj->Attributes->WithId($search_id);
981             # Build new SearchId
982             $SearchId = ref( $session{'CurrentUser'}->UserObj ) . '-'
983               . $session{'CurrentUser'}->UserObj->Id . '-SavedSearch-' . $search->Id;
984         }
985         unless ($search->id) {
986             push @actions, [loc("Can't find a saved search to work with"), 0];
987         }
988
989     }
990     else {
991         push @actions, [loc("Can't save this search"), 0];
992     }
993
994 }
995 # }}}
996
997 # {{{ If we're modifying an old query, check if it has changed
998 my $dirty = 0;
999 $dirty = 1 if defined $search and 
1000   ($search->SubValue('Format')      ne $Format      or
1001    $search->SubValue('Query')       ne $Query       or 
1002    $search->SubValue('Order')       ne $Order       or
1003    $search->SubValue('OrderBy')     ne $OrderBy     or
1004    $search->SubValue('RowsPerPage') ne $RowsPerPage);
1005 # }}}
1006
1007 # {{{ Push the updates into the session so we don't loose 'em
1008 $search_hash->{'SearchId'} = $SearchId;
1009 $search_hash->{'Format'} = $Format;
1010 $search_hash->{'Query'} = $Query;
1011 $search_hash->{'Description'} = $Description;
1012 $search_hash->{'Object'} = $search;
1013 $search_hash->{'Order'} = $Order;
1014 $search_hash->{'OrderBy'} = $OrderBy;
1015 $search_hash->{'RowsPerPage'} = $RowsPerPage;
1016
1017 $session{'CurrentSearchHash'} = $search_hash;
1018 # }}}
1019
1020 # {{{ Show the results, if we were asked.
1021 if ( $ARGS{"DoSearch"} ) {
1022 my $redirurl = "$came_from?Query=$Query&Order=$Order&OrderBy=$OrderBy&Rows=$RowsPerPage";
1023    $m->redirect($redirurl);
1024     $m->abort();
1025 }
1026
1027 # }}}
1028
1029 # {{{ Build a querystring for the tabs
1030
1031 my $QueryString;
1032 if ($NewQuery) {
1033     $QueryString = '?NewQuery=1';
1034 } else {
1035     $QueryString = '?' . $m->comp('/Elements/QueryString', 
1036             Query => $Query,
1037             Format => $Format,
1038             Order => $Order,
1039             OrderBy => $OrderBy,
1040             Rows => $RowsPerPage) if ($Query);
1041 }
1042 # }}}
1043
1044 </%INIT>
1045
1046 <%ARGS>
1047 $SearchId => undef
1048 $Query => undef
1049 $Format => undef 
1050 $Description => undef
1051 $Order => undef
1052 $OrderBy => undef
1053 $RowsPerPage => 30
1054 $HideResults => 0
1055 $came_from => '/comments/rt/readsay.html'
1056 $Delete => 1
1057 $NewQuery => 1
1058 </%ARGS>
1059
1060