Annotation of loncom/interface/longroup.pm, revision 1.22
1.1 raeburn 1: # The LearningOnline Network with CAPA
2: # accessor routines used to provide information about course groups
3: #
1.22 ! hauer 4: # $Id: longroup.pm,v 1.21 2009/02/13 17:20:29 bisitz Exp $
1.21 bisitz 5: #
1.1 raeburn 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: #
1.4 albertel 28:
1.1 raeburn 29: package Apache::longroup;
1.4 albertel 30:
1.1 raeburn 31: use strict;
32: use Apache::lonnet;
1.19 schafran 33: use Apache::lonlocal;
1.1 raeburn 34:
35: ###############################################
36: =pod
37:
38: =item coursegroups
39:
40: Retrieve information about groups in a course,
41:
42: Input:
43: 1. Optional course domain
44: 2. Optional course number
45: 3. Optional group name
1.13 raeburn 46: 4. Optional namespace
1.1 raeburn 47:
48: Course domain and number will be taken from user's
49: environment if not supplied. Optional group name will
1.13 raeburn 50: be passed to lonnet function as a regexp to
51: use in the call to the dump function. Optional namespace
52: will determine whether information is retrieved about current
53: groups (default) or deleted groups (namespace = deleted_groups).
1.4 albertel 54:
1.1 raeburn 55: Output
1.13 raeburn 56: Returns hash of groups in a course (subject to the
1.1 raeburn 57: optional group name filter). In the hash, the keys are
58: group names, and their corresponding values
59: are scalars containing group information in XML. This
60: can be sent to &get_group_settings() to be parsed.
61:
62: Side effects:
63: None.
1.15 albertel 64:
1.1 raeburn 65: =cut
66:
67: ###############################################
68:
69: sub coursegroups {
1.13 raeburn 70: my ($cdom,$cnum,$group,$namespace) = @_;
1.1 raeburn 71: if (!defined($cdom) || !defined($cnum)) {
72: my $cid = $env{'request.course.id'};
73:
74: return if (!defined($cid));
75:
76: $cdom = $env{'course.'.$cid.'.domain'};
77: $cnum = $env{'course.'.$cid.'.num'};
78: }
1.13 raeburn 79: if (!defined($namespace)) {
80: $namespace = 'coursegroups';
81: }
82: my %groups = &Apache::lonnet::get_coursegroups($cdom,$cnum,$group,
83: $namespace);
84: if (my $tmp = &Apache::lonnet::error(%groups)) {
85: undef(%groups);
86: &Apache::lonnet::logthis('Error retrieving groups: '.$tmp.' in '.$cnum.':'.$cdom.' - '.$namespace);
1.1 raeburn 87: }
1.14 raeburn 88: if (defined($groups{'group_allfolders'."\0".'locked_folder'})) {
89: delete($groups{'group_allfolders'."\0".'locked_folder'});
90: }
1.13 raeburn 91: return %groups;
1.1 raeburn 92: }
93:
94: ###############################################
95:
1.15 albertel 96: =pod
97:
1.1 raeburn 98: =item get_group_settings
1.4 albertel 99:
1.1 raeburn 100: Uses TokeParser to extract group information from the
101: XML used to describe course groups.
1.4 albertel 102:
1.1 raeburn 103: Input:
104: Scalar containing XML - as retrieved from &coursegroups().
1.4 albertel 105:
1.1 raeburn 106: Output:
107: Hash containing group information as key=values for (a), and
108: hash of hashes for (b)
1.4 albertel 109:
1.1 raeburn 110: Keys (in two categories):
1.6 raeburn 111: (a) groupname, creator, creation, modified, startdate, enddate, quota.
1.1 raeburn 112: Corresponding values are name of the group, creator of the group
113: (username:domain), UNIX time for date group was created, and
1.6 raeburn 114: settings were last modified, file quota, and default start and end
115: access times for group members.
1.4 albertel 116:
1.1 raeburn 117: (b) functions returned in hash of hashes.
118: Outer hash key is functions.
119: Inner hash keys are chat,discussion,email,files,homepage,roster.
120: Corresponding values are either on or off, depending on
121: whether this type of functionality is available for the group.
1.4 albertel 122:
1.1 raeburn 123: =cut
124:
125: ###############################################
126:
127: sub get_group_settings {
128: my ($groupinfo)=@_;
129: my $parser=HTML::TokeParser->new(\$groupinfo);
130: my $token;
131: my $tool = '';
132: my $role = '';
133: my %content=();
134: while ($token=$parser->get_token) {
135: if ($token->[0] eq 'S') {
136: my $entry=$token->[1];
137: if ($entry eq 'functions' || $entry eq 'autosec') {
138: %{$content{$entry}} = ();
139: $tool = $entry;
140: } elsif ($entry eq 'role') {
141: if ($tool eq 'autosec') {
142: $role = $token->[2]{id};
143: @{$content{$tool}{$role}} = ();
144: }
145: } else {
146: my $value=$parser->get_text('/'.$entry);
147: if ($entry eq 'name') {
148: if ($tool eq 'functions') {
149: my $function = $token->[2]{id};
150: $content{$tool}{$function} = $value;
151: }
152: } elsif ($entry eq 'groupname') {
153: $content{$entry}=&unescape($value);
154: } elsif (($entry eq 'roles') || ($entry eq 'types') ||
155: ($entry eq 'sectionpick') || ($entry eq 'defpriv')) {
156: push(@{$content{$entry}},$value);
157: } elsif ($entry eq 'section') {
158: if ($tool eq 'autosec' && $role ne '') {
159: push(@{$content{$tool}{$role}},$value);
160: }
161: } else {
162: $content{$entry}=$value;
163: }
164: }
165: } elsif ($token->[0] eq 'E') {
166: if ($token->[1] eq 'functions' || $token->[1] eq 'autosec') {
167: $tool = '';
168: } elsif ($token->[1] eq 'role') {
169: $role = '';
170: }
171: }
172: }
173: return %content;
174: }
175:
176: ###############################################
177:
178: sub check_group_access {
179: my ($group) = @_;
180: my $access = 1;
181: my $now = time;
182: my ($start,$end) = split(/\./,$env{'user.role.gr/'.$env{'request.course,id'}.'/'.$group});
183: if (($end!=0) && ($end<$now)) { $access = 0; }
184: if (($start!=0) && ($start>$now)) { $access=0; }
185: return $access;
186: }
187:
188: ###############################################
189:
190: =pod
1.4 albertel 191:
1.1 raeburn 192: =item group_changes
193:
194: Add or drop group memberships in a course as a result of
195: changes in a user's roles/sections. Called by
196: &Apache::lonnet:assignrole()
1.4 albertel 197:
1.1 raeburn 198: Input:
199: 1. User's domain
200: 2. User's username
201: 3. Url of role
202: 4. Role
203: 5. End date of role
204: 6. Start date of role
1.16 raeburn 205: 7. Selfenroll
206: 8. Context
1.1 raeburn 207:
208: Checks to see if role for which assignment is being made is in a course.
209: If so, gathers information about auto-group population settings for
210: groups in the course.
211:
212: If role is being expired, will also expire any group memberships that
213: are specified for auto-group population for the specific role and
214: section (including section 'none' and 'all' sections), unless a
215: different role/section also included in auto-group population
216: for the course is included amongst the user's unexpired roles
217: and would trigger membership in teh same group(s)
218:
219: If role is being added, will add any group memberships specified
220: for auto-group population, unless use is already a group member.
221: Uses default group privileges and default start and end group access
1.16 raeburn 222: times.
223:
224: Flag for selfenroll (value of 1), and context (auto, updatenow,
225: automated, course, domain etc.) can be used to log the reason for
226: the role change.
1.1 raeburn 227:
228: Output
229: None
230:
231: Side effects:
232: May result in calls to Apache::lonnet::modify_group_roles()
233: and Apache::lonnet::modify_coursegroup_membership() to add
234: or expire group membership(s) for a user.
235:
236: =cut
237:
238: sub group_changes {
1.16 raeburn 239: my ($udom,$uname,$url,$role,$origend,$origstart,$selfenroll,$context) = @_;
1.1 raeburn 240: my $now = time;
241: my $chgtype;
242: if ($origend > 0 && $origend <= $now) {
243: $chgtype = 'drop';
244: } else {
245: $chgtype = 'add';
246: }
247: my ($cid,$cdom,$cnum,$sec);
248: if ($url =~ m-^(/[^/]+/[^/]+)/([^/]+)$-) {
249: $cid = $1;
250: $sec = $2;
251: } else {
252: $cid = $url;
253: }
254: my $courseid = $cid;
255: $courseid =~ s|^/||;
256: $courseid =~ s|/|_|;
257: my %crshash=&Apache::lonnet::coursedescription($cid);
258: $cdom = $crshash{'domain'};
259: $cnum = $crshash{'num'};
260: if (defined($cdom) && defined($cnum)) {
261: my %settings;
262: my @changegroups = ();
263: my %dropgroup = ();
264: my %dropstart = ();
265: my %addgroup = ();
266: my %curr_groups = &coursegroups($cdom,$cnum);
267: if (%curr_groups) {
268: foreach my $group (keys(%curr_groups)) {
269: %{$settings{$group}}=&get_group_settings($curr_groups{$group});
270: if ($chgtype eq 'add') {
271: if (!($settings{$group}{autoadd} eq 'on')) {
272: next;
273: }
274: } else {
275: if (!($settings{$group}{autodrop} eq 'on')) {
276: next;
277: }
278: }
279: my @autosec = ();
280: if (ref($settings{$group}{'autosec'}{$role}) eq 'ARRAY') {
281: @autosec = @{$settings{$group}{'autosec'}{$role}};
282: }
283: if ($sec eq '') {
284: $sec = 'none';
285: }
286: if ((grep(/^$sec$/,@autosec)) || (grep(/^all$/,@autosec))) {
287: push(@changegroups,$group);
288: }
289: }
290: }
291: if (@changegroups > 0) {
292: my %currpriv;
293: my %roleshash = &Apache::lonnet::dump('roles',$udom,$uname,$cid);
1.5 albertel 294: if (my $tmp = &Apache::lonnet::error(%roleshash)) {
1.1 raeburn 295: &Apache::lonnet::logthis('Error retrieving roles: '.$tmp.
296: ' for '.$uname.':'.$udom);
297: } else {
298: my $group_privs = '';
299: foreach my $group (@changegroups) {
300: if ($chgtype eq 'add') {
301: if (ref($settings{$group}{'defpriv'}) eq 'ARRAY') {
302: $group_privs =
303: join(':',@{$settings{$group}{'defpriv'}});
304: }
305: }
306: my $key = $cid.'/'.$group.'_gr';
307: if (defined($roleshash{$key})) {
308: if ($roleshash{$key}=~ /^gr\/([^_]*)_(\d+)_([\-\d]+)$/) {
309: my $grpstart = $3;
310: my $grpend = $2;
311: $currpriv{$group} = $1;
312: if ($chgtype eq 'drop') {
313: if ($grpstart == -1) { next; } # deleted
314: if ($grpend == 0 || $grpend > $now) {
1.2 albertel 315: if (!defined($dropgroup{$group})) {
1.1 raeburn 316: $dropstart{$group} = $grpstart;
317: if ($grpstart > $now) {
318: $dropstart{$group} = $now;
319: }
320: $dropgroup{$group} = $now.':'.
321: $dropstart{$group}.
322: ':'.$currpriv{$group};
323: }
324: }
325: } elsif ($chgtype eq 'add') {
326: if (($grpstart == -1) || ($grpend > 0 &&
327: ($grpend < $settings{$group}{'enddate'} ||
328: $settings{$group}{'enddate'} == 0)) ||
329: ($grpstart > $settings{$group}{'startdate'})) {
1.2 albertel 330: if (!defined($addgroup{$group})) {
1.1 raeburn 331: $addgroup{$group} =
332: $settings{$group}{'enddate'}.':'.
333: $settings{$group}{'startdate'}.':'.
334: $group_privs;
335: }
336: }
337: }
338: }
339: } elsif ($chgtype eq 'add') {
340: $addgroup{$group} = $settings{$group}{'enddate'}.':'.
341: $settings{$group}{'startdate'}.':'.
342: $group_privs;
343: }
344: }
345: if ($chgtype eq 'add') {
346: foreach my $add (keys(%addgroup)) {
347: if (&Apache::lonnet::modify_group_roles($cdom,$cnum,
348: $add,$uname.':'.$udom,
349: $settings{$add}{'enddate'},
350: $settings{$add}{'startdate'},
1.16 raeburn 351: $group_privs,$selfenroll,$context) eq 'ok') {
1.1 raeburn 352: my %usersettings;
353: $usersettings{$add.':'.$uname.':'.$udom} =
354: $addgroup{$add};
355: my $roster_result =
356: &Apache::lonnet::modify_coursegroup_membership(
357: $cdom,$cnum,\%usersettings);
358: }
359: }
360: } elsif ($chgtype eq 'drop') {
361: foreach my $drop (keys(%dropgroup)) {
362: my $nodrop = 0;
363: if ($settings{$drop}{'autoadd'} eq 'on') {
364: foreach my $urole (keys(%{$settings{$drop}{'autosec'}})) {
365: if ($nodrop) {
366: last;
367: } else {
368: my @autosec = ();
369: if (ref($settings{$drop}{'autosec'}{$urole}) eq 'ARRAY') {
370: @autosec = @{$settings{$drop}{'autosec'}{$urole}};
371: }
372: foreach my $usec (@autosec) {
373: if ($usec eq 'all') {
374: foreach my $ukey (keys(%roleshash)) {
375: if ($ukey =~ /^\Q$cid\E(\/?\w*)_($urole)$/) {
1.2 albertel 376: if ($sec ne $1) {
1.1 raeburn 377: if ($roleshash{$ukey} =~ /_?(\d*)_?([\-\d]*)$/) {
378: my $roleend = $1;
379: if ((!$roleend) ||
380: ($roleend > $now)) {
381: $nodrop = 1;
382: last;
383: }
384: }
385: }
386: }
387: }
388: } else {
389: my $ukey = $cid.'/'.$usec.'_'.$urole;
390: if ($usec eq 'none') {
391: if ($sec eq '') {
392: next;
393: }
394: } else {
395: if ($usec eq $sec) {
396: next;
397: }
398: }
399: if (exists($roleshash{$ukey})) {
400: if ($roleshash{$ukey} =~
401: /_?(\d*)_?([\-\d]*)$/) {
402: my $roleend = $1;
403: if ((!$roleend) ||
404: ($roleend > $now)) {
405: $nodrop = 1;
406: last;
407: }
408: }
409: }
410: }
411: }
412: }
413: }
414: }
415: if (!$nodrop) {
416: if (&Apache::lonnet::modify_group_roles($cdom,
417: $cnum,$drop,
418: $uname.':'.$udom,$now,
419: $dropstart{$drop},
1.16 raeburn 420: $currpriv{$drop},
421: $selfenroll,$context)
1.1 raeburn 422: eq 'ok') {
423: my %usersettings;
424: $usersettings{$drop.':'.$uname.':'.$udom} =
425: $dropgroup{$drop};
426: my $roster_result =
427: &Apache::lonnet::modify_coursegroup_membership(
428: $cdom,$cnum,\%usersettings);
429: }
430: }
431: }
432: }
433: }
434: }
435: }
436: return;
437: }
438:
439: ###############################################
440:
1.8 raeburn 441: sub get_fixed_privs {
442: my $fixedprivs = {
1.20 schafran 443: email => {sgm => 1},
1.19 schafran 444: discussion => {vgb => 1},
445: chat => {pgc => 1},
446: files => {rgf => 1},
447: roster => {vgm => 1},
448: homepage => {vgh => 1},
1.8 raeburn 449: };
450: return $fixedprivs;
451: }
452:
453: ###############################################
454:
455: sub get_tool_privs {
456: my ($gpterm) = @_;
457: my $toolprivs = {
1.20 schafran 458: email => {
1.18 schafran 459: sgm => 'Send '.$gpterm.' message',
460: sgb => 'Broadcast message',
1.8 raeburn 461: },
462: discussion => {
463: cgb => 'Create boards',
464: pgd => 'Post',
1.10 raeburn 465: egp => 'Edit own posts',
466: dgp => 'Hide/Delete any post',
1.8 raeburn 467: vgb => 'View boards',
468: },
469: chat => {
1.22 ! hauer 470: pgc => 'Chat Room',
1.8 raeburn 471: },
472: files => {
473: rgf => 'Retrieve',
474: ugf => 'Upload',
475: mgf => 'Modify',
476: dgf => 'Delete',
477: agf => 'Control Access',
478: },
479: roster => {
1.10 raeburn 480: vgm => 'Basic Display',
481: vmd => 'Detailed Display',
1.8 raeburn 482: },
483: homepage => {
484: vgh => 'View page',
485: mgh => 'Modify page',
486: },
487: };
488: return $toolprivs;
489: }
490:
491: ###############################################
492:
493:
494: sub group_memberlist {
495: my ($cdom,$cnum,$groupname,$fixedprivs,$available) = @_;
496: my %membership = &Apache::lonnet::get_group_membership($cdom,$cnum,
497: $groupname);
498: my %current = ();
499: my $hastools = 0;
500: my $addtools = 0;
1.9 raeburn 501: my %member_nums = (
502: 'previous' => 0,
503: 'future' => 0,
504: 'active' => 0,
505: );
1.8 raeburn 506: my $now = time;
507: if (keys(%membership) > 0) {
508: my %allnames = ();
509: foreach my $key (sort(keys(%membership))) {
510: if ($key =~ /^\Q$groupname\E:([^:]+):([^:]+)$/) {
511: my $uname = $1;
512: my $udom = $2;
513: my $user = $uname.':'.$udom;
514: my($end,$start,@userprivs) = split(/:/,$membership{$key});
515: unless ($start == -1) {
516: $allnames{$udom}{$uname} = 1;
517: $current{$user} = {
518: uname => $uname,
519: udom => $udom,
520: start => &Apache::lonlocal::locallocaltime($start),
521: currtools => [],
522: newtools => [],
523: privs => \@userprivs,
524: };
525:
526: if ($end == 0) {
527: $current{$user}{end} = 'No end date';
528: } else {
529: $current{$user}{end} =
530: &Apache::lonlocal::locallocaltime($end);
531: }
532: my $now = time;
533: if (($end > 0) && ($end < $now)) {
534: $current{$user}{changestate} = 'reenable';
535: $current{$user}{'status'} = 'previous';
1.9 raeburn 536: $member_nums{'previous'} ++;
1.8 raeburn 537: } elsif (($start > $now)) {
538: $current{$user}{changestate} = 'activate';
539: $current{$user}{'status'} = 'future';
1.9 raeburn 540: $member_nums{'future'} ++;
1.8 raeburn 541: } else {
542: $current{$user}{changestate} = 'expire';
543: $current{$user}{'status'} = 'active';
1.9 raeburn 544: $member_nums{'active'} ++;
1.8 raeburn 545: }
1.10 raeburn 546: if ((@userprivs > 0) && (ref($fixedprivs) eq 'HASH')) {
1.8 raeburn 547: foreach my $tool (sort(keys(%{$fixedprivs}))) {
548: foreach my $priv (keys(%{$$fixedprivs{$tool}})) {
549: if (grep/^$priv$/,@userprivs) {
550: push(@{$current{$user}{currtools}},$tool);
551: last;
552: }
553: }
554: }
555: $hastools = 1;
556: }
1.10 raeburn 557: if ((ref($available) eq 'ARRAY') && (@{$available} > 0)) {
1.8 raeburn 558: if (@{$current{$user}{currtools}} > 0) {
559: if ("@{$available}" ne "@{$current{$user}{currtools}}") {
560: foreach my $tool (@{$available}) {
561: unless (grep/^$tool$/,@{$current{$user}{currtools}}) {
562: push(@{$current{$user}{newtools}},$tool); }
563: }
564: }
565: } else {
566: @{$current{$user}{newtools}} = @{$available};
567:
568: }
569: if (@{$current{$user}{newtools}} > 0) {
570: $addtools = 1;
571: }
572: }
573: }
574: }
575: }
576: if (keys(%current) > 0) {
577: my %idhash;
578: foreach my $udom (keys(%allnames)) {
579: %{$idhash{$udom}} = &Apache::lonnet::idrget($udom,
580: keys(%{$allnames{$udom}}));
581: foreach my $uname (keys(%{$idhash{$udom}})) {
582: $current{$uname.':'.$udom}{'id'} = $idhash{$udom}{$uname};
583: }
584: foreach my $uname (keys(%{$allnames{$udom}})) {
585: $current{$uname.':'.$udom}{'fullname'} =
586: &Apache::loncommon::plainname($uname,$udom,
587: 'lastname');
588: }
589: }
590: }
591: }
1.10 raeburn 592: return (\%current,\%member_nums,$hastools,$addtools);
1.8 raeburn 593: }
594:
595: ###############################################
596:
1.6 raeburn 597: sub sum_quotas {
598: my ($courseid) = @_;
599: my $totalquotas = 0;
600: my ($cdom,$cnum);
601: if (!defined($courseid)) {
602: if (defined($env{'request.course.id'})) {
603: $courseid = $env{'request.course.id'};
604: $cdom = $env{'course.'.$courseid.'.domain'};
605: $cnum = $env{'course.'.$courseid.'.num'};
606: } else {
607: return '';
608: }
609: } else {
610: ($cdom,$cnum) = split(/_/,$courseid);
611: }
612: if ($cdom && $cnum) {
613: my %curr_groups = &coursegroups($cdom,$cnum);
614: if (%curr_groups) {
615: foreach my $group (keys(%curr_groups)) {
616: my %settings=&get_group_settings($curr_groups{$group});
617: my $quota = $settings{'quota'};
618: if ($quota eq '') {
619: $quota = 0;
620: }
621: $totalquotas += $quota;
622: }
623: } else {
624: return 0;
625: }
626: } else {
627: return '';
628: }
629: return $totalquotas;
630: }
631:
632: ###############################################
633:
1.7 raeburn 634: sub get_bbfolder_url {
635: my ($cdom,$cnum,$group) = @_;
636: my %curr_groups = &coursegroups($cdom,$cnum,$group);
637: my $grpbbmap;
638: if (%curr_groups) {
639: my $crspath = '/uploaded/'.$cdom.'/'.$cnum.'/';
1.9 raeburn 640: $grpbbmap = $crspath.'group_boards_'.$group.'.sequence';
1.7 raeburn 641: }
642: return $grpbbmap;
643: }
644:
645: ###############################################
646:
647: sub get_group_bbinfo {
1.10 raeburn 648: my ($cdom,$cnum,$group,$boardurl) = @_;
1.17 raeburn 649: my @groupboards = ();
650: my %boardshash = ();
1.7 raeburn 651: my $navmap = Apache::lonnavmaps::navmap->new();
1.17 raeburn 652: if (defined($navmap)) {
653: my $grpbbmap = &get_bbfolder_url($cdom,$cnum,$group);
654: if ($grpbbmap) {
655: my $bbfolderres = $navmap->getResourceByUrl($grpbbmap);
656: if ($bbfolderres) {
657: my @boards = $navmap->retrieveResources($bbfolderres,undef,0,0);
658: foreach my $res (@boards) {
659: my $url = $res->src();
660: if ($url =~ m|^(/adm/\Q$cdom\E/\Q$cnum\E/\d+/bulletinboard)|) {
661: if ($boardurl) {
662: if ($boardurl =~ /^\Q$1\E/) {
663: push(@groupboards,$res->symb());
664: $boardshash{$res->symb()} = {
665: title => $res->title(),
666: url => $res->src(),
667: };
668: last;
669: }
670: } else {
1.10 raeburn 671: push(@groupboards,$res->symb());
672: $boardshash{$res->symb()} = {
1.17 raeburn 673: title => $res->title(),
674: url => $res->src(),
1.10 raeburn 675: };
676: }
677: }
1.7 raeburn 678: }
679: }
680: }
1.17 raeburn 681: undef($navmap);
682: } else {
683: &Apache::lonnet::logthis('Retrieval of group boards failed - could not create navmap object for group: '.$group.' in course: '.$cdom.':'.$cnum);
1.7 raeburn 684: }
685: return (\@groupboards,\%boardshash);
686: }
687:
688: ###############################################
689:
1.1 raeburn 690: 1;
691:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>