![]() ![]() | ![]() |
Breadcrumbs updated with proper help links.
1: # The LearningOnline Network with CAPA 2: # Search Catalog 3: # 4: # $Id: lonsearchcat.pm,v 1.229 2004/06/03 20:32:33 matthew Exp $ 5: # 6: # Copyright Michigan State University Board of Trustees 7: # 8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA). 9: # 10: # LON-CAPA is free software; you can redistribute it and/or modify 11: # it under the terms of the GNU General Public License as published by 12: # the Free Software Foundation; either version 2 of the License, or 13: # (at your option) any later version. 14: # 15: # LON-CAPA is distributed in the hope that it will be useful, 16: # but WITHOUT ANY WARRANTY; without even the implied warranty of 17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18: # GNU General Public License for more details. 19: # 20: # You should have received a copy of the GNU General Public License 21: # along with LON-CAPA; if not, write to the Free Software 22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23: # 24: # /home/httpd/html/adm/gpl.txt 25: # 26: # http://www.lon-capa.org/ 27: # 28: ############################################################################### 29: ############################################################################### 30: 31: =pod 32: 33: =head1 NAME 34: 35: lonsearchcat - LONCAPA Search Interface 36: 37: =head1 SYNOPSIS 38: 39: Search interface to LON-CAPAs digital library 40: 41: =head1 DESCRIPTION 42: 43: This module enables searching for a distributed browseable catalog. 44: 45: This is part of the LearningOnline Network with CAPA project 46: described at http://www.lon-capa.org. 47: 48: lonsearchcat presents the user with an interface to search the LON-CAPA 49: digital library. lonsearchcat also initiates the execution of a search 50: by sending the search parameters to LON-CAPA servers. The progress of 51: search (on a server basis) is displayed to the user in a separate window. 52: 53: =head1 Internals 54: 55: =over 4 56: 57: =cut 58: 59: ############################################################################### 60: ############################################################################### 61: 62: package Apache::lonsearchcat; 63: 64: use strict; 65: use Apache::Constants qw(:common :http); 66: use Apache::lonnet(); 67: use Apache::File(); 68: use CGI qw(:standard); 69: use Text::Query; 70: use GDBM_File; 71: use Apache::loncommon(); 72: use Apache::lonmysql(); 73: use Apache::lonmeta; 74: use Apache::lonhtmlcommon; 75: use Apache::lonlocal; 76: use LONCAPA::lonmetadata(); 77: use HTML::Entities(); 78: use Parse::RecDescent; 79: 80: ###################################################################### 81: ###################################################################### 82: ## 83: ## Global variables 84: ## 85: ###################################################################### 86: ###################################################################### 87: my %groupsearch_db; # Database hash used to save values for the 88: # groupsearch RAT interface. 89: my %persistent_db; # gdbm hash which holds data which is supposed to 90: # persist across calls to lonsearchcat.pm 91: 92: # The different view modes and associated functions 93: 94: my %Views = ("detailed" => \&detailed_citation_view, 95: "summary" => \&summary_view, 96: "fielded" => \&fielded_format_view, 97: "xml" => \&xml_sgml_view, 98: "compact" => \&compact_view); 99: 100: ###################################################################### 101: ###################################################################### 102: sub handler { 103: my $r = shift; 104: # &set_defaults(); 105: # 106: # set form defaults 107: # 108: my $hidden_fields;# Hold all the hidden fields used to keep track 109: # of the search system state 110: my $importbutton; # button to take the selected results and go to group 111: # sorting 112: my $diropendb; # The full path to the (temporary) search database file. 113: # This is set and used in &handler() and is also used in 114: # &output_results(). 115: my $bodytag; # LON-CAPA standard body tag, gotten from 116: # &Apache::lonnet::bodytag. 117: # No title, no table, just a <body> tag. 118: 119: my $loaderror=&Apache::lonnet::overloaderror($r); 120: if ($loaderror) { return $loaderror; } 121: # 122: my $closebutton; # button that closes the search window 123: # This button is different for the RAT compared to 124: # normal invocation. 125: # 126: &Apache::loncommon::content_type($r,'text/html'); 127: $r->send_http_header; 128: return OK if $r->header_only; 129: ## 130: ## Prevent caching of the search interface window. Hopefully this means 131: ## we will get the launch=1 passed in a little more. 132: &Apache::loncommon::no_cache($r); 133: ## 134: ## Pick up form fields passed in the links. 135: ## 136: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, 137: ['catalogmode','launch','acts','mode','form','element','pause', 138: 'phase','persistent_db_id','table','start','show', 139: 'cleargroupsort','titleelement']); 140: ## 141: ## The following is a trick - we wait a few seconds if asked to so 142: ## the daemon running the search can get ahead of the daemon 143: ## printing the results. We only need (theoretically) to do 144: ## this once, so the pause indicator is deleted 145: ## 146: if (exists($ENV{'form.pause'})) { 147: sleep(1); 148: delete($ENV{'form.pause'}); 149: } 150: ## 151: ## Initialize global variables 152: ## 153: my $domain = $r->dir_config('lonDefDomain'); 154: $diropendb= "/home/httpd/perl/tmp/". 155: "$ENV{'user.domain'}_$ENV{'user.name'}_searchcat.db"; 156: # 157: # set the name of the persistent database 158: # $ENV{'form.persistent_db_id'} can only have digits in it. 159: if (! exists($ENV{'form.persistent_db_id'}) || 160: ($ENV{'form.persistent_db_id'} =~ /\D/) || 161: ($ENV{'form.launch'} eq '1')) { 162: $ENV{'form.persistent_db_id'} = time; 163: } 164: $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); 165: my $persistent_db_file = "/home/httpd/perl/tmp/". 166: &Apache::lonnet::escape($domain). 167: '_'.&Apache::lonnet::escape($ENV{'user.name'}). 168: '_'.$ENV{'form.persistent_db_id'}.'_persistent_search.db'; 169: ## 170: &Apache::lonhtmlcommon::clear_breadcrumbs(); 171: if (exists($ENV{'request.course.id'}) && $ENV{'request.course.id'} ne '') { 172: &Apache::lonhtmlcommon::add_breadcrumb 173: ({href=>'/adm/searchcat?'. 174: 'catalogmode='.$ENV{'form.catalogmode'}. 175: '&launch='.$ENV{'form.launch'}. 176: '&mode='.$ENV{'form.mode'}, 177: text=>"Course and Catalog Search", 178: target=>'_top', 179: bug=>'Searching',}); 180: } else { 181: &Apache::lonhtmlcommon::add_breadcrumb 182: ({href=>'/adm/searchcat?'. 183: 'catalogmode='.$ENV{'form.catalogmode'}. 184: '&launch='.$ENV{'form.launch'}. 185: '&mode='.$ENV{'form.mode'}, 186: text=>"Catalog Search", 187: target=>'_top', 188: bug=>'Searching',}); 189: } 190: # 191: if ($ENV{'form.phase'} !~ m/(basic|adv|course)_search/) { 192: if (! &get_persistent_form_data($persistent_db_file)) { 193: if ($ENV{'form.phase'} =~ /(run_search|results)/) { 194: &Apache::lonnet::logthis('lonsearchcat:'. 195: 'Unable to recover data from '. 196: $persistent_db_file); 197: $r->print(<<END); 198: <html> 199: <head><title>LON-CAPA Search Error</title></head> 200: $bodytag 201: We were unable to retrieve data describing your search. This is a serious 202: error and has been logged. Please alert your LON-CAPA administrator. 203: </body> 204: </html> 205: END 206: return OK; 207: } 208: } 209: } else { 210: &clean_up_environment(); 211: } 212: ## 213: ## Clear out old values from groupsearch database 214: ## 215: untie %groupsearch_db if (tied(%groupsearch_db)); 216: if (($ENV{'form.cleargroupsort'} eq '1') || 217: (($ENV{'form.launch'} eq '1') && 218: ($ENV{'form.catalogmode'} eq 'groupsearch'))) { 219: if (tie(%groupsearch_db,'GDBM_File',$diropendb,&GDBM_WRCREAT(),0640)) { 220: &start_fresh_session(); 221: untie %groupsearch_db; 222: delete($ENV{'form.cleargroupsort'}); 223: } else { 224: # This is a stupid error to give to the user. 225: # It really tells them nothing. 226: $r->print('<html><head></head>'.$bodytag. 227: 'Unable to tie hash to db file</body></html>'); 228: return OK; 229: } 230: } 231: ## 232: ## Configure hidden fields 233: ## 234: $hidden_fields = '<input type="hidden" name="persistent_db_id" value="'. 235: $ENV{'form.persistent_db_id'}.'" />'."\n"; 236: if (exists($ENV{'form.catalogmode'})) { 237: $hidden_fields .= &hidden_field('catalogmode'); 238: } 239: if (exists($ENV{'form.form'})) { 240: $hidden_fields .= &hidden_field('form'); 241: } 242: if (exists($ENV{'form.element'})) { 243: $hidden_fields .= &hidden_field('element'); 244: } 245: if (exists($ENV{'form.titleelement'})) { 246: $hidden_fields .= &hidden_field('titleelement'); 247: } 248: if (exists($ENV{'form.mode'})) { 249: $hidden_fields .= &hidden_field('mode'); 250: } 251: ## 252: ## Configure dynamic components of interface 253: ## 254: if ($ENV{'form.catalogmode'} eq 'interactive') { 255: $closebutton="<input type='button' name='close' value='CLOSE' "; 256: if ($ENV{'form.phase'} =~ /(results|run_search)/) { 257: $closebutton .="onClick='parent.close()'"; 258: } else { 259: $closebutton .="onClick='self.close()'"; 260: } 261: $closebutton .=">\n"; 262: } elsif ($ENV{'form.catalogmode'} eq 'groupsearch') { 263: $closebutton="<input type='button' name='close' value='CLOSE' "; 264: if ($ENV{'form.phase'} =~ /(results|run_search)/) { 265: $closebutton .="onClick='parent.close()'"; 266: } else { 267: $closebutton .="onClick='self.close()'"; 268: } 269: $closebutton .= ">"; 270: $importbutton=<<END; 271: <input type='button' name='import' value='IMPORT' 272: onClick='javascript:select_group()'> 273: END 274: } else { 275: $closebutton = ''; 276: $importbutton = ''; 277: } 278: ## 279: ## Sanity checks on form elements 280: ## 281: if (!defined($ENV{'form.viewselect'})) { 282: if (($ENV{'form.catalogmode'} eq 'groupsearch') || 283: ($ENV{'form.catalogmode'} eq 'interactive')) { 284: $ENV{'form.viewselect'} ="Compact View"; 285: } else { 286: $ENV{'form.viewselect'} ="Detailed Citation View"; 287: } 288: } 289: $ENV{'form.phase'} = 'disp_basic' if (! exists($ENV{'form.phase'})); 290: $ENV{'form.show'} = 20 if (! exists($ENV{'form.show'})); 291: # 292: $ENV{'form.searchmode'} = 'basic' if (! exists($ENV{'form.searchmode'})); 293: if ($ENV{'form.phase'} eq 'adv_search' || 294: $ENV{'form.phase'} eq 'disp_adv') { 295: $ENV{'form.searchmode'} = 'advanced'; 296: } elsif ($ENV{'form.phase'} eq 'course_search') { 297: $ENV{'form.searchmode'} = 'course_search'; 298: } 299: # 300: if ($ENV{'form.searchmode'} eq 'advanced') { 301: &Apache::lonhtmlcommon::add_breadcrumb 302: ({href=>'/adm/searchcat?phase=disp_adv&'. 303: 'catalogmode='.$ENV{'form.catalogmode'}. 304: '&launch='.$ENV{'form.launch'}. 305: '&mode='.$ENV{'form.mode'}, 306: text=>"Advanced Search", 307: bug=>'Searching',}); 308: } elsif ($ENV{'form.searchmode'} eq 'course search') { 309: &Apache::lonhtmlcommon::add_breadcrumb 310: ({href=>'/adm/searchcat?phase=disp_adv&'. 311: 'catalogmode='.$ENV{'form.catalogmode'}. 312: '&launch='.$ENV{'form.launch'}. 313: '&mode='.$ENV{'form.mode'}, 314: text=>"Course Search", 315: bug=>'Searching',}); 316: } 317: ## 318: ## Switch on the phase 319: ## 320: if ($ENV{'form.phase'} eq 'disp_basic') { 321: &print_basic_search_form($r,$closebutton,$hidden_fields); 322: } elsif ($ENV{'form.phase'} eq 'disp_adv') { 323: &print_advanced_search_form($r,$closebutton,$hidden_fields); 324: } elsif ($ENV{'form.phase'} eq 'results') { 325: &display_results($r,$importbutton,$closebutton,$diropendb); 326: } elsif ($ENV{'form.phase'} =~ /^(sort|run_search)$/) { 327: my ($query,$customquery,$customshow,$libraries,$pretty_string) = 328: &get_persistent_data($persistent_db_file, 329: ['query','customquery','customshow', 330: 'libraries','pretty_string']); 331: if ($ENV{'form.phase'} eq 'sort') { 332: &print_sort_form($r,$pretty_string); 333: } elsif ($ENV{'form.phase'} eq 'run_search') { 334: &run_search($r,$query,$customquery,$customshow, 335: $libraries,$pretty_string); 336: } 337: } elsif ($ENV{'form.phase'} eq 'course_search') { 338: &course_search($r); 339: } elsif(($ENV{'form.phase'} eq 'basic_search') || 340: ($ENV{'form.phase'} eq 'adv_search')) { 341: # 342: # We are running a search, try to parse it 343: my ($query,$customquery,$customshow,$libraries) = 344: (undef,undef,undef,undef); 345: my $pretty_string; 346: if ($ENV{'form.phase'} eq 'basic_search') { 347: ($query,$pretty_string,$libraries) = 348: &parse_basic_search($r,$closebutton,$hidden_fields); 349: return OK if (! defined($query)); 350: &make_persistent({ basicexp => $ENV{'form.basicexp'}}, 351: $persistent_db_file); 352: } else { # Advanced search 353: ($query,$customquery,$customshow,$libraries,$pretty_string) 354: = &parse_advanced_search($r,$closebutton,$hidden_fields); 355: return OK if (! defined($query)); 356: } 357: &make_persistent({ query => $query, 358: customquery => $customquery, 359: customshow => $customshow, 360: libraries => $libraries, 361: pretty_string => $pretty_string }, 362: $persistent_db_file); 363: # 364: # Set up table 365: if (! defined(&create_results_table())) { 366: my $errorstring=&Apache::lonmysql::get_error(); 367: &Apache::lonnet::logthis('lonsearchcat.pm: Unable to create '. 368: 'needed table. lonmysql error:'. 369: $errorstring); 370: $r->print(<<END); 371: <html><head><title>Search Error</title></head> 372: $bodytag 373: Unable to create table in which to store search results. 374: The search has been aborted. 375: </body> 376: </html> 377: END 378: return OK; 379: } 380: delete($ENV{'form.launch'}); 381: if (! &make_form_data_persistent($r,$persistent_db_file)) { 382: $r->print(<<END); 383: <html><head><title>Search Error</title></head> 384: $bodytag 385: Unable to properly store search information. The search has been aborted. 386: </body> 387: </html> 388: END 389: return OK; 390: } 391: ## 392: ## Print out the frames interface 393: ## 394: if (defined($query)) { 395: &print_frames_interface($r); 396: } 397: } 398: return OK; 399: } 400: 401: # 402: # The mechanism used to store values away and retrieve them does not 403: # handle the case of missing environment variables being significant. 404: # 405: # This routine sets non existant checkbox form elements to ''. 406: # 407: sub clean_up_environment { 408: if ($ENV{'form.phase'} eq 'basic_search') { 409: if (! exists($ENV{'form.related'})) { 410: $ENV{'form.related'} = ''; 411: } 412: if (! exists($ENV{'form.domains'})) { 413: $ENV{'form.domains'} = ''; 414: } 415: } elsif ($ENV{'form.phase'} eq 'adv_search') { 416: foreach my $field ('title','keywords','notes', 417: 'abstract','standards','mime') { 418: if (! exists($ENV{'form.'.$field.'_related'})) { 419: $ENV{'form.'.$field.'_related'} = ''; 420: } 421: } 422: } elsif ($ENV{'form.phase'} eq 'course_search') { 423: if (! exists($ENV{'form.crsrelated'})) { 424: $ENV{'form.crsrelated'} = ''; 425: } 426: } 427: } 428: 429: sub hidden_field { 430: my ($name,$value) = @_; 431: if (! defined($value)) { 432: $value = $ENV{'form.'.$name}; 433: } 434: return '<input type="hidden" name="'.$name.'" value="'.$value.'" />'.$/; 435: } 436: 437: ###################################################################### 438: ###################################################################### 439: ## 440: ## Course Search 441: ## 442: ###################################################################### 443: ###################################################################### 444: { # Scope the course search to avoid global variables 445: # 446: # Variables For course search 447: my %alreadyseen; 448: my %hash; 449: my $totalfound; 450: 451: sub course_search { 452: my $r=shift; 453: my $bodytag=&Apache::loncommon::bodytag('Course Search'); 454: my $pretty_search_string = '<b>'.$ENV{'form.courseexp'}.'</b>'; 455: my $search_string = $ENV{'form.courseexp'}; 456: my @New_Words; 457: if ($ENV{'form.crsrelated'}) { 458: ($search_string,@New_Words) = &related_version($ENV{'form.courseexp'}); 459: if (@New_Words) { 460: $pretty_search_string .= ' '.&mt("with related words").": <b>@New_Words</b>."; 461: } else { 462: $pretty_search_string .= ' '.&mt('with no related words')."."; 463: } 464: } 465: my $fulltext=$ENV{'form.crsfulltext'}; 466: my @allwords=($search_string,@New_Words); 467: $totalfound=0; 468: $r->print('<html><head><title>LON-CAPA Course Search</title></head>'. 469: $bodytag.'<hr /><center><font size="+2" face="arial">'.$pretty_search_string.'</font></center><hr />'); 470: $r->rflush(); 471: # ======================================================= Go through the course 472: undef %alreadyseen; 473: %alreadyseen=(); 474: my $c=$r->connection; 475: if (tie(%hash,'GDBM_File',$ENV{'request.course.fn'}.".db", 476: &GDBM_READER(),0640)) { 477: foreach (keys %hash) { 478: if ($c->aborted()) { last; } 479: if (($_=~/^src\_(.+)$/) && (!$alreadyseen{$hash{$_}})) { 480: &checkonthis($r,$hash{$_},0,$hash{'title_'.$1},$fulltext, 481: @allwords); 482: } 483: } 484: untie(%hash); 485: } 486: unless ($totalfound) { 487: $r->print('<p>'.&mt('No resources found').'.</p>'); 488: } 489: # =================================================== Done going through course 490: $r->print('</body></html>'); 491: } 492: 493: # =============================== This pulls up a resource and its dependencies 494: 495: sub checkonthis { 496: my ($r,$url,$level,$title,$fulltext,@allwords)=@_; 497: $alreadyseen{$url}=1; 498: $r->rflush(); 499: my $result=&Apache::lonnet::metadata($url,'title').' '. 500: &Apache::lonnet::metadata($url,'subject').' '. 501: &Apache::lonnet::metadata($url,'abstract').' '. 502: &Apache::lonnet::metadata($url,'keywords'); 503: if (($url) && ($fulltext)) { 504: $result.=&Apache::lonnet::ssi_body($url); 505: } 506: $result=~s/\s+/ /gs; 507: my $applies=0; 508: foreach (@allwords) { 509: if ($_=~/\w/) { 510: if ($result=~/$_/si) { 511: $applies++; 512: } 513: } 514: } 515: # Does this resource apply? 516: if ($applies) { 517: $r->print('<br />'); 518: for (my $i=0;$i<=$level*5;$i++) { 519: $r->print(' '); 520: } 521: $r->print('<a href="'.$url.'" target="cat">'. 522: ($title?$title:$url).'</a><br />'); 523: $totalfound++; 524: } elsif ($fulltext) { 525: $r->print(' .'); 526: } 527: $r->rflush(); 528: # Check also the dependencies of this one 529: my $dependencies= 530: &Apache::lonnet::metadata($url,'dependencies'); 531: foreach (split(/\,/,$dependencies)) { 532: if (($_=~/^\/res\//) && (!$alreadyseen{$_})) { 533: &checkonthis($r,$_,$level+1,'',$fulltext,@allwords); 534: } 535: } 536: } 537: 538: sub untiehash { 539: if (tied(%hash)) { 540: untie(%hash); 541: } 542: } 543: 544: } # End of course search scoping 545: 546: sub search_html_header { 547: my $Str = <<ENDHEADER; 548: <html> 549: <head> 550: <title>The LearningOnline Network with CAPA</title> 551: </head> 552: ENDHEADER 553: return $Str; 554: } 555: 556: ###################################################################### 557: ###################################################################### 558: 559: =pod 560: 561: =item &print_basic_search_form() 562: 563: Prints the form for the basic search. Sorry the name is so cryptic. 564: 565: =cut 566: 567: ###################################################################### 568: ###################################################################### 569: sub print_basic_search_form { 570: my ($r,$closebutton,$hidden_fields) = @_; 571: my $result = ($ENV{'form.catalogmode'} ne 'groupsearch'); 572: my $bodytag=&Apache::loncommon::bodytag('Search'). 573: &Apache::lonhtmlcommon::breadcrumbs(undef,'Searching','Search_Basic', 574: undef,undef, 575: $ENV{'form.catalogmode'} ne 'groupsearch'); 576: my $scrout = &search_html_header().$bodytag; 577: if (&Apache::lonnet::allowed('bre',$ENV{'request.role.domain'})) { 578: # Define interface components 579: my $userelatedwords= 580: &mt('[_1] use related words', 581: &Apache::lonhtmlcommon::checkbox 582: ('related',$ENV{'form.related'},'related')); 583: my $onlysearchdomain= 584: &mt('[_1] only search domain [_2]', 585: &Apache::lonhtmlcommon::checkbox('domains', 586: $ENV{'form.domains'}, 587: $r->dir_config('lonDefDomain') 588: ), 589: $r->dir_config('lonDefDomain') 590: ); 591: my $adv_search_link = 592: '<a href="/adm/searchcat?'. 593: 'phase=disp_adv&'. 594: 'catalogmode='.$ENV{'form.catalogmode'}. 595: '&launch='.$ENV{'form.launch'}. 596: '&mode='.$ENV{'form.mode'}. 597: '">'.&mt('Advanced Search').'</a>'; 598: # 599: $scrout.='<form name="loncapa_search" method="post" '. 600: 'action="/adm/searchcat">'. 601: '<input type="hidden" name="phase" value="basic_search" />'. 602: $hidden_fields; 603: # 604: $scrout .= '<center>'.$/; 605: if ($ENV{'request.course.id'}) { 606: $scrout .= '<h1>'.&mt('LON-CAPA Catalog Search').'</h1>'; 607: } else { 608: # No need to tell them they are searching 609: $scrout.= ('<br />'x2); 610: } 611: $scrout.='<table>'. 612: '<tr><td align="center" valign="top">'. 613: &Apache::lonhtmlcommon::textbox 614: ('basicexp', 615: &HTML::Entities::encode($ENV{'form.basicexp'},'<>&"'),50 616: ). 617: '<br />'. 618: '<font size="-1">'.&searchhelp().'</font>'.'</td>'. 619: '<td><font size="-1">'. 620: '<nobr>'.(' 'x3).$adv_search_link.'</nobr>'.'<br />'. 621: '<nobr>'.(' 'x1).$userelatedwords.'</nobr>'.'<br />'. 622: '<nobr>'.(' 'x1).$onlysearchdomain.'</nobr>'.'<br />'. 623: '</font></td>'. 624: '</tr>'.$/; 625: # 626: $scrout .= '<tr><td align="center" colspan="2">'. 627: '<font size="-1">'. 628: '<input type="submit" name="basicsubmit" '. 629: 'value="'.&mt('Search').'" />'. 630: (' 'x2).$closebutton.(' 'x2). 631: &viewoptions(). 632: '</font>'. 633: '</td></tr>'.$/; 634: $scrout .= '</table>'.$/.'</center>'.'</form>'; 635: } 636: if ($ENV{'request.course.id'}) { 637: my %lt=&Apache::lonlocal::texthash('srch' => 'Search', 638: 'header' => 'Course Search', 639: 'note' => 'Enter terms or phrases, then press "Search" below', 640: 'use' => 'use related words', 641: 'full' =>'fulltext search (time consuming)' 642: ); 643: $scrout.=(<<ENDCOURSESEARCH); 644: <form name="loncapa_search" method="post" action="/adm/searchcat"> 645: <center> 646: <hr /> 647: <h1>$lt{'header'}</h1> 648: <input type="hidden" name="phase" value="course_search" /> 649: $hidden_fields 650: <p> 651: $lt{'note'}. 652: </p> 653: <p> 654: <table> 655: <tr><td> 656: ENDCOURSESEARCH 657: $scrout.=' '. 658: &Apache::lonhtmlcommon::textbox('courseexp', 659: $ENV{'form.courseexp'},40); 660: my $crscheckbox = 661: &Apache::lonhtmlcommon::checkbox('crsfulltext', 662: $ENV{'form.crsfulltext'}); 663: my $relcheckbox = 664: &Apache::lonhtmlcommon::checkbox('crsrelated', 665: $ENV{'form.crsrelated'}); 666: $scrout.=(<<ENDENDCOURSE); 667: </td></tr> 668: <tr><td>$relcheckbox $lt{'use'}</td><td></td></tr> 669: <tr><td>$crscheckbox $lt{'full'}</td><td></td></tr> 670: </table><p> 671: <input type="submit" name="coursesubmit" value='$lt{'srch'}' /> 672: </p> 673: </center> 674: </form> 675: ENDENDCOURSE 676: } 677: $scrout.=(<<ENDDOCUMENT); 678: </body> 679: </html> 680: ENDDOCUMENT 681: $r->print($scrout); 682: return; 683: } 684: ###################################################################### 685: ###################################################################### 686: 687: =pod 688: 689: =item &advanced_search_form() 690: 691: Prints the advanced search form. 692: 693: =cut 694: 695: ###################################################################### 696: ###################################################################### 697: sub print_advanced_search_form{ 698: my ($r,$closebutton,$hidden_fields) = @_; 699: my $bodytag=&Apache::loncommon::bodytag('Advanced Catalog Search'). 700: &Apache::lonhtmlcommon::breadcrumbs(undef,'Searching', 701: 'Search_Advanced', 702: undef,undef, 703: $ENV{'form.catalogmode'} ne 'groupsearch'); 704: my %lt=&Apache::lonlocal::texthash('srch' => 'Search', 705: 'reset' => 'Reset', 706: 'help' => 'Help'); 707: my $advanced_buttons=<<"END"; 708: <input type="submit" name="advancedsubmit" value='$lt{"srch"}' /> 709: <input type="reset" name="reset" value='$lt{"reset"}' /> 710: $closebutton 711: END 712: my $scrout=&search_html_header(); 713: $scrout .= <<"ENDHEADER"; 714: $bodytag 715: <form method="post" action="/adm/searchcat" name="advsearch"> 716: <p> 717: $advanced_buttons 718: ENDHEADER 719: $scrout.=(' 'x2).&viewoptions().'</p>'.$hidden_fields. 720: '<input type="hidden" name="phase" value="adv_search" />'; 721: my %fields=&Apache::lonmeta::fieldnames(); 722: # 723: $scrout .= '<h3>'.&mt('Standard Metadata').'</h3>'; 724: $scrout .= "<table>\n"; 725: $scrout .= '<tr><td> </td><td colspan="2"><font size="-1">'. 726: (' 'x2).&searchhelp()."</font></td></tr>\n"; 727: my %related_word_search = 728: ('title' => 1, 729: 'author' => 0, 730: 'owner' => 0, 731: 'authorspace' => 0, 732: 'modifyinguser'=> 0, 733: 'keywords' => 1, 734: 'notes' => 1, 735: 'abstract' => 1, 736: 'standards'=> 1, 737: 'mime' => 1, 738: ); 739: # 740: foreach my $field ('title','author','owner','authorspace','modifyinguser', 741: 'keywords','notes','abstract','standards','mime') { 742: $scrout.='<tr><td align="right">'.&titlefield($fields{$field}).'</td><td>'. 743: &Apache::lonmeta::prettyinput($field, 744: $ENV{'form.'.$field}, 745: $field, 746: 'advsearch', 747: $related_word_search{$field}, 748: '</td><td align="left">', 749: $ENV{'form.'.$field.'_related'}, 750: 50); 751: if ($related_word_search{$field}) { 752: $scrout .= 'related words'; 753: } else { 754: $scrout .= '</td><td> '; 755: } 756: $scrout .= '</td></tr>'.$/; 757: } 758: foreach my $field ('lowestgradelevel','highestgradelevel') { 759: $scrout.='<tr>'. 760: '<td align="right">'.&titlefield($fields{$field}).'</td>'. 761: '<td colspan="2">'. 762: &Apache::lonmeta::prettyinput($field, 763: $ENV{'form.'.$field}, 764: $field, 765: 'advsearch', 766: 0). 767: '</td></tr>'.$/; 768: } 769: $scrout.='<tr><td align="right">'. 770: &titlefield(&mt('MIME Type Category')).'</td><td colspan="2">'. 771: &Apache::loncommon::filecategoryselect('category', 772: $ENV{'form.category'}). 773: '</td></tr>'.$/; 774: $scrout.='<tr><td align="right" valign="top">'. 775: &titlefield(&mt('Domains')).'</td><td colspan="2">'. 776: &Apache::loncommon::domain_select('domains', 777: $ENV{'form.domains'},1). 778: '</td></tr>'.$/; 779: # 780: # Misc metadata 781: $scrout.='<tr><td align="right" valign="top">'. 782: &titlefield(&mt('Copyright/Distribution')).'</td><td colspan="2">'. 783: &Apache::lonmeta::selectbox('copyright', 784: '',, 785: \&Apache::loncommon::copyrightdescription, 786: ( undef, 787: &Apache::loncommon::copyrightids) 788: ).'</td></tr>'.$/; 789: $scrout.='<tr><td align="right" valign="top">'. 790: &titlefield(&mt('Language')).'</td><td colspan="2">'. 791: &Apache::lonmeta::selectbox('language', 792: 'notset',, 793: \&Apache::loncommon::languagedescription, 794: ('any',&Apache::loncommon::languageids) 795: ).'</td></tr>'; 796: $scrout .= "</table>\n"; 797: # 798: # Dynamic metadata 799: $scrout .= '<h3>'.&mt('Problem Statistics').'</h3>'; 800: $scrout .= "<table>\n"; 801: $scrout .= '<tr><td> </td><td align="center">'.&mt('Minimum').'</td>'. 802: '<td align="center">'.&mt('Maximum').'</td></tr>'."\n"; 803: foreach my $statistic 804: ({ name=>'count', 805: description=>'Network-wide number of accesses (hits)',}, 806: { name=>'stdno', 807: description=> 808: 'Total number of students who have worked on this problem',}, 809: { name => 'avetries', 810: description=>'Average number of tries till solved',}, 811: { name => 'difficulty', 812: description=>'Degree of difficulty',}, 813: { name => 'disc', 814: description=>'Degree of discrimination'}) { 815: $scrout .= '<tr><td align="right">'. 816: &titlefield(&mt($statistic->{'description'})). 817: '</td><td align="center">'. 818: '<input type="text" name="'.$statistic->{'name'}.'_min" '. 819: 'value="" size="6" />'. 820: '</td><td align="center">'. 821: '<input type="text" name="'.$statistic->{'name'}.'_max" '. 822: 'value="" size="6" />'. 823: '</td></tr>'.$/; 824: } 825: $scrout .= "</table>\n"; 826: $scrout .= '<h3>'.&mt('Evaluation Data').'</h3>'; 827: $scrout .= "<table>\n"; 828: $scrout .= '<tr><td> </td><td align="center">'.&mt('Minimum').'</td>'. 829: '<td align="center">'.&mt('Maximum').'</td></tr>'."\n"; 830: foreach my $evaluation 831: ( { name => 'clear', 832: description => 'Material presented in clear way'}, 833: { name =>'depth', 834: description => 'Material covered with sufficient depth'}, 835: { name => 'helpful', 836: description => 'Material is helpful'}, 837: { name => 'correct', 838: description => 'Material appears to be correct'}, 839: { name => 'technical', 840: description => 'Resource is technically correct'}){ 841: $scrout .= '<tr><td align="right">'. 842: &titlefield(&mt($evaluation->{'description'})). 843: '</td><td align="center">'. 844: '<input type="text" name="'.$evaluation->{'name'}.'_min" '. 845: 'value="" size="6" />'. 846: '</td><td align="center">'. 847: '<input type="text" name="'.$evaluation->{'name'}.'_max" '. 848: 'value="" size="6" />'. 849: '</td></tr>'.$/; 850: } 851: $scrout .= "</table>\n"; 852: # 853: # Creation/Modification date limits 854: $scrout .= '<h3>'.&mt('Creation and Modification dates').'</h3>'; 855: $scrout .= "\n<table>\n"; 856: my $cafter = 857: &Apache::lonhtmlcommon::date_setter('advsearch', # formname 858: 'creationdate1', # fieldname 859: 0, # current value 860: '', # special 861: 1, # includeempty 862: '', # state 863: 1, # no_hh_mm_ss 864: ); 865: my $cbefore = 866: &Apache::lonhtmlcommon::date_setter('advsearch', # formname 867: 'creationdate2', # fieldname 868: 0, # current value 869: '', # special 870: 1, # includeempty 871: '', # state 872: 1, # no_hh_mm_ss 873: ); 874: $scrout .= &mt('<tr><td align="right">Created between</td>'. 875: '<td>[_1]</td></tr>'. 876: '<tr><td align="right">and </td>'. 877: '<td>[_2]</td></tr>',$cafter,$cbefore); 878: my $lafter = 879: &Apache::lonhtmlcommon::date_setter('advsearch', 880: 'revisiondate1', 881: 0, # current value 882: '', # special 883: 1, # includeempty 884: '', # state 885: 1, # no_hh_mm_ss 886: ); 887: my $lbefore = 888: &Apache::lonhtmlcommon::date_setter('advsearch', 889: 'revisiondate2', 890: 0, # current value 891: '', # special 892: 1, # includeempty 893: '', # state 894: 1, # no_hh_mm_ss 895: ); 896: $scrout .= &mt('<tr><td align="right">Last modified between </td>'. 897: '<td>[_1]</td></tr>'. 898: '<tr><td align="right">and</td>'. 899: '<td>[_2]</td></tr>',$lafter,$lbefore); 900: $scrout.="</table>\n"; 901: $scrout.=<<ENDDOCUMENT; 902: $advanced_buttons 903: </form> 904: </body> 905: </html> 906: ENDDOCUMENT 907: $r->print($scrout); 908: return; 909: } 910: 911: ###################################################################### 912: ###################################################################### 913: 914: =pod 915: 916: =item &titlefield() 917: 918: Inputs: title text 919: 920: Outputs: titletext with font wrapper 921: 922: =cut 923: 924: ###################################################################### 925: ###################################################################### 926: sub titlefield { 927: my $title=shift; 928: return $title; 929: } 930: 931: ###################################################################### 932: ###################################################################### 933: 934: =pod 935: 936: =item viewoptiontext() 937: 938: Inputs: codename for view option 939: 940: Outputs: displayed text 941: 942: =cut 943: 944: ###################################################################### 945: ###################################################################### 946: sub viewoptiontext { 947: my $code=shift; 948: my %desc=&Apache::lonlocal::texthash 949: ('detailed' => "Detailed Citation View", 950: 'xml' => 'XML/SGML', 951: 'compact' => 'Compact View', 952: 'fielded' => 'Fielded Format', 953: 'summary' => 'Summary View'); 954: return $desc{$code}; 955: } 956: 957: ###################################################################### 958: ###################################################################### 959: 960: =pod 961: 962: =item viewoptions() 963: 964: Inputs: none 965: 966: Outputs: text for box with view options 967: 968: =cut 969: 970: ###################################################################### 971: ###################################################################### 972: sub viewoptions { 973: my $scrout; 974: if (! defined($ENV{'form.viewselect'})) { 975: $ENV{'form.viewselect'}='detailed'; 976: } 977: $scrout.=&Apache::lonmeta::selectbox('viewselect', 978: $ENV{'form.viewselect'}, 979: \&viewoptiontext, 980: sort(keys(%Views))); 981: $scrout.= ' '; 982: my $countselect = &Apache::lonmeta::selectbox('show', 983: $ENV{'form.show'}, 984: undef, 985: (10,20,50,100,1000,10000)); 986: $scrout .= (' 'x2).&mt('[_1] Records per Page',$countselect). 987: '</nobr>'.$/; 988: return $scrout; 989: } 990: 991: ###################################################################### 992: ###################################################################### 993: 994: =pod 995: 996: =item searchhelp() 997: 998: Inputs: none 999: 1000: Outputs: return little blurb on how to enter searches 1001: 1002: =cut 1003: 1004: ###################################################################### 1005: ###################################################################### 1006: sub searchhelp { 1007: return &mt('Enter words and quoted phrases'); 1008: } 1009: 1010: ###################################################################### 1011: ###################################################################### 1012: 1013: =pod 1014: 1015: =item &get_persistent_form_data() 1016: 1017: Inputs: filename of database 1018: 1019: Outputs: returns undef on database errors. 1020: 1021: This function is the reverse of &make_persistent() for form data. 1022: Retrieve persistent data from %persistent_db. Retrieved items will have their 1023: values unescaped. If a form value already exists in $ENV, it will not be 1024: overwritten. Form values that are array references may have values appended 1025: to them. 1026: 1027: =cut 1028: 1029: ###################################################################### 1030: ###################################################################### 1031: sub get_persistent_form_data { 1032: my $filename = shift; 1033: return 0 if (! -e $filename); 1034: return undef if (! tie(%persistent_db,'GDBM_File',$filename, 1035: &GDBM_READER(),0640)); 1036: # 1037: # These make sure we do not get array references printed out as 'values'. 1038: my %arrays_allowed = ('form.domains'=>1); 1039: # 1040: # Loop through the keys, looking for 'form.' 1041: foreach my $name (keys(%persistent_db)) { 1042: next if ($name !~ /^form./); 1043: # Kludgification begins! 1044: if ($name eq 'form.domains' && 1045: $ENV{'form.searchmode'} eq 'basic' && 1046: $ENV{'form.phase'} ne 'disp_basic') { 1047: next; 1048: } 1049: # End kludge (hopefully) 1050: next if (exists($ENV{$name})); 1051: my @values = map { 1052: &Apache::lonnet::unescape($_); 1053: } split(',',$persistent_db{$name}); 1054: next if (@values <1); 1055: if ($arrays_allowed{$name}) { 1056: $ENV{$name} = [@values]; 1057: } else { 1058: $ENV{$name} = $values[0] if ($values[0]); 1059: } 1060: } 1061: untie (%persistent_db); 1062: return 1; 1063: } 1064: 1065: ###################################################################### 1066: ###################################################################### 1067: 1068: =pod 1069: 1070: =item &get_persistent_data() 1071: 1072: Inputs: filename of database, ref to array of values to recover. 1073: 1074: Outputs: array of values. Returns undef on error. 1075: 1076: This function is the reverse of &make_persistent(); 1077: Retrieve persistent data from %persistent_db. Retrieved items will have their 1078: values unescaped. If the item contains commas (before unescaping), the 1079: returned value will be an array pointer. 1080: 1081: =cut 1082: 1083: ###################################################################### 1084: ###################################################################### 1085: sub get_persistent_data { 1086: my $filename = shift; 1087: my @Vars = @{shift()}; 1088: my @Values; # Return array 1089: return undef if (! -e $filename); 1090: return undef if (! tie(%persistent_db,'GDBM_File',$filename, 1091: &GDBM_READER(),0640)); 1092: foreach my $name (@Vars) { 1093: if (! exists($persistent_db{$name})) { 1094: push @Values, undef; 1095: next; 1096: } 1097: my @values = map { 1098: &Apache::lonnet::unescape($_); 1099: } split(',',$persistent_db{$name}); 1100: if (@values <= 1) { 1101: push @Values,$values[0]; 1102: } else { 1103: push @Values,\@values; 1104: } 1105: } 1106: untie (%persistent_db); 1107: return @Values; 1108: } 1109: 1110: ###################################################################### 1111: ###################################################################### 1112: 1113: =pod 1114: 1115: =item &make_persistent() 1116: 1117: Inputs: Hash of values to save, filename of persistent database. 1118: 1119: Store variables away to the %persistent_db. 1120: Values will be escaped. Values that are array pointers will have their 1121: elements escaped and concatenated in a comma separated string. 1122: 1123: =cut 1124: 1125: ###################################################################### 1126: ###################################################################### 1127: sub make_persistent { 1128: my %save = %{shift()}; 1129: my $filename = shift; 1130: return undef if (! tie(%persistent_db,'GDBM_File', 1131: $filename,&GDBM_WRCREAT(),0640)); 1132: foreach my $name (keys(%save)) { 1133: my @values = (ref($save{$name}) ? @{$save{$name}} : ($save{$name})); 1134: # We handle array references, but not recursively. 1135: my $store = join(',', map { &Apache::lonnet::escape($_); } @values ); 1136: $persistent_db{$name} = $store; 1137: } 1138: untie(%persistent_db); 1139: return 1; 1140: } 1141: 1142: ###################################################################### 1143: ###################################################################### 1144: 1145: =pod 1146: 1147: =item &make_form_data_persistent() 1148: 1149: Inputs: filename of persistent database. 1150: 1151: Store most form variables away to the %persistent_db. 1152: Values will be escaped. Values that are array pointers will have their 1153: elements escaped and concatenated in a comma separated string. 1154: 1155: =cut 1156: 1157: ###################################################################### 1158: ###################################################################### 1159: sub make_form_data_persistent { 1160: my $r = shift; 1161: my $filename = shift; 1162: my %save; 1163: foreach (keys(%ENV)) { 1164: next if (!/^form/ || /submit/); 1165: $save{$_} = $ENV{$_}; 1166: } 1167: return &make_persistent(\%save,$filename); 1168: } 1169: 1170: ###################################################################### 1171: ###################################################################### 1172: 1173: =pod 1174: 1175: =item &parse_advanced_search() 1176: 1177: Parse advanced search form and return the following: 1178: 1179: =over 4 1180: 1181: =item $query Scalar containing an SQL query. 1182: 1183: =item $customquery Scalar containing a custom query. 1184: 1185: =item $customshow Scalar containing commands to show custom metadata. 1186: 1187: =item $libraries_to_query Reference to array of domains to search. 1188: 1189: =back 1190: 1191: =cut 1192: 1193: ###################################################################### 1194: ###################################################################### 1195: sub parse_advanced_search { 1196: my ($r,$closebutton,$hidden_fields)=@_; 1197: my @BasicFields = ('title','author','subject','keywords','url','version', 1198: 'notes','abstract','extension','owner','authorspace', 1199: # 'custommetadata','customshow', 1200: 'modifyinguser','standards','mime'); 1201: my @StatsFields = &statfields(); 1202: my @EvalFields = &evalfields(); 1203: my $fillflag=0; 1204: my $pretty_search_string = "<br />\n"; 1205: # Clean up fields for safety 1206: for my $field (@BasicFields, 1207: 'creationdatestart_month','creationdatestart_day', 1208: 'creationdatestart_year','creationdateend_month', 1209: 'creationdateend_day','creationdateend_year', 1210: 'lastrevisiondatestart_month','lastrevisiondatestart_day', 1211: 'lastrevisiondatestart_year','lastrevisiondateend_month', 1212: 'lastrevisiondateend_day','lastrevisiondateend_year') { 1213: $ENV{'form.'.$field}=~s/[^\w\/\s\(\)\=\-\"\']//g; 1214: } 1215: foreach ('mode','form','element') { 1216: # is this required? Hmmm. 1217: next if (! exists($ENV{'form.'.$_})); 1218: $ENV{'form.'.$_}=&Apache::lonnet::unescape($ENV{'form.'.$_}); 1219: $ENV{'form.'.$_}=~s/[^\w\/\s\(\)\=\-\"\']//g; 1220: } 1221: # Preprocess the category form element. 1222: $ENV{'form.category'} = 'any' if (! defined($ENV{'form.category'}) || 1223: ref($ENV{'form.category'})); 1224: # 1225: # Check to see if enough information was filled in 1226: foreach my $field (@BasicFields) { 1227: if (&filled($ENV{'form.'.$field})) { 1228: $fillflag++; 1229: } 1230: } 1231: foreach my $field (@StatsFields,@EvalFields) { 1232: if (&filled($ENV{'form.'.$field.'_max'})) { 1233: $fillflag++; 1234: } 1235: if (&filled($ENV{'form.'.$field.'_min'})) { 1236: $fillflag++; 1237: } 1238: } 1239: 1240: for my $field ('lowestgradelevel','highestgradelevel') { 1241: if ( $ENV{'form.'.$field} =~ /^\d+$/ && 1242: $ENV{'form.'.$field} > 0) { 1243: $fillflag++; 1244: } 1245: } 1246: if (! $fillflag) { 1247: &output_blank_field_error($r,$closebutton, 1248: 'phase=disp_adv',$hidden_fields); 1249: return ; 1250: } 1251: # Turn the form input into a SQL-based query 1252: my $query=''; 1253: my @queries; 1254: my $font = '<font color="#800000" face="helvetica">'; 1255: # Evaluate logical expression AND/OR/NOT phrase fields. 1256: foreach my $field (@BasicFields) { 1257: next if (!defined($ENV{'form.'.$field}) || $ENV{'form.'.$field} eq ''); 1258: my ($error,$SQLQuery) = 1259: &process_phrase_input($ENV{'form.'.$field}, 1260: $ENV{'form.'.$field.'_related'},$field); 1261: if (defined($error)) { 1262: &output_unparsed_phrase_error($r,$closebutton,'phase=disp_adv', 1263: $hidden_fields,$field); 1264: return; 1265: } else { 1266: $pretty_search_string .= 1267: $font.$field.'</font>: '.$ENV{'form.'.$field}; 1268: if ($ENV{'form.'.$field.'_related'}) { 1269: my @Words = 1270: &Apache::loncommon::get_related_words 1271: ($ENV{'form.'.$field}); 1272: if (@Words) { 1273: $pretty_search_string.= ' with related words: '. 1274: join(', ',@Words[0..4]); 1275: } else { 1276: $pretty_search_string.= ' with related words.'; 1277: } 1278: } 1279: $pretty_search_string .= '<br />'; 1280: push (@queries,$SQLQuery); 1281: } 1282: } 1283: # 1284: # Make the 'mime' from 'form.category' and 'form.extension' 1285: # 1286: my $searchphrase; 1287: if (exists($ENV{'form.category'}) && 1288: $ENV{'form.category'} !~ /^\s*$/ && 1289: $ENV{'form.category'} ne 'any') { 1290: my @extensions = &Apache::loncommon::filecategorytypes 1291: ($ENV{'form.category'}); 1292: if (scalar(@extensions) > 0) { 1293: $searchphrase = join(' OR ',@extensions); 1294: } 1295: } 1296: if (defined($searchphrase)) { 1297: my ($error,$SQLsearch) = &process_phrase_input($searchphrase,0,'mime'); 1298: push @queries,$SQLsearch; 1299: $pretty_search_string .=$font.'mime</font> contains <b>'. 1300: $searchphrase.'</b><br />'; 1301: } 1302: # 1303: # Evaluate option lists 1304: if ($ENV{'form.lowestgradelevel'} && 1305: $ENV{'form.lowestgradelevel'} ne '0' && 1306: $ENV{'form.lowestgradelevel'} =~ /^\d+$/) { 1307: push(@queries, 1308: '(lowestgradelevel>='.$ENV{'form.lowestgradelevel'}.')'); 1309: $pretty_search_string.="lowestgradelevel>=". 1310: $ENV{'form.lowestgradelevel'}."<br />\n"; 1311: } 1312: if ($ENV{'form.highestgradelevel'} && 1313: $ENV{'form.highestgradelevel'} ne '0' && 1314: $ENV{'form.highestgradelevel'} =~ /^\d+$/) { 1315: push(@queries, 1316: '(highestgradelevel<='.$ENV{'form.highestgradelevel'}.')'); 1317: $pretty_search_string.="highestgradelevel<=". 1318: $ENV{'form.highestgradelevel'}."<br />\n"; 1319: } 1320: if ($ENV{'form.language'} and $ENV{'form.language'} ne 'any') { 1321: push @queries,"(language like \"$ENV{'form.language'}\")"; 1322: $pretty_search_string.=$font."language</font>= ". 1323: &Apache::loncommon::languagedescription($ENV{'form.language'}). 1324: "<br />\n"; 1325: } 1326: if ($ENV{'form.copyright'} and $ENV{'form.copyright'} ne 'any') { 1327: push @queries,"(copyright like \"$ENV{'form.copyright'}\")"; 1328: $pretty_search_string.=$font."copyright</font> = ". 1329: &Apache::loncommon::copyrightdescription($ENV{'form.copyright'}). 1330: "<br \>\n"; 1331: } 1332: # 1333: # Statistics 1334: foreach my $field (@StatsFields,@EvalFields) { 1335: my ($min,$max); 1336: if (exists($ENV{'form.'.$field.'_min'}) && 1337: $ENV{'form.'.$field.'_min'} ne '') { 1338: $min = $ENV{'form.'.$field.'_min'}; 1339: } 1340: if (exists($ENV{'form.'.$field.'_max'}) && 1341: $ENV{'form.'.$field.'_max'} ne '') { 1342: $max = $ENV{'form.'.$field.'_max'}; 1343: } 1344: next if (! defined($max) && ! defined($min)); 1345: if (defined($min) && defined($max)) { 1346: ($min,$max) = sort {$a <=>$b} ($min,$max); 1347: } 1348: if (defined($min) && $min =~ /^(\d+\.\d+|\d+|\.\d+)$/) { 1349: push(@queries,'('.$field.'>'.$min.')'); 1350: $pretty_search_string.=$font.$field.'</font>>'.$min.'<br />'; 1351: } 1352: if (defined($max) && $max =~ /^(\d+\.\d+|\d+|\.\d+)$/) { 1353: push(@queries,'('.$field.'<'.$max.')'); 1354: $pretty_search_string.=$font.$field.'</font><'.$max.'<br />'; 1355: } 1356: } 1357: # 1358: # Evaluate date windows 1359: my $cafter = 1360: &Apache::lonhtmlcommon::get_date_from_form('creationdate1'); 1361: my $cbefore = 1362: &Apache::lonhtmlcommon::get_date_from_form('creationdate2'); 1363: if ($cafter > $cbefore) { 1364: my $tmp = $cafter; 1365: $cafter = $cbefore; 1366: $cbefore = $tmp; 1367: } 1368: my $mafter = 1369: &Apache::lonhtmlcommon::get_date_from_form('revisiondate1'); 1370: my $mbefore = 1371: &Apache::lonhtmlcommon::get_date_from_form('revisiondate2'); 1372: if ($mafter > $mbefore) { 1373: my $tmp = $mafter; 1374: $mafter = $mbefore; 1375: $mbefore = $tmp; 1376: } 1377: my ($datequery,$error,$prettydate)=&build_date_queries($cafter,$cbefore, 1378: $mafter,$mbefore); 1379: if (defined($error)) { 1380: &output_date_error($r,$error,$closebutton,$hidden_fields); 1381: } elsif (defined($datequery)) { 1382: # Here is where you would set up pretty_search_string to output 1383: # date query information. 1384: $pretty_search_string .= '<br />'.$prettydate.'<br />'; 1385: push @queries,$datequery; 1386: } 1387: # 1388: # Process form information for custom metadata querying 1389: my $customquery=undef; 1390: ## 1391: ## The custom metadata search was removed q long time ago mostly 1392: ## because I was unable to figureout exactly how it worked and could 1393: ## not imagine people actually using it. MH 1394: ## 1395: # if ($ENV{'form.custommetadata'}) { 1396: # $pretty_search_string .=$font."Custom Metadata Search</font>: <b>". 1397: # $ENV{'form.custommetadata'}."</b><br />\n"; 1398: # $customquery=&build_custommetadata_query('custommetadata', 1399: # $ENV{'form.custommetadata'}); 1400: # } 1401: my $customshow=undef; 1402: # if ($ENV{'form.customshow'}) { 1403: # $pretty_search_string .=$font."Custom Metadata Display</font>: <b>". 1404: # $ENV{'form.customshow'}."</b><br />\n"; 1405: # $customshow=$ENV{'form.customshow'}; 1406: # $customshow=~s/[^\w\s]//g; 1407: # my @fields=split(/\s+/,$customshow); 1408: # $customshow=join(" ",@fields); 1409: # } 1410: ## 1411: ## Deal with restrictions to given domains 1412: ## 1413: my ($libraries_to_query,$pretty_domains_string) = 1414: &parse_domain_restrictions(); 1415: $pretty_search_string .= $pretty_domains_string."<br />\n"; 1416: # 1417: if (@queries) { 1418: $query="SELECT * FROM metadata WHERE (".join(") AND (",@queries).')'; 1419: } elsif ($customquery) { 1420: $query = ''; 1421: } 1422: # &Apache::lonnet::logthis('query = '.$/.$query); 1423: return ($query,$customquery,$customshow,$libraries_to_query, 1424: $pretty_search_string); 1425: } 1426: 1427: sub parse_domain_restrictions { 1428: my $libraries_to_query = undef; 1429: # $ENV{'form.domains'} can be either a scalar or an array reference. 1430: # We need an array. 1431: if (! exists($ENV{'form.domains'}) || $ENV{'form.domains'} eq '') { 1432: return (undef,''); 1433: } 1434: my @allowed_domains; 1435: if (ref($ENV{'form.domains'})) { 1436: @allowed_domains = @{$ENV{'form.domains'}}; 1437: } else { 1438: @allowed_domains = ($ENV{'form.domains'}); 1439: } 1440: # 1441: my %domain_hash = (); 1442: my $pretty_domains_string; 1443: foreach (@allowed_domains) { 1444: $domain_hash{$_}++; 1445: } 1446: if ($domain_hash{'any'}) { 1447: $pretty_domains_string = "In all LON-CAPA domains."; 1448: } else { 1449: if (@allowed_domains > 1) { 1450: $pretty_domains_string = "In LON-CAPA domains:"; 1451: } else { 1452: $pretty_domains_string = "In LON-CAPA domain "; 1453: } 1454: foreach (sort @allowed_domains) { 1455: $pretty_domains_string .= "<b>".$_."</b> "; 1456: } 1457: foreach (keys(%Apache::lonnet::libserv)) { 1458: if (exists($domain_hash{$Apache::lonnet::hostdom{$_}})) { 1459: push @$libraries_to_query,$_; 1460: } 1461: } 1462: } 1463: return ($libraries_to_query,$pretty_domains_string); 1464: } 1465: 1466: ###################################################################### 1467: ###################################################################### 1468: 1469: =pod 1470: 1471: =item &parse_basic_search() 1472: 1473: Parse the basic search form and return a scalar containing an sql query. 1474: 1475: =cut 1476: 1477: ###################################################################### 1478: ###################################################################### 1479: sub parse_basic_search { 1480: my ($r,$closebutton)=@_; 1481: # 1482: # Clean up fields for safety 1483: for my $field ('basicexp') { 1484: $ENV{"form.$field"}=~s/[^\w\s\'\"\!\(\)\-]//g; 1485: } 1486: foreach ('mode','form','element') { 1487: # is this required? Hmmm. 1488: next unless (exists($ENV{"form.$_"})); 1489: $ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"}); 1490: $ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g; 1491: } 1492: my ($libraries_to_query,$pretty_domains_string) = 1493: &parse_domain_restrictions(); 1494: # 1495: # Check to see if enough of a query is filled in 1496: my $search_string = $ENV{'form.basicexp'}; 1497: if (! &filled($search_string)) { 1498: &output_blank_field_error($r,$closebutton,'phase=disp_basic'); 1499: return OK; 1500: } 1501: my $pretty_search_string=$search_string; 1502: my @Queries; 1503: my $searchfield = 'concat_ws(" ",'.join(',', 1504: ('title','author','subject', 1505: 'notes','abstract','keywords') 1506: ).')'; 1507: my ($error,$SQLQuery) = &process_phrase_input($search_string, 1508: $ENV{'form.related'}, 1509: $searchfield); 1510: if ($error) { 1511: &output_unparsed_phrase_error($r,$closebutton,'phase=disp_basic', 1512: '','basicexp'); 1513: return; 1514: } 1515: push(@Queries,$SQLQuery); 1516: #foreach my $q (@Queries) { 1517: # &Apache::lonnet::logthis(' '.$q); 1518: #} 1519: my $final_query = 'SELECT * FROM metadata WHERE '.join(" AND ",@Queries); 1520: # 1521: if (defined($pretty_domains_string) && $pretty_domains_string ne '') { 1522: $pretty_search_string .= ' '.$pretty_domains_string; 1523: } 1524: $pretty_search_string .= "<br />\n"; 1525: $pretty_search_string =~ s:^<br /> and ::; 1526: # &Apache::lonnet::logthis($final_query); 1527: return ($final_query,$pretty_search_string, 1528: $libraries_to_query); 1529: } 1530: 1531: 1532: ############################################################### 1533: ############################################################### 1534: 1535: my @Phrases; 1536: 1537: sub concat { 1538: my ($item) = @_; 1539: my $results = ''; 1540: foreach (@$item) { 1541: if (ref($_) eq 'ARRAY') { 1542: $results .= join(' ',@$_); 1543: } 1544: } 1545: return $results; 1546: } 1547: 1548: sub process_phrase_input { 1549: my ($phrase,$related,$field)=@_; 1550: #&Apache::lonnet::logthis('phrase = :'.$phrase.':'); 1551: my $grammar = <<'ENDGRAMMAR'; 1552: searchphrase: 1553: expression /^\Z/ { 1554: # &Apache::lonsearchcat::print_item(\@item,0); 1555: [@item]; 1556: } 1557: expression: 1558: phrase(s) { 1559: [@item]; 1560: } 1561: phrase: 1562: orword { 1563: [@item]; 1564: } 1565: | andword { 1566: [@item]; 1567: } 1568: | minusword { 1569: unshift(@::Phrases,$item[1]->[0]); 1570: unshift(@::Phrases,$item[1]->[1]); 1571: [@item]; 1572: } 1573: | word { 1574: unshift(@::Phrases,$item[1]); 1575: [@item]; 1576: } 1577: # 1578: orword: 1579: word 'OR' phrase { 1580: unshift(@::Phrases,'OR'); 1581: unshift(@::Phrases,$item[1]); 1582: [@item]; 1583: } 1584: | word 'or' phrase { 1585: unshift(@::Phrases,'OR'); 1586: unshift(@::Phrases,$item[1]); 1587: [@item]; 1588: } 1589: | minusword 'OR' phrase { 1590: unshift(@::Phrases,'OR'); 1591: unshift(@::Phrases,$item[1]->[0]); 1592: unshift(@::Phrases,$item[1]->[1]); 1593: [@item]; 1594: } 1595: | minusword 'or' phrase { 1596: unshift(@::Phrases,'OR'); 1597: unshift(@::Phrases,$item[1]->[0]); 1598: unshift(@::Phrases,$item[1]->[1]); 1599: [@item]; 1600: } 1601: andword: 1602: word phrase { 1603: unshift(@::Phrases,'AND'); 1604: unshift(@::Phrases,$item[1]); 1605: [@item]; 1606: } 1607: | minusword phrase { 1608: unshift(@::Phrases,'AND'); 1609: unshift(@::Phrases,$item[1]->[0]); 1610: unshift(@::Phrases,$item[1]->[1]); 1611: [@item]; 1612: } 1613: # 1614: minusword: 1615: '-' word { 1616: [$item[2],'NOT']; 1617: } 1618: word: 1619: "'" term(s) "'" { 1620: &Apache::lonsearchcat::concat(\@item); 1621: } 1622: | '"' term(s) '"' { 1623: &Apache::lonsearchcat::concat(\@item); 1624: } 1625: | term { 1626: $item[1]; 1627: } 1628: term: 1629: /[\w\Q:!@#$%^&*()+_=|{}<>,.;\\\/?\E]+/ { 1630: $item[1]; 1631: } 1632: ENDGRAMMAR 1633: # 1634: # The end result of parsing the phrase with the grammar is an array 1635: # @::Phrases. 1636: # $phrase = "gene splicing" or cat -> "gene splicing","OR","cat" 1637: # $phrase = "genetic engineering" -dna -> 1638: # "genetic engineering","AND","NOT","dna" 1639: # $phrase = cat or dog -poodle -> "cat","OR","dog","AND","NOT","poodle" 1640: undef(@::Phrases); 1641: my $p = new Parse::RecDescent($grammar); 1642: if (! defined($p->searchphrase($phrase))) { 1643: &Apache::lonnet::logthis('lonsearchcat:unable to process:'.$phrase); 1644: return 'Unable to process phrase '.$phrase; 1645: } 1646: # 1647: # Go through the phrases and make sense of them. 1648: # Apply modifiers NOT OR and AND to the phrases. 1649: my @NewPhrases; 1650: while(@::Phrases) { 1651: my $phrase = shift(@::Phrases); 1652: # &Apache::lonnet::logthis('phrase = '.$phrase); 1653: my $phrasedata; 1654: if ($phrase =~ /^(NOT|OR|AND)$/) { 1655: if ($phrase eq 'OR') { 1656: $phrasedata->{'or'}++; 1657: if (! @::Phrases) { $phrasedata = undef; last; } 1658: $phrase = shift(@::Phrases); 1659: } elsif ($phrase eq 'AND') { 1660: $phrasedata->{'and'}++; 1661: if (! @::Phrases) { $phrasedata = undef; last; } 1662: $phrase = shift(@::Phrases); 1663: } 1664: if ($phrase eq 'NOT') { 1665: $phrasedata->{'negate'}++; 1666: if (! @::Phrases) { $phrasedata = undef; last; } 1667: $phrase = shift(@::Phrases); 1668: } 1669: } 1670: $phrasedata->{'phrase'} = $phrase; 1671: if ($related) { 1672: my @NewWords; 1673: (undef,@NewWords) = &related_version($phrasedata->{'phrase'}); 1674: $phrasedata->{'related_words'} = \@NewWords; 1675: } 1676: push(@NewPhrases,$phrasedata); 1677: } 1678: # 1679: # Actually build the sql query from the phrases 1680: my $SQLQuery; 1681: foreach my $phrase (@NewPhrases) { 1682: my $query; 1683: if ($phrase->{'negate'}) { 1684: $query .= $field.' NOT LIKE "%'.$phrase->{'phrase'}.'%"'; 1685: } else { 1686: $query .= $field.' LIKE "%'.$phrase->{'phrase'}.'%"'; 1687: } 1688: foreach my $related (@{$phrase->{'related_words'}}) { 1689: if ($phrase->{'negate'}) { 1690: $query .= ' AND '.$field.' NOT LIKE "%'.$related.'%"'; 1691: } else { 1692: $query .= ' OR '.$field.' LIKE "%'.$related.'%"'; 1693: } 1694: } 1695: if ($SQLQuery) { 1696: if ($phrase->{'or'}) { 1697: $SQLQuery .= ' OR ('.$query.')'; 1698: } else { 1699: $SQLQuery .= ' AND ('.$query.')'; 1700: } 1701: } else { 1702: $SQLQuery = '('.$query.')'; 1703: } 1704: } 1705: # 1706: # &Apache::lonnet::logthis("SQLQuery = $SQLQuery"); 1707: # 1708: return undef,$SQLQuery; 1709: } 1710: 1711: ###################################################################### 1712: ###################################################################### 1713: 1714: =pod 1715: 1716: =item &related_version() 1717: 1718: Modifies an input string to include related words. Words in the string 1719: are replaced with parenthesized lists of 'OR'd words. For example 1720: "torque" is replaced with "(torque OR word1 OR word2 OR ...)". 1721: 1722: Note: Using this twice on a string is probably silly. 1723: 1724: =cut 1725: 1726: ###################################################################### 1727: ###################################################################### 1728: sub related_version { 1729: my ($word) = @_; 1730: return (undef) if (lc($word) =~ /\b(or|and|not)\b/); 1731: my @Words = &Apache::loncommon::get_related_words($word); 1732: # Only use 4 related words 1733: @Words = ($#Words>4? @Words[0..4] : @Words); 1734: my $result = join " OR ", ($word,@Words); 1735: return $result,sort(@Words); 1736: } 1737: 1738: 1739: ###################################################################### 1740: ###################################################################### 1741: 1742: =pod 1743: 1744: =item &build_custommetadata_query() 1745: 1746: Constructs a custom metadata query using a rather heinous regular 1747: expression. 1748: 1749: =cut 1750: 1751: ###################################################################### 1752: ###################################################################### 1753: sub build_custommetadata_query { 1754: my ($field_name,$logic_statement)=@_; 1755: my $q=new Text::Query('abc', 1756: -parse => 'Text::Query::ParseAdvanced', 1757: -build => 'Text::Query::BuildAdvancedString'); 1758: $q->prepare($logic_statement); 1759: my $matchexp=${$q}{'-parse'}{'-build'}{'matchstring'}; 1760: # quick fix to change literal into xml tag-matching 1761: # will eventually have to write a separate builder module 1762: # wordone=wordtwo becomes\<wordone\>[^\<] *wordtwo[^\<]*\<\/wordone\> 1763: $matchexp =~ s/(\w+)\\=([\w\\\+]+)?# wordone=wordtwo is changed to 1764: /\\<$1\\>?# \<wordone\> 1765: \[\^\\<\]?# [^\<] 1766: \*$2\[\^\\<\]?# *wordtwo[^\<] 1767: \*\\<\\\/$1\\>?# *\<\/wordone\> 1768: /g; 1769: return $matchexp; 1770: } 1771: 1772: 1773: ###################################################################### 1774: ###################################################################### 1775: 1776: =pod 1777: 1778: =item &build_date_queries() 1779: 1780: Builds a SQL logic query to check time/date entries. 1781: Also reports errors (check for /^Incorrect/). 1782: 1783: =cut 1784: 1785: ###################################################################### 1786: ###################################################################### 1787: sub build_date_queries { 1788: my ($cafter,$cbefore,$mafter,$mbefore) = @_; 1789: my ($result,$error,$pretty_string); 1790: # 1791: # Verify the input 1792: if (! defined($cafter) && ! defined($cbefore) && 1793: ! defined($mafter) && ! defined($mbefore)) { 1794: # This is an okay situation, so return undef for the error 1795: return (undef,undef,undef); 1796: } 1797: if ((defined($cafter) && ! defined($cbefore)) || 1798: (defined($cbefore) && ! defined($cafter))) { 1799: # This is bad, so let them know 1800: $error = &mt('Incorrect entry for the creation date. '. 1801: 'You must specify both the beginning and ending dates.'); 1802: } 1803: if (! defined($error) && 1804: ((defined($mafter) && ! defined($mbefore)) || 1805: (defined($mbefore) && ! defined($mafter)))) { 1806: # This is also bad, so let them know 1807: $error = &mt('Incorrect entry for the last revision date. '. 1808: 'You must specify both the beginning and ending dates.'); 1809: } 1810: if (! defined($error)) { 1811: # 1812: # Build the queries 1813: my @queries; 1814: if (defined($cbefore) && defined($cafter)) { 1815: my (undef,undef,undef,$caday,$camon,$cayear) = localtime($cafter); 1816: my (undef,undef,undef,$cbday,$cbmon,$cbyear) = localtime($cbefore); 1817: # Correct for year being relative to 1900 1818: $cayear+=1900; $cbyear+=1900; 1819: my $cquery= 1820: '(creationdate BETWEEN '. 1821: "'".$cayear.'-'.$camon.'-'.$caday."'". 1822: ' AND '. 1823: "'".$cbyear.'-'.$cbmon.'-'.$cbday." 23:59:59')"; 1824: $pretty_string .= '<br />' if (defined($pretty_string)); 1825: $pretty_string .= 1826: &mt('created between [_1] and [_2]', 1827: &Apache::lonlocal::locallocaltime($cafter), 1828: &Apache::lonlocal::locallocaltime($cbefore+24*60*60-1)); 1829: push(@queries,$cquery); 1830: $pretty_string =~ s/ 00:00:00//g; 1831: } 1832: if (defined($mbefore) && defined($mafter)) { 1833: my (undef,undef,undef,$maday,$mamon,$mayear) = localtime($mafter); 1834: my (undef,undef,undef,$mbday,$mbmon,$mbyear) = localtime($mbefore); 1835: # Correct for year being relative to 1900 1836: $mayear+=1900; $mbyear+=1900; 1837: my $mquery= 1838: '(lastrevisiondate BETWEEN '. 1839: "'".$mayear.'-'.$mamon.'-'.$maday."'". 1840: ' AND '. 1841: "'".$mbyear.'-'.$mbmon.'-'.$mbday." 23:59:59')"; 1842: push(@queries,$mquery); 1843: $pretty_string .= '<br />' if (defined($pretty_string)); 1844: $pretty_string .= 1845: &mt('last revised between [_1] and [_2]', 1846: &Apache::lonlocal::locallocaltime($mafter), 1847: &Apache::lonlocal::locallocaltime($mbefore+24*60*60-1)); 1848: $pretty_string =~ s/ 00:00:00//g; 1849: } 1850: if (@queries) { 1851: $result .= join(" AND ",@queries); 1852: } 1853: } 1854: return ($result,$error,$pretty_string); 1855: } 1856: 1857: ###################################################################### 1858: ###################################################################### 1859: 1860: =pod 1861: 1862: =item ©right_check() 1863: 1864: Inputs: $Metadata, a hash pointer of metadata for a resource. 1865: 1866: Returns: 1 if the resource is available to the user making the query, 1867: 0 otherwise. 1868: 1869: =cut 1870: 1871: ###################################################################### 1872: ###################################################################### 1873: sub copyright_check { 1874: my $Metadata = shift; 1875: # Check copyright tags and skip results the user cannot use 1876: my (undef,undef,$resdom,$resname) = split('/', 1877: $Metadata->{'url'}); 1878: # Check for priv 1879: if (($Metadata->{'copyright'} eq 'priv') && 1880: (($ENV{'user.name'} ne $resname) && 1881: ($ENV{'user.domain'} ne $resdom))) { 1882: return 0; 1883: } 1884: # Check for domain 1885: if (($Metadata->{'copyright'} eq 'domain') && 1886: ($ENV{'user.domain'} ne $resdom)) { 1887: return 0; 1888: } 1889: return 1; 1890: } 1891: 1892: ###################################################################### 1893: ###################################################################### 1894: 1895: =pod 1896: 1897: =item &ensure_db_and_table() 1898: 1899: Ensure we can get lonmysql to connect to the database and the table we 1900: need exists. 1901: 1902: Inputs: $r, table id 1903: 1904: Returns: undef on error, 1 if the table exists. 1905: 1906: =cut 1907: 1908: ###################################################################### 1909: ###################################################################### 1910: sub ensure_db_and_table { 1911: my ($r,$table) = @_; 1912: ## 1913: ## Sanity check the table id. 1914: ## 1915: if (! defined($table) || $table eq '' || $table =~ /\D/ ) { 1916: $r->print("Unable to retrieve search results. ". 1917: "Unable to determine the table results were stored in. ". 1918: "</body></html>"); 1919: return undef; 1920: } 1921: ## 1922: ## Make sure we can connect and the table exists. 1923: ## 1924: my $connection_result = &Apache::lonmysql::connect_to_db(); 1925: if (!defined($connection_result)) { 1926: $r->print("Unable to connect to the MySQL database where your results". 1927: " are stored. </body></html>"); 1928: &Apache::lonnet::logthis("lonsearchcat: unable to get lonmysql to". 1929: " connect to database."); 1930: &Apache::lonnet::logthis(&Apache::lonmysql::get_error()); 1931: return undef; 1932: } 1933: my $table_check = &Apache::lonmysql::check_table($table); 1934: if (! defined($table_check)) { 1935: $r->print("A MySQL error has occurred.</form></body></html>"); 1936: &Apache::lonnet::logthis("lonmysql was unable to determine the status". 1937: " of table ".$table); 1938: return undef; 1939: } elsif (! $table_check) { 1940: $r->print("The table of results could not be found."); 1941: &Apache::lonnet::logthis("The user requested a table, ".$table. 1942: ", that could not be found."); 1943: return undef; 1944: } 1945: return 1; 1946: } 1947: 1948: ###################################################################### 1949: ###################################################################### 1950: 1951: =pod 1952: 1953: =item &print_sort_form() 1954: 1955: The sort feature is not implemented at this time. This form just prints 1956: a link to change the search query. 1957: 1958: =cut 1959: 1960: ###################################################################### 1961: ###################################################################### 1962: sub print_sort_form { 1963: my ($r,$pretty_query_string) = @_; 1964: my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1). 1965: &Apache::lonhtmlcommon::breadcrumbs 1966: (undef,'Searching','Searching',undef,undef, 1967: $ENV{'form.catalogmode'} ne 'groupsearch'); 1968: 1969: ## 1970: my %SortableFields=&Apache::lonlocal::texthash( 1971: id => 'Default', 1972: title => 'Title', 1973: author => 'Author', 1974: subject => 'Subject', 1975: url => 'URL', 1976: version => 'Version Number', 1977: mime => 'Mime type', 1978: lang => 'Language', 1979: owner => 'Owner/Publisher', 1980: copyright => 'Copyright', 1981: hostname => 'Host', 1982: creationdate => 'Creation Date', 1983: lastrevisiondate => 'Revision Date' 1984: ); 1985: ## 1986: my $table = $ENV{'form.table'}; 1987: return if (! &ensure_db_and_table($r,$table)); 1988: ## 1989: ## Get the number of results 1990: ## 1991: my $total_results = &Apache::lonmysql::number_of_rows($table); 1992: if (! defined($total_results)) { 1993: $r->print("A MySQL error has occurred.</form></body></html>"); 1994: &Apache::lonnet::logthis("lonmysql was unable to determine the number". 1995: " of rows in table ".$table); 1996: &Apache::lonnet::logthis(&Apache::lonmysql::get_error()); 1997: return; 1998: } 1999: my $result; 2000: $result.=<<END; 2001: <html> 2002: <head> 2003: <script> 2004: function change_sort() { 2005: var newloc = "/adm/searchcat?phase=results"; 2006: newloc += "&persistent_db_id=$ENV{'form.persistent_db_id'}"; 2007: newloc += "&sortby="; 2008: newloc += document.forms.statusform.elements.sortby.value; 2009: parent.resultsframe.location= newloc; 2010: } 2011: </script> 2012: <title>Results</title> 2013: </head> 2014: $bodytag 2015: <form name="statusform" action="" method="post"> 2016: <input type="hidden" name="Queue" value="" /> 2017: END 2018: 2019: #<h2>Sort Results</h2> 2020: #Sort by: <select size="1" name="sortby" onchange="javascript:change_sort();"> 2021: # $ENV{'form.sortby'} = 'id' if (! defined($ENV{'form.sortby'})); 2022: # foreach (keys(%SortableFields)) { 2023: # $result.="<option name=\"$_\""; 2024: # if ($_ eq $ENV{'form.sortby'}) { 2025: # $result.=" selected "; 2026: # } 2027: # $result.=" >$SortableFields{$_}</option>\n"; 2028: # } 2029: # $result.="</select>\n"; 2030: my $revise = &revise_button(); 2031: $result.=<<END; 2032: <p> 2033: There are $total_results matches to your query. $revise 2034: </p><p> 2035: Search:$pretty_query_string 2036: </p> 2037: </form> 2038: </body> 2039: </html> 2040: END 2041: $r->print($result); 2042: return; 2043: } 2044: 2045: ##################################################################### 2046: ##################################################################### 2047: 2048: =pod 2049: 2050: =item MySQL Table Description 2051: 2052: MySQL table creation requires a precise description of the data to be 2053: stored. The use of the correct types to hold data is vital to efficient 2054: storage and quick retrieval of records. The columns must be described in 2055: the following format: 2056: 2057: =cut 2058: 2059: ##################################################################### 2060: ##################################################################### 2061: # 2062: # These should probably be scoped but I don't have time right now... 2063: # 2064: my @Datatypes; 2065: my @Fullindicies; 2066: 2067: ###################################################################### 2068: ###################################################################### 2069: 2070: =pod 2071: 2072: =item &create_results_table() 2073: 2074: Creates the table of search results by calling lonmysql. Stores the 2075: table id in $ENV{'form.table'} 2076: 2077: Inputs: none. 2078: 2079: Returns: the identifier of the table on success, undef on error. 2080: 2081: =cut 2082: 2083: ###################################################################### 2084: ###################################################################### 2085: sub set_up_table_structure { 2086: my ($datatypes,$fullindicies) = 2087: &LONCAPA::lonmetadata::describe_metadata_storage(); 2088: # Copy the table description before modifying it... 2089: @Datatypes = @{$datatypes}; 2090: unshift(@Datatypes,{name => 'id', 2091: type => 'MEDIUMINT', 2092: restrictions => 'UNSIGNED NOT NULL', 2093: primary_key => 'yes', 2094: auto_inc => 'yes' }); 2095: @Fullindicies = @{$fullindicies}; 2096: return; 2097: } 2098: 2099: sub create_results_table { 2100: &set_up_table_structure(); 2101: my $table = &Apache::lonmysql::create_table 2102: ( { columns => \@Datatypes, 2103: FULLTEXT => [{'columns' => \@Fullindicies},], 2104: } ); 2105: if (defined($table)) { 2106: $ENV{'form.table'} = $table; 2107: return $table; 2108: } 2109: return undef; # Error... 2110: } 2111: 2112: ###################################################################### 2113: ###################################################################### 2114: 2115: =pod 2116: 2117: =item Search Status update functions 2118: 2119: Each of the following functions changes the values of one of the 2120: input fields used to display the search status to the user. The names 2121: should be explanatory. 2122: 2123: Inputs: Apache request handler ($r), text to display. 2124: 2125: Returns: Nothing. 2126: 2127: =over 4 2128: 2129: =item &update_count_status() 2130: 2131: =item &update_status() 2132: 2133: =item &update_seconds() 2134: 2135: =back 2136: 2137: =cut 2138: 2139: ###################################################################### 2140: ###################################################################### 2141: sub update_count_status { 2142: my ($r,$text) = @_; 2143: $text =~ s/\'/\\\'/g; 2144: $r->print 2145: ("<script>document.statusform.count.value = ' $text'</script>\n"); 2146: $r->rflush(); 2147: } 2148: 2149: sub update_status { 2150: my ($r,$text) = @_; 2151: $text =~ s/\'/\\\'/g; 2152: $r->print 2153: ("<script>document.statusform.status.value = ' $text'</script>\n"); 2154: $r->rflush(); 2155: } 2156: 2157: { 2158: my $max_time = 40; # seconds for the search to complete 2159: my $start_time = 0; 2160: my $last_time = 0; 2161: 2162: sub reset_timing { 2163: $start_time = 0; 2164: $last_time = 0; 2165: } 2166: 2167: sub time_left { 2168: if ($start_time == 0) { 2169: $start_time = time; 2170: } 2171: my $time_left = $max_time - (time - $start_time); 2172: $time_left = 0 if ($time_left < 0); 2173: return $time_left; 2174: } 2175: 2176: sub update_seconds { 2177: my ($r) = @_; 2178: my $time = &time_left(); 2179: if (($last_time-$time) > 0) { 2180: $r->print("<script>". 2181: "document.statusform.seconds.value = '$time'". 2182: "</script>\n"); 2183: $r->rflush(); 2184: } 2185: $last_time = $time; 2186: } 2187: 2188: } 2189: 2190: ###################################################################### 2191: ###################################################################### 2192: 2193: =pod 2194: 2195: =item &revise_button() 2196: 2197: Inputs: None 2198: 2199: Returns: html string for a 'revise search' button. 2200: 2201: =cut 2202: 2203: ###################################################################### 2204: ###################################################################### 2205: sub revise_button { 2206: my $revise_phase = 'disp_basic'; 2207: $revise_phase = 'disp_adv' if ($ENV{'form.searchmode'} eq 'advanced'); 2208: my $newloc = '/adm/searchcat'. 2209: '?persistent_db_id='.$ENV{'form.persistent_db_id'}. 2210: '&cleargroupsort=1'. 2211: '&phase='.$revise_phase; 2212: my $result = qq{<input type="button" value="Revise search" name="revise"} . 2213: qq{ onClick="parent.location='$newloc';" /> }; 2214: return $result; 2215: } 2216: 2217: ###################################################################### 2218: ###################################################################### 2219: 2220: =pod 2221: 2222: =item &run_search() 2223: 2224: Executes a search query by sending it the the other servers and putting the 2225: results into MySQL. 2226: 2227: =cut 2228: 2229: ###################################################################### 2230: ###################################################################### 2231: sub run_search { 2232: my ($r,$query,$customquery,$customshow,$serverlist,$pretty_string) = @_; 2233: my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); 2234: $bodytag.=&Apache::lonhtmlcommon::breadcrumbs 2235: (undef,'Searching','Searching',undef,undef, 2236: $ENV{'form.catalogmode'} ne 'groupsearch'); 2237: my $connection = $r->connection; 2238: # 2239: # Print run_search header 2240: # 2241: $r->print(<<END); 2242: <html> 2243: <head><title>Search Status</title></head> 2244: $bodytag 2245: <form name="statusform" action="" method="post"> 2246: <input type="hidden" name="Queue" value="" /> 2247: END 2248: # Check to see if $pretty_string has more than one carriage return. 2249: # Assume \n s are following <br /> s and truncate the value. 2250: # (there is probably a better way)... 2251: my @Lines = split /<br \/>/,$pretty_string; 2252: if (@Lines > 2) { 2253: $pretty_string = join '<br \>',(@Lines[0..2],'....<br />'); 2254: } 2255: $r->print(&mt("Search: [_1]",$pretty_string)); 2256: $r->rflush(); 2257: # 2258: # Determine the servers we need to contact. 2259: my @Servers_to_contact; 2260: if (defined($serverlist)) { 2261: if (ref($serverlist) eq 'ARRAY') { 2262: @Servers_to_contact = @$serverlist; 2263: } else { 2264: @Servers_to_contact = ($serverlist); 2265: } 2266: } else { 2267: @Servers_to_contact = sort(keys(%Apache::lonnet::libserv)); 2268: } 2269: my %Server_status; 2270: # 2271: # Check on the mysql table we will use to store results. 2272: my $table =$ENV{'form.table'}; 2273: if (! defined($table) || $table eq '' || $table =~ /\D/ ) { 2274: $r->print("Unable to determine table id to store search results in.". 2275: "The search has been aborted.</body></html>"); 2276: return; 2277: } 2278: my $table_status = &Apache::lonmysql::check_table($table); 2279: if (! defined($table_status)) { 2280: $r->print("Unable to determine status of table.</body></html>"); 2281: &Apache::lonnet::logthis("Bogus table id of $table for ". 2282: "$ENV{'user.name'} @ $ENV{'user.domain'}"); 2283: &Apache::lonnet::logthis("lonmysql error = ". 2284: &Apache::lonmysql::get_error()); 2285: return; 2286: } 2287: if (! $table_status) { 2288: &Apache::lonnet::logthis("lonmysql error = ". 2289: &Apache::lonmysql::get_error()); 2290: &Apache::lonnet::logthis("lonmysql debug = ". 2291: &Apache::lonmysql::get_debug()); 2292: &Apache::lonnet::logthis('table status = "'.$table_status.'"'); 2293: $r->print("The table id,$table, we tried to use is invalid.". 2294: "The search has been aborted.</body></html>"); 2295: return; 2296: } 2297: ## 2298: ## Prepare for the big loop. 2299: my $hitcountsum; 2300: my $server; 2301: my $status; 2302: my $revise = &revise_button(); 2303: $r->print(<<END); 2304: <table> 2305: <tr><th>Status</th><th>Total Matches</th><th>Time Remaining</th><th></th></tr> 2306: <tr> 2307: <td><input type="text" name="status" value="" size="30" /></td> 2308: <td><input type="text" name="count" value="" size="10" /></td> 2309: <td><input type="text" name="seconds" value="" size="8" /></td> 2310: <td>$revise</td> 2311: </tr> 2312: </table> 2313: </form> 2314: END 2315: $r->rflush(); 2316: &reset_timing(); 2317: &update_seconds($r); 2318: &update_status($r,&mt('contacting [_1]',$Servers_to_contact[0])); 2319: while (&time_left() && 2320: ((@Servers_to_contact) || keys(%Server_status))) { 2321: &update_seconds($r); 2322: # 2323: # Send out a search request 2324: if (@Servers_to_contact) { 2325: # Contact one server 2326: my $server = shift(@Servers_to_contact); 2327: &update_status($r,&mt('contacting [_1]',$server)); 2328: my $reply=&Apache::lonnet::metadata_query($query,$customquery, 2329: $customshow,[$server]); 2330: ($server) = keys(%$reply); 2331: $Server_status{$server} = $reply->{$server}; 2332: } else { 2333: # wait a sec. to give time for files to be written 2334: # This sleep statement is here instead of outside the else 2335: # block because we do not want to pause if we have servers 2336: # left to contact. 2337: if (scalar (keys(%Server_status))) { 2338: &update_status($r, 2339: &mt('waiting on [_1]',join(' ',keys(%Server_status)))); 2340: } 2341: sleep(1); 2342: } 2343: # 2344: # Loop through the servers we have contacted but do not 2345: # have results from yet, looking for results. 2346: foreach my $server (keys(%Server_status)) { 2347: last if ($connection->aborted()); 2348: &update_seconds($r); 2349: my $status = $Server_status{$server}; 2350: if ($status eq 'con_lost') { 2351: delete ($Server_status{$server}); 2352: next; 2353: } 2354: $status=~/^([\.\w]+)$/; 2355: my $datafile=$r->dir_config('lonDaemons').'/tmp/'.$1; 2356: if (-e $datafile && ! -e "$datafile.end") { 2357: &update_status($r,&mt('Receiving results from [_1]',$server)); 2358: next; 2359: } 2360: last if ($connection->aborted()); 2361: if (-e "$datafile.end") { 2362: &update_status($r,&mt('Reading results from [_1]',$server)); 2363: if (-z "$datafile") { 2364: delete($Server_status{$server}); 2365: next; 2366: } 2367: my $fh; 2368: if (!($fh=Apache::File->new($datafile))) { 2369: $r->print("Unable to open search results file for ". 2370: "server $server. Omitting from search"); 2371: delete($Server_status{$server}); 2372: next; 2373: } 2374: # Read in the whole file. 2375: while (my $result = <$fh>) { 2376: last if ($connection->aborted()); 2377: # 2378: # Records are stored one per line 2379: chomp($result); 2380: next if (! $result); 2381: # 2382: # Parse the result. 2383: my %Fields = &parse_raw_result($result,$server); 2384: $Fields{'hostname'} = $server; 2385: # 2386: # Skip based on copyright 2387: next if (! ©right_check(\%Fields)); 2388: # 2389: # Store the result in the mysql database 2390: my $result = &Apache::lonmysql::store_row($table,\%Fields); 2391: if (! defined($result)) { 2392: $r->print(&Apache::lonmysql::get_error()); 2393: } 2394: # 2395: $hitcountsum ++; 2396: &update_seconds($r); 2397: if ($hitcountsum % 50 == 0) { 2398: &update_count_status($r,$hitcountsum); 2399: } 2400: } 2401: $fh->close(); 2402: # $server is only deleted if the results file has been 2403: # found and (successfully) opened. This may be a bad idea. 2404: delete($Server_status{$server}); 2405: } 2406: last if ($connection->aborted()); 2407: &update_count_status($r,$hitcountsum); 2408: } 2409: last if ($connection->aborted()); 2410: &update_seconds($r); 2411: } 2412: &update_status($r,&mt('Search Complete [_1]',$server)); 2413: &update_seconds($r); 2414: # 2415: &Apache::lonmysql::disconnect_from_db(); # This is unneccessary 2416: # 2417: # We have run out of time or run out of servers to talk to and 2418: # results to get, so let the client know the top frame needs to be 2419: # loaded from /adm/searchcat 2420: $r->print("</body></html>"); 2421: # if ($ENV{'form.catalogmode'} ne 'groupsearch') { 2422: $r->print("<script>". 2423: "window.location='/adm/searchcat?". 2424: "phase=sort&". 2425: "persistent_db_id=$ENV{'form.persistent_db_id'}';". 2426: "</script>"); 2427: # } 2428: return; 2429: } 2430: 2431: ###################################################################### 2432: ###################################################################### 2433: 2434: =pod 2435: 2436: =item &prev_next_buttons() 2437: 2438: Returns html for the previous and next buttons on the search results page. 2439: 2440: =cut 2441: 2442: ###################################################################### 2443: ###################################################################### 2444: sub prev_next_buttons { 2445: my ($current_min,$show,$total,$parms) = @_; 2446: return '' if ($show eq 'all'); # No links if you get them all at once. 2447: # 2448: # Create buttons 2449: my $buttons = '<input type="submit" name="prev" value="'.&mt('Prev').'" '; 2450: $buttons .= '/>'; 2451: $buttons .= ' 'x3; 2452: $buttons .= '<input type="submit" name="reload" '. 2453: 'value="'.&mt('Reload').'" />'; 2454: $buttons .= ' 'x3; 2455: $buttons .= '<input type="submit" name="next" value="'.&mt('Next').'" '; 2456: $buttons .= '/>'; 2457: return $buttons; 2458: } 2459: 2460: ###################################################################### 2461: ###################################################################### 2462: 2463: =pod 2464: 2465: =item &display_results() 2466: 2467: Prints the results out for selection and perusal. 2468: 2469: =cut 2470: 2471: ###################################################################### 2472: ###################################################################### 2473: sub display_results { 2474: my ($r,$importbutton,$closebutton,$diropendb) = @_; 2475: my $connection = $r->connection; 2476: $r->print(&search_results_header($importbutton,$closebutton)); 2477: ## 2478: ## Set viewing function 2479: ## 2480: my $viewfunction = $Views{$ENV{'form.viewselect'}}; 2481: if (!defined($viewfunction)) { 2482: $r->print("Internal Error - Bad view selected.\n"); 2483: $r->rflush(); 2484: return; 2485: } 2486: ## 2487: ## $checkbox_num is a count of the number of checkboxes output on the 2488: ## page this is used only during catalogmode=groupsearch. 2489: my $checkbox_num = 0; 2490: ## 2491: ## Get the catalog controls setup 2492: ## 2493: my $action = "/adm/searchcat?phase=results"; 2494: ## 2495: ## Deal with groupsearch by opening the groupsearch db file. 2496: if ($ENV{'form.catalogmode'} eq 'groupsearch') { 2497: if (! tie(%groupsearch_db,'GDBM_File',$diropendb, 2498: &GDBM_WRCREAT(),0640)) { 2499: $r->print('Unable to store import results.</form></body></html>'); 2500: $r->rflush(); 2501: return; 2502: } 2503: } 2504: ## 2505: ## Prepare the table for querying 2506: my $table = $ENV{'form.table'}; 2507: return if (! &ensure_db_and_table($r,$table)); 2508: ## 2509: ## Get the number of results 2510: my $total_results = &Apache::lonmysql::number_of_rows($table); 2511: if (! defined($total_results)) { 2512: $r->print("A MySQL error has occurred.</form></body></html>"); 2513: &Apache::lonnet::logthis("lonmysql was unable to determine the number". 2514: " of rows in table ".$table); 2515: &Apache::lonnet::logthis(&Apache::lonmysql::get_error()); 2516: return; 2517: } 2518: ## 2519: ## Determine how many results we need to get 2520: $ENV{'form.start'} = 1 if (! exists($ENV{'form.start'})); 2521: $ENV{'form.show'} = 20 if (! exists($ENV{'form.show'})); 2522: if (exists($ENV{'form.prev'})) { 2523: $ENV{'form.start'} -= $ENV{'form.show'}; 2524: } elsif (exists($ENV{'form.next'})) { 2525: $ENV{'form.start'} += $ENV{'form.show'}; 2526: } 2527: $ENV{'form.start'} = 1 if ($ENV{'form.start'}<1); 2528: $ENV{'form.start'} = $total_results if ($ENV{'form.start'}>$total_results); 2529: my $min = $ENV{'form.start'}; 2530: my $max; 2531: if ($ENV{'form.show'} eq 'all') { 2532: $max = $total_results ; 2533: } else { 2534: $max = $min + $ENV{'form.show'} - 1; 2535: $max = $total_results if ($max > $total_results); 2536: } 2537: ## 2538: ## Output form elements 2539: $r->print(&hidden_field('table'). 2540: &hidden_field('phase'). 2541: &hidden_field('persistent_db_id'). 2542: &hidden_field('start') 2543: ); 2544: ## 2545: ## Output links (if necessary) for 'prev' and 'next' pages. 2546: $r->print 2547: ('<table width="100%"><tr><td width="50%" align="right">'. 2548: &prev_next_buttons($min,$ENV{'form.show'},$total_results). 2549: '</td><td align="right">'. 2550: &viewoptions().'</td></tr></table>' 2551: ); 2552: if ($total_results == 0) { 2553: $r->print('<meta HTTP-EQUIV="Refresh" CONTENT="2">'. 2554: '<h3>'.&mt('There are currently no results').'.</h3>'. 2555: "</form></body></html>"); 2556: return; 2557: } else { 2558: $r->print 2559: ("<center>Results $min to $max out of $total_results</center>\n"); 2560: } 2561: ## 2562: ## Get results from MySQL table 2563: my @Results = &Apache::lonmysql::get_rows($table, 2564: 'id>='.$min.' AND id<='.$max); 2565: ## 2566: ## Loop through the results and output them. 2567: foreach my $row (@Results) { 2568: if ($connection->aborted()) { 2569: &cleanup(); 2570: return; 2571: } 2572: my %Fields = %{&parse_row(@$row)}; 2573: my $output="<p>\n"; 2574: if (! defined($Fields{'title'}) || $Fields{'title'} eq '') { 2575: $Fields{'title'} = 'Untitled'; 2576: } 2577: my $prefix=&catalogmode_output($Fields{'title'},$Fields{'url'}, 2578: $Fields{'id'},$checkbox_num++); 2579: # Render the result into html 2580: $output.= &$viewfunction($prefix,%Fields); 2581: # Print them out as they come in. 2582: $r->print($output); 2583: $r->rflush(); 2584: } 2585: if (@Results < 1) { 2586: $r->print(&mt("There were no results matching your query")); 2587: } else { 2588: $r->print 2589: ('<center>'. 2590: &prev_next_buttons($min,$ENV{'form.show'},$total_results, 2591: "table=".$ENV{'form.table'}. 2592: "&phase=results". 2593: "&persistent_db_id=". 2594: $ENV{'form.persistent_db_id'}) 2595: ."</center>\n" 2596: ); 2597: } 2598: $r->print("</form></body></html>"); 2599: $r->rflush(); 2600: untie %groupsearch_db if (tied(%groupsearch_db)); 2601: return; 2602: } 2603: 2604: ###################################################################### 2605: ###################################################################### 2606: 2607: =pod 2608: 2609: =item &catalogmode_output($title,$url,$fnum,$checkbox_num) 2610: 2611: Returns html needed for the various catalog modes. Gets inputs from 2612: $ENV{'form.catalogmode'}. Stores data in %groupsearch_db. 2613: 2614: =cut 2615: 2616: ###################################################################### 2617: ###################################################################### 2618: sub catalogmode_output { 2619: my $output = ''; 2620: my ($title,$url,$fnum,$checkbox_num) = @_; 2621: if ($ENV{'form.catalogmode'} eq 'interactive') { 2622: $title=~ s/\'/\\\'/g; 2623: if ($ENV{'form.catalogmode'} eq 'interactive') { 2624: $output.=<<END 2625: <font size='-1'><INPUT TYPE="button" NAME="returnvalues" VALUE="SELECT" 2626: onClick="javascript:select_data('$title','$url')"> 2627: </font> 2628: END 2629: } 2630: } elsif ($ENV{'form.catalogmode'} eq 'groupsearch') { 2631: $groupsearch_db{"pre_${fnum}_link"}=$url; 2632: $groupsearch_db{"pre_${fnum}_title"}=$title; 2633: $output.=<<END; 2634: <font size='-1'> 2635: <input type="checkbox" name="returnvalues" value="SELECT" 2636: onClick="javascript:queue($checkbox_num,$fnum)" /> 2637: </font> 2638: END 2639: } 2640: return $output; 2641: } 2642: ###################################################################### 2643: ###################################################################### 2644: 2645: =pod 2646: 2647: =item &parse_row() 2648: 2649: Parse a row returned from the database. 2650: 2651: =cut 2652: 2653: ###################################################################### 2654: ###################################################################### 2655: sub parse_row { 2656: my @Row = @_; 2657: my %Fields; 2658: if (! scalar(@Datatypes)) { 2659: &set_up_table_structure(); 2660: } 2661: for (my $i=0;$i<=$#Row;$i++) { 2662: $Fields{$Datatypes[$i]->{'name'}}=&Apache::lonnet::unescape($Row[$i]); 2663: } 2664: $Fields{'language'} = 2665: &Apache::loncommon::languagedescription($Fields{'language'}); 2666: $Fields{'copyrighttag'} = 2667: &Apache::loncommon::copyrightdescription($Fields{'copyright'}); 2668: $Fields{'mimetag'} = 2669: &Apache::loncommon::filedescription($Fields{'mime'}); 2670: return \%Fields; 2671: } 2672: 2673: ########################################################### 2674: ########################################################### 2675: 2676: =pod 2677: 2678: =item &parse_raw_result() 2679: 2680: Takes a line from the file of results and parse it. Returns a hash 2681: with keys according to column labels 2682: 2683: In addition, the following tags are set by calling the appropriate 2684: lonnet function: 'language', 'copyrighttag', 'mimetag'. 2685: 2686: The 'title' field is set to "Untitled" if the title field is blank. 2687: 2688: 'abstract' and 'keywords' are truncated to 200 characters. 2689: 2690: =cut 2691: 2692: ########################################################### 2693: ########################################################### 2694: sub parse_raw_result { 2695: my ($result,$hostname) = @_; 2696: # conclude from self to others regarding fields 2697: my %Fields=&LONCAPA::lonmetadata::metadata_col_to_hash 2698: (map { 2699: &Apache::lonnet::unescape($_); 2700: } (split(/\,/,$result)) ); 2701: return %Fields; 2702: } 2703: 2704: ########################################################### 2705: ########################################################### 2706: 2707: =pod 2708: 2709: =item &handle_custom_fields() 2710: 2711: =cut 2712: 2713: ########################################################### 2714: ########################################################### 2715: sub handle_custom_fields { 2716: my @results = @{shift()}; 2717: my $customshow=''; 2718: my $extrashow=''; 2719: my @customfields; 2720: if ($ENV{'form.customshow'}) { 2721: $customshow=$ENV{'form.customshow'}; 2722: $customshow=~s/[^\w\s]//g; 2723: my @fields=map { 2724: "<font color=\"#008000\">$_:</font><!-- $_ -->"; 2725: } split(/\s+/,$customshow); 2726: @customfields=split(/\s+/,$customshow); 2727: if ($customshow) { 2728: $extrashow="<ul><li>".join("</li><li>",@fields)."</li></ul>\n"; 2729: } 2730: } 2731: my $customdata=''; 2732: my %customhash; 2733: foreach my $result (@results) { 2734: if ($result=~/^(custom\=.*)$/) { # grab all custom metadata 2735: my $tmp=$result; 2736: $tmp=~s/^custom\=//; 2737: my ($k,$v)=map {&Apache::lonnet::unescape($_); 2738: } split(/\,/,$tmp); 2739: $customhash{$k}=$v; 2740: } 2741: } 2742: return ($extrashow,\@customfields,\%customhash); 2743: } 2744: 2745: ###################################################################### 2746: ###################################################################### 2747: 2748: =pod 2749: 2750: =item &search_results_header() 2751: 2752: Output the proper html headers and javascript code to deal with different 2753: calling modes. 2754: 2755: Takes most inputs directly from %ENV, except $mode. 2756: 2757: =over 4 2758: 2759: =item $mode is either (at this writing) 'Basic' or 'Advanced' 2760: 2761: =back 2762: 2763: The following environment variables are checked: 2764: 2765: =over 4 2766: 2767: =item 'form.catalogmode' 2768: 2769: Checked for 'interactive' and 'groupsearch'. 2770: 2771: =item 'form.mode' 2772: 2773: Checked for existance & 'edit' mode. 2774: 2775: =item 'form.form' 2776: 2777: Contains the name of the form that has the input fields to set 2778: 2779: =item 'form.element' 2780: 2781: the name of the input field to put the URL into 2782: 2783: =item 'form.titleelement' 2784: 2785: the name of the input field to put the title into 2786: 2787: =back 2788: 2789: =cut 2790: 2791: ###################################################################### 2792: ###################################################################### 2793: sub search_results_header { 2794: my ($importbutton,$closebutton) = @_; 2795: my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); 2796: my $result = ''; 2797: # output beginning of search page 2798: # conditional output of script functions dependent on the mode in 2799: # which the search was invoked 2800: if ($ENV{'form.catalogmode'} eq 'interactive'){ 2801: if (! exists($ENV{'form.mode'}) || $ENV{'form.mode'} ne 'edit') { 2802: $result.=<<SCRIPT; 2803: <script type="text/javascript"> 2804: function select_data(title,url) { 2805: changeTitle(title); 2806: changeURL(url); 2807: parent.close(); 2808: } 2809: function changeTitle(val) { 2810: if (parent.opener.inf.document.forms.resinfo.elements.t) { 2811: parent.opener.inf.document.forms.resinfo.elements.t.value=val; 2812: } 2813: } 2814: function changeURL(val) { 2815: if (parent.opener.inf.document.forms.resinfo.elements.u) { 2816: parent.opener.inf.document.forms.resinfo.elements.u.value=val; 2817: } 2818: } 2819: </script> 2820: SCRIPT 2821: } elsif ($ENV{'form.mode'} eq 'edit') { 2822: my $form = $ENV{'form.form'}; 2823: my $element = $ENV{'form.element'}; 2824: my $titleelement = $ENV{'form.titleelement'}; 2825: my $changetitle; 2826: if (!$titleelement) { 2827: $changetitle='function changeTitle(val) {}'; 2828: } else { 2829: $changetitle=<<END; 2830: function changeTitle(val) { 2831: if (parent.targetwin.document) { 2832: parent.targetwin.document.forms["$form"].elements["$titleelement"].value=val; 2833: } else { 2834: var url = 'forms[\"$form\"].elements[\"$titleelement\"].value'; 2835: alert("Unable to transfer data to "+url); 2836: } 2837: } 2838: END 2839: } 2840: 2841: $result.=<<SCRIPT; 2842: <script type="text/javascript"> 2843: function select_data(title,url) { 2844: changeURL(url); 2845: changeTitle(title); 2846: parent.close(); 2847: } 2848: $changetitle 2849: function changeURL(val) { 2850: if (parent.targetwin.document) { 2851: parent.targetwin.document.forms["$form"].elements["$element"].value=val; 2852: } else { 2853: var url = 'forms[\"$form\"].elements[\"$element\"].value'; 2854: alert("Unable to transfer data to "+url); 2855: } 2856: } 2857: </script> 2858: SCRIPT 2859: } 2860: } 2861: $result.=<<SCRIPT if $ENV{'form.catalogmode'} eq 'groupsearch'; 2862: <script type="text/javascript"> 2863: function queue(checkbox_num,val) { 2864: if (document.forms.results.returnvalues.length != "undefined" && 2865: typeof(document.forms.results.returnvalues.length) == "number") { 2866: if (document.forms.results.returnvalues[checkbox_num].checked) { 2867: parent.statusframe.document.forms.statusform.elements.Queue.value +='1a'+val+'b'; 2868: } else { 2869: parent.statusframe.document.forms.statusform.elements.Queue.value +='0a'+val+'b'; 2870: } 2871: } else { 2872: if (document.forms.results.returnvalues.checked) { 2873: parent.statusframe.document.forms.statusform.elements.Queue.value +='1a'+val+'b'; 2874: } else { 2875: parent.statusframe.document.forms.statusform.elements.Queue.value +='0a'+val+'b'; 2876: } 2877: } 2878: } 2879: function select_group() { 2880: parent.window.location= 2881: "/adm/groupsort?mode=$ENV{'form.mode'}&catalogmode=groupsearch&acts="+ 2882: parent.statusframe.document.forms.statusform.elements.Queue.value; 2883: } 2884: </script> 2885: SCRIPT 2886: $result.=<<END; 2887: </head> 2888: $bodytag 2889: <form name="results" method="post" action="/adm/searchcat" > 2890: <input type="hidden" name="Queue" value="" /> 2891: $importbutton 2892: END 2893: return $result; 2894: } 2895: 2896: ###################################################################### 2897: ###################################################################### 2898: sub search_status_header { 2899: my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); 2900: return <<ENDSTATUS; 2901: <html><head><title>Search Status</title></head> 2902: $bodytag 2903: <h3>Search Status</h3> 2904: Sending search request to LON-CAPA servers.<br /> 2905: ENDSTATUS 2906: } 2907: 2908: sub results_link { 2909: my $basic_link = "/adm/searchcat?"."&table=".$ENV{'form.table'}. 2910: "&persistent_db_id=".$ENV{'form.persistent_db_id'}; 2911: my $results_link = $basic_link."&phase=results". 2912: "&pause=1"."&start=1"; 2913: return $results_link; 2914: } 2915: 2916: ###################################################################### 2917: ###################################################################### 2918: sub print_frames_interface { 2919: my $r = shift; 2920: my $basic_link = "/adm/searchcat?"."&table=".$ENV{'form.table'}. 2921: "&persistent_db_id=".$ENV{'form.persistent_db_id'}; 2922: my $run_search_link = $basic_link."&phase=run_search"; 2923: my $results_link = &results_link(); 2924: my $result = <<"ENDFRAMES"; 2925: <html> 2926: <head> 2927: <script> 2928: var targetwin = opener; 2929: var queue = ''; 2930: </script> 2931: <title>LON-CAPA Digital Library Search Results</title> 2932: </head> 2933: <frameset rows="150,*"> 2934: <frame name="statusframe" src="$run_search_link"> 2935: <frame name="resultsframe" src="$results_link"> 2936: </frameset> 2937: </html> 2938: ENDFRAMES 2939: 2940: $r->print($result); 2941: return; 2942: } 2943: 2944: ###################################################################### 2945: ###################################################################### 2946: 2947: sub has_stat_data { 2948: my ($values) = @_; 2949: if ( (defined($values->{'count'}) && $values->{'count'} ne '') || 2950: (defined($values->{'stdno'}) && $values->{'stdno'} ne '') || 2951: (defined($values->{'disc'}) && $values->{'disc'} ne '') || 2952: (defined($values->{'avetries'}) && $values->{'avetries'} ne '') || 2953: (defined($values->{'difficulty'}) && $values->{'difficulty'} ne '')) { 2954: return 1; 2955: } 2956: return 0; 2957: } 2958: 2959: sub statfields { 2960: return ('count','stdno','disc','avetries','difficulty'); 2961: } 2962: 2963: sub has_eval_data { 2964: my ($values) = @_; 2965: if ( (defined($values->{'clear'}) && $values->{'clear'} ne '') || 2966: (defined($values->{'technical'}) && $values->{'technical'} ne '') || 2967: (defined($values->{'correct'}) && $values->{'correct'} ne '') || 2968: (defined($values->{'helpful'}) && $values->{'helpful'} ne '') || 2969: (defined($values->{'depth'}) && $values->{'depth'} ne '')) { 2970: return 1; 2971: } 2972: return 0; 2973: } 2974: 2975: sub evalfields { 2976: return ('clear','technical','correct','helpful','depth'); 2977: } 2978: 2979: ###################################################################### 2980: ###################################################################### 2981: 2982: =pod 2983: 2984: =item Metadata Viewing Functions 2985: 2986: Output is a HTML-ified string. 2987: 2988: Input arguments are title, author, subject, url, keywords, version, 2989: notes, short abstract, mime, language, creation date, 2990: last revision date, owner, copyright, hostname, and 2991: extra custom metadata to show. 2992: 2993: =over 4 2994: 2995: =item &detailed_citation_view() 2996: 2997: =cut 2998: 2999: ###################################################################### 3000: ###################################################################### 3001: sub detailed_citation_view { 3002: my ($prefix,%values) = @_; 3003: my $result; 3004: $result .= '<b>'.$prefix. 3005: '<img src="'.&Apache::loncommon::icon($values{'url'}).' " />'.' '. 3006: '<a href="http://'.$ENV{'HTTP_HOST'}.$values{'url'}.'" '. 3007: 'target="search_preview">'.$values{'title'}."</a></b>\n"; 3008: $result .= "<p>\n"; 3009: $result .= '<b>'.$values{'author'}.'</b>,'. 3010: ' <i>'.$values{'owner'}.'</i><br />'; 3011: foreach my $field 3012: ( 3013: { name=>'url', 3014: translate => '<b>URL:</b> [_1]', 3015: special => 'url link',}, 3016: { name=>'subject', 3017: translate => '<b>Subject:</b> [_1]',}, 3018: { name=>'keywords', 3019: translate => '<b>Keywords:</b> [_1]',}, 3020: { name=>'notes', 3021: translate => '<b>Notes:</b> [_1]',}, 3022: { name=>'mimetag', 3023: translate => '<b>MIME Type:</b> [_1]',}, 3024: { name=>'standards', 3025: translate => '<b>Standards:</b>[_1]',}, 3026: { name=>'copyrighttag', 3027: translate => '<b>Copyright/Distribution:</b> [_1]',}, 3028: { name=>'count', 3029: format => "%d", 3030: translate => '<b>Access Count:</b> [_1]',}, 3031: { name=>'stdno', 3032: format => "%d", 3033: translate => '<b>Number of Students:</b> [_1]',}, 3034: { name=>'avetries', 3035: format => "%.2f", 3036: translate => '<b>Average Tries:</b> [_1]',}, 3037: { name=>'disc', 3038: format => "%.2f", 3039: translate => '<b>Degree of Discrimination:</b> [_1]',}, 3040: { name=>'difficulty', 3041: format => "%.2f", 3042: translate => '<b>Degree of Difficulty:</b> [_1]',}, 3043: { name=>'clear', 3044: format => "%.2f", 3045: translate => '<b>Clear:</b> [_1]',}, 3046: { name=>'depth', 3047: format => "%.2f", 3048: translate => '<b>Depth:</b> [_1]',}, 3049: { name=>'helpful', 3050: format => "%.2f", 3051: translate => '<b>Helpful:</b> [_1]',}, 3052: { name=>'correct', 3053: format => "%.2f", 3054: translate => '<b>Correct:</b> [_1]',}, 3055: { name=>'technical', 3056: format => "%.2f", 3057: translate => '<b>Technical:</b> [_1]',}, 3058: { name=>'comefrom_list', 3059: type => 'list', 3060: translate => 'Resources that lead up to this resource in maps',}, 3061: { name=>'goto_list', 3062: type => 'list', 3063: translate => 'Resources that follow this resource in maps',}, 3064: { name=>'sequsage_list', 3065: type => 'list', 3066: translate => 'Resources using or importing resource',}, 3067: ) { 3068: next if (! exists($values{$field->{'name'}}) || 3069: $values{$field->{'name'}} eq ''); 3070: if (exists($field->{'type'}) && $field->{'type'} eq 'list') { 3071: $result .= '<b>'.&mt($field->{'translate'}).'</b><ul>'; 3072: foreach my $item (split(',',$values{$field->{'name'}})){ 3073: $result .= '<li>'. 3074: '<a target="search_preview" '. 3075: 'href="/res/'.$item.'">'.$item.'</a></li>'; 3076: } 3077: $result .= '</ul>'; 3078: } elsif (exists($field->{'format'}) && $field->{'format'} ne ''){ 3079: $result.= &mt($field->{'translate'}, 3080: sprintf($field->{'format'}, 3081: $values{$field->{'name'}})).'<br />'."\n"; 3082: } else { 3083: if ($field->{'special'} eq 'url link') { 3084: $result.= 3085: &mt($field->{'translate'}, 3086: '<a href="'.$values{'url'}.'" '. 3087: 'target="search_preview">'. 3088: $values{$field->{'name'}}. 3089: '</a>'); 3090: } else { 3091: $result.= &mt($field->{'translate'}, 3092: $values{$field->{'name'}}); 3093: } 3094: $result .= "<br />\n"; 3095: } 3096: } 3097: $result .= "</p>"; 3098: if (exists($values{'extrashow'}) && $values{'extrashow'} ne '') { 3099: $result .= '<p>'.$values{'extrashow'}.'</p>'; 3100: } 3101: if (exists($values{'shortabstract'}) && $values{'shortabstract'} ne '') { 3102: $result .= '<p>'.$values{'shortabstract'}.'</p>'; 3103: } 3104: $result .= '<hr align="left" width="200" noshade />'."\n"; 3105: return $result; 3106: } 3107: 3108: ###################################################################### 3109: ###################################################################### 3110: 3111: =pod 3112: 3113: =item &summary_view() 3114: 3115: =cut 3116: ###################################################################### 3117: ###################################################################### 3118: sub summary_view { 3119: my ($prefix,%values) = @_; 3120: my $icon=&Apache::loncommon::icon($values{'url'}); 3121: my $result=<<END; 3122: $prefix<img src="$icon" /> 3123: <a href="http://$ENV{'HTTP_HOST'}$values{'url'}" 3124: target='search_preview'>$values{'author'}</a><br /> 3125: $values{'title'}<br /> 3126: $values{'owner'} -- $values{'lastrevisiondate'}<br /> 3127: $values{'copyrighttag'}<br /> 3128: $values{'extrashow'} 3129: </p> 3130: <hr align='left' width='200' noshade /> 3131: END 3132: return $result; 3133: } 3134: 3135: ###################################################################### 3136: ###################################################################### 3137: 3138: =pod 3139: 3140: =item &compact_view() 3141: 3142: =cut 3143: 3144: ###################################################################### 3145: ###################################################################### 3146: sub compact_view { 3147: my ($prefix,%values) = @_; 3148: my $result = 3149: $prefix.'<img src="'.&Apache::loncommon::icon($values{'url'}). 3150: '"> <a href="'.$values{'url'}.'" target="search_preview">'. 3151: $values{'title'}.'</a>'.(' 'x2). 3152: '<b>'.$values{'author'}.'</b><br />'; 3153: return $result; 3154: } 3155: 3156: 3157: ###################################################################### 3158: ###################################################################### 3159: 3160: =pod 3161: 3162: =item &fielded_format_view() 3163: 3164: =cut 3165: 3166: ###################################################################### 3167: ###################################################################### 3168: sub fielded_format_view { 3169: my ($prefix,%values) = @_; 3170: my $icon=&Apache::loncommon::icon($values{'url'}); 3171: my %Translated = &Apache::lonmeta::fieldnames(); 3172: my $result=<<END; 3173: $prefix <img src="$icon" /> 3174: <dl> 3175: <dt>URL:</dt> 3176: <dd><a href="http://$ENV{'HTTP_HOST'}$values{'url'}" 3177: target='search_preview'>$values{'url'}</a></dd> 3178: END 3179: foreach my $field ('title','author','subject','keywords','notes', 3180: 'mimetag','language','creationdate','lastrevisiondate', 3181: 'owner','copyrighttag','hostname','abstract') { 3182: $result .= (' 'x4).'<dt>'.$Translated{$field}.'</dt>'."\n". 3183: (' 'x8).'<dd>'.$values{$field}.'</dd>'."\n"; 3184: } 3185: if (&has_stat_data(\%values)) { 3186: foreach my $field (&statfields()) { 3187: $result .= (' 'x4).'<dt>'.$Translated{$field}.'</dt>'."\n". 3188: (' 'x8).'<dd>'.$values{$field}.'</dd>'."\n"; 3189: } 3190: } 3191: if (&has_eval_data(\%values)) { 3192: foreach my $field (&evalfields()) { 3193: $result .= (' 'x4).'<dt>'.$Translated{$field}.'</dt>'."\n". 3194: (' 'x8).'<dd>'.$values{$field}.'</dd>'."\n"; 3195: } 3196: } 3197: $result .= "</dl>\n"; 3198: $result .= $values{'extrashow'}; 3199: $result .= '<hr align="left" width="200" noshade />'."\n"; 3200: return $result; 3201: } 3202: 3203: ###################################################################### 3204: ###################################################################### 3205: 3206: =pod 3207: 3208: =item &xml_sgml_view() 3209: 3210: =back 3211: 3212: =cut 3213: 3214: ###################################################################### 3215: ###################################################################### 3216: sub xml_sgml_view { 3217: my ($prefix,%values) = @_; 3218: my $xml = '<LonCapaResource>'."\n"; 3219: # The usual suspects 3220: foreach my $field ('url','title','author','subject','keywords','notes') { 3221: $xml .= qq{<$field>$values{$field}</$field>}."\n"; 3222: } 3223: # 3224: $xml .= "<mimeInfo>\n"; 3225: foreach my $field ('mime','mimetag') { 3226: $xml .= qq{<$field>$values{$field}</$field>}."\n"; 3227: } 3228: $xml .= "</mimeInfo>\n"; 3229: # 3230: $xml .= "<languageInfo>\n"; 3231: foreach my $field ('language','languagetag') { 3232: $xml .= qq{<$field>$values{$field}</$field>}."\n"; 3233: } 3234: $xml .= "</languageInfo>\n"; 3235: # 3236: foreach my $field ('creationdate','lastrevisiondate','owner') { 3237: $xml .= qq{<$field>$values{$field}</$field>}."\n"; 3238: } 3239: # 3240: $xml .= "<copyrightInfo>\n"; 3241: foreach my $field ('copyright','copyrighttag') { 3242: $xml .= qq{<$field>$values{$field}</$field>}."\n"; 3243: } 3244: $xml .= "</copyrightInfo>\n"; 3245: $xml .= qq{<repositoryLocation>$values{'hostname'}</repositoryLocation>}. 3246: "\n"; 3247: $xml .= qq{<shortabstract>$values{'shortabstract'}</shortabstract>}."\n"; 3248: # 3249: if (&has_stat_data(\%values)){ 3250: $xml .= "<problemstatistics>\n"; 3251: foreach my $field (&statfields()) { 3252: $xml .= qq{<$field>$values{$field}</$field>}."\n"; 3253: } 3254: $xml .= "</problemstatistics>\n"; 3255: } 3256: # 3257: if (&has_eval_data(\%values)) { 3258: $xml .= "<evaluation>\n"; 3259: foreach my $field (&evalfields) { 3260: $xml .= qq{<$field>$values{$field}</$field>}."\n"; 3261: } 3262: $xml .= "</evaluation>\n"; 3263: } 3264: # 3265: $xml .= "</LonCapaResource>\n"; 3266: $xml = &HTML::Entities::encode($xml,'<>&'); 3267: my $result=<<END; 3268: $prefix 3269: <pre> 3270: $xml 3271: </pre> 3272: $values{'extrashow'} 3273: <hr align='left' width='200' noshade /> 3274: END 3275: return $result; 3276: } 3277: 3278: ###################################################################### 3279: ###################################################################### 3280: 3281: =pod 3282: 3283: =item &filled() see if field is filled. 3284: 3285: =cut 3286: 3287: ###################################################################### 3288: ###################################################################### 3289: sub filled { 3290: my ($field)=@_; 3291: if ($field=~/\S/ && $field ne 'any') { 3292: return 1; 3293: } else { 3294: return 0; 3295: } 3296: } 3297: 3298: ###################################################################### 3299: ###################################################################### 3300: 3301: =pod 3302: 3303: =item &output_unparsed_phrase_error() 3304: 3305: =cut 3306: 3307: ###################################################################### 3308: ###################################################################### 3309: sub output_unparsed_phrase_error { 3310: my ($r,$closebutton,$parms,$hidden_fields,$field)=@_; 3311: my $errorstring; 3312: if ($field eq 'basicexp') { 3313: $errorstring = &mt('Unable to understand the search phrase <i>[_1]</i>. Please modify your search.',$ENV{'form.basicexp'}); 3314: } else { 3315: $errorstring = &mt('Unable to understand the search phrase <b>[_1]</b>:<i>[_2]</i>.',$field,$ENV{'form.'.$field}); 3316: } 3317: my $bodytag = &Apache::loncommon::bodytag('Search'); 3318: my $heading = &mt('Unparsed Field'); 3319: my $revise = &mt('Revise search request'); 3320: # make query information persistent to allow for subsequent revision 3321: $r->print(<<ENDPAGE); 3322: <html> 3323: <head> 3324: <title>The LearningOnline Network with CAPA</title> 3325: </head> 3326: $bodytag 3327: <form method="post" action="/adm/searchcat"> 3328: $hidden_fields 3329: $closebutton 3330: <hr /> 3331: <h2>$heading</h2> 3332: <p> 3333: $errorstring 3334: </p> 3335: <p> 3336: <a href="/adm/searchcat?$parms&persistent_db_id=$ENV{'form.persistent_db_id'}">$revise</a> 3337: </p> 3338: </body> 3339: </html> 3340: ENDPAGE 3341: } 3342: 3343: ###################################################################### 3344: ###################################################################### 3345: 3346: =pod 3347: 3348: =item &output_blank_field_error() 3349: 3350: Output a complete page that indicates the user has not filled in enough 3351: information to do a search. 3352: 3353: Inputs: $r (Apache request handle), $closebutton, $parms. 3354: 3355: Returns: nothing 3356: 3357: $parms is extra information to include in the 'Revise search request' link. 3358: 3359: =cut 3360: 3361: ###################################################################### 3362: ###################################################################### 3363: sub output_blank_field_error { 3364: my ($r,$closebutton,$parms,$hidden_fields)=@_; 3365: my $bodytag=&Apache::loncommon::bodytag('Search'); 3366: my $errormsg = &mt('You did not fill in enough information for the search to be started. You need to fill in relevant fields on the search page in order for a query to be processed.'); 3367: my $revise = &mt('Revise Search Request'); 3368: my $heading = &mt('Unactionable Search Queary'); 3369: $r->print(<<ENDPAGE); 3370: <html> 3371: <head> 3372: <title>The LearningOnline Network with CAPA</title> 3373: </head> 3374: $bodytag 3375: <form method="post" action="/adm/searchcat"> 3376: $hidden_fields 3377: $closebutton 3378: <hr /> 3379: <h2>$heading</h2> 3380: <p> 3381: $errormsg 3382: </p> 3383: <p> 3384: <a href="/adm/searchcat?$parms&persistent_db_id=$ENV{'form.persistent_db_id'}">$revise</a> 3385: </p> 3386: </body> 3387: </html> 3388: ENDPAGE 3389: return; 3390: } 3391: 3392: ###################################################################### 3393: ###################################################################### 3394: 3395: =pod 3396: 3397: =item &output_date_error() 3398: 3399: Output a full html page with an error message. 3400: 3401: Inputs: 3402: 3403: $r, the request pointer. 3404: $message, the error message for the user. 3405: $closebutton, the specialized close button needed for groupsearch. 3406: 3407: =cut 3408: 3409: ###################################################################### 3410: ###################################################################### 3411: sub output_date_error { 3412: my ($r,$message,$closebutton,$hidden_fields)=@_; 3413: # make query information persistent to allow for subsequent revision 3414: my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); 3415: $r->print(<<RESULTS); 3416: <html> 3417: <head> 3418: <title>The LearningOnline Network with CAPA</title> 3419: </head> 3420: $bodytag 3421: <img align='right' src='/adm/lonIcons/lonlogos.gif' /> 3422: <h1>Search Catalog</h1> 3423: <form method="post" action="/adm/searchcat"> 3424: $hidden_fields 3425: <input type='button' value='Revise search request' 3426: onClick='this.form.submit();' /> 3427: $closebutton 3428: <hr /> 3429: <h3>Error</h3> 3430: <p> 3431: $message 3432: </p> 3433: </body> 3434: </html> 3435: RESULTS 3436: } 3437: 3438: ###################################################################### 3439: ###################################################################### 3440: 3441: =pod 3442: 3443: =item &start_fresh_session() 3444: 3445: Cleans the global %groupsearch_db by removing all fields which begin with 3446: 'pre_' or 'store'. 3447: 3448: =cut 3449: 3450: ###################################################################### 3451: ###################################################################### 3452: sub start_fresh_session { 3453: delete $groupsearch_db{'mode_catalog'}; 3454: foreach (keys %groupsearch_db) { 3455: if ($_ =~ /^pre_/) { 3456: delete $groupsearch_db{$_}; 3457: } 3458: if ($_ =~ /^store/) { 3459: delete $groupsearch_db{$_}; 3460: } 3461: } 3462: } 3463: 3464: 1; 3465: 3466: sub cleanup { 3467: if (tied(%groupsearch_db)) { 3468: unless (untie(%groupsearch_db)) { 3469: &Apache::lonnet::logthis('Failed cleanup searchcat: groupsearch_db'); 3470: } 3471: } 3472: &untiehash(); 3473: &Apache::lonmysql::disconnect_from_db(); 3474: } 3475: 3476: __END__ 3477: 3478: =pod 3479: 3480: =back 3481: 3482: =cut