File:
[LON-CAPA] /
rat /
lonuserstate.pm
Revision
1.42:
download - view:
text,
annotated -
select for diffs
Fri Sep 6 15:18:23 2002 UTC (21 years, 11 months ago) by
www
Branches:
MAIN
CVS tags:
HEAD
"showdoc" needs to be URL-encode like all the other functionality in order to
properly register with the remote. Remote has now additional fields to store symb, but
nothing is done with them yet. Eventually, this should improve navigation and contents
display for resources showing up multiple times.
1: # The LearningOnline Network with CAPA
2: # Construct and maintain state and binary representation of course for user
3: #
4: # $Id: lonuserstate.pm,v 1.42 2002/09/06 15:18:23 www 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: # (Server for RAT Maps
29: #
30: # (Edit Handler for RAT Maps
31: # (TeX Content Handler
32: #
33: # YEAR=2000
34: # 05/29/00,05/30 Gerd Kortemeyer)
35: # 7/1 Gerd Kortemeyer)
36: # 7/1,7/3,7/4,7/7,7/8,7/10 Gerd Kortemeyer)
37: #
38: # 7/15,7/17,7/18,8/1,8/2,8/4,8/5,8/21,8/22,8/23,8/30,
39: # 9/2,9/4,9/29,9/30,10/2,10/11,10/30,10/31,
40: # 11/1,11/2,11/14,11/16,11/22,12/28,
41: # YEAR=2001
42: # 07/05/01,08/30,08/31 Gerd Kortemeyer
43: # 12/16 Scott Harrison
44: #
45: ###
46:
47: package Apache::lonuserstate;
48:
49: # ------------------------------------------------- modules used by this module
50: use strict;
51: use Apache::Constants qw(:common :http);
52: use Apache::File;
53: use HTML::TokeParser;
54: use Apache::lonnet();
55: use Apache::loncommon();
56: use GDBM_File;
57: use Apache::lonmsg;
58: use Safe;
59: use Safe::Hole;
60: use Opcode;
61:
62: # ---------------------------------------------------- Globals for this package
63:
64: my $pc; # Package counter
65: my %hash; # The big tied hash
66: my %parmhash;# The hash with the parameters
67: my @cond; # Array with all of the conditions
68: my $errtext; # variable with all errors
69: my $retfurl; # variable with the very first URL in the course
70: my %randompick; # randomly picked resources
71: # --------------------------------------------------------- Loads map from disk
72:
73: sub loadmap {
74: my $uri=shift;
75: if ($hash{'map_pc_'.$uri}) { return OK; }
76:
77: $pc++;
78: my $lpc=$pc;
79: $hash{'map_pc_'.$uri}=$lpc;
80: $hash{'map_id_'.$lpc}=$uri;
81:
82: # Determine and check filename
83: my $fn=&Apache::lonnet::filelocation('',$uri);
84:
85: my $ispage=($fn=~/\.page$/);
86:
87: unless (($fn=~/\.sequence$/) ||
88: ($fn=~/\.page$/)) {
89: $errtext.="Invalid map: $fn\n";
90: return OK;
91: }
92:
93: my $instr=&Apache::lonnet::getfile($fn);
94:
95: unless ($instr == -1) {
96:
97: # Successfully got file, parse it
98:
99: my $parser = HTML::TokeParser->new(\$instr);
100: my $token;
101:
102: my $linkpc=0;
103:
104: $fn=~/\.(\w+)$/;
105:
106: $hash{'map_type_'.$lpc}=$1;
107:
108: while ($token = $parser->get_token) {
109: if ($token->[0] eq 'S') {
110: if ($token->[1] eq 'resource') {
111: # -------------------------------------------------------------------- Resource
112:
113: my $rid=$lpc.'.'.$token->[2]->{'id'};
114:
115: $hash{'kind_'.$rid}='res';
116: $hash{'title_'.$rid}=$token->[2]->{'title'};
117: my $turi=$token->[2]->{'src'};
118: unless ($ispage) {
119: $turi=~/\.(\w+)$/;
120: my $embstyle=&Apache::loncommon::fileembstyle($1);
121: if ($token->[2]->{'external'} eq 'true') { # external
122: $turi=~s/^http\:\/\//\/adm\/wrapper\/ext\//;
123: } elsif ($turi=~/^\/*uploaded\//) { # uploaded
124: if (($embstyle eq 'img') || ($embstyle eq 'emb')
125: || ($embstyle eq 'ssi')) {
126: $turi='/adm/wrapper'.$turi;
127: } elsif ($turi!~/\.(sequence|page)$/) {
128: $turi='/adm/coursedocs/showdoc'.$turi;
129: }
130: } else { # normal internal resource
131: if (($embstyle eq 'img') || ($embstyle eq 'emb')
132: || ($turi=~/\/syllabus$/) || ($turi=~/\/aboutme$/)
133: || ($turi=~/\/navmaps$/)) {
134: $turi='/adm/wrapper'.$turi;
135: }
136: }
137: }
138: $hash{'src_'.$rid}=$turi;
139:
140: if (defined($hash{'ids_'.$turi})) {
141: $hash{'ids_'.$turi}.=','.$rid;
142: } else {
143: $hash{'ids_'.$turi}=''.$rid;
144: }
145:
146: if ($token->[2]->{'external'} eq 'true') {
147: $hash{'ext_'.$rid}='true:';
148: } else {
149: $hash{'ext_'.$rid}='false:';
150: }
151: if ($token->[2]->{'type'}) {
152: $hash{'type_'.$rid}=$token->[2]->{'type'};
153: if ($token->[2]->{'type'} eq 'start') {
154: $hash{'map_start_'.$uri}="$rid";
155: }
156: if ($token->[2]->{'type'} eq 'finish') {
157: $hash{'map_finish_'.$uri}="$rid";
158: }
159: } else {
160: $hash{'type_'.$rid}='normal';
161: }
162:
163: if (($turi=~/\.sequence$/) ||
164: ($turi=~/\.page$/)) {
165: $hash{'is_map_'.$rid}=1;
166: &loadmap($turi);
167: }
168:
169: } elsif ($token->[1] eq 'condition') {
170: # ------------------------------------------------------------------- Condition
171:
172: my $rid=$lpc.'.'.$token->[2]->{'id'};
173:
174: $hash{'kind_'.$rid}='cond';
175: $cond[$#cond+1]=$token->[2]->{'value'};
176: $hash{'condid_'.$rid}=$#cond;
177: if ($token->[2]->{'type'}) {
178: $cond[$#cond].=':'.$token->[2]->{'type'};
179: } else {
180: $cond[$#cond].=':normal';
181: }
182:
183: } elsif ($token->[1] eq 'link') {
184: # ----------------------------------------------------------------------- Links
185:
186: $linkpc++;
187: my $linkid=$lpc.'.'.$linkpc;
188:
189: my $goesto=$lpc.'.'.$token->[2]->{'to'};
190: my $comesfrom=$lpc.'.'.$token->[2]->{'from'};
191: my $undercond=0;
192:
193: if ($token->[2]->{'condition'}) {
194: $undercond=$lpc.'.'.$token->[2]->{'condition'};
195: }
196:
197: $hash{'goesto_'.$linkid}=$goesto;
198: $hash{'comesfrom_'.$linkid}=$comesfrom;
199: $hash{'undercond_'.$linkid}=$undercond;
200:
201: if (defined($hash{'to_'.$comesfrom})) {
202: $hash{'to_'.$comesfrom}.=','.$linkid;
203: } else {
204: $hash{'to_'.$comesfrom}=''.$linkid;
205: }
206: if (defined($hash{'from_'.$goesto})) {
207: $hash{'from_'.$goesto}.=','.$linkid;
208: } else {
209: $hash{'from_'.$goesto}=''.$linkid;
210: }
211: } elsif ($token->[1] eq 'param') {
212: # ------------------------------------------------------------------- Parameter
213:
214: my $referid=$lpc.'.'.$token->[2]->{'to'};
215: my $part=$token->[2]->{'part'};
216: unless ($part) { $part=0; }
217: my $newparam=
218: &Apache::lonnet::escape($token->[2]->{'type'}).':'.
219: &Apache::lonnet::escape($part.'.'.
220: $token->[2]->{'name'}).'='.
221: &Apache::lonnet::escape($token->[2]->{'value'});
222: if (defined($hash{'param_'.$referid})) {
223: $hash{'param_'.$referid}.='&'.$newparam;
224: } else {
225: $hash{'param_'.$referid}=''.$newparam;
226: }
227: if ($token->[2]->{'name'} eq 'parameter_mapalias') {
228: $hash{'mapalias_'.$token->[2]->{'value'}}=$referid;
229: }
230: if ($token->[2]->{'name'} eq 'parameter_randompick') {
231: $randompick{$referid}=$token->[2]->{'value'};
232: }
233: }
234:
235: }
236: }
237:
238: } else {
239: $errtext.='Map not loaded: The file does not exist. ';
240: }
241: }
242:
243: # --------------------------------------------------------- Simplify expression
244:
245: sub simplify {
246: my $expression=shift;
247: # (8)=8
248: $expression=~s/\((\d+)\)/$1/g;
249: # 8&8=8
250: $expression=~s/(\D)(\d+)\&\2(\D)/$1$2$3/g;
251: # 8|8=8
252: $expression=~s/(\D)(\d+)\|\2(\D)/$1$2$3/g;
253: # (5&3)&4=5&3&4
254: $expression=~s/\((\d+)((?:\&\d+)+)\)\&(\d+\D)/$1$2\&$3/g;
255: # (((5&3)|(4&6)))=((5&3)|(4&6))
256: $expression=~
257: s/\((\(\(\d+(?:\&\d+)*\)(?:\|\(\d+(?:\&\d+)*\))+\))\)/$1/g;
258: # ((5&3)|(4&6))|(1&2)=(5&3)|(4&6)|(1&2)
259: $expression=~
260: s/\((\(\d+(?:\&\d+)*\))((?:\|\(\d+(?:\&\d+)*\))+)\)\|(\(\d+(?:\&\d+)*\))/\($1$2\|$3\)/g;
261: return $expression;
262: }
263:
264: # -------------------------------------------------------- Build condition hash
265:
266: sub traceroute {
267: my ($sofar,$rid,$beenhere)=@_;
268: $sofar=simplify($sofar);
269: unless ($beenhere=~/\&$rid\&/) {
270: $beenhere.=$rid.'&';
271: if (($retfurl eq '') && ($hash{'src_'.$rid})) {
272: my ($mapid,$resid)=split(/\./,$rid);
273: $retfurl=$hash{'src_'.$rid}.
274: (($hash{'src_'.$rid}=~/\?/)?'&':'?').'symb='.
275: &Apache::lonnet::symbclean(
276: &Apache::lonnet::declutter($hash{'map_id_'.$mapid}).
277: '___'.$resid.'___'.
278: &Apache::lonnet::declutter($hash{'src_'.$rid}));
279: }
280: if (defined($hash{'conditions_'.$rid})) {
281: $hash{'conditions_'.$rid}=simplify(
282: '('.$hash{'conditions_'.$rid}.')|('.$sofar.')');
283: } else {
284: $hash{'conditions_'.$rid}=$sofar;
285: }
286: if (defined($hash{'is_map_'.$rid})) {
287: if (defined($hash{'map_start_'.$hash{'src_'.$rid}})) {
288: &traceroute($sofar,$hash{'map_start_'.$hash{'src_'.$rid}},'&');
289: if (defined($hash{'map_finish_'.$hash{'src_'.$rid}})) {
290: $sofar=
291: $hash{'conditions_'.$hash{'map_finish_'.$hash{'src_'.$rid}}};
292: }
293: }
294: }
295: if (defined($hash{'to_'.$rid})) {
296: foreach (split(/\,/,$hash{'to_'.$rid})) {
297: my $further=$sofar;
298: if ($hash{'undercond_'.$_}) {
299: if (defined($hash{'condid_'.$hash{'undercond_'.$_}})) {
300: $further=simplify('('.$further.')&('.
301: $hash{'condid_'.$hash{'undercond_'.$_}}.')');
302: } else {
303: $errtext.='Undefined condition ID: '
304: .$hash{'undercond_'.$_}.'. ';
305: }
306: }
307: &traceroute($further,$hash{'goesto_'.$_},$beenhere);
308: }
309: }
310: }
311: }
312:
313: # ------------------------------ Cascading conditions, quick access, parameters
314:
315: sub accinit {
316: my ($uri,$short,$fn)=@_;
317: my %acchash=();
318: my %captured=();
319: my $condcounter=0;
320: $acchash{'acc.cond.'.$short.'.0'}=0;
321: foreach (keys %hash) {
322: if ($_=~/^conditions/) {
323: my $expr=$hash{$_};
324: foreach ($expr=~m/(\(\(\d+(?:\&\d+)+\)(?:\|\(\d+(?:\&\d+)+\))+\))/g) {
325: my $sub=$_;
326: my $orig=$_;
327: $sub=~/\(\((\d+\&(:?\d+\&)*)(?:\d+\&*)+\)(?:\|\(\1(?:\d+\&*)+\))+\)/;
328: my $factor=$1;
329: $sub=~s/$factor//g;
330: $sub=~s/^\(/\($factor\(/;
331: $sub.=')';
332: $sub=simplify($sub);
333: $orig=~s/(\W)/\\$1/g;
334: $expr=~s/$orig/$sub/;
335: }
336: $hash{$_}=$expr;
337: unless (defined($captured{$expr})) {
338: $condcounter++;
339: $captured{$expr}=$condcounter;
340: $acchash{'acc.cond.'.$short.'.'.$condcounter}=$expr;
341: }
342: } elsif ($_=~/^param_(\d+)\.(\d+)/) {
343: my $prefix=&Apache::lonnet::declutter($hash{'map_id_'.$1}).
344: '___'.$2.'___'.&Apache::lonnet::declutter($hash{'src_'.$1.'.'.$2});
345: foreach (split(/\&/,$hash{$_})) {
346: my ($typename,$value)=split(/\=/,$_);
347: my ($type,$name)=split(/\:/,$typename);
348: $parmhash{$prefix.'.'.&Apache::lonnet::unescape($name)}=
349: &Apache::lonnet::unescape($value);
350: $parmhash{$prefix.'.'.&Apache::lonnet::unescape($name).'.type'}=
351: &Apache::lonnet::unescape($type);
352: }
353: }
354: }
355: foreach (keys %hash) {
356: if ($_=~/^ids/) {
357: foreach (split(/\,/,$hash{$_})) {
358: my $resid=$_;
359: my $uri=$hash{'src_'.$resid};
360: $uri=~s/^\/adm\/wrapper//;
361: my @uriparts=split(/\//,$uri);
362: my $urifile=$uriparts[$#uriparts];
363: $#uriparts--;
364: my $uripath=join('/',@uriparts);
365: $uripath=~s/^\/res\///;
366: if ($uripath) {
367: my $uricond='0';
368: if (defined($hash{'conditions_'.$resid})) {
369: $uricond=$captured{$hash{'conditions_'.$resid}};
370: }
371: if (defined($acchash{'acc.res.'.$short.'.'.$uripath})) {
372: if ($acchash{'acc.res.'.$short.'.'.$uripath}=~
373: /(\&$urifile\:[^\&]*)/) {
374: my $replace=$1;
375: my $regexp=$replace;
376: $regexp=~s/\|/\\\|/g;
377: $acchash{'acc.res.'.$short.'.'.$uripath}
378: =~s/$regexp/$replace\|$uricond/;
379: } else {
380: $acchash{'acc.res.'.$short.'.'.$uripath}.=
381: $urifile.':'.$uricond.'&';
382: }
383: } else {
384: $acchash{'acc.res.'.$short.'.'.$uripath}=
385: '&'.$urifile.':'.$uricond.'&';
386: }
387: }
388: }
389: }
390: }
391: $acchash{'acc.res.'.$short.'.'}='&:0&';
392: my $courseuri=$uri;
393: $courseuri=~s/^\/res\///;
394: &Apache::lonnet::delenv('(acc\.|httpref\.)');
395: &Apache::lonnet::appenv(%acchash,
396: "request.course.id" => $short,
397: "request.course.fn" => $fn,
398: "request.course.uri" => $courseuri);
399: }
400:
401: # ------------------------------------- Selectively delete from randompick maps
402:
403: sub pickrandom {
404: my $randomoutentry='';
405: foreach my $rid (keys %randompick) {
406: my $rndpick=$randompick{$rid};
407: my $mpc=$hash{'map_pc_'.$hash{'src_'.$rid}};
408: # ------------------------------------------- put existing resources into array
409: my @currentrids=();
410: foreach (keys %hash) {
411: if ($_=~/^src_($mpc\.\d+)/) {
412: if ($hash{'src_'.$1}) { push @currentrids, $1; }
413: }
414: }
415: next if ($#currentrids<$rndpick);
416: # -------------------------------- randomly eliminate the ones that should stay
417: srand(&Apache::lonnet::rndseed($rid)); # use rid instead of symb
418: for (my $i=1;$i<=$rndpick;$i++) {
419: while (1) {
420: my $randomidx=int(rand($#currentrids+1));
421: if ($currentrids[$randomidx]) {
422: $currentrids[$randomidx]='';
423: last;
424: }
425: }
426: }
427: # -------------------------------------------------------- delete the leftovers
428: for (my $k=0; $k<=$#currentrids; $k++) {
429: if ($currentrids[$k]) {
430: $hash{'randomout_'.$currentrids[$k]}=1;
431: my ($mapid,$resid)=split(/\./,$currentrids[$k]);
432: $randomoutentry.='&'.
433: &Apache::lonnet::symbclean(
434: &Apache::lonnet::declutter($hash{'map_id_'.$mapid}).
435: '___'.$resid.'___'.
436: &Apache::lonnet::declutter($hash{'src_'.$currentrids[$k]})
437: ).'&';
438: }
439: }
440: }
441: if ($randomoutentry) {
442: &Apache::lonnet::appenv('acc.randomout' => $randomoutentry);
443: }
444: }
445:
446: # ---------------------------------------------------- Read map and all submaps
447:
448: sub readmap {
449: my $short=shift;
450: $short=~s/^\///;
451: my %cenv=&Apache::lonnet::coursedescription($short);
452: my $fn=$cenv{'fn'};
453: my $uri;
454: $short=~s/\//\_/g;
455: unless ($uri=$cenv{'url'}) {
456: &Apache::lonnet::logthis("<font color=blue>WARNING: ".
457: "Could not load course $short.</font>");
458: return 'No course data available.';
459: }
460: @cond=('true:normal');
461: unlink($fn.'.db');
462: unlink($fn.'_symb.db');
463: unlink($fn.'.state');
464: unlink($fn.'parms.db');
465: undef %randompick;
466: $retfurl='';
467: if ((tie(%hash,'GDBM_File',"$fn.db",&GDBM_WRCREAT(),0640)) &&
468: (tie(%parmhash,'GDBM_File',$fn.'_parms.db',&GDBM_WRCREAT(),0640))) {
469: %hash=();
470: %parmhash=();
471: $errtext='';
472: $pc=0;
473: my $furi=&Apache::lonnet::clutter($uri);
474: $hash{'src_0.0'}=$furi;
475: $hash{'title_0.0'}=&Apache::lonnet::metadata($uri,'title');
476: $hash{'ids_'.$furi}='0.0';
477: $hash{'is_map_0.0'}=1;
478: loadmap($uri);
479: if (defined($hash{'map_start_'.$uri})) {
480: &traceroute('0',$hash{'map_start_'.$uri},'&');
481: &accinit($uri,$short,$fn);
482: &pickrandom();
483: }
484: unless ((untie(%hash)) && (untie(%parmhash))) {
485: &Apache::lonnet::logthis("<font color=blue>WARNING: ".
486: "Could not untie coursemap $fn for $uri.</font>");
487: }
488: {
489: my $cfh;
490: if ($cfh=Apache::File->new(">$fn.state")) {
491: print $cfh join("\n",@cond);
492: } else {
493: &Apache::lonnet::logthis("<font color=blue>WARNING: ".
494: "Could not write statemap $fn for $uri.</font>");
495: }
496: }
497: } else {
498: &Apache::lonnet::logthis("<font color=blue>WARNING: ".
499: "Could not tie coursemap $fn for $uri.</font>");
500: }
501: &Apache::lonmsg::author_res_msg($ENV{'request.course.uri'},$errtext);
502: return ($retfurl,$errtext);
503: }
504:
505: # ------------------------------------------------------- Evaluate state string
506:
507: sub evalstate {
508:
509: my $fn=$ENV{'request.course.fn'}.'.state';
510: my $state='2';
511: if (-e $fn) {
512: my @conditions=();
513: {
514: my $fh=Apache::File->new($fn);
515: @conditions=<$fh>;
516: }
517: my $safeeval = new Safe;
518: my $safehole = new Safe::Hole;
519: $safeeval->permit("entereval");
520: $safeeval->permit(":base_math");
521: $safeeval->deny(":base_io");
522: $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT');
523: foreach (@conditions) {
524: my $line=$_;
525: chomp($line);
526: my ($condition,$weight)=split(/\:/,$_);
527: if ($safeeval->reval($condition)) {
528: if ($weight eq 'force') {
529: $state.='3';
530: } else {
531: $state.='2';
532: }
533: } else {
534: if ($weight eq 'stop') {
535: $state.='0';
536: } else {
537: $state.='1';
538: }
539: }
540: }
541: }
542: &Apache::lonnet::appenv('user.state.'.$ENV{'request.course.id'} => $state);
543: return $state;
544: }
545:
546: 1;
547: __END__
548:
549: =head1 NAME
550:
551: Apache::lonuserstate - Construct and maintain state and binary representation
552: of course for user
553:
554: =head1 SYNOPSIS
555:
556: Invoked by lonroles.pm.
557:
558: &Apache::lonuserstate::readmap($cdom.'/'.$cnum);
559:
560: =head1 INTRODUCTION
561:
562: This module constructs and maintains state and binary representation
563: of course for user.
564:
565: This is part of the LearningOnline Network with CAPA project
566: described at http://www.lon-capa.org.
567:
568: =head1 HANDLER SUBROUTINE
569:
570: There is no handler subroutine.
571:
572: =head1 OTHER SUBROUTINES
573:
574: =over 4
575:
576: =item *
577:
578: loadmap() : Loads map from disk
579:
580: =item *
581:
582: simplify() : Simplify expression
583:
584: =item *
585:
586: traceroute() : Build condition hash
587:
588: =item *
589:
590: accinit() : Cascading conditions, quick access, parameters
591:
592: =item *
593:
594: readmap() : Read map and all submaps
595:
596: =item *
597:
598: evalstate() : Evaluate state string
599:
600: =back
601:
602: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>