Annotation of loncom/interface/lonnavmaps.pm, revision 1.562

1.481     foxr        1: # The LearningOnline Network with CAPA
1.2       www         2: # Navigate Maps Handler
1.1       www         3: #
1.562   ! raeburn     4: # $Id: lonnavmaps.pm,v 1.561 2022/10/04 14:38:08 raeburn Exp $
1.20      albertel    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: #
1.245     www        28: ###
1.2       www        29: 
1.416     jms        30: =pod
                     31: 
                     32: =head1 NAME
                     33: 
1.435     raeburn    34: Apache::lonnavmaps - Subroutines to handle and render the navigation
1.416     jms        35: 
                     36: =head1 SYNOPSIS
                     37: 
                     38: Handles navigational maps.
                     39: 
                     40: The main handler generates the navigational listing for the course,
                     41: the other objects export this information in a usable fashion for
                     42: other modules.
                     43: 
                     44: 
                     45: This is part of the LearningOnline Network with CAPA project
                     46: described at http://www.lon-capa.org.
                     47: 
                     48: 
                     49: =head1 OVERVIEW
                     50: 
1.439     wenzelju   51: X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the
1.416     jms        52: course structure and caches it in what is often referred to as the
                     53: "big hash" X<big hash>. You can see it if you are logged into
1.532     raeburn    54: LON-CAPA, in a course, by going to /adm/test. The content of 
                     55: the hash will be under the heading "Big Hash".
                     56: 
                     57: Access to /adm/test is controlled by a domain configuration, 
                     58: which a Domain Coordinator will set for a server's default domain
                     59: via: Main Menu > Set domain configuration > Display (Access to 
                     60: server status pages checked), and entering a username:domain
                     61: or IP address in the "Show user environment" row. Users with
                     62: an unexpired domain coordinator role in the server's domain
                     63: automatically receive access to /adm/test.
1.416     jms        64: 
                     65: Big Hash contains, among other things, how resources are related
                     66: to each other (next/previous), what resources are maps, which 
                     67: resources are being chosen to not show to the student (for random
                     68: selection), and a lot of other things that can take a lot of time
                     69: to compute due to the amount of data that needs to be collected and
                     70: processed.
                     71: 
                     72: Apache::lonnavmaps provides an object model for manipulating this
                     73: information in a higher-level fashion than directly manipulating 
1.541     raeburn    74: the hash. It also provides access to several auxiliary functions 
1.416     jms        75: that aren't necessarily stored in the Big Hash, but are a per-
                     76: resource sort of value, like whether there is any feedback on 
                     77: a given resource.
                     78: 
                     79: Apache::lonnavmaps also abstracts away branching, and someday, 
                     80: conditions, for the times where you don't really care about those
                     81: things.
                     82: 
                     83: Apache::lonnavmaps also provides fairly powerful routines for
                     84: rendering navmaps, and last but not least, provides the navmaps
                     85: view for when the user clicks the NAV button.
                     86: 
1.532     raeburn    87: B<Note>: Apache::lonnavmaps by default will show information 
                     88: for the "currently logged in user".  However, if information
                     89: about resources is needed for a different user, e.g., a bubblesheet
                     90: exam which uses randomorder, or randompick needs to be printed or 
                     91: graded for named user(s) or specific CODEs, then the username,
                     92: domain, or CODE can be passed as arguments when creating a new 
                     93: navmap object.
                     94: 
1.541     raeburn    95: Note if you want things like "due dates for another student",
1.532     raeburn    96: you would use the EXT function instead of lonnavmaps.
                     97: That said, the lonnavmaps module can still help, because many
                     98: things, such as the course structure, are usually constant
1.416     jms        99: between users, and Apache::lonnavmaps can help by providing
                    100: symbs for the EXT call.
                    101: 
                    102: The rest of this file will cover the provided rendering routines, 
                    103: which can often be used without fiddling with the navmap object at
                    104: all, then documents the Apache::lonnavmaps::navmap object, which
                    105: is the key to accessing the Big Hash information, covers the use
                    106: of the Iterator (which provides the logic for traversing the 
                    107: somewhat-complicated Big Hash data structure), documents the
1.532     raeburn   108: Apache::lonnavmaps::Resource objects that are returned singularly
                    109: by: getBySymb(), getById(), getByMapPc(), and getResourceByUrl()
                    110: (can also be as an array), or in an array by retrieveResources().
1.416     jms       111: 
                    112: =head1 Subroutine: render
                    113: 
                    114: The navmap renderer package provides a sophisticated rendering of the
                    115: standard navigation maps interface into HTML. The provided nav map
                    116: handler is actually just a glorified call to this.
                    117: 
                    118: Because of the large number of parameters this function accepts,
                    119: instead of passing it arguments as is normal, pass it in an anonymous
                    120: hash with the desired options.
                    121: 
                    122: The package provides a function called 'render', called as
                    123: Apache::lonnavmaps::render({}).
                    124: 
                    125: =head2 Overview of Columns
                    126: 
                    127: The renderer will build an HTML table for the navmap and return
                    128: it. The table consists of several columns, and a row for each
                    129: resource (or possibly each part). You tell the renderer how many
                    130: columns to create and what to place in each column, optionally using
                    131: one or more of the prepared columns, and the renderer will assemble
                    132: the table.
                    133: 
                    134: Any additional generally useful column types should be placed in the
                    135: renderer code here, so anybody can use it anywhere else. Any code
                    136: specific to the current application (such as the addition of <input>
                    137: elements in a column) should be placed in the code of the thing using
                    138: the renderer.
                    139: 
                    140: At the core of the renderer is the array reference COLS (see Example
                    141: section below for how to pass this correctly). The COLS array will
                    142: consist of entries of one of two types of things: Either an integer
                    143: representing one of the pre-packaged column types, or a sub reference
                    144: that takes a resource reference, a part number, and a reference to the
                    145: argument hash passed to the renderer, and returns a string that will
                    146: be inserted into the HTML representation as it.
                    147: 
                    148: All other parameters are ways of either changing how the columns
                    149: are printing, or which rows are shown.
                    150: 
                    151: The pre-packaged column names are refered to by constants in the
                    152: Apache::lonnavmaps namespace. The following currently exist:
                    153: 
                    154: =over 4
                    155: 
                    156: =item * B<Apache::lonnavmaps::resource>:
                    157: 
                    158: The general info about the resource: Link, icon for the type, etc. The
                    159: first column in the standard nav map display. This column provides the
                    160: indentation effect seen in the B<NAV> screen. This column also accepts
                    161: the following parameters in the renderer hash:
                    162: 
                    163: =over 4
                    164: 
                    165: =item * B<resource_nolink>: default false
                    166: 
                    167: If true, the resource will not be linked. By default, all non-folder
                    168: resources are linked.
                    169: 
                    170: =item * B<resource_part_count>: default true
                    171: 
                    172: If true, the resource will show a part count B<if> the full
                    173: part list is not displayed. (See "condense_parts" later.) If false,
                    174: the resource will never show a part count.
                    175: 
                    176: =item * B<resource_no_folder_link>:
                    177: 
                    178: If true, the resource's folder will not be clickable to open or close
                    179: it. Default is false. True implies printCloseAll is false, since you
                    180: can't close or open folders when this is on anyhow.
                    181: 
1.493     raeburn   182: =item * B<map_no_edit_link>:
                    183: 
                    184: If true, the title of the folder or page will not be followed by an
                    185: icon/link to direct editing of a folder or composite page, originally
                    186: added via the Course Editor.
                    187: 
1.416     jms       188: =back
                    189: 
                    190: =item * B<Apache::lonnavmaps::communication_status>:
                    191: 
                    192: Whether there is discussion on the resource, email for the user, or
                    193: (lumped in here) perl errors in the execution of the problem. This is
                    194: the second column in the main nav map.
                    195: 
                    196: =item * B<Apache::lonnavmaps::quick_status>:
                    197: 
                    198: An icon for the status of a problem, with five possible states:
                    199: Correct, incorrect, open, awaiting grading (for a problem where the
                    200: computer's grade is suppressed, or the computer can't grade, like
                    201: essay problem), or none (not open yet, not a problem). The
                    202: third column of the standard navmap.
                    203: 
                    204: =item * B<Apache::lonnavmaps::long_status>:
                    205: 
                    206: A text readout of the details of the current status of the problem,
                    207: such as "Due in 22 hours". The fourth column of the standard navmap.
                    208: 
                    209: =item * B<Apache::lonnavmaps::part_status_summary>:
                    210: 
                    211: A text readout summarizing the status of the problem. If it is a
                    212: single part problem, will display "Correct", "Incorrect", 
                    213: "Not yet open", "Open", "Attempted", or "Error". If there are
                    214: multiple parts, this will output a string that in HTML will show a
                    215: status of how many parts are in each status, in color coding, trying
                    216: to match the colors of the icons within reason.
1.1       www       217: 
1.416     jms       218: Note this only makes sense if you are I<not> showing parts. If 
                    219: C<showParts> is true (see below), this column will not output
                    220: anything. 
1.2       www       221: 
1.416     jms       222: =back
1.133     bowersj2  223: 
1.416     jms       224: If you add any others please be sure to document them here.
1.133     bowersj2  225: 
1.416     jms       226: An example of a column renderer that will show the ID number of a
                    227: resource, along with the part name if any:
1.140     bowersj2  228: 
1.416     jms       229:  sub { 
                    230:   my ($resource, $part, $params) = @_;   
                    231:   if ($part) { return '<td>' . $resource->{ID} . ' ' . $part . '</td>'; }
                    232:   return '<td>' . $resource->{ID} . '</td>';
                    233:  }
1.135     bowersj2  234: 
1.416     jms       235: Note these functions are responsible for the TD tags, which allow them
                    236: to override vertical and horizontal alignment, etc.
1.133     bowersj2  237: 
1.416     jms       238: =head2 Parameters
1.130     www       239: 
1.416     jms       240: Minimally, you should be
                    241: able to get away with just using 'cols' (to specify the columns
                    242: shown), 'url' (necessary for the folders to link to the current screen
                    243: correctly), and possibly 'queryString' if your app calls for it. In
                    244: that case, maintaining the state of the folders will be done
                    245: automatically.
1.268     albertel  246: 
1.416     jms       247: =over 4
1.268     albertel  248: 
1.483     raeburn   249: =item * B<iterator>: default: constructs one from %env
1.51      bowersj2  250: 
1.416     jms       251: A reference to a fresh ::iterator to use from the navmaps. The
                    252: rendering will reflect the options passed to the iterator, so you can
                    253: use that to just render a certain part of the course, if you like. If
                    254: one is not passed, the renderer will attempt to construct one from
                    255: env{'form.filter'} and env{'form.condition'} information, plus the
                    256: 'iterator_map' parameter if any.
1.51      bowersj2  257: 
1.416     jms       258: =item * B<iterator_map>: default: not used
1.51      bowersj2  259: 
1.416     jms       260: If you are letting the renderer do the iterator handling, you can
                    261: instruct the renderer to render only a particular map by passing it
                    262: the source of the map you want to process, like
                    263: '/res/103/jerf/navmap.course.sequence'.
1.51      bowersj2  264: 
1.416     jms       265: =item * B<include_top_level_map>: default: false
1.51      bowersj2  266: 
1.416     jms       267: If you need to include the top level map (meaning the course) in the
                    268: rendered output set this to true
1.51      bowersj2  269: 
1.416     jms       270: =item * B<navmap>: default: constructs one from %env
1.51      bowersj2  271: 
1.416     jms       272: A reference to a navmap, used only if an iterator is not passed in. If
                    273: this is necessary to make an iterator but it is not passed in, a new
                    274: one will be constructed based on env info. This is useful to do basic
                    275: error checking before passing it off to render.
1.51      bowersj2  276: 
1.416     jms       277: =item * B<r>: default: must be passed in
1.53      bowersj2  278: 
1.416     jms       279: The standard Apache response object. This must be passed to the
                    280: renderer or the course hash will be locked.
1.53      bowersj2  281: 
1.416     jms       282: =item * B<cols>: default: empty (useless)
1.407     raeburn   283: 
1.416     jms       284: An array reference
1.53      bowersj2  285: 
1.416     jms       286: =item * B<showParts>:default true
1.115     bowersj2  287: 
1.416     jms       288: A flag. If true, a line for the resource itself, and a line
                    289: for each part will be displayed. If not, only one line for each
                    290: resource will be displayed.
1.115     bowersj2  291: 
1.416     jms       292: =item * B<condenseParts>: default true
1.115     bowersj2  293: 
1.416     jms       294: A flag. If true, if all parts of the problem have the same
                    295: status and that status is Nothing Set, Correct, or Network Failure,
                    296: then only one line will be displayed for that resource anyhow. If no,
                    297: all parts will always be displayed. If showParts is 0, this is
                    298: ignored.
1.115     bowersj2  299: 
1.416     jms       300: =item * B<jumpCount>: default: determined from %env
1.115     bowersj2  301: 
1.416     jms       302: A string identifying the URL to place the anchor 'curloc' at.
                    303: It is the responsibility of the renderer user to
                    304: ensure that the #curloc is in the URL. By default, determined through
                    305: the use of the env{} 'jump' information, and should normally "just
                    306: work" correctly.
1.177     www       307: 
1.416     jms       308: =item * B<here>: default: empty string
1.68      bowersj2  309: 
1.416     jms       310: A Symb identifying where to place the 'here' marker. The empty
                    311: string means no marker.
1.73      bowersj2  312: 
1.416     jms       313: =item * B<indentString>: default: 25 pixel whitespace image
1.347     www       314: 
1.416     jms       315: A string identifying the indentation string to use. 
1.347     www       316: 
1.416     jms       317: =item * B<queryString>: default: empty
1.346     foxr      318: 
1.416     jms       319: A string which will be prepended to the query string used when the
                    320: folders are opened or closed. You can use this to pass
                    321: application-specific values.
1.73      bowersj2  322: 
1.416     jms       323: =item * B<url>: default: none
1.73      bowersj2  324: 
1.416     jms       325: The url the folders will link to, which should be the current
                    326: page. Required if the resource info column is shown, and you 
                    327: are allowing the user to open and close folders.
1.73      bowersj2  328: 
1.416     jms       329: =item * B<currentJumpIndex>: default: no jumping
1.73      bowersj2  330: 
1.416     jms       331: Describes the currently-open row number to cause the browser to jump
                    332: to, because the user just opened that folder. By default, pulled from
                    333: the Jump information in the env{'form.*'}.
1.73      bowersj2  334: 
1.416     jms       335: =item * B<printKey>: default: false
1.101     bowersj2  336: 
1.416     jms       337: If true, print the key that appears on the top of the standard
                    338: navmaps.
1.73      bowersj2  339: 
1.416     jms       340: =item * B<printCloseAll>: default: true
1.73      bowersj2  341: 
1.416     jms       342: If true, print the "Close all folders" or "open all folders"
                    343: links.
1.403     albertel  344: 
1.416     jms       345: =item * B<filterFunc>: default: sub {return 1;} (accept everything)
1.346     foxr      346: 
1.416     jms       347: A function that takes the resource object as its only parameter and
                    348: returns a true or false value. If true, the resource is displayed. If
                    349: false, it is simply skipped in the display.
1.346     foxr      350: 
1.416     jms       351: =item * B<suppressEmptySequences>: default: false
1.346     foxr      352: 
1.416     jms       353: If you're using a filter function, and displaying sequences to orient
                    354: the user, then frequently some sequences will be empty. Setting this to
                    355: true will cause those sequences not to display, so as not to confuse the
                    356: user into thinking that if the sequence is there there should be things
                    357: under it; for example, see the "Show Uncompleted Homework" view on the
                    358: B<NAV> screen.
1.73      bowersj2  359: 
1.556     raeburn   360: =item * B<suppressNavmap>: default: false
1.53      bowersj2  361: 
1.416     jms       362: If true, will not display Navigate Content resources. 
1.51      bowersj2  363: 
1.416     jms       364: =back
1.133     bowersj2  365: 
1.416     jms       366: =head2 Additional Info
1.174     albertel  367: 
1.416     jms       368: In addition to the parameters you can pass to the renderer, which will
                    369: be passed through unchange to the column renderers, the renderer will
                    370: generate the following information which your renderer may find
                    371: useful:
1.174     albertel  372: 
1.416     jms       373: =over 4
1.174     albertel  374: 
1.416     jms       375: =item * B<counter>: 
1.133     bowersj2  376: 
1.416     jms       377: Contains the number of rows printed. Useful after calling the render 
                    378: function, as you can detect whether anything was printed at all.
1.216     bowersj2  379: 
1.416     jms       380: =item * B<isNewBranch>:
1.216     bowersj2  381: 
1.416     jms       382: Useful for renderers: If this resource is currently the first resource
                    383: of a new branch, this will be true. The Resource column (leftmost in the
                    384: navmaps screen) uses this to display the "new branch" icon 
1.216     bowersj2  385: 
1.416     jms       386: =back
1.216     bowersj2  387: 
1.416     jms       388: =cut
1.216     bowersj2  389: 
                    390: 
1.416     jms       391: =head1 SUBROUTINES
1.216     bowersj2  392: 
1.416     jms       393: =over
1.216     bowersj2  394: 
1.416     jms       395: =item update()
1.133     bowersj2  396: 
1.416     jms       397: =item addToFilter()
1.133     bowersj2  398: 
1.416     jms       399: Convenience functions: Returns a string that adds or subtracts
                    400: the second argument from the first hash, appropriate for the 
                    401: query string that determines which folders to recurse on
1.174     albertel  402: 
1.416     jms       403: =item removeFromFilter()
1.51      bowersj2  404: 
1.416     jms       405: =item getLinkForResource()
1.51      bowersj2  406: 
1.416     jms       407: Convenience function: Given a stack returned from getStack on the iterator,
                    408: return the correct src() value.
1.174     albertel  409: 
1.416     jms       410: =item getDescription()
1.174     albertel  411: 
1.416     jms       412: Convenience function: This separates the logic of how to create
                    413: the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",
                    414: etc.) into a separate function. It takes a resource object as the
                    415: first parameter, and the part number of the resource as the second.
                    416: It's basically a big switch statement on the status of the resource.
1.174     albertel  417: 
1.416     jms       418: =item dueInLessThan24Hours()
1.216     bowersj2  419: 
1.416     jms       420: Convenience function, so others can use it: Is the problem due in less than 24 hours, and still can be done?
1.51      bowersj2  421: 
1.416     jms       422: =item lastTry()
1.51      bowersj2  423: 
1.416     jms       424: Convenience function, so others can use it: Is there only one try remaining for the
                    425: part, with more than one try to begin with, not due yet and still can be done?
1.51      bowersj2  426: 
1.416     jms       427: =item advancedUser()
1.51      bowersj2  428: 
1.416     jms       429: This puts a human-readable name on the env variable.
1.51      bowersj2  430: 
1.416     jms       431: =item timeToHumanString()
1.51      bowersj2  432: 
1.416     jms       433: timeToHumanString takes a time number and converts it to a
                    434: human-readable representation, meant to be used in the following
                    435: manner:
1.174     albertel  436: 
1.416     jms       437: =over 4
1.51      bowersj2  438: 
1.416     jms       439: =item * print "Due $timestring"
1.133     bowersj2  440: 
1.416     jms       441: =item * print "Open $timestring"
1.133     bowersj2  442: 
1.416     jms       443: =item * print "Answer available $timestring"
1.144     bowersj2  444: 
1.133     bowersj2  445: =back
1.51      bowersj2  446: 
1.416     jms       447: Very, very, very, VERY English-only... goodness help a localizer on
                    448: this func...
1.174     albertel  449: 
1.417     jms       450: =item resource()
1.174     albertel  451: 
1.417     jms       452: returns 0
1.51      bowersj2  453: 
1.417     jms       454: =item communication_status()
1.51      bowersj2  455: 
1.417     jms       456: returns 1
1.174     albertel  457: 
1.417     jms       458: =item quick_status()
1.51      bowersj2  459: 
1.417     jms       460: returns 2
1.225     bowersj2  461: 
1.417     jms       462: =item long_status()
1.225     bowersj2  463: 
1.417     jms       464: returns 3
1.225     bowersj2  465: 
1.417     jms       466: =item part_status_summary()
1.51      bowersj2  467: 
1.417     jms       468: returns 4
1.51      bowersj2  469: 
1.417     jms       470: =item render_resource()
1.51      bowersj2  471: 
1.417     jms       472: =item render_communication_status()
1.51      bowersj2  473: 
1.417     jms       474: =item render_quick_status()
                    475: 
                    476: =item render_long_status()
                    477: 
                    478: =item render_parts_summary_status()
                    479: 
                    480: =item setDefault()
                    481: 
                    482: =item cmp_title()
                    483: 
                    484: =item render()
                    485: 
                    486: =item add_linkitem()
                    487: 
1.476     raeburn   488: =item show_linkitems_toolbar()
1.130     www       489: 
1.416     jms       490: =back
1.115     bowersj2  491: 
1.416     jms       492: =cut
1.140     bowersj2  493: 
1.416     jms       494: package Apache::lonnavmaps;
1.51      bowersj2  495: 
1.416     jms       496: use strict;
                    497: use GDBM_File;
                    498: use Apache::loncommon();
                    499: use Apache::lonenc();
                    500: use Apache::lonlocal;
                    501: use Apache::lonnet;
1.465     foxr      502: use Apache::lonmap;
1.443     foxr      503: 
1.535     raeburn   504: use POSIX qw (ceil floor strftime);
1.416     jms       505: use Time::HiRes qw( gettimeofday tv_interval );
                    506: use LONCAPA;
                    507: use DateTime();
1.486     raeburn   508: use HTML::Entities;
1.465     foxr      509: 
                    510: # For debugging
                    511: 
1.499     raeburn   512: #use Data::Dumper;
1.465     foxr      513: 
                    514: 
1.416     jms       515: # symbolic constants
                    516: sub SYMB { return 1; }
                    517: sub URL { return 2; }
                    518: sub NOTHING { return 3; }
1.174     albertel  519: 
1.416     jms       520: # Some data
1.174     albertel  521: 
1.416     jms       522: my $resObj = "Apache::lonnavmaps::resource";
1.174     albertel  523: 
1.443     foxr      524: # Keep these mappings in sync with lonquickgrades, which usesthe colors
1.416     jms       525: # instead of the icons.
                    526: my %statusIconMap = 
                    527:     (
                    528:      $resObj->CLOSED       => '',
                    529:      $resObj->OPEN         => 'navmap.open.gif',
                    530:      $resObj->CORRECT      => 'navmap.correct.gif',
                    531:      $resObj->PARTIALLY_CORRECT      => 'navmap.partial.gif',
                    532:      $resObj->INCORRECT    => 'navmap.wrong.gif',
                    533:      $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',
                    534:      $resObj->ERROR        => ''
                    535:      );
1.401     albertel  536: 
1.438     wenzelju  537: my %iconAltTags =   #texthash does not work here
1.439     wenzelju  538:     ( 'navmap.correct.gif'  => 'Correct',
                    539:       'navmap.wrong.gif'    => 'Incorrect',
1.462     christia  540:       'navmap.open.gif'     => 'Is Open',
1.439     wenzelju  541:       'navmap.partial.gif'  => 'Partially Correct',
                    542:       'navmap.ellipsis.gif' => 'Attempted',
                    543:      );
1.401     albertel  544: 
1.416     jms       545: # Defines a status->color mapping, null string means don't color
                    546: my %colormap = 
                    547:     ( $resObj->NETWORK_FAILURE        => '',
                    548:       $resObj->CORRECT                => '',
                    549:       $resObj->EXCUSED                => '#3333FF',
                    550:       $resObj->PAST_DUE_ANSWER_LATER  => '',
                    551:       $resObj->PAST_DUE_NO_ANSWER     => '',
                    552:       $resObj->ANSWER_OPEN            => '#006600',
                    553:       $resObj->OPEN_LATER             => '',
                    554:       $resObj->TRIES_LEFT             => '',
                    555:       $resObj->INCORRECT              => '',
                    556:       $resObj->OPEN                   => '',
                    557:       $resObj->NOTHING_SET            => '',
                    558:       $resObj->ATTEMPTED              => '',
1.450     raeburn   559:       $resObj->CREDIT_ATTEMPTED       => '',
1.416     jms       560:       $resObj->ANSWER_SUBMITTED       => '',
                    561:       $resObj->PARTIALLY_CORRECT      => '#006600'
                    562:       );
                    563: # And a special case in the nav map; what to do when the assignment
                    564: # is not yet done and due in less than 24 hours
                    565: my $hurryUpColor = "#FF0000";
1.174     albertel  566: 
1.416     jms       567: sub addToFilter {
                    568:     my $hashIn = shift;
                    569:     my $addition = shift;
                    570:     my %hash = %$hashIn;
                    571:     $hash{$addition} = 1;
1.174     albertel  572: 
1.416     jms       573:     return join (",", keys(%hash));
                    574: }
1.174     albertel  575: 
1.416     jms       576: sub removeFromFilter {
                    577:     my $hashIn = shift;
                    578:     my $subtraction = shift;
                    579:     my %hash = %$hashIn;
1.174     albertel  580: 
1.416     jms       581:     delete $hash{$subtraction};
                    582:     return join(",", keys(%hash));
                    583: }
1.174     albertel  584: 
1.416     jms       585: sub getLinkForResource {
                    586:     my $stack = shift;
                    587:     my $res;
1.174     albertel  588: 
1.416     jms       589:     # Check to see if there are any pages in the stack
                    590:     foreach $res (@$stack) {
                    591:         if (defined($res)) {
                    592: 	    my $anchor;
                    593: 	    if ($res->is_page()) {
                    594: 		foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; }  }
1.549     raeburn   595: 		if ($anchor->encrypted() && !&advancedUser()) {
                    596: 		    $anchor='LC_'.$anchor->id();
                    597: 		} else {
                    598: 		    $anchor=&escape($anchor->shown_symb());
                    599: 		}
1.416     jms       600: 		return ($res->link(),$res->shown_symb(),$anchor);
                    601: 	    }
                    602:             # in case folder was skipped over as "only sequence"
                    603: 	    my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());
                    604: 	    if ($map=~/\.page$/) {
                    605: 		my $url=&Apache::lonnet::clutter($map);
1.437     foxr      606: 		$anchor=&escape($res->shown_symb());
1.416     jms       607: 		return ($url,$res->shown_symb(),$anchor);
                    608: 	    }
                    609:         }
                    610:     }
1.174     albertel  611: 
1.416     jms       612:     # Failing that, return the src of the last resource that is defined
                    613:     # (when we first recurse on a map, it puts an undefined resource
                    614:     # on the bottom because $self->{HERE} isn't defined yet, and we
                    615:     # want the src for the map anyhow)
                    616:     foreach my $item (@$stack) {
                    617:         if (defined($item)) { $res = $item; }
                    618:     }
1.174     albertel  619: 
1.416     jms       620:     if ($res) {
                    621: 	return ($res->link(),$res->shown_symb());
                    622:     }
                    623:     return;
                    624: }
1.174     albertel  625: 
1.417     jms       626: 
1.140     bowersj2  627: 
1.416     jms       628: sub getDescription {
                    629:     my $res = shift;
                    630:     my $part = shift;
                    631:     my $status = $res->status($part);
1.92      bowersj2  632: 
1.416     jms       633:     my $open = $res->opendate($part);
                    634:     my $due = $res->duedate($part);
                    635:     my $answer = $res->answerdate($part);
1.164     bowersj2  636: 
1.416     jms       637:     if ($status == $res->NETWORK_FAILURE) { 
                    638:         return &mt("Having technical difficulties; please check status later"); 
                    639:     }
                    640:     if ($status == $res->NOTHING_SET) {
1.494     raeburn   641:         return &Apache::lonhtmlcommon::direct_parm_link(&mt('Not currently assigned'),$res->symb(),'opendate',$part);
1.416     jms       642:     }
                    643:     if ($status == $res->OPEN_LATER) {
1.467     www       644:         return &mt("Open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($open,'start'),$res->symb(),'opendate',$part));
1.416     jms       645:     }
1.509     raeburn   646:     my $slotinfo;
1.432     raeburn   647:     if ($res->simpleStatus($part) == $res->OPEN) {
                    648:         unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) {
                    649:             my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part);
1.509     raeburn   650:             my $slotmsg;
1.432     raeburn   651:             if ($slot_status == $res->UNKNOWN) {
1.509     raeburn   652:                 $slotmsg = &mt('Reservation status unknown');
1.432     raeburn   653:             } elsif ($slot_status == $res->RESERVED) {
1.509     raeburn   654:                 $slotmsg = &mt('Reserved - ends [_1]',
1.433     bisitz    655:                            timeToHumanString($slot_time,'end'));
1.432     raeburn   656:             } elsif ($slot_status == $res->RESERVED_LOCATION) {
1.509     raeburn   657:                 $slotmsg = &mt('Reserved - specific location(s) - ends [_1]',
1.433     bisitz    658:                            timeToHumanString($slot_time,'end'));
1.432     raeburn   659:             } elsif ($slot_status == $res->RESERVED_LATER) {
1.509     raeburn   660:                 $slotmsg = &mt('Reserved - next open [_1]',
1.433     bisitz    661:                            timeToHumanString($slot_time,'start'));
1.432     raeburn   662:             } elsif ($slot_status == $res->RESERVABLE) {
1.509     raeburn   663:                 $slotmsg = &mt('Reservable, reservations close [_1]',
1.433     bisitz    664:                            timeToHumanString($slot_time,'end'));
1.538     raeburn   665:             } elsif ($slot_status == $res->NEEDS_CHECKIN) {
                    666:                 $slotmsg = &mt('Reserved, check-in needed - ends [_1]',
                    667:                            timeToHumanString($slot_time,'end'));
1.432     raeburn   668:             } elsif ($slot_status == $res->RESERVABLE_LATER) {
1.509     raeburn   669:                 $slotmsg = &mt('Reservable, reservations open [_1]',
1.433     bisitz    670:                            timeToHumanString($slot_time,'start'));
1.432     raeburn   671:             } elsif ($slot_status == $res->NOT_IN_A_SLOT) {
1.509     raeburn   672:                 $slotmsg = &mt('Reserve a time/place to work');
1.432     raeburn   673:             } elsif ($slot_status == $res->NOTRESERVABLE) {
1.509     raeburn   674:                 $slotmsg = &mt('Reservation not available');
1.432     raeburn   675:             } elsif ($slot_status == $res->WAITING_FOR_GRADE) {
1.509     raeburn   676:                 $slotmsg = &mt('Submission in grading queue');
                    677:             }
                    678:             if ($slotmsg) {
                    679:                 if ($res->is_task() || !$due) {
                    680:                      return $slotmsg;
                    681:                 }
                    682:                 $slotinfo = ('&nbsp;' x 2).'('.$slotmsg.')';
1.432     raeburn   683:             }
                    684:         }
                    685:     }
1.416     jms       686:     if ($status == $res->OPEN) {
                    687:         if ($due) {
                    688: 	    if ($res->is_practice()) {
1.509     raeburn   689: 		return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'duedate',$part)).$slotinfo;
1.416     jms       690: 	    } else {
1.509     raeburn   691: 		return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)).$slotinfo;
1.416     jms       692: 	    }
                    693:         } else {
1.509     raeburn   694:             return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part).$slotinfo;
1.416     jms       695:         }
                    696:     }
                    697:     if ($status == $res->PAST_DUE_ANSWER_LATER) {
1.467     www       698:         return &mt("Answer open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($answer,'start'),$res->symb(),'answerdate',$part));
1.416     jms       699:     }
                    700:     if ($status == $res->PAST_DUE_NO_ANSWER) {
                    701: 	if ($res->is_practice()) {
1.467     www       702: 	    return &mt("Closed [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'answerdate,duedate',$part));
1.416     jms       703: 	} else {
1.467     www       704: 	    return &mt("Was due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'answerdate,duedate',$part));
1.416     jms       705: 	}
                    706:     }
                    707:     if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
                    708: 	&& $res->handgrade($part) ne 'yes') {
1.539     raeburn   709:         my $msg = &mt('Answer available');
                    710:         my $parmlist = 'answerdate,duedate';
                    711:         if (($res->is_tool) && ($res->is_gradable())) {
                    712:             if (($status == $res->PARTIALLY_CORRECT) && ($res->parmval('retrypartial',$part))) {
                    713:                 $msg = &mt('Grade received');
                    714:                 $parmlist = 'retrypartial';
                    715:             } else {
                    716:                 $msg = &mt('Grade available');
                    717:             }
                    718:         }
                    719:         return &Apache::lonhtmlcommon::direct_parm_link($msg,$res->symb(),$parmlist,$part);
1.416     jms       720:     }
                    721:     if ($status == $res->EXCUSED) {
                    722:         return &mt("Excused by instructor");
                    723:     }
                    724:     if ($status == $res->ATTEMPTED) {
1.444     raeburn   725:         if ($res->is_anonsurvey($part) || $res->is_survey($part)) {
                    726:             return &mt("Survey submission recorded");
                    727:         } else {
                    728:             return &mt("Answer submitted, not yet graded");
                    729:         }
1.416     jms       730:     }
1.450     raeburn   731:     if ($status == $res->CREDIT_ATTEMPTED) {
                    732:         if ($res->is_anonsurvey($part) || $res->is_survey($part)) {
                    733:             return &mt("Credit for survey submission");
                    734:         }
                    735:     }
1.416     jms       736:     if ($status == $res->TRIES_LEFT) {
                    737:         my $tries = $res->tries($part);
                    738:         my $maxtries = $res->maxtries($part);
                    739:         my $triesString = "";
                    740:         if ($tries && $maxtries) {
1.488     bisitz    741:             $triesString = '<span class="LC_fontsize_medium"><i>('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')</i></span>';
1.416     jms       742:             if ($maxtries > 1 && $maxtries - $tries == 1) {
                    743:                 $triesString = "<b>$triesString</b>";
                    744:             }
                    745:         }
                    746:         if ($due) {
1.467     www       747:             return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)) .
1.416     jms       748:                 " $triesString";
                    749:         } else {
1.455     www       750:             return &Apache::lonhtmlcommon::direct_parm_link(&mt("No due date"),$res->symb(),'duedate',$part)." $triesString";
1.416     jms       751:         }
                    752:     }
                    753:     if ($status == $res->ANSWER_SUBMITTED) {
                    754:         return &mt('Answer submitted');
                    755:     }
                    756: }
1.92      bowersj2  757: 
1.51      bowersj2  758: 
1.416     jms       759: sub dueInLessThan24Hours {
                    760:     my $res = shift;
                    761:     my $part = shift;
                    762:     my $status = $res->status($part);
1.55      bowersj2  763: 
1.416     jms       764:     return ($status == $res->OPEN() ||
                    765:             $status == $res->TRIES_LEFT()) &&
                    766: 	    $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) &&
                    767: 	    $res->duedate($part) > time();
                    768: }
1.84      bowersj2  769: 
1.417     jms       770: 
1.416     jms       771: sub lastTry {
                    772:     my $res = shift;
                    773:     my $part = shift;
1.115     bowersj2  774: 
1.416     jms       775:     my $tries = $res->tries($part);
                    776:     my $maxtries = $res->maxtries($part);
                    777:     return $tries && $maxtries && $maxtries > 1 &&
                    778:         $maxtries - $tries == 1 && $res->duedate($part) &&
                    779:         $res->duedate($part) > time();
                    780: }
1.55      bowersj2  781: 
1.51      bowersj2  782: 
1.416     jms       783: sub advancedUser {
                    784:     return $env{'request.role.adv'};
                    785: }
1.51      bowersj2  786: 
1.416     jms       787: sub timeToHumanString {
                    788:     my ($time,$type,$format) = @_;
1.153     bowersj2  789: 
1.416     jms       790:     # zero, '0' and blank are bad times
                    791:     if (!$time) {
                    792:         return &mt('never');
                    793:     }
                    794:     unless (&Apache::lonlocal::current_language()=~/^en/) {
                    795: 	return &Apache::lonlocal::locallocaltime($time);
                    796:     } 
                    797:     my $now = time();
1.174     albertel  798: 
1.416     jms       799:     # Positive = future
                    800:     my $delta = $time - $now;
1.190     bowersj2  801: 
1.416     jms       802:     my $minute = 60;
                    803:     my $hour = 60 * $minute;
                    804:     my $day = 24 * $hour;
                    805:     my $week = 7 * $day;
                    806:     my $inPast = 0;
1.190     bowersj2  807: 
1.416     jms       808:     # Logic in comments:
                    809:     # Is it now? (extremely unlikely)
                    810:     if ( $delta == 0 ) {
                    811:         return "this instant";
                    812:     }
1.174     albertel  813: 
1.416     jms       814:     if ($delta < 0) {
                    815:         $inPast = 1;
                    816:         $delta = -$delta;
                    817:     }
1.143     bowersj2  818: 
1.416     jms       819:     if ( $delta > 0 ) {
1.51      bowersj2  820: 
1.416     jms       821:         my $tense = $inPast ? " ago" : "";
                    822:         my $prefix = $inPast ? "" : "in ";
                    823:         
                    824:         # Less than a minute
                    825:         if ( $delta < $minute ) {
                    826:             if ($delta == 1) { return "${prefix}1 second$tense"; }
                    827:             return "$prefix$delta seconds$tense";
                    828:         }
1.51      bowersj2  829: 
1.416     jms       830:         # Less than an hour
                    831:         if ( $delta < $hour ) {
1.464     raeburn   832:             # If so, use minutes; or minutes, seconds (if format requires)
1.416     jms       833:             my $minutes = floor($delta / 60);
1.464     raeburn   834:             if (($format ne '') && ($format =~ /\%(T|S)/)) {
                    835:                 my $display;
                    836:                 if ($minutes == 1) {
                    837:                     $display = "${prefix}1 minute";
                    838:                 } else {
                    839:                     $display = "$prefix$minutes minutes";
                    840:                 }
                    841:                 my $seconds = $delta % $minute;
                    842:                 if ($seconds == 0) {
                    843:                     $display .= $tense;
                    844:                 } elsif ($seconds == 1) {
                    845:                     $display .= ", 1 second$tense";
                    846:                 } else {
                    847:                     $display .= ", $seconds seconds$tense";
                    848:                 }
                    849:                 return $display;
                    850:             }
1.416     jms       851:             if ($minutes == 1) { return "${prefix}1 minute$tense"; }
                    852:             return "$prefix$minutes minutes$tense";
                    853:         }
                    854:         
                    855:         # Is it less than 24 hours away? If so,
1.464     raeburn   856:         # display hours + minutes, (and + seconds, if format specified it)  
1.416     jms       857:         if ( $delta < $hour * 24) {
                    858:             my $hours = floor($delta / $hour);
                    859:             my $minutes = floor(($delta % $hour) / $minute);
                    860:             my $hourString = "$hours hours";
                    861:             my $minuteString = ", $minutes minutes";
                    862:             if ($hours == 1) {
                    863:                 $hourString = "1 hour";
                    864:             }
                    865:             if ($minutes == 1) {
                    866:                 $minuteString = ", 1 minute";
                    867:             }
                    868:             if ($minutes == 0) {
                    869:                 $minuteString = "";
                    870:             }
1.464     raeburn   871:             if (($format ne '') && ($format =~ /\%(T|S)/)) {
                    872:                 my $display = "$prefix$hourString$minuteString";
                    873:                 my $seconds = $delta-(($hours * $hour)+($minutes * $minute));
                    874:                 if ($seconds == 0) {
                    875:                     $display .= $tense;
                    876:                 } elsif ($seconds == 1) {
                    877:                     $display .= ", 1 second$tense";
                    878:                 } else {
                    879:                     $display .= ", $seconds seconds$tense";
                    880:                 }
                    881:                 return $display;
                    882:             }
1.416     jms       883:             return "$prefix$hourString$minuteString$tense";
                    884:         }
1.174     albertel  885: 
1.464     raeburn   886:         # Date/time is more than 24 hours away
                    887: 
1.416     jms       888: 	my $dt = DateTime->from_epoch(epoch => $time)
                    889: 	                 ->set_time_zone(&Apache::lonlocal::gettimezone());
1.216     bowersj2  890: 
1.464     raeburn   891: 	# If there's a caller supplied format, use it, unless it only displays
                    892:         # H:M:S or H:M.
1.145     bowersj2  893: 
1.464     raeburn   894: 	if (($format ne '') && ($format ne '%T') && ($format ne '%R')) {
1.416     jms       895: 	    my $timeStr = $dt->strftime($format);
                    896: 	    return $timeStr.' ('.$dt->time_zone_short_name().')';
                    897: 	}
1.216     bowersj2  898: 
1.416     jms       899:         # Less than 5 days away, display day of the week and
                    900:         # HH:MM
1.216     bowersj2  901: 
1.416     jms       902:         if ( $delta < $day * 5 ) {
                    903:             my $timeStr = $dt->strftime("%A, %b %e at %I:%M %P (%Z)");
                    904:             $timeStr =~ s/12:00 am/00:00/;
                    905:             $timeStr =~ s/12:00 pm/noon/;
                    906:             return ($inPast ? "last " : "this ") .
                    907:                 $timeStr;
                    908:         }
                    909:         
                    910: 	my $conjunction='on';
                    911: 	if ($type eq 'start') {
                    912: 	    $conjunction='at';
                    913: 	} elsif ($type eq 'end') {
                    914: 	    $conjunction='by';
                    915: 	}
                    916:         # Is it this year?
                    917: 	my $dt_now = DateTime->from_epoch(epoch => $now)
                    918: 	                     ->set_time_zone(&Apache::lonlocal::gettimezone());
                    919:         if ( $dt->year() == $dt_now->year()) {
                    920:             # Return on Month Day, HH:MM meridian
                    921:             my $timeStr = $dt->strftime("$conjunction %A, %b %e at %I:%M %P (%Z)");
                    922:             $timeStr =~ s/12:00 am/00:00/;
                    923:             $timeStr =~ s/12:00 pm/noon/;
                    924:             return $timeStr;
                    925:         }
1.59      bowersj2  926: 
1.416     jms       927:         # Not this year, so show the year
                    928:         my $timeStr = 
                    929: 	    $dt->strftime("$conjunction %A, %b %e %Y at %I:%M %P (%Z)");
                    930:         $timeStr =~ s/12:00 am/00:00/;
                    931:         $timeStr =~ s/12:00 pm/noon/;
                    932:         return $timeStr;
                    933:     }
                    934: }
1.59      bowersj2  935: 
                    936: 
1.133     bowersj2  937: sub resource { return 0; }
                    938: sub communication_status { return 1; }
                    939: sub quick_status { return 2; }
                    940: sub long_status { return 3; }
1.225     bowersj2  941: sub part_status_summary { return 4; }
1.51      bowersj2  942: 
1.133     bowersj2  943: sub render_resource {
                    944:     my ($resource, $part, $params) = @_;
1.51      bowersj2  945: 
1.477     raeburn   946:     my $editmapLink;
1.133     bowersj2  947:     my $nonLinkedText = ''; # stuff after resource title not in link
1.51      bowersj2  948: 
1.134     bowersj2  949:     my $link = $params->{"resourceLink"};
1.512     raeburn   950:     if ($resource->ext()) {
                    951:         $link =~ s/\#.+(\?)/$1/g;
                    952:     }
1.306     foxr      953: 
                    954:     #  The URL part is not escaped at this point, but the symb is... 
                    955: 
1.134     bowersj2  956:     my $src = $resource->src();
                    957:     my $it = $params->{"iterator"};
1.133     bowersj2  958:     my $filter = $it->{FILTER};
                    959: 
                    960:     my $title = $resource->compTitle();
1.258     albertel  961: 
1.133     bowersj2  962:     my $partLabel = "";
                    963:     my $newBranchText = "";
1.298     albertel  964:     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
1.133     bowersj2  965:     # If this is a new branch, label it so
                    966:     if ($params->{'isNewBranch'}) {
1.429     droeschl  967:         $newBranchText = "<img src='$location/branch.gif' alt=".mt('Branch')." />";
1.51      bowersj2  968:     }
                    969: 
1.427     droeschl  970:     my $whitespace = $location.'/whitespace_21.gif';
1.543     raeburn   971:     my ($nomodal,$linkopen,$linkclose);
                    972:     unless ($resource->is_map() || $params->{'resource_nolink'}) {
                    973:         $linkopen = "<img src='$whitespace' alt='' />";
                    974:         $linkclose = "</a>";
                    975:         if (($params->{'modalLink'}) && (!$resource->is_sequence())) {
                    976:             if ($link =~m{^(?:|/adm/wrapper)/ext/([^#]+)}) {
                    977:                 my $exturl = $1;
                    978:                 if (($ENV{'SERVER_PORT'} == 443) && ($exturl !~ /^https:/)) {
                    979:                     $nomodal = 1;
                    980:                 }
                    981:             } elsif (($link eq "/public/$LONCAPA::match_domain/$LONCAPA::match_courseid/syllabus") &&
                    982:                      ($env{'request.course.id'}) && ($ENV{'SERVER_PORT'} == 443) &&
                    983:                      ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) {
1.540     raeburn   984:                 $nomodal = 1;
                    985:             }
1.543     raeburn   986:             my $esclink = &js_escape($link);
                    987:             if ($nomodal) {
                    988:                 $linkopen .= "<a href=\"#\" onclick=\"javascript:window.open('$esclink','resourcepreview','height=400,width=500,scrollbars=1,resizable=1,menubar=0,location=1'); return false;\" />";
                    989:             } else {
                    990:                 $linkopen .= "<a href=\"$link\" onclick=\"javascript:openMyModal('$esclink',600,500,'yes','true'); return false;\">";
                    991:             }
1.540     raeburn   992:         } else {
1.543     raeburn   993:             $linkopen .= "<a href=\"$link\">";
1.540     raeburn   994:         }
                    995:     }
1.51      bowersj2  996: 
1.204     albertel  997:     # Default icon: unknown page
1.426     droeschl  998:     my $icon = "<img class=\"LC_contentImage\" src='$location/unknown.gif' alt='' />";
1.133     bowersj2  999:     
                   1000:     if ($resource->is_problem()) {
1.192     bowersj2 1001:         if ($part eq '0' || $params->{'condensed'}) {
1.426     droeschl 1002: 	    $icon = '<img class="LC_contentImage" src="'.$location.'/';
1.389     albertel 1003: 	    if ($resource->is_task()) {
                   1004: 		$icon .= 'task.gif" alt="'.&mt('Task');
                   1005: 	    } else {
                   1006: 		$icon .= 'problem.gif" alt="'.&mt('Problem');
                   1007: 	    }
1.426     droeschl 1008: 	    $icon .='" />';
1.133     bowersj2 1009:         } else {
                   1010:             $icon = $params->{'indentString'};
                   1011:         }
1.204     albertel 1012:     } else {
1.426     droeschl 1013: 	$icon = "<img class=\"LC_contentImage\" src='".&Apache::loncommon::icon($resource->src)."' alt='' />";
1.133     bowersj2 1014:     }
1.51      bowersj2 1015: 
1.133     bowersj2 1016:     # Display the correct map icon to open or shut map
                   1017:     if ($resource->is_map()) {
                   1018:         my $mapId = $resource->map_pc();
                   1019:         my $nowOpen = !defined($filter->{$mapId});
                   1020:         if ($it->{CONDITION}) {
                   1021:             $nowOpen = !$nowOpen;
                   1022:         }
1.562   ! raeburn  1023: 	my $folderType;
        !          1024: 	if (&advancedUser() && $resource->is_missing_map()) {
        !          1025: 	    $folderType = 'none';
        !          1026: 	} else {
        !          1027: 	    $folderType = $resource->is_sequence() ? 'folder' : 'page';
        !          1028: 	}
1.362     www      1029:         my $title=$resource->title;
1.426     droeschl 1030: 		$title=~s/\"/\&qout;/g;
1.144     bowersj2 1031:         if (!$params->{'resource_no_folder_link'}) {
1.205     bowersj2 1032:             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
1.426     droeschl 1033:             $icon = "<img src='$location/arrow." . ($nowOpen ? 'closed' : 'open') . ".gif' alt='' />"
                   1034:                     ."<img class=\"LC_contentImage\" src='$location/$icon' alt=\""
                   1035:                     .($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />";			
1.349     foxr     1036:             $linkopen = "<a href=\"" . $params->{'url'} . '?' . 
1.392     albertel 1037:                 $params->{'queryString'} . '&amp;filter=';
1.144     bowersj2 1038:             $linkopen .= ($nowOpen xor $it->{CONDITION}) ?
                   1039:                 addToFilter($filter, $mapId) :
                   1040:                 removeFromFilter($filter, $mapId);
1.392     albertel 1041:             $linkopen .= "&amp;condition=" . $it->{CONDITION} . '&amp;hereType='
                   1042:                 . $params->{'hereType'} . '&amp;here=' .
1.384     www      1043:                 &escape($params->{'here'}) . 
1.392     albertel 1044:                 '&amp;jump=' .
1.384     www      1045:                 &escape($resource->symb()) . 
1.392     albertel 1046:                 "&amp;folderManip=1\">";
1.543     raeburn  1047:             $linkclose = '</a>';
1.144     bowersj2 1048:         } else {
                   1049:             # Don't allow users to manipulate folder
1.426     droeschl 1050:             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
1.427     droeschl 1051:             $icon = "<img class=\"LC_space\" src='$whitespace' alt='' />"."<img class=\"LC_contentImage\" src='$location/$icon' alt=\"".($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />";
1.533     raeburn  1052:             if ($params->{'caller'} eq 'sequence') {
                   1053:                 $linkopen = "<a href=\"$link\">";
1.543     raeburn  1054:                 $linkclose = '</a>';
1.533     raeburn  1055:             } else {
                   1056:                 $linkopen = "";
                   1057:                 $linkclose = "";
                   1058:             }
1.144     bowersj2 1059:         }
1.525     raeburn  1060:         if (((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) ||
                   1061:               (&Apache::lonnet::allowed('cev',$env{'request.course.id'}))) &&
1.477     raeburn  1062:             ($resource->symb=~/\_\_\_[^\_]+\_\_\_uploaded/)) {
1.493     raeburn  1063:             if (!$params->{'map_no_edit_link'}) {
                   1064:                 my $icon = &Apache::loncommon::lonhttpdurl('/res/adm/pages').'/editmap.png';
                   1065:                 $editmapLink='&nbsp;'.
1.495     raeburn  1066:                          '<a href="/adm/coursedocs?command=directnav&amp;symb='.&escape($resource->symb()).'">'.
1.477     raeburn  1067:                          '<img src="'.$icon.'" alt="'.&mt('Edit Content').'" title="'.&mt('Edit Content').'" />'.
                   1068:                          '</a>';
1.493     raeburn  1069:             }
1.477     raeburn  1070:         }
1.518     raeburn  1071:         if ($params->{'mapHidden'} || $resource->randomout()) {
                   1072:             $nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> ';
1.543     raeburn  1073:         } elsif ($params->{'mapUnlisted'}) {
                   1074:             $nonLinkedText .= ' <span class="LC_warning">('.&mt('unlisted').')</span> ';
1.553     raeburn  1075:         } elsif ($params->{'mapHiddenDeepLink'} || $resource->deeplinkout()) {
                   1076:             $nonLinkedText .= ' <span class="LC_warning">('.&mt('not shown').')</span> ';
1.518     raeburn  1077:         }
                   1078:     } else {
                   1079:         if ($resource->randomout()) {
                   1080:             $nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> ';
1.553     raeburn  1081:         } elsif ($resource->deeplinkout()) {
                   1082:             $nonLinkedText .= ' <span class="LC_warning">('.&mt('not shown').')</span> ';
                   1083:         } else {
                   1084:             my $deeplink = $resource->deeplink($params->{caller});
                   1085:             if ((($deeplink eq 'absent') || ($deeplink eq 'grades')) &&
                   1086:                   &advancedUser()) {
                   1087:                 $nonLinkedText .= ' <span class="LC_warning">('.&mt('unlisted').')</span> ';
                   1088:             } elsif (($deeplink) && ($deeplink) ne 'full') {
                   1089:                 if (&advancedUser()) {
                   1090:                     $nonLinkedText .= ' <span class="LC_warning">('.&mt('deep-link access').
                   1091:                                       ')</span> ';
                   1092:                 } else {
                   1093:                     $nonLinkedText .= ' <span class="LC_warning">('.&mt('access via external site').
                   1094:                                       ')</span> ';
                   1095:                 }
                   1096:             }
1.518     raeburn  1097:         }
1.133     bowersj2 1098:     }
1.342     albertel 1099:     if (!$resource->condval()) {
1.458     bisitz   1100:         $nonLinkedText .= ' <span class="LC_info">('.&mt('conditionally hidden').')</span> ';
1.342     albertel 1101:     }
1.413     www      1102:     if (($resource->is_practice()) && ($resource->is_raw_problem())) {
1.488     bisitz   1103:         $nonLinkedText .=' <span class="LC_info"><b>'.&mt('not graded').'</b></span>';
1.412     www      1104:     }
1.426     droeschl 1105: 
1.133     bowersj2 1106:     # We're done preparing and finally ready to start the rendering
1.426     droeschl 1107:     my $result = '<td class="LC_middle">';
                   1108:     my $newfolderType = $resource->is_sequence() ? 'folder' : 'page';
                   1109: 	
1.140     bowersj2 1110:     my $indentLevel = $params->{'indentLevel'};
                   1111:     if ($newBranchText) { $indentLevel--; }
                   1112: 
1.133     bowersj2 1113:     # print indentation
1.140     bowersj2 1114:     for (my $i = 0; $i < $indentLevel; $i++) {
1.133     bowersj2 1115:         $result .= $params->{'indentString'};
1.84      bowersj2 1116:     }
                   1117: 
1.133     bowersj2 1118:     # Decide what to display
                   1119:     $result .= "$newBranchText$linkopen$icon$linkclose";
                   1120:     
                   1121:     my $curMarkerBegin = '';
                   1122:     my $curMarkerEnd = '';
1.51      bowersj2 1123: 
1.133     bowersj2 1124:     # Is this the current resource?
1.158     bowersj2 1125:     if (!$params->{'displayedHereMarker'} && 
                   1126:         $resource->symb() eq $params->{'here'} ) {
1.477     raeburn  1127:         unless ($resource->is_map()) {
                   1128:             $curMarkerBegin = '<span class="LC_current_nav_location">';
                   1129:             $curMarkerEnd = '</span>';
                   1130:         }
1.424     schulted 1131: 	$params->{'displayedHereMarker'} = 1;
1.51      bowersj2 1132:     }
                   1133: 
1.192     bowersj2 1134:     if ($resource->is_problem() && $part ne '0' && 
1.133     bowersj2 1135:         !$params->{'condensed'}) {
1.274     matthew  1136: 	my $displaypart=$resource->part_display($part);
1.363     albertel 1137:         $partLabel = " (".&mt('Part: [_1]', $displaypart).")";
1.384     www      1138: 	if ($link!~/\#/) { $link.='#'.&escape($part); }
1.133     bowersj2 1139:         $title = "";
1.51      bowersj2 1140:     }
                   1141: 
1.163     bowersj2 1142:     if ($params->{'condensed'} && $resource->countParts() > 1) {
1.363     albertel 1143:         $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')';
1.51      bowersj2 1144:     }
                   1145: 
1.266     raeburn  1146:     if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {
1.540     raeburn  1147:         $linkclose = '</a>';
                   1148:         if ($params->{'modalLink'}) {
                   1149:             my $esclink = &js_escape($link);
                   1150:             if ($nomodal) {
                   1151:                 $linkopen = "<a href=\"#\" onclick=\"javascript:window.open('$esclink','resourcepreview','height=400,width=500,scrollbars=1,resizable=1,menubar=0,location=1'); return false;\" />";
                   1152:             } else {
                   1153:                 $linkopen = "<a href=\"$link\" onclick=\"javascript:openMyModal('$esclink',600,500,'yes','true'); return false;\">";
                   1154:             }
                   1155:         } else {
                   1156:             $linkopen = "<a href=\"$link\">";
                   1157:         }
1.144     bowersj2 1158:     }
1.540     raeburn  1159:     $result .= "$curMarkerBegin$linkopen$title$partLabel$linkclose$curMarkerEnd$editmapLink$nonLinkedText</td>";
1.51      bowersj2 1160: 
1.133     bowersj2 1161:     return $result;
                   1162: }
1.51      bowersj2 1163: 
1.133     bowersj2 1164: sub render_communication_status {
                   1165:     my ($resource, $part, $params) = @_;
1.134     bowersj2 1166:     my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";
                   1167: 
                   1168:     my $link = $params->{"resourceLink"};
1.445     droeschl 1169:     my $linkopen = "<a href=\"$link\">";
1.134     bowersj2 1170:     my $linkclose = "</a>";
1.298     albertel 1171:     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
1.477     raeburn  1172: 
1.134     bowersj2 1173:     if ($resource->hasDiscussion()) {
                   1174:         $discussionHTML = $linkopen .
1.442     wenzelju 1175:             '<img alt="'.&mt('New Discussion').'" src="'.$location.'/chat.gif" title="'.&mt('New Discussion').'"/>' .
1.134     bowersj2 1176:             $linkclose;
                   1177:     }
                   1178:     
                   1179:     if ($resource->getFeedback()) {
                   1180:         my $feedback = $resource->getFeedback();
1.394     raeburn  1181:         foreach my $msgid (split(/\,/, $feedback)) {
                   1182:             if ($msgid) {
1.445     droeschl 1183:                 $feedbackHTML .= '&nbsp;<a href="/adm/email?display='
1.394     raeburn  1184:                     . &escape($msgid) . '">'
1.442     wenzelju 1185:                     . '<img alt="'.&mt('New E-mail').'" src="'.$location.'/feedback.gif" title="'.&mt('New E-mail').'"/></a>';
1.134     bowersj2 1186:             }
                   1187:         }
                   1188:     }
                   1189:     
                   1190:     if ($resource->getErrors()) {
                   1191:         my $errors = $resource->getErrors();
1.254     matthew  1192:         my $errorcount = 0;
1.394     raeburn  1193:         foreach my $msgid (split(/,/, $errors)) {
1.254     matthew  1194:             last if ($errorcount>=10); # Only output 10 bombs maximum
1.394     raeburn  1195:             if ($msgid) {
1.254     matthew  1196:                 $errorcount++;
1.445     droeschl 1197:                 $errorHTML .= '&nbsp;<a href="/adm/email?display='
1.394     raeburn  1198:                     . &escape($msgid) . '">'
1.442     wenzelju 1199:                     . '<img alt="'.&mt('New Error').'" src="'.$location.'/bomb.gif" title="'.&mt('New Error').'"/></a>';
1.134     bowersj2 1200:             }
                   1201:         }
1.197     bowersj2 1202:     }
                   1203: 
                   1204:     if ($params->{'multipart'} && $part != '0') {
                   1205: 	$discussionHTML = $feedbackHTML = $errorHTML = '';
1.134     bowersj2 1206:     }
1.426     droeschl 1207:     return "<td class=\"LC_middle\">$discussionHTML$feedbackHTML$errorHTML&nbsp;</td>";
1.134     bowersj2 1208: 
1.133     bowersj2 1209: }
                   1210: sub render_quick_status {
                   1211:     my ($resource, $part, $params) = @_;
1.135     bowersj2 1212:     my $result = "";
                   1213:     my $firstDisplayed = !$params->{'condensed'} && 
                   1214:         $params->{'multipart'} && $part eq "0";
                   1215: 
                   1216:     my $link = $params->{"resourceLink"};
1.445     droeschl 1217:     my $linkopen = "<a href=\"$link\">";
1.135     bowersj2 1218:     my $linkclose = "</a>";
1.426     droeschl 1219: 	
                   1220: 	$result .= '<td class="LC_middle">';
1.537     raeburn  1221:     if ($resource->is_gradable() &&
1.135     bowersj2 1222:         !$firstDisplayed) {
1.224     bowersj2 1223:         my $icon = $statusIconMap{$resource->simpleStatus($part)};
1.135     bowersj2 1224:         my $alt = $iconAltTags{$icon};
                   1225:         if ($icon) {
1.298     albertel 1226: 	    my $location=
                   1227: 		&Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");
1.438     wenzelju 1228: 		$result .= $linkopen.'<img src="'.$location.'" alt="'.&mt($alt).'" title="'.&mt($alt).'" />'.$linkclose;            
1.135     bowersj2 1229:         } else {
1.426     droeschl 1230:             $result .= "&nbsp;";
1.135     bowersj2 1231:         }
                   1232:     } else { # not problem, no icon
1.426     droeschl 1233:         $result .= "&nbsp;";
1.135     bowersj2 1234:     }
1.426     droeschl 1235: 	$result .= "</td>\n";
1.135     bowersj2 1236:     return $result;
1.133     bowersj2 1237: }
                   1238: sub render_long_status {
                   1239:     my ($resource, $part, $params) = @_;
1.426     droeschl 1240:     my $result = '<td class="LC_middle LC_right">';
1.136     bowersj2 1241:     my $firstDisplayed = !$params->{'condensed'} && 
                   1242:         $params->{'multipart'} && $part eq "0";
                   1243:                 
                   1244:     my $color;
1.488     bisitz   1245:     my $info = '';
1.537     raeburn  1246:     if ($resource->is_gradable() || $resource->is_practice()) {
1.136     bowersj2 1247:         $color = $colormap{$resource->status};
1.502     raeburn  1248: 
                   1249:         if (dueInLessThan24Hours($resource, $part)) {
1.136     bowersj2 1250:             $color = $hurryUpColor;
1.488     bisitz   1251:             $info = ' title="'.&mt('Due in less than 24 hours!').'"';
1.502     raeburn  1252:         } elsif (lastTry($resource, $part)) {
                   1253:             unless (($resource->problemstatus($part) eq 'no') ||
                   1254:                     ($resource->problemstatus($part) eq 'no_feedback_ever')) {
                   1255:                 $color = $hurryUpColor;
                   1256:                 $info = ' title="'.&mt('One try remaining!').'"';
                   1257:             }
                   1258:          }
1.136     bowersj2 1259:     }
1.537     raeburn  1260: 
                   1261:     if (($resource->kind() eq "res") &&
                   1262:         ($resource->is_raw_problem() || $resource->is_gradable()) &&
1.463     raeburn  1263:         !$firstDisplayed) {
1.488     bisitz   1264:         if ($color) {$result .= '<span style="color:'.$color.'"'.$info.'><b>'; }
1.136     bowersj2 1265:         $result .= getDescription($resource, $part);
1.488     bisitz   1266:         if ($color) {$result .= "</b></span>"; }
1.136     bowersj2 1267:     }
1.402     albertel 1268:     if ($resource->is_map() && &advancedUser() && $resource->randompick()) {
1.400     albertel 1269:         $result .= &mt('(randomly select [_1])', $resource->randompick());
                   1270:     }
                   1271:     if ($resource->is_map() && &advancedUser() && $resource->randomorder()) {
                   1272:         $result .= &mt('(randomly ordered)');
1.136     bowersj2 1273:     }
1.210     bowersj2 1274: 
1.213     bowersj2 1275:     # Debugging code
                   1276:     #$result .= " " . $resource->awarded($part) . '/' . $resource->weight($part) .
                   1277:     #	' - Part: ' . $part;
                   1278: 
1.210     bowersj2 1279:     $result .= "</td>\n";
1.136     bowersj2 1280:     
                   1281:     return $result;
1.51      bowersj2 1282: }
                   1283: 
1.227     bowersj2 1284: # Colors obtained by taking the icons, matching the colors, and
                   1285: # possibly reducing the Value (HSV) of the color, if it's too bright
                   1286: # for text, generally by one third or so.
1.225     bowersj2 1287: my %statusColors = 
                   1288:     (
                   1289:      $resObj->CLOSED => '#000000',
1.227     bowersj2 1290:      $resObj->OPEN   => '#998b13',
                   1291:      $resObj->CORRECT => '#26933f',
                   1292:      $resObj->INCORRECT => '#c48207',
                   1293:      $resObj->ATTEMPTED => '#a87510',
1.225     bowersj2 1294:      $resObj->ERROR => '#000000'
                   1295:      );
                   1296: my %statusStrings = 
                   1297:     (
                   1298:      $resObj->CLOSED => 'Not yet open',
                   1299:      $resObj->OPEN   => 'Open',
                   1300:      $resObj->CORRECT => 'Correct',
                   1301:      $resObj->INCORRECT => 'Incorrect',
                   1302:      $resObj->ATTEMPTED => 'Attempted',
                   1303:      $resObj->ERROR => 'Network Error'
                   1304:      );
                   1305: my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR);
                   1306: 
                   1307: sub render_parts_summary_status {
                   1308:     my ($resource, $part, $params) = @_;
1.537     raeburn  1309:     if (!$resource->is_gradable() && !$resource->contains_problem) { return '<td></td>'; }
1.225     bowersj2 1310:     if ($params->{showParts}) { 
                   1311: 	return '<td></td>';
                   1312:     }
                   1313: 
                   1314:     my $td = "<td align='right'>\n";
                   1315:     my $endtd = "</td>\n";
1.256     albertel 1316:     my @probs;
1.225     bowersj2 1317: 
1.256     albertel 1318:     if ($resource->contains_problem) {
                   1319: 	@probs=$resource->retrieveResources($resource,sub { $_[0]->is_problem() },1,0);
                   1320:     } else {
                   1321: 	@probs=($resource);
                   1322:     }
                   1323:     my $return;
                   1324:     my %overallstatus;
                   1325:     my $totalParts;
                   1326:     foreach my $resource (@probs) {
                   1327: 	# If there is a single part, just show the simple status
                   1328: 	if ($resource->singlepart()) {
                   1329: 	    my $status = $resource->simpleStatus(${$resource->parts}[0]);
                   1330: 	    $overallstatus{$status}++;
                   1331: 	    $totalParts++;
                   1332: 	    next;
                   1333: 	}
                   1334: 	# Now we can be sure the $part doesn't really matter.
                   1335: 	my $statusCount = $resource->simpleStatusCount();
                   1336: 	my @counts;
                   1337: 	foreach my $status (@statuses) {
                   1338: 	    # decouple display order from the simpleStatusCount order
                   1339: 	    my $slot = Apache::lonnavmaps::resource::statusToSlot($status);
                   1340: 	    if ($statusCount->[$slot]) {
                   1341: 		$overallstatus{$status}+=$statusCount->[$slot];
                   1342: 		$totalParts+=$statusCount->[$slot];
                   1343: 	    }
                   1344: 	}
                   1345:     }
                   1346:     $return.= $td . $totalParts . ' parts: ';
                   1347:     foreach my $status (@statuses) {
1.488     bisitz   1348:         if ($overallstatus{$status}) {
                   1349:             $return.='<span style="color:' . $statusColors{$status}
                   1350:                    . '">' . $overallstatus{$status} . ' '
                   1351:                    . $statusStrings{$status} . '</span>';
                   1352:         }
1.225     bowersj2 1353:     }
1.256     albertel 1354:     $return.= $endtd;
                   1355:     return $return;
1.225     bowersj2 1356: }
                   1357: 
1.133     bowersj2 1358: my @preparedColumns = (\&render_resource, \&render_communication_status,
1.225     bowersj2 1359:                        \&render_quick_status, \&render_long_status,
                   1360: 		       \&render_parts_summary_status);
1.132     bowersj2 1361: 
                   1362: sub setDefault {
                   1363:     my ($val, $default) = @_;
                   1364:     if (!defined($val)) { return $default; }
                   1365:     return $val;
                   1366: }
                   1367: 
1.296     albertel 1368: sub cmp_title {
                   1369:     my ($atitle,$btitle) = (lc($_[0]->compTitle),lc($_[1]->compTitle));
                   1370:     $atitle=~s/^\s*//;
                   1371:     $btitle=~s/^\s*//;
                   1372:     return $atitle cmp $btitle;
                   1373: }
                   1374: 
1.132     bowersj2 1375: sub render {
                   1376:     my $args = shift;
1.140     bowersj2 1377:     &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
                   1378:     my $result = '';
1.132     bowersj2 1379:     # Configure the renderer.
                   1380:     my $cols = $args->{'cols'};
                   1381:     if (!defined($cols)) {
                   1382:         # no columns, no nav maps.
                   1383:         return '';
                   1384:     }
1.140     bowersj2 1385:     my $navmap;
                   1386:     if (defined($args->{'navmap'})) {
                   1387:         $navmap = $args->{'navmap'};
                   1388:     }
                   1389: 
1.158     bowersj2 1390:     my $r = $args->{'r'};
1.140     bowersj2 1391:     my $queryString = $args->{'queryString'};
1.159     bowersj2 1392:     my $jump = $args->{'jump'};
1.158     bowersj2 1393:     my $here = $args->{'here'};
1.153     bowersj2 1394:     my $suppressNavmap = setDefault($args->{'suppressNavmap'}, 0);
1.256     albertel 1395:     my $closeAllPages = setDefault($args->{'closeAllPages'}, 0);
1.140     bowersj2 1396:     my $currentJumpDelta = 2; # change this to change how many resources are displayed
                   1397:                              # before the current resource when using #current
                   1398: 
                   1399:     # If we were passed 'here' information, we are not rendering
                   1400:     # after a folder manipulation, and we were not passed an
                   1401:     # iterator, make sure we open the folders to show the "here"
                   1402:     # marker
                   1403:     my $filterHash = {};
                   1404:     # Figure out what we're not displaying
1.394     raeburn  1405:     foreach my $item (split(/\,/, $env{"form.filter"})) {
                   1406:         if ($item) {
                   1407:             $filterHash->{$item} = "1";
1.140     bowersj2 1408:         }
                   1409:     }
                   1410: 
1.191     bowersj2 1411:     # Filter: Remember filter function and add our own filter: Refuse
                   1412:     # to show hidden resources unless the user can see them.
                   1413:     my $userCanSeeHidden = advancedUser();
                   1414:     my $filterFunc = setDefault($args->{'filterFunc'},
                   1415:                                 sub {return 1;});
                   1416:     if (!$userCanSeeHidden) {
                   1417:         # Without renaming the filterfunc, the server seems to go into
                   1418:         # an infinite loop
                   1419:         my $oldFilterFunc = $filterFunc;
1.543     raeburn  1420:         $filterFunc = sub { my $res = shift; return !$res->randomout() &&
                   1421:                                 ($res->deeplink($args->{'caller'}) ne 'absent') &&
                   1422:                                 ($res->deeplink($args->{'caller'}) ne 'grades') &&
1.553     raeburn  1423:                                 !$res->deeplinkout() &&
1.191     bowersj2 1424:                                 &$oldFilterFunc($res);};
                   1425:     }
                   1426: 
1.140     bowersj2 1427:     my $condition = 0;
1.320     albertel 1428:     if ($env{'form.condition'}) {
1.140     bowersj2 1429:         $condition = 1;
1.559     raeburn  1430:     } elsif (($env{'request.deeplink.login'}) && ($env{'request.course.id'}) && (!$userCanSeeHidden)) {
                   1431:         if (!defined($navmap)) {
                   1432:             $navmap = Apache::lonnavmaps::navmap->new();
                   1433:         }
                   1434:         if (defined($navmap)) {
                   1435:             my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   1436:             my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
                   1437:             my $symb = &Apache::loncommon::symb_from_tinyurl($env{'request.deeplink.login'},$cnum,$cdom);
                   1438:             if ($symb) {
                   1439:                 my $deeplink;
                   1440:                 my $res = $navmap->getBySymb($symb);
                   1441:                 if ($res->is_map()) {
                   1442:                     my $mapname = &Apache::lonnet::declutter($res->src());
                   1443:                     $mapname = &Apache::lonnet::deversion($mapname);
                   1444:                     $deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink");
                   1445:                 } else {
                   1446:                     $deeplink = $res->deeplink();
                   1447:                 }
                   1448:                 if ($deeplink ne '') {
                   1449:                     if ((split(/,/,$deeplink))[1] eq 'hide') {
                   1450:                         if ($res->is_map()) {
                   1451:                             map { $filterHash->{$_} = 1 if $_ } split(/,/,$res->map_hierarchy());
                   1452:                         } else {
                   1453:                             my $mapurl = (&Apache::lonnet::decode_symb($symb))[0];
                   1454:                             my $map = $navmap->getResourceByUrl($mapurl);
                   1455:                             map { $filterHash->{$_} = 1 if $_ } split(/,/,$map->map_hierarchy());
                   1456:                         }
                   1457:                     }
                   1458:                 }
                   1459:             }
                   1460:         }
1.140     bowersj2 1461:     }
                   1462: 
1.558     raeburn  1463:     if (!$env{'form.folderManip'} && !defined($args->{'iterator'}) && !$args->{'nocurrloc'}) {
1.140     bowersj2 1464:         # Step 1: Check to see if we have a navmap
                   1465:         if (!defined($navmap)) {
1.221     bowersj2 1466:             $navmap = Apache::lonnavmaps::navmap->new();
1.340     albertel 1467: 	    if (!defined($navmap)) {
1.423     raeburn  1468: 		# no longer in course
1.388     albertel 1469: 		return '<span class="LC_error">'.&mt('No course selected').'</span><br />
1.362     www      1470:                         <a href="/adm/roles">'.&mt('Select a course').'</a><br />';
1.340     albertel 1471: 	    }
                   1472: 	}
1.140     bowersj2 1473: 
                   1474:         # Step two: Locate what kind of here marker is necessary
                   1475:         # Determine where the "here" marker is and where the screen jumps to.
                   1476: 
1.332     albertel 1477:         if ($env{'form.postsymb'} ne '') {
1.320     albertel 1478:             $here = $jump = &Apache::lonnet::symbclean($env{'form.postsymb'});
1.332     albertel 1479:         } elsif ($env{'form.postdata'} ne '') {
1.140     bowersj2 1480:             # couldn't find a symb, is there a URL?
1.320     albertel 1481:             my $currenturl = $env{'form.postdata'};
1.158     bowersj2 1482:             #$currenturl=~s/^http\:\/\///;
                   1483:             #$currenturl=~s/^[^\/]+//;
1.533     raeburn  1484:             unless ($args->{'caller'} eq 'sequence') {
                   1485:                 $here = $jump = &Apache::lonnet::symbread($currenturl);
                   1486:             }
1.331     albertel 1487: 	}
1.533     raeburn  1488: 	if (($here eq '') && ($args->{'caller'} ne 'sequence')) {
1.317     albertel 1489: 	    my $last;
1.320     albertel 1490: 	    if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db',
1.317     albertel 1491:                     &GDBM_READER(),0640)) {
                   1492: 		$last=$hash{'last_known'};
                   1493: 		untie(%hash);
                   1494: 	    }
                   1495: 	    if ($last) { $here = $jump = $last; }
                   1496: 	}
1.158     bowersj2 1497: 
1.140     bowersj2 1498:         # Step three: Ensure the folders are open
                   1499:         my $mapIterator = $navmap->getIterator(undef, undef, undef, 1);
1.222     bowersj2 1500:         my $curRes;
1.140     bowersj2 1501:         my $found = 0;
1.555     raeburn  1502:         my $here_is_navmaps = 0; 
                   1503:         if ($here =~ m{___\d+___adm/navmaps$}) {
                   1504:             $here_is_navmaps = 1;
                   1505:         }
1.140     bowersj2 1506:         
                   1507:         # We only need to do this if we need to open the maps to show the
                   1508:         # current position. This will change the counter so we can't count
                   1509:         # for the jump marker with this loop.
1.555     raeburn  1510:         while ($here && ($curRes = $mapIterator->next()) && !$found && !$here_is_navmaps) {
1.158     bowersj2 1511:             if (ref($curRes) && $curRes->symb() eq $here) {
1.140     bowersj2 1512:                 my $mapStack = $mapIterator->getStack();
                   1513:                 
                   1514:                 # Ensure the parent maps are open
                   1515:                 for my $map (@{$mapStack}) {
                   1516:                     if ($condition) {
                   1517:                         undef $filterHash->{$map->map_pc()};
                   1518:                     } else {
                   1519:                         $filterHash->{$map->map_pc()} = 1;
                   1520:                     }
                   1521:                 }
                   1522:                 $found = 1;
                   1523:             }
                   1524:         }            
1.159     bowersj2 1525:     }        
1.140     bowersj2 1526: 
1.320     albertel 1527:     if ( !defined($args->{'iterator'}) && $env{'form.folderManip'} ) { # we came from a user's manipulation of the nav page
1.140     bowersj2 1528:         # If this is a click on a folder or something, we want to preserve the "here"
                   1529:         # from the querystring, and get the new "jump" marker
1.320     albertel 1530:         $here = $env{'form.here'};
                   1531:         $jump = $env{'form.jump'};
1.140     bowersj2 1532:     } 
                   1533:     
1.132     bowersj2 1534:     my $it = $args->{'iterator'};
                   1535:     if (!defined($it)) {
1.320     albertel 1536:         # Construct a default iterator based on $env{'form.'} information
1.140     bowersj2 1537:         
                   1538:         # Step 1: Check to see if we have a navmap
                   1539:         if (!defined($navmap)) {
1.221     bowersj2 1540:             $navmap = Apache::lonnavmaps::navmap->new();
1.423     raeburn  1541:             if (!defined($navmap)) {
                   1542:                 # no longer in course
                   1543:                 return '<span class="LC_error">'.&mt('No course selected').'</span><br />
                   1544:                         <a href="/adm/roles">'.&mt('Select a course').'</a><br />';
                   1545:             }
1.140     bowersj2 1546:         }
                   1547: 
1.144     bowersj2 1548:         # See if we're being passed a specific map
                   1549:         if ($args->{'iterator_map'}) {
                   1550:             my $map = $args->{'iterator_map'};
1.145     bowersj2 1551:             $map = $navmap->getResourceByUrl($map);
1.546     raeburn  1552:             if (ref($map)) {
                   1553:                 my $firstResource = $map->map_start();
                   1554:                 my $finishResource = $map->map_finish();
                   1555:                 $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition);
                   1556:             } else {
                   1557:                 return;
                   1558:             }
1.144     bowersj2 1559:         } else {
1.401     albertel 1560:             $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition,undef,$args->{'include_top_level_map'});
1.144     bowersj2 1561:         }
1.132     bowersj2 1562:     }
1.256     albertel 1563: 
1.159     bowersj2 1564:     # (re-)Locate the jump point, if any
1.191     bowersj2 1565:     # Note this does not take filtering or hidden into account... need
                   1566:     # to be fixed?
1.159     bowersj2 1567:     my $mapIterator = $navmap->getIterator(undef, undef, $filterHash, 0);
1.222     bowersj2 1568:     my $curRes;
1.159     bowersj2 1569:     my $foundJump = 0;
                   1570:     my $counter = 0;
                   1571:     
1.222     bowersj2 1572:     while (($curRes = $mapIterator->next()) && !$foundJump) {
1.159     bowersj2 1573:         if (ref($curRes)) { $counter++; }
                   1574:         
                   1575:         if (ref($curRes) && $jump eq $curRes->symb()) {
                   1576:             
                   1577:             # This is why we have to use the main iterator instead of the
                   1578:             # potentially faster DFS: The count has to be the same, so
                   1579:             # the order has to be the same, which DFS won't give us.
                   1580:             $args->{'currentJumpIndex'} = $counter;
                   1581:             $foundJump = 1;
                   1582:         }
                   1583:     }
                   1584: 
1.132     bowersj2 1585:     my $showParts = setDefault($args->{'showParts'}, 1);
                   1586:     my $condenseParts = setDefault($args->{'condenseParts'}, 1);
1.139     bowersj2 1587:     # keeps track of when the current resource is found,
                   1588:     # so we can back up a few and put the anchor above the
                   1589:     # current resource
1.140     bowersj2 1590:     my $printKey = $args->{'printKey'};
                   1591:     my $printCloseAll = $args->{'printCloseAll'};
                   1592:     if (!defined($printCloseAll)) { $printCloseAll = 1; }
1.425     schulted 1593:    
1.140     bowersj2 1594:     # Print key?
                   1595:     if ($printKey) {
                   1596:         $result .= '<table border="0" cellpadding="2" cellspacing="0">';
                   1597:         $result.='<tr><td align="right" valign="bottom">Key:&nbsp;&nbsp;</td>';
1.298     albertel 1598: 	my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
1.140     bowersj2 1599:         if ($navmap->{LAST_CHECK}) {
                   1600:             $result .= 
1.434     bisitz   1601:                 '<img src="'.$location.'/chat.gif" alt="" /> '.&mt('New discussion since').' '.
1.140     bowersj2 1602:                 strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).
                   1603:                 '</td><td align="center" valign="bottom">&nbsp;&nbsp;'.
1.434     bisitz   1604:                 '<img src="'.$location.'/feedback.gif" alt="" /> '.&mt('New message (click to open)').'<p>'.
1.140     bowersj2 1605:                 '</td>'; 
                   1606:         } else {
                   1607:             $result .= '<td align="center" valign="bottom">&nbsp;&nbsp;'.
1.434     bisitz   1608:                 '<img src="'.$location.'/chat.gif" alt="" /> '.&mt('Discussions').'</td><td align="center" valign="bottom">'.
                   1609:                 '&nbsp;&nbsp;<img src="'.$location.'/feedback.gif" alt="" /> '.&mt('New message (click to open)').
1.140     bowersj2 1610:                 '</td>'; 
                   1611:         }
                   1612: 
                   1613:         $result .= '</tr></table>';
                   1614:     }
                   1615: 
1.172     bowersj2 1616:     if ($printCloseAll && !$args->{'resource_no_folder_link'}) {
1.281     albertel 1617: 	my ($link,$text);
1.140     bowersj2 1618:         if ($condition) {
1.446     raeburn  1619: 	    $link='navmaps?condition=0&amp;filter=&amp;'.$queryString.
1.449     bisitz   1620: 		'&amp;here='.&escape($here);
1.377     www      1621: 	    $text='Close all folders';
1.140     bowersj2 1622:         } else {
1.446     raeburn  1623: 	    $link='navmaps?condition=1&amp;filter=&amp;'.$queryString.
1.449     bisitz   1624: 		'&amp;here='.&escape($here);
1.377     www      1625: 	    $text='Open all folders';
1.281     albertel 1626:         }
                   1627: 	if ($args->{'caller'} eq 'navmapsdisplay') {
1.478     raeburn  1628:             unless ($args->{'notools'}) {
                   1629:                 &add_linkitem($args->{'linkitems'},'changefolder',
                   1630:                               "location.href='$link'",$text);
                   1631:             }
1.281     albertel 1632: 	} else {
1.446     raeburn  1633: 	    $result.= '<a href="'.$link.'">'.&mt($text).'</a>';
1.281     albertel 1634: 	}
1.266     raeburn  1635:         $result .= "\n";
                   1636:     }
1.140     bowersj2 1637: 
1.266     raeburn  1638:     # Check for any unread discussions in all resources.
1.478     raeburn  1639:     if (($args->{'caller'} eq 'navmapsdisplay') && (!$args->{'notools'})) {
1.296     albertel 1640: 	&add_linkitem($args->{'linkitems'},'clearbubbles',
                   1641: 		      'document.clearbubbles.submit()',
                   1642: 		      'Mark all posts read');
1.297     albertel 1643: 	my $time=time;
1.486     raeburn  1644:         my $querystr = &HTML::Entities::encode($ENV{'QUERY_STRING'},'<>&"');
1.296     albertel 1645: 	$result .= (<<END);
                   1646:     <form name="clearbubbles" method="post" action="/adm/feedback">
1.486     raeburn  1647: 	<input type="hidden" name="navurl" value="$querystr" />
1.297     albertel 1648: 	<input type="hidden" name="navtime" value="$time" />
1.296     albertel 1649: END
                   1650:         if ($args->{'sort'} eq 'discussion') { 
                   1651: 	    my $totdisc = 0;
                   1652: 	    my $haveDisc = '';
                   1653: 	    my @allres=$navmap->retrieveResources();
                   1654: 	    foreach my $resource (@allres) {
                   1655: 		if ($resource->hasDiscussion()) {
1.323     albertel 1656: 		    $haveDisc .= $resource->wrap_symb().':';
1.296     albertel 1657: 		    $totdisc ++;
1.267     albertel 1658: 		}
                   1659: 	    }
1.296     albertel 1660: 	    if ($totdisc > 0) {
                   1661: 		$haveDisc =~ s/:$//;
                   1662: 		$result .= (<<END);
                   1663: 	<input type="hidden" name="navmaps" value="$haveDisc" />
                   1664:     </form>
                   1665: END
                   1666:             }
1.297     albertel 1667: 	}
                   1668: 	$result.='</form>';
1.478     raeburn  1669:     }
1.558     raeburn  1670:     if (($args->{'caller'} eq 'navmapsdisplay') && ($env{'request.course.id'})) {
1.478     raeburn  1671:         my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   1672:         my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
1.558     raeburn  1673:         if ($env{'course.'.$env{'request.course.id'}.'.url'} eq
1.478     raeburn  1674:             "uploaded/$cdom/$cnum/default.sequence") {
1.558     raeburn  1675:             if ((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) ||
                   1676:                 (&Apache::lonnet::allowed('cev',$env{'request.course.id'}))) {
                   1677:                 &add_linkitem($args->{'linkitems'},'edittoplevel',
                   1678:                               "javascript:gocmd('/adm/coursedocs','editdocs');",
                   1679:                               'Content Editor');
                   1680:             }
                   1681:             if ($counter) {
                   1682:                 &add_linkitem($args->{'linkitems'},'printout',
                   1683:                               "javascript:gopost('/adm/printout','/adm/navmaps');",
                   1684:                               'Prepare a printable document');
                   1685:             }
1.475     raeburn  1686:         }
1.266     raeburn  1687:     }
1.276     albertel 1688: 
1.281     albertel 1689:     if ($args->{'caller'} eq 'navmapsdisplay') {
1.476     raeburn  1690:         $result .= &show_linkitems_toolbar($args,$condition);
1.281     albertel 1691:     } elsif ($args->{'sort_html'}) { 
                   1692:         $result.=$args->{'sort_html'}; 
                   1693:     }
1.276     albertel 1694: 
1.425     schulted 1695:     #$result .= "<br />\n";
1.140     bowersj2 1696:     if ($r) {
                   1697:         $r->print($result);
                   1698:         $r->rflush();
                   1699:         $result = "";
                   1700:     }
1.132     bowersj2 1701:     # End parameter setting
1.425     schulted 1702:     
                   1703:     $result .= "<br />\n";
                   1704: 
1.132     bowersj2 1705:     # Data
1.431     schulted 1706:     $result.=&Apache::loncommon::start_data_table("LC_tableOfContent");    
1.426     droeschl 1707: 
1.132     bowersj2 1708:     my $res = "Apache::lonnavmaps::resource";
                   1709:     my %condenseStatuses =
                   1710:         ( $res->NETWORK_FAILURE    => 1,
                   1711:           $res->NOTHING_SET        => 1,
                   1712:           $res->CORRECT            => 1 );
1.133     bowersj2 1713: 
                   1714:     # Shared variables
                   1715:     $args->{'counter'} = 0; # counts the rows
                   1716:     $args->{'indentLevel'} = 0;
                   1717:     $args->{'isNewBranch'} = 0;
1.426     droeschl 1718:     $args->{'condensed'} = 0;   
                   1719: 
1.428     droeschl 1720:     my $location = &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace_21.gif");
1.426     droeschl 1721:     $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' alt='' />");
1.133     bowersj2 1722:     $args->{'displayedHereMarker'} = 0;
1.132     bowersj2 1723: 
1.518     raeburn  1724:     # If we're suppressing empty sequences, look for them here.
                   1725:     # We also do this even if $args->{'suppressEmptySequences'}
                   1726:     # is not true, so we can hide empty sequences for which the
                   1727:     # hiddenresource parameter is set to yes (at map level), or
                   1728:     # mark as hidden for users who have $userCanSeeHidden.  
                   1729:     # Use DFS for speed, since structure actually doesn't matter,
                   1730:     # except what map has what resources.
1.560     raeburn  1731:     #
                   1732:     # To ensure the "Selected Resources from selected folder in course"
                   1733:     # printout generation option will work in sessions launched via a
                   1734:     # deep link, the value of $args->{'filterFunc'} included in the 
                   1735:     # call to lonnavmaps::render() is omitted from the filter function
                   1736:     # used with the DFS Iterator when $args->{'caller'} is 'printout'.
                   1737:     #
                   1738:     # As a result $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} can be
                   1739:     # set to 1 for folder(s) which include resources only accessible
                   1740:     # for sessions launched via a deep link, when the current session 
                   1741:     # is of that type.
1.518     raeburn  1742: 
                   1743:     my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,
                   1744:                                                      $it->{FIRST_RESOURCE},
                   1745:                                                      $it->{FINISH_RESOURCE},
                   1746:                                                      {}, undef, 1);
1.560     raeburn  1747:     my $dfsFilterFunc;
                   1748:     if ($args->{'caller'} eq 'printout') {
                   1749:         $dfsFilterFunc = sub { my $res = shift; return !$res->randomout() &&
                   1750:                               ($res->deeplink($args->{'caller'}) ne 'absent') &&
                   1751:                               ($res->deeplink($args->{'caller'}) ne 'grades') &&
                   1752:                               !$res->deeplinkout();};
                   1753:     } else {
                   1754:         $dfsFilterFunc = $filterFunc;
                   1755:     }
1.518     raeburn  1756:     my $depth = 0;
                   1757:     $dfsit->next();
                   1758:     my $curRes = $dfsit->next();
                   1759:     while ($depth > -1) {
                   1760:         if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; }
                   1761:         if ($curRes == $dfsit->END_MAP()) { $depth--; }
                   1762: 
                   1763:         if (ref($curRes)) { 
                   1764:             # Parallel pre-processing: Do sequences have non-filtered-out children?
                   1765:             if ($curRes->is_map()) {
                   1766:                 $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0;
                   1767:                 # Sequences themselves do not count as visible children,
                   1768:                 # unless those sequences also have visible children.
                   1769:                 # This means if a sequence appears, there's a "promise"
                   1770:                 # that there's something under it if you open it, somewhere.
                   1771:             } elsif ($curRes->src()) {
                   1772:                 # Not a sequence: if it's filtered, ignore it, otherwise
                   1773:                 # rise up the stack and mark the sequences as having children
1.560     raeburn  1774:                 if (&$dfsFilterFunc($curRes)) {
1.518     raeburn  1775:                     for my $sequence (@{$dfsit->getStack()}) {
1.560     raeburn  1776:                         next unless ($sequence->is_map());
1.518     raeburn  1777:                         $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1;
1.190     bowersj2 1778:                     }
                   1779:                 }
                   1780:             }
                   1781:         }
1.518     raeburn  1782:     } continue {
                   1783:         $curRes = $dfsit->next();
1.190     bowersj2 1784:     }
                   1785: 
1.133     bowersj2 1786:     my $displayedJumpMarker = 0;
1.132     bowersj2 1787:     # Set up iteration.
                   1788:     my $now = time();
                   1789:     my $in24Hours = $now + 24 * 60 * 60;
                   1790:     my $rownum = 0;
                   1791: 
1.141     bowersj2 1792:     # export "here" marker information
                   1793:     $args->{'here'} = $here;
                   1794: 
1.222     bowersj2 1795:     $args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0
1.270     albertel 1796:     my @resources;
                   1797:     my $code='';# sub { !(shift->is_map();) };
                   1798:     if ($args->{'sort'} eq 'title') {
1.276     albertel 1799:         my $oldFilterFunc = $filterFunc;
                   1800: 	my $filterFunc= 
                   1801: 	    sub {
                   1802: 		my ($res)=@_;
                   1803: 		if ($res->is_map()) { return 0;}
                   1804: 		return &$oldFilterFunc($res);
                   1805: 	    };
                   1806: 	@resources=$navmap->retrieveResources(undef,$filterFunc);
1.296     albertel 1807: 	@resources= sort { &cmp_title($a,$b) } @resources;
                   1808:     } elsif ($args->{'sort'} eq 'duedate') {
                   1809: 	my $oldFilterFunc = $filterFunc;
                   1810: 	my $filterFunc= 
                   1811: 	    sub {
                   1812: 		my ($res)=@_;
                   1813: 		if (!$res->is_problem()) { return 0;}
                   1814: 		return &$oldFilterFunc($res);
                   1815: 	    };
                   1816: 	@resources=$navmap->retrieveResources(undef,$filterFunc);
1.289     raeburn  1817: 	@resources= sort {
1.270     albertel 1818: 	    if ($a->duedate ne $b->duedate) {
                   1819: 	        return $a->duedate cmp $b->duedate;
                   1820: 	    }
1.296     albertel 1821: 	    my $value=&cmp_title($a,$b);
                   1822: 	    return $value;
1.270     albertel 1823: 	} @resources;
1.296     albertel 1824:     } elsif ($args->{'sort'} eq 'discussion') {
                   1825: 	my $oldFilterFunc = $filterFunc;
                   1826: 	my $filterFunc= 
                   1827: 	    sub {
                   1828: 		my ($res)=@_;
                   1829: 		if (!$res->hasDiscussion() &&
                   1830: 		    !$res->getFeedback() &&
                   1831: 		    !$res->getErrors()) { return 0;}
                   1832: 		return &$oldFilterFunc($res);
                   1833: 	    };
                   1834: 	@resources=$navmap->retrieveResources(undef,$filterFunc);
                   1835: 	@resources= sort { &cmp_title($a,$b) } @resources;
1.276     albertel 1836:     } else {
                   1837: 	#unknow sort mechanism or default
                   1838: 	undef($args->{'sort'});
1.270     albertel 1839:     }
                   1840: 
1.527     raeburn  1841:     # Determine if page will be served with https in case
                   1842:     # it contains a syllabus which uses an external URL
                   1843:     # which points at an http site.
                   1844: 
1.529     raeburn  1845:     my ($is_ssl,$cdom,$cnum,$hostname);
1.527     raeburn  1846:     if ($ENV{'SERVER_PORT'} == 443) {
                   1847:         $is_ssl = 1;
1.530     raeburn  1848:         if ($r) {
                   1849:             $hostname = $r->hostname();
                   1850:         } else {
                   1851:             $hostname = $ENV{'SERVER_NAME'};
                   1852:         }
1.527     raeburn  1853:     }
                   1854:     if ($env{'request.course.id'}) {
                   1855:         $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   1856:         $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
                   1857:     }
1.276     albertel 1858: 
1.540     raeburn  1859:     my $inhibitmenu;
                   1860:     if ($args->{'modalLink'}) {
                   1861:         $inhibitmenu = '&amp;inhibitmenu=yes';
                   1862:     }
                   1863: 
1.270     albertel 1864:     while (1) {
                   1865: 	if ($args->{'sort'}) {
                   1866: 	    $curRes = shift(@resources);
                   1867: 	} else {
                   1868: 	    $curRes = $it->next($closeAllPages);
                   1869: 	}
                   1870: 	if (!$curRes) { last; }
                   1871: 
1.132     bowersj2 1872:         # Maintain indentation level.
                   1873:         if ($curRes == $it->BEGIN_MAP() ||
                   1874:             $curRes == $it->BEGIN_BRANCH() ) {
1.133     bowersj2 1875:             $args->{'indentLevel'}++;
1.132     bowersj2 1876:         }
                   1877:         if ($curRes == $it->END_MAP() ||
                   1878:             $curRes == $it->END_BRANCH() ) {
1.133     bowersj2 1879:             $args->{'indentLevel'}--;
1.132     bowersj2 1880:         }
                   1881:         # Notice new branches
                   1882:         if ($curRes == $it->BEGIN_BRANCH()) {
1.133     bowersj2 1883:             $args->{'isNewBranch'} = 1;
                   1884:         }
                   1885: 
                   1886:         # If this isn't an actual resource, continue on
                   1887:         if (!ref($curRes)) {
                   1888:             next;
1.132     bowersj2 1889:         }
1.133     bowersj2 1890: 
1.143     bowersj2 1891:         # If this has been filtered out, continue on
                   1892:         if (!(&$filterFunc($curRes))) {
                   1893:             $args->{'isNewBranch'} = 0; # Don't falsely remember this
                   1894:             next;
                   1895:         } 
1.132     bowersj2 1896: 
1.190     bowersj2 1897:         # If this is an empty sequence and we're filtering them, continue on
1.518     raeburn  1898:         $args->{'mapHidden'} = 0;
1.543     raeburn  1899:         $args->{'mapUnlisted'} = 0;
1.553     raeburn  1900:         $args->{'mapHiddenDeepLink'} = 0;
1.518     raeburn  1901:         if (($curRes->is_map()) && (!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN})) {
                   1902:             if ($args->{'suppressEmptySequences'}) {
                   1903:                 next;
                   1904:             } else {
                   1905:                 my $mapname = &Apache::lonnet::declutter($curRes->src());
                   1906:                 $mapname = &Apache::lonnet::deversion($mapname); 
                   1907:                 if (lc($navmap->get_mapparam(undef,$mapname,"0.hiddenresource")) eq 'yes') {
                   1908:                     if ($userCanSeeHidden) {
                   1909:                         $args->{'mapHidden'} = 1;
                   1910:                     } else {
                   1911:                         next;
                   1912:                     }
1.553     raeburn  1913:                 } elsif ($curRes->deeplinkout) {
                   1914:                     if ($userCanSeeHidden) {
                   1915:                         $args->{'mapHiddenDeepLink'} = 1;
                   1916:                     } else {
                   1917:                         next;
                   1918:                     }
1.543     raeburn  1919:                 } else {
                   1920:                     my $deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink");
1.554     raeburn  1921:                     my ($state,$others,$listed) = split(/,/,$deeplink);
                   1922:                     if (($listed eq 'absent') || ($listed eq 'grades')) {
1.543     raeburn  1923:                         if ($userCanSeeHidden) {
                   1924:                             $args->{'mapUnlisted'} = 1;
                   1925:                         } else {
                   1926:                             next;
                   1927:                         }
                   1928:                     }
1.518     raeburn  1929:                 }
                   1930:             }
1.190     bowersj2 1931:         }
                   1932: 
1.153     bowersj2 1933:         # If we're suppressing navmaps and this is a navmap, continue on
                   1934:         if ($suppressNavmap && $curRes->src() =~ /^\/adm\/navmaps/) {
                   1935:             next;
                   1936:         }
                   1937: 
1.191     bowersj2 1938:         $args->{'counter'}++;
                   1939: 
1.132     bowersj2 1940:         # Does it have multiple parts?
1.133     bowersj2 1941:         $args->{'multipart'} = 0;
                   1942:         $args->{'condensed'} = 0;
1.132     bowersj2 1943:         my @parts;
                   1944:             
                   1945:         # Decide what parts to show.
1.133     bowersj2 1946:         if ($curRes->is_problem() && $showParts) {
1.132     bowersj2 1947:             @parts = @{$curRes->parts()};
1.192     bowersj2 1948:             $args->{'multipart'} = $curRes->multipart();
1.132     bowersj2 1949:             
                   1950:             if ($condenseParts) { # do the condensation
1.133     bowersj2 1951:                 if (!$args->{'condensed'}) {
1.132     bowersj2 1952:                     # Decide whether to condense based on similarity
1.192     bowersj2 1953:                     my $status = $curRes->status($parts[0]);
                   1954:                     my $due = $curRes->duedate($parts[0]);
                   1955:                     my $open = $curRes->opendate($parts[0]);
1.132     bowersj2 1956:                     my $statusAllSame = 1;
                   1957:                     my $dueAllSame = 1;
                   1958:                     my $openAllSame = 1;
1.192     bowersj2 1959:                     for (my $i = 1; $i < scalar(@parts); $i++) {
1.132     bowersj2 1960:                         if ($curRes->status($parts[$i]) != $status){
                   1961:                             $statusAllSame = 0;
                   1962:                         }
                   1963:                         if ($curRes->duedate($parts[$i]) != $due ) {
                   1964:                             $dueAllSame = 0;
                   1965:                         }
                   1966:                         if ($curRes->opendate($parts[$i]) != $open) {
                   1967:                             $openAllSame = 0;
                   1968:                         }
                   1969:                     }
                   1970:                     # $*allSame is true if all the statuses were
                   1971:                     # the same. Now, if they are all the same and
                   1972:                     # match one of the statuses to condense, or they
                   1973:                     # are all open with the same due date, or they are
                   1974:                     # all OPEN_LATER with the same open date, display the
                   1975:                     # status of the first non-zero part (to get the 'correct'
                   1976:                     # status right, since 0 is never 'correct' or 'open').
                   1977:                     if (($statusAllSame && defined($condenseStatuses{$status})) ||
                   1978:                         ($dueAllSame && $status == $curRes->OPEN && $statusAllSame)||
                   1979:                         ($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){
1.192     bowersj2 1980:                         @parts = ($parts[0]);
1.133     bowersj2 1981:                         $args->{'condensed'} = 1;
1.132     bowersj2 1982:                     }
                   1983:                 }
1.198     bowersj2 1984: 		# Multipart problem with one part: always "condense" (happens
                   1985: 		#  to match the desirable behavior)
                   1986: 		if ($curRes->countParts() == 1) {
                   1987: 		    @parts = ($parts[0]);
                   1988: 		    $args->{'condensed'} = 1;
                   1989: 		}
1.132     bowersj2 1990:             }
1.543     raeburn  1991:         }
                   1992:         # If deep-link parameter is set (and is not set to full) suppress link
1.553     raeburn  1993:         # unless privileged user, tinyurl used for login resolved to a map, and
                   1994:         # the resource is within the map.
1.543     raeburn  1995:         if ((!$curRes->deeplink($args->{'caller'})) ||
1.553     raeburn  1996:             ($curRes->deeplink($args->{'caller'}) eq 'full') || &advancedUser()) {
1.543     raeburn  1997:             $args->{'resource_nolink'} = 0;
                   1998:         } else {
                   1999:             $args->{'resource_nolink'} = 1;
                   2000:         }
                   2001: 
1.132     bowersj2 2002:         # If the multipart problem was condensed, "forget" it was multipart
                   2003:         if (scalar(@parts) == 1) {
1.133     bowersj2 2004:             $args->{'multipart'} = 0;
1.192     bowersj2 2005:         } else {
                   2006:             # Add part 0 so we display it correctly.
                   2007:             unshift @parts, '0';
1.132     bowersj2 2008:         }
1.310     albertel 2009: 	
                   2010: 	{
                   2011: 	    my ($src,$symb,$anchor,$stack);
                   2012: 	    if ($args->{'sort'}) {
                   2013: 		my $it = $navmap->getIterator(undef, undef, undef, 1);
                   2014: 		while ( my $res=$it->next()) {
                   2015: 		    if (ref($res) &&
                   2016: 			$res->symb() eq  $curRes->symb()) { last; }
                   2017: 		}
                   2018: 		$stack=$it->getStack();
                   2019: 	    } else {
                   2020: 		$stack=$it->getStack();
                   2021: 	    }
                   2022: 	    ($src,$symb,$anchor)=getLinkForResource($stack);
1.527     raeburn  2023:             my $srcHasQuestion = $src =~ /\?/;
                   2024:             if ($env{'request.course.id'}) {
1.529     raeburn  2025:                 if (($is_ssl) && ($src =~ m{^\Q/public/$cdom/$cnum/syllabus\E($|\?)}) &&
1.527     raeburn  2026:                     ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) {
1.551     raeburn  2027:                     unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
1.544     raeburn  2028:                         if ($hostname ne '') {
                   2029:                             $src = 'http://'.$hostname.$src;
                   2030:                         }
                   2031:                         $src .= ($srcHasQuestion? '&amp;' : '?') . 'usehttp=1';
1.545     raeburn  2032:                         $srcHasQuestion = 1;
1.528     raeburn  2033:                     }
1.529     raeburn  2034:                 } elsif (($is_ssl) && ($src =~ m{^\Q/adm/wrapper/ext/\E(?!https:)})) {
1.551     raeburn  2035:                     unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
1.544     raeburn  2036:                         if ($hostname ne '') {
                   2037:                             $src = 'http://'.$hostname.$src;
                   2038:                         }
                   2039:                         $src .= ($srcHasQuestion? '&amp;' : '?') . 'usehttp=1';
                   2040:                         $srcHasQuestion = 1;
1.529     raeburn  2041:                     }
1.527     raeburn  2042:                 }
                   2043:             }
1.310     albertel 2044: 	    if (defined($anchor)) { $anchor='#'.$anchor; }
1.533     raeburn  2045: 	    if (($args->{'caller'} eq 'sequence') && ($curRes->is_map())) {
                   2046: 	        $args->{"resourceLink"} = $src.($srcHasQuestion?'&amp;':'?') .'navmap=1';
                   2047: 	    } else {
                   2048: 	        $args->{"resourceLink"} = $src.
                   2049: 		    ($srcHasQuestion?'&amp;':'?') .
1.540     raeburn  2050: 		    'symb=' . &escape($symb).$inhibitmenu.$anchor;
1.533     raeburn  2051: 	    }
1.310     albertel 2052: 	}
1.132     bowersj2 2053:         # Now, we've decided what parts to show. Loop through them and
                   2054:         # show them.
1.192     bowersj2 2055:         foreach my $part (@parts) {
1.132     bowersj2 2056:             $rownum ++;
                   2057:             
1.431     schulted 2058:             $result .= &Apache::loncommon::start_data_table_row();
1.134     bowersj2 2059: 
                   2060:             # Set up some data about the parts that the cols might want
                   2061:             my $filter = $it->{FILTER};
1.270     albertel 2062: 
1.132     bowersj2 2063:             # Now, display each column.
                   2064:             foreach my $col (@$cols) {
1.139     bowersj2 2065:                 my $colHTML = '';
                   2066:                 if (ref($col)) {
                   2067:                     $colHTML .= &$col($curRes, $part, $args);
                   2068:                 } else {
                   2069:                     $colHTML .= &{$preparedColumns[$col]}($curRes, $part, $args);
                   2070:                 }
1.132     bowersj2 2071: 
1.133     bowersj2 2072:                 # If this is the first column and it's time to print
                   2073:                 # the anchor, do so
                   2074:                 if ($col == $cols->[0] && 
                   2075:                     $args->{'counter'} == $args->{'currentJumpIndex'} - 
1.139     bowersj2 2076:                     $currentJumpDelta) {
                   2077:                     # Jam the anchor after the <td> tag;
                   2078:                     # necessary for valid HTML (which Mozilla requires)
1.548     raeburn  2079:                     $colHTML =~ s/\>/\>\<a name="curloc" \>\<\/a\>/;
1.133     bowersj2 2080:                     $displayedJumpMarker = 1;
                   2081:                 }
1.139     bowersj2 2082:                 $result .= $colHTML . "\n";
1.132     bowersj2 2083:             }
1.431     schulted 2084:             $result .= &Apache::loncommon::end_data_table_row();
1.140     bowersj2 2085:             $args->{'isNewBranch'} = 0;
1.139     bowersj2 2086:         }
1.153     bowersj2 2087: 
1.139     bowersj2 2088:         if ($r && $rownum % 20 == 0) {
                   2089:             $r->print($result);
                   2090:             $result = "";
                   2091:             $r->rflush();
1.132     bowersj2 2092:         }
1.156     bowersj2 2093:     } continue {
1.208     bowersj2 2094: 	if ($r) {
                   2095: 	    # If we have the connection, make sure the user is still connected
                   2096: 	    my $c = $r->connection;
                   2097: 	    if ($c->aborted()) {
                   2098: 		# Who cares what we do, nobody will see it anyhow.
                   2099: 		return '';
                   2100: 	    }
                   2101: 	}
1.132     bowersj2 2102:     }
1.486     raeburn  2103: 
                   2104:     $result.=&Apache::loncommon::end_data_table();
1.132     bowersj2 2105:     
1.134     bowersj2 2106:     # Print out the part that jumps to #curloc if it exists
1.159     bowersj2 2107:     # delay needed because the browser is processing the jump before
                   2108:     # it finishes rendering, so it goes to the wrong place!
                   2109:     # onload might be better, but this routine has no access to that.
                   2110:     # On mozilla, the 0-millisecond timeout seems to prevent this;
                   2111:     # it's quite likely this might fix other browsers, too, and 
                   2112:     # certainly won't hurt anything.
1.139     bowersj2 2113:     if ($displayedJumpMarker) {
1.449     bisitz   2114:         $result .= &Apache::lonhtmlcommon::scripttag("
1.247     albertel 2115: if (location.href.indexOf('#curloc')==-1) {
                   2116:     setTimeout(\"location += '#curloc';\", 0)
                   2117: }
1.449     bisitz   2118: ");
1.134     bowersj2 2119:     }
                   2120: 
1.140     bowersj2 2121:     if ($r) {
                   2122:         $r->print($result);
                   2123:         $result = "";
                   2124:         $r->rflush();
                   2125:     }
                   2126:         
1.132     bowersj2 2127:     return $result;
                   2128: }
                   2129: 
1.281     albertel 2130: sub add_linkitem {
                   2131:     my ($linkitems,$name,$cmd,$text)=@_;
                   2132:     $$linkitems{$name}{'cmd'}=$cmd;
                   2133:     $$linkitems{$name}{'text'}=&mt($text);
                   2134: }
                   2135: 
1.430     schulted 2136: sub show_linkitems_toolbar {
1.476     raeburn  2137:     my ($args,$condition) = @_;
1.478     raeburn  2138:     my $result;
1.476     raeburn  2139:     if (ref($args) eq 'HASH') {
                   2140:         if (ref($args->{'linkitems'}) eq 'HASH') {
1.478     raeburn  2141:             my $numlinks = scalar(keys(%{$args->{'linkitems'}}));
                   2142:             if ($numlinks > 1) {
                   2143:                 $result = '<td>'.
                   2144:                           &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',
                   2145:                                                              undef,'RAT').
                   2146:                           '</td>'.
                   2147:                           '<td>&nbsp;</td>'.
                   2148:                           '<td class="LC_middle">'.&mt('Tools:').'</td>';
                   2149:             }
                   2150:             $result .= '<td align="left">'."\n".
                   2151:                        '<ul id="LC_toolbar">';
1.476     raeburn  2152:             my @linkorder = ('firsthomework','everything','uncompleted',
1.558     raeburn  2153:                              'changefolder','clearbubbles','printout','edittoplevel');
1.476     raeburn  2154:             foreach my $link (@linkorder) {
                   2155:                 if (ref($args->{'linkitems'}{$link}) eq 'HASH') {
                   2156:                     if ($args->{'linkitems'}{$link}{'text'} ne '') {
                   2157:                         $args->{'linkitems'}{$link}{'cmd'}=~s/"/'/g;
                   2158:                         if ($args->{'linkitems'}{$link}{'cmd'}) {
1.478     raeburn  2159:                             my $link_id = 'LC_content_toolbar_'.$link;
1.476     raeburn  2160:                             if ($link eq 'changefolder') {
                   2161:                                 if ($condition) {
                   2162:                                     $link_id='LC_content_toolbar_changefolder_toggled';
                   2163:                                 } else {
                   2164:                                     $link_id='LC_content_toolbar_changefolder';
                   2165:                                 }
                   2166:                             }
                   2167:                             $result .= '<li><a href="#" '.
                   2168:                                        'onclick="'.$args->{'linkitems'}{$link}{'cmd'}.'" '.
                   2169:                                        'id="'.$link_id.'" '.
                   2170:                                        'class="LC_toolbarItem" '.
                   2171:                                        'title="'.$args->{'linkitems'}{$link}{'text'}.'">'.
                   2172:                                        '</a></li>'."\n";
1.446     raeburn  2173:                         }
                   2174:                     }
                   2175:                 }
                   2176:             }
1.476     raeburn  2177:             $result .= '</ul>'.
1.486     raeburn  2178:                        '</td>';
1.478     raeburn  2179:             if (($numlinks==1) && (exists($args->{'linkitems'}{'edittoplevel'}))) {
                   2180:                 $result .= '<td><a href="'.$args->{'linkitems'}{'edittoplevel'}{'cmd'}.'">'.
                   2181:                            &mt('Content Editor').'</a></td>';
                   2182:             }
1.476     raeburn  2183:         }
                   2184:         if ($args->{'sort_html'}) {
                   2185:             $result .= '<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>'.
                   2186:                        '<td align="right">'.$args->{'sort_html'}.'</td>';
1.446     raeburn  2187:         }
1.430     schulted 2188:     }
1.478     raeburn  2189:     if ($result) {
                   2190:         $result = "<table><tr>$result</tr></table>";
                   2191:     }
1.430     schulted 2192:     return $result;
                   2193: }
                   2194: 
1.133     bowersj2 2195: 1;
                   2196: 
1.418     jms      2197: 
                   2198: 
                   2199: 
                   2200: 
                   2201: 
                   2202: 
                   2203: 
                   2204: 
1.133     bowersj2 2205: package Apache::lonnavmaps::navmap;
                   2206: 
                   2207: =pod
                   2208: 
1.216     bowersj2 2209: =head1 Object: Apache::lonnavmaps::navmap
1.133     bowersj2 2210: 
1.217     bowersj2 2211: =head2 Overview
1.133     bowersj2 2212: 
1.217     bowersj2 2213: The navmap object's job is to provide access to the resources
                   2214: in the course as Apache::lonnavmaps::resource objects, and to
                   2215: query and manage the relationship between those resource objects.
                   2216: 
                   2217: Generally, you'll use the navmap object in one of three basic ways.
                   2218: In order of increasing complexity and power:
                   2219: 
                   2220: =over 4
                   2221: 
1.358     albertel 2222: =item * C<$navmap-E<gt>getByX>, where X is B<Id>, B<Symb> or B<MapPc> and getResourceByUrl. This provides
1.217     bowersj2 2223:     various ways to obtain resource objects, based on various identifiers.
                   2224:     Use this when you want to request information about one object or 
                   2225:     a handful of resources you already know the identities of, from some
                   2226:     other source. For more about Ids, Symbs, and MapPcs, see the
                   2227:     Resource documentation. Note that Url should be a B<last resort>,
1.358     albertel 2228:     not your first choice; it only really works when there is only one
1.217     bowersj2 2229:     instance of the resource in the course, which only applies to
1.358     albertel 2230:     maps, and even that may change in the future (see the B<getResourceByUrl>
                   2231:     documentation for more details.)
1.217     bowersj2 2232: 
                   2233: =item * C<my @resources = $navmap-E<gt>retrieveResources(args)>. This
                   2234:     retrieves resources matching some criterion and returns them
                   2235:     in a flat array, with no structure information. Use this when
                   2236:     you are manipulating a series of resources, based on what map
                   2237:     the are in, but do not care about branching, or exactly how
                   2238:     the maps and resources are related. This is the most common case.
                   2239: 
                   2240: =item * C<$it = $navmap-E<gt>getIterator(args)>. This allows you traverse
                   2241:     the course's navmap in various ways without writing the traversal
                   2242:     code yourself. See iterator documentation below. Use this when
                   2243:     you need to know absolutely everything about the course, including
                   2244:     branches and the precise relationship between maps and resources.
                   2245: 
                   2246: =back
                   2247: 
                   2248: =head2 Creation And Destruction
                   2249: 
                   2250: To create a navmap object, use the following function:
1.133     bowersj2 2251: 
                   2252: =over 4
                   2253: 
1.221     bowersj2 2254: =item * B<Apache::lonnavmaps::navmap-E<gt>new>():
1.133     bowersj2 2255: 
1.221     bowersj2 2256: Creates a new navmap object. Returns the navmap object if this is
                   2257: successful, or B<undef> if not.
1.174     albertel 2258: 
1.216     bowersj2 2259: =back
                   2260: 
                   2261: =head2 Methods
                   2262: 
                   2263: =over 4
                   2264: 
1.174     albertel 2265: =item * B<getIterator>(first, finish, filter, condition):
                   2266: 
                   2267: See iterator documentation below.
1.133     bowersj2 2268: 
                   2269: =cut
                   2270: 
                   2271: use strict;
                   2272: use GDBM_File;
1.320     albertel 2273: use Apache::lonnet;
1.397     albertel 2274: use LONCAPA;
1.133     bowersj2 2275: 
                   2276: sub new {
                   2277:     # magic invocation to create a class instance
                   2278:     my $proto = shift;
                   2279:     my $class = ref($proto) || $proto;
                   2280:     my $self = {};
1.501     raeburn  2281:     bless($self);		# So we can call change_user if necessary
1.133     bowersj2 2282: 
1.453     www      2283:     $self->{USERNAME} = shift || $env{'user.name'};
                   2284:     $self->{DOMAIN}   = shift || $env{'user.domain'};
1.491     raeburn  2285:     $self->{CODE}     = shift;
1.496     raeburn  2286:     $self->{NOHIDE} = shift;
1.453     www      2287: 
1.465     foxr     2288: 
                   2289: 
1.133     bowersj2 2290:     # Resource cache stores navmap resources as we reference them. We generate
                   2291:     # them on-demand so we don't pay for creating resources unless we use them.
                   2292:     $self->{RESOURCE_CACHE} = {};
                   2293: 
                   2294:     # Network failure flag, if we accessed the course or user opt and
                   2295:     # failed
                   2296:     $self->{NETWORK_FAILURE} = 0;
                   2297: 
1.465     foxr     2298:     # We can only tie the nav hash as done below if the username/domain
                   2299:     # match the env one. Otherwise change_user does everything we need...since we can't
1.501     raeburn  2300:     # assume there are course hashes for the specific requested user:domain
                   2301:     # Note: change_user is also called if we need the nav hash when printing CODEd 
                   2302:     # assignments or printing an exam, in which the enclosing folder for the items in
                   2303:     # the exam has hidden set.
1.465     foxr     2304:     #
1.133     bowersj2 2305: 
1.501     raeburn  2306:     if (($self->{USERNAME} eq $env{'user.name'}) && ($self->{DOMAIN} eq $env{'user.domain'}) &&
                   2307:          !$self->{CODE} && !$self->{NOHIDE}) {
1.465     foxr     2308: 	
                   2309: 	# tie the nav hash
                   2310: 	
                   2311: 	my %navmaphash;
                   2312: 	my %parmhash;
                   2313: 	my $courseFn = $env{"request.course.fn"};
                   2314: 	if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",
                   2315: 		  &GDBM_READER(), 0640))) {
                   2316: 	    return undef;
                   2317: 	}
                   2318: 	
                   2319: 	if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db",
                   2320: 		  &GDBM_READER(), 0640)))
                   2321: 	{
                   2322: 	    untie %{$self->{PARM_HASH}};
                   2323: 	    return undef;
                   2324: 	}
                   2325: 	
                   2326: 	$self->{NAV_HASH} = \%navmaphash;
                   2327: 	$self->{PARM_HASH} = \%parmhash;
                   2328: 	$self->{PARM_CACHE} = {};
                   2329:     } else {
1.496     raeburn  2330: 	$self->change_user($self->{USERNAME}, $self->{DOMAIN},  $self->{CODE}, $self->{NOHIDE});
1.133     bowersj2 2331:     }
1.470     foxr     2332: 
1.465     foxr     2333:     return $self;
                   2334: }
                   2335: 
                   2336: #
                   2337: #  In some instances it is useful to be able to dynamically change the
                   2338: # username/domain associated with a navmap (e.g. to navigate for someone
                   2339: # else besides the current user...if sufficiently privileged.
                   2340: # Parameters:
                   2341: #    user  - New user.
                   2342: #    domain- Domain the user belongs to.
1.491     raeburn  2343: #    code  - Anonymous CODE in use.
1.465     foxr     2344: # Implicit inputs:
                   2345: #   
                   2346: sub change_user {
                   2347:     my $self = shift;
                   2348:     $self->{USERNAME} = shift;
                   2349:     $self->{DOMAIN}   = shift;
1.491     raeburn  2350:     $self->{CODE}     = shift;
1.496     raeburn  2351:     $self->{NOHIDE}   = shift;
1.465     foxr     2352: 
                   2353:     # If the hashes are already tied make sure to break that bond:
                   2354: 
                   2355:     untie %{$self->{NAV_HASH}}; 
                   2356:     untie %{$self->{PARM_HASH}};
                   2357: 
                   2358:     # The assumption is that we have to
                   2359:     # use lonmap here to re-read the hash and from it reconstruct
                   2360:     # new big and parameter hashes.  An implicit assumption at this time
                   2361:     # is that the course file is probably not created locally yet
                   2362:     # an that we will therefore just read without tying.
                   2363: 
                   2364:     my ($cdom, $cnum) = split(/\_/, $env{'request.course.id'});
                   2365: 
                   2366:     my %big_hash;
1.496     raeburn  2367:     &Apache::lonmap::loadmap($cnum, $cdom, $self->{USERNAME}, $self->{DOMAIN}, $self->{CODE}, $self->{NOHIDE}, \%big_hash);
1.465     foxr     2368:     $self->{NAV_HASH} = \%big_hash;
                   2369: 
1.470     foxr     2370: 
                   2371: 
1.514     raeburn  2372:     # Now clear the parm cache and reconstruct the parm hash from the big_hash
1.465     foxr     2373:     # param.xxxx keys.
                   2374: 
                   2375:     $self->{PARM_CACHE} = {};
1.133     bowersj2 2376:     
1.465     foxr     2377:     my %parm_hash = {};
1.504     raeburn  2378:     foreach my $key (keys(%big_hash)) {
1.465     foxr     2379: 	if ($key =~ /^param\./) {
                   2380: 	    my $param_key = $key;
                   2381: 	    $param_key =~ s/^param\.//;
                   2382: 	    $parm_hash{$param_key} = $big_hash{$key};
                   2383: 	}
1.133     bowersj2 2384:     }
                   2385: 
1.465     foxr     2386:     $self->{PARM_HASH} = \%parm_hash;
                   2387: 
1.500     raeburn  2388: }
1.133     bowersj2 2389: 
1.221     bowersj2 2390: sub generate_course_user_opt {
1.133     bowersj2 2391:     my $self = shift;
1.221     bowersj2 2392:     if ($self->{COURSE_USER_OPT_GENERATED}) { return; }
1.133     bowersj2 2393: 
1.453     www      2394:     my $uname=$self->{USERNAME};
                   2395:     my $udom=$self->{DOMAIN};
                   2396: 
1.320     albertel 2397:     my $cid=$env{'request.course.id'};
1.326     albertel 2398:     my $cdom=$env{'course.'.$cid.'.domain'};
                   2399:     my $cnum=$env{'course.'.$cid.'.num'};
1.221     bowersj2 2400:     
1.133     bowersj2 2401: # ------------------------------------------------- Get coursedata (if present)
1.326     albertel 2402:     my $courseopt=&Apache::lonnet::get_courseresdata($cnum,$cdom);
                   2403:     # Check for network failure
                   2404:     if (!ref($courseopt)) {
                   2405: 	if ( $courseopt =~ /no.such.host/i || $courseopt =~ /con_lost/i) {
1.324     albertel 2406: 	    $self->{NETWORK_FAILURE} = 1;
1.221     bowersj2 2407: 	}
1.326     albertel 2408: 	undef($courseopt);
                   2409:     }
1.325     albertel 2410: 
1.133     bowersj2 2411: # --------------------------------------------------- Get userdata (if present)
1.325     albertel 2412: 	
1.326     albertel 2413:     my $useropt=&Apache::lonnet::get_userresdata($uname,$udom);
                   2414:     # Check for network failure
                   2415:     if (!ref($useropt)) {
                   2416: 	if ( $useropt =~ /no.such.host/i || $useropt =~ /con_lost/i) {
1.324     albertel 2417: 	    $self->{NETWORK_FAILURE} = 1;
1.221     bowersj2 2418: 	}
1.326     albertel 2419: 	undef($useropt);
1.221     bowersj2 2420:     }
                   2421: 
1.326     albertel 2422:     $self->{COURSE_OPT} = $courseopt;
                   2423:     $self->{USER_OPT} = $useropt;
                   2424: 
1.221     bowersj2 2425:     $self->{COURSE_USER_OPT_GENERATED} = 1;
                   2426:     
                   2427:     return;
                   2428: }
                   2429: 
1.485     foxr     2430: 
                   2431: 
1.221     bowersj2 2432: sub generate_email_discuss_status {
                   2433:     my $self = shift;
1.259     raeburn  2434:     my $symb = shift;
1.221     bowersj2 2435:     if ($self->{EMAIL_DISCUSS_GENERATED}) { return; }
1.133     bowersj2 2436: 
1.320     albertel 2437:     my $cid=$env{'request.course.id'};
1.326     albertel 2438:     my $cdom=$env{'course.'.$cid.'.domain'};
                   2439:     my $cnum=$env{'course.'.$cid.'.num'};
1.221     bowersj2 2440:     
1.454     www      2441:     my %emailstatus = &Apache::lonnet::dump('email_status',$self->{DOMAIN},$self->{USERNAME});
1.221     bowersj2 2442:     my $logoutTime = $emailstatus{'logout'};
1.320     albertel 2443:     my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};
1.221     bowersj2 2444:     $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
                   2445: 			   $courseLeaveTime : $logoutTime);
                   2446:     my %discussiontime = &Apache::lonnet::dump('discussiontimes', 
                   2447: 					       $cdom, $cnum);
1.259     raeburn  2448:     my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
1.453     www      2449:                                         $self->{DOMAIN},$self->{USERNAME},'lastread');
1.259     raeburn  2450:     my %lastreadtime = ();
1.504     raeburn  2451:     foreach my $key (keys(%lastread)) {
1.394     raeburn  2452:         my $shortkey = $key;
                   2453:         $shortkey =~ s/_lastread$//;
                   2454:         $lastreadtime{$shortkey} = $lastread{$key};
1.259     raeburn  2455:     }
                   2456: 
1.221     bowersj2 2457:     my %feedback=();
                   2458:     my %error=();
1.453     www      2459:     my @keys = &Apache::lonnet::getkeys('nohist_email',$self->{DOMAIN},
                   2460: 					$self->{USERNAME});
1.221     bowersj2 2461:     
1.325     albertel 2462:     foreach my $msgid (@keys) {
1.295     albertel 2463: 	if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
1.395     raeburn  2464:             my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,
1.536     raeburn  2465:                 $symb,$error) = &Apache::lonmsg::unpackmsgid(&LONCAPA::escape($msgid));
1.396     raeburn  2466:             &Apache::lonenc::check_decrypt(\$symb); 
                   2467:             if (($fromcid ne '') && ($fromcid ne $cid)) {
                   2468:                 next;
                   2469:             }
1.395     raeburn  2470:             if (defined($symb)) {
                   2471:                 if (defined($error) && $error == 1) {
                   2472:                     $error{$symb}.=','.$msgid;
                   2473:                 } else {
                   2474:                     $feedback{$symb}.=','.$msgid;
                   2475:                 }
                   2476:             } else {
                   2477:                 my $plain=
                   2478:                     &LONCAPA::unescape(&LONCAPA::unescape($msgid));
                   2479:                 if ($plain=~/ \[([^\]]+)\]\:/) {
                   2480:                     my $url=$1;
                   2481:                     if ($plain=~/\:Error \[/) {
                   2482:                         $error{$url}.=','.$msgid;
                   2483:                     } else {
                   2484:                         $feedback{$url}.=','.$msgid;
                   2485:                     }
                   2486:                 }
                   2487:             }
1.221     bowersj2 2488: 	}
1.211     bowersj2 2489:     }
1.221     bowersj2 2490:     
1.395     raeburn  2491:     #symbs of resources that have feedbacks (will be urls pre-2.3)
1.221     bowersj2 2492:     $self->{FEEDBACK} = \%feedback;
1.395     raeburn  2493:     #or errors (will be urls pre 2.3)
1.295     albertel 2494:     $self->{ERROR_MSG} = \%error;
1.221     bowersj2 2495:     $self->{DISCUSSION_TIME} = \%discussiontime;
                   2496:     $self->{EMAIL_STATUS} = \%emailstatus;
1.259     raeburn  2497:     $self->{LAST_READ} = \%lastreadtime;
1.221     bowersj2 2498:     
                   2499:     $self->{EMAIL_DISCUSS_GENERATED} = 1;
                   2500: }
                   2501: 
                   2502: sub get_user_data {
                   2503:     my $self = shift;
                   2504:     if ($self->{RETRIEVED_USER_DATA}) { return; }
1.211     bowersj2 2505: 
1.221     bowersj2 2506:     # Retrieve performance data on problems
1.320     albertel 2507:     my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},
1.453     www      2508: 						   $self->{DOMAIN},
                   2509: 						   $self->{USERNAME});
1.221     bowersj2 2510:     $self->{STUDENT_DATA} = \%student_data;
1.133     bowersj2 2511: 
1.221     bowersj2 2512:     $self->{RETRIEVED_USER_DATA} = 1;
1.133     bowersj2 2513: }
                   2514: 
1.354     raeburn  2515: sub get_discussion_data {
                   2516:     my $self = shift;
                   2517:     if ($self->{RETRIEVED_DISCUSSION_DATA}) {
1.366     albertel 2518: 	return $self->{DISCUSSION_DATA};
1.354     raeburn  2519:     }
1.366     albertel 2520: 
                   2521:     $self->generate_email_discuss_status();    
                   2522: 
1.354     raeburn  2523:     my $cid=$env{'request.course.id'};
                   2524:     my $cdom=$env{'course.'.$cid.'.domain'};
                   2525:     my $cnum=$env{'course.'.$cid.'.num'};
                   2526:     # Retrieve discussion data for resources in course
1.367     albertel 2527:     my %discussion_data = &Apache::lonnet::dumpstore($cid,$cdom,$cnum);
1.366     albertel 2528: 
                   2529: 
1.354     raeburn  2530:     $self->{DISCUSSION_DATA} = \%discussion_data;
                   2531:     $self->{RETRIEVED_DISCUSSION_DATA} = 1;
                   2532:     return $self->{DISCUSSION_DATA};
                   2533: }
                   2534: 
                   2535: 
1.133     bowersj2 2536: # Internal function: Takes a key to look up in the nav hash and implements internal
                   2537: # memory caching of that key.
                   2538: sub navhash {
                   2539:     my $self = shift; my $key = shift;
                   2540:     return $self->{NAV_HASH}->{$key};
                   2541: }
                   2542: 
1.217     bowersj2 2543: =pod
                   2544: 
                   2545: =item * B<courseMapDefined>(): Returns true if the course map is defined, 
                   2546:     false otherwise. Undefined course maps indicate an error somewhere in
                   2547:     LON-CAPA, and you will not be able to proceed with using the navmap.
                   2548:     See the B<NAV> screen for an example of using this.
                   2549: 
                   2550: =cut
                   2551: 
1.133     bowersj2 2552: # Checks to see if coursemap is defined, matching test in old lonnavmaps
                   2553: sub courseMapDefined {
                   2554:     my $self = shift;
1.320     albertel 2555:     my $uri = &Apache::lonnet::clutter($env{'request.course.uri'});
1.133     bowersj2 2556: 
                   2557:     my $firstres = $self->navhash("map_start_$uri");
                   2558:     my $lastres = $self->navhash("map_finish_$uri");
                   2559:     return $firstres && $lastres;
                   2560: }
                   2561: 
                   2562: sub getIterator {
                   2563:     my $self = shift;
                   2564:     my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,
1.314     albertel 2565:                                                      shift, undef, shift,
1.553     raeburn  2566: 						     shift, shift, shift);
1.133     bowersj2 2567:     return $iterator;
                   2568: }
                   2569: 
                   2570: # Private method: Does the given resource (as a symb string) have
                   2571: # current discussion? Returns 0 if chat/mail data not extracted.
                   2572: sub hasDiscussion {
                   2573:     my $self = shift;
                   2574:     my $symb = shift;
1.221     bowersj2 2575:     $self->generate_email_discuss_status();
                   2576: 
1.133     bowersj2 2577:     if (!defined($self->{DISCUSSION_TIME})) { return 0; }
                   2578: 
                   2579:     #return defined($self->{DISCUSSION_TIME}->{$symb});
1.259     raeburn  2580: 
1.323     albertel 2581:     # backward compatibility (bulletin boards used to be 'wrapped')
                   2582:     my $ressymb = $self->wrap_symb($symb);
1.259     raeburn  2583:     if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) {
                   2584:         return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb};
                   2585:     } else {
1.266     raeburn  2586: #        return $self->{DISCUSSION_TIME}->{$ressymb} >  $self->{LAST_CHECK}; # v.1.1 behavior 
                   2587:         return $self->{DISCUSSION_TIME}->{$ressymb} >  0; # in 1.2 will display speech bubble icons for all items with posts until marked as read (even if read in v 1.1).
1.259     raeburn  2588:     }
1.133     bowersj2 2589: }
                   2590: 
1.366     albertel 2591: sub last_post_time {
                   2592:     my $self = shift;
                   2593:     my $symb = shift;
                   2594:     my $ressymb = $self->wrap_symb($symb);
                   2595:     return $self->{DISCUSSION_TIME}->{$ressymb};
                   2596: }
                   2597: 
1.393     raeburn  2598: sub discussion_info {
1.366     albertel 2599:     my $self = shift;
                   2600:     my $symb = shift;
1.393     raeburn  2601:     my $filter = shift;
1.366     albertel 2602: 
                   2603:     $self->get_discussion_data();
1.372     raeburn  2604: 
1.366     albertel 2605:     my $ressymb = $self->wrap_symb($symb);
1.372     raeburn  2606:     # keys used to store bulletinboard postings use 'unwrapped' symb. 
1.397     albertel 2607:     my $discsymb = &escape($self->unwrap_symb($ressymb));
1.372     raeburn  2608:     my $version = $self->{DISCUSSION_DATA}{'version:'.$discsymb};
1.366     albertel 2609:     if (!$version) { return; }
                   2610: 
                   2611:     my $prevread = $self->{LAST_READ}{$ressymb};
                   2612: 
1.393     raeburn  2613:     my $count = 0;
1.366     albertel 2614:     my $hiddenflag = 0;
                   2615:     my $deletedflag = 0;
1.393     raeburn  2616:     my ($hidden,$deleted,%info);
1.366     albertel 2617: 
                   2618:     for (my $id=$version; $id>0; $id--) {
1.372     raeburn  2619: 	my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb};
1.366     albertel 2620: 	my @keys=split(/:/,$vkeys);
                   2621: 	if (grep(/^hidden$/ ,@keys)) {
                   2622: 	    if (!$hiddenflag) {
1.372     raeburn  2623: 		$hidden = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':hidden'};
1.366     albertel 2624: 		$hiddenflag = 1;
                   2625: 	    }
                   2626: 	} elsif (grep(/^deleted$/,@keys)) {
                   2627: 	    if (!$deletedflag) {
1.372     raeburn  2628: 		$deleted = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':deleted'};
1.366     albertel 2629: 		$deletedflag = 1;
                   2630: 	    }
                   2631: 	} else {
1.393     raeburn  2632: 	    if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)) {
                   2633:                 if ($filter eq 'unread') {
                   2634: 		    if ($prevread >= $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) {
                   2635:                         next;
                   2636:                     }
                   2637:                 }
                   2638: 		$count++;
                   2639: 		$info{$count}{'subject'} =
                   2640: 		    $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'};
                   2641:                 $info{$count}{'id'} = $id;
                   2642:                 $info{$count}{'timestamp'} = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'};
                   2643:             }
1.366     albertel 2644: 	}
                   2645:     }
                   2646:     if (wantarray) {
1.393     raeburn  2647: 	return ($count,%info);
1.366     albertel 2648:     }
1.393     raeburn  2649:     return $count;
1.366     albertel 2650: }
                   2651: 
1.321     raeburn  2652: sub wrap_symb {
1.322     albertel 2653:     my $self = shift;
1.321     raeburn  2654:     my $symb = shift;
1.373     albertel 2655:     if ($symb =~ m-___(adm/[^/]+/[^/]+/)(\d+)(/bulletinboard)$-) {
1.322     albertel 2656:         unless ($symb =~ m|adm/wrapper/adm|) {
                   2657:             $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3;
1.321     raeburn  2658:         }
                   2659:     }
1.322     albertel 2660:     return $symb;
1.321     raeburn  2661: }
                   2662: 
1.372     raeburn  2663: sub unwrap_symb {
                   2664:     my $self = shift;
                   2665:     my $ressymb = shift;
                   2666:     my $discsymb = $ressymb;
1.373     albertel 2667:     if ($ressymb =~ m-^(bulletin___\d+___)adm/wrapper/(adm/[^/]+/[^/]+/\d+/bulletinboard)$-) {
1.372     raeburn  2668:          $discsymb = $1.$2;
                   2669:     }
                   2670:     return $discsymb;
                   2671: }
                   2672: 
1.322     albertel 2673: # Private method: Does the given resource (as a symb string) have
                   2674: # current feedback? Returns the string in the feedback hash, which
                   2675: # will be false if it does not exist.
                   2676: 
1.133     bowersj2 2677: sub getFeedback { 
                   2678:     my $self = shift;
                   2679:     my $symb = shift;
1.395     raeburn  2680:     my $source = shift;
1.133     bowersj2 2681: 
1.221     bowersj2 2682:     $self->generate_email_discuss_status();
                   2683: 
1.133     bowersj2 2684:     if (!defined($self->{FEEDBACK})) { return ""; }
                   2685:     
1.395     raeburn  2686:     my $feedback;
                   2687:     if ($self->{FEEDBACK}->{$symb}) {
                   2688:         $feedback = $self->{FEEDBACK}->{$symb};
                   2689:         if ($self->{FEEDBACK}->{$source}) {
                   2690:             $feedback .= ','.$self->{FEEDBACK}->{$source};
                   2691:         }
                   2692:     } else {
                   2693:         if ($self->{FEEDBACK}->{$source}) {
                   2694:             $feedback = $self->{FEEDBACK}->{$source};
                   2695:         }
                   2696:     }
                   2697:     return $feedback;
1.133     bowersj2 2698: }
                   2699: 
                   2700: # Private method: Get the errors for that resource (by source).
                   2701: sub getErrors { 
                   2702:     my $self = shift;
1.395     raeburn  2703:     my $symb = shift;
1.133     bowersj2 2704:     my $src = shift;
1.221     bowersj2 2705: 
                   2706:     $self->generate_email_discuss_status();
                   2707: 
1.133     bowersj2 2708:     if (!defined($self->{ERROR_MSG})) { return ""; }
1.395     raeburn  2709: 
                   2710:     my $errors;
                   2711:     if ($self->{ERROR_MSG}->{$symb}) {
                   2712:         $errors = $self->{ERROR_MSG}->{$symb};
                   2713:         if ($self->{ERROR_MSG}->{$src}) {
                   2714:             $errors .= ','.$self->{ERROR_MSG}->{$src};
                   2715:         }
                   2716:     } else {
                   2717:         if ($self->{ERROR_MSG}->{$src}) {
                   2718:             $errors = $self->{ERROR_MSG}->{$src};
                   2719:         }
                   2720:     }
                   2721:     return $errors;
1.133     bowersj2 2722: }
                   2723: 
                   2724: =pod
                   2725: 
1.174     albertel 2726: =item * B<getById>(id):
                   2727: 
                   2728: Based on the ID of the resource (1.1, 3.2, etc.), get a resource
                   2729: object for that resource. This method, or other methods that use it
                   2730: (as in the resource object) is the only proper way to obtain a
                   2731: resource object.
1.133     bowersj2 2732: 
1.194     bowersj2 2733: =item * B<getBySymb>(symb):
                   2734: 
                   2735: Based on the symb of the resource, get a resource object for that
                   2736: resource. This is one of the proper ways to get a resource object.
                   2737: 
1.448     raeburn  2738: =item * B<getByMapPc>(map_pc):
1.194     bowersj2 2739: 
                   2740: Based on the map_pc of the resource, get a resource object for
                   2741: the given map. This is one of the proper ways to get a resource object.
                   2742: 
1.133     bowersj2 2743: =cut
                   2744: 
                   2745: # The strategy here is to cache the resource objects, and only construct them
                   2746: # as we use them. The real point is to prevent reading any more from the tied
1.398     banghart 2747: # hash than we have to, which should hopefully alleviate speed problems.
1.133     bowersj2 2748: 
                   2749: sub getById {
                   2750:     my $self = shift;
                   2751:     my $id = shift;
                   2752: 
                   2753:     if (defined ($self->{RESOURCE_CACHE}->{$id}))
                   2754:     {
                   2755:         return $self->{RESOURCE_CACHE}->{$id};
                   2756:     }
                   2757: 
                   2758:     # resource handles inserting itself into cache.
                   2759:     # Not clear why the quotes are necessary, but as of this
                   2760:     # writing it doesn't work without them.
                   2761:     return "Apache::lonnavmaps::resource"->new($self, $id);
1.132     bowersj2 2762: }
1.133     bowersj2 2763: 
1.172     bowersj2 2764: sub getBySymb {
                   2765:     my $self = shift;
                   2766:     my $symb = shift;
1.277     matthew  2767: 
1.228     albertel 2768:     my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb);
1.172     bowersj2 2769:     my $map = $self->getResourceByUrl($mapUrl);
1.277     matthew  2770:     my $returnvalue = undef;
                   2771:     if (ref($map)) {
                   2772:         $returnvalue = $self->getById($map->map_pc() .'.'.$id);
                   2773:     }
                   2774:     return $returnvalue;
1.194     bowersj2 2775: }
                   2776: 
                   2777: sub getByMapPc {
                   2778:     my $self = shift;
                   2779:     my $map_pc = shift;
                   2780:     my $map_id = $self->{NAV_HASH}->{'map_id_' . $map_pc};
                   2781:     $map_id = $self->{NAV_HASH}->{'ids_' . $map_id};
                   2782:     return $self->getById($map_id);
1.172     bowersj2 2783: }
                   2784: 
1.133     bowersj2 2785: =pod
                   2786: 
1.174     albertel 2787: =item * B<firstResource>():
                   2788: 
                   2789: Returns a resource object reference corresponding to the first
                   2790: resource in the navmap.
1.133     bowersj2 2791: 
                   2792: =cut
                   2793: 
                   2794: sub firstResource {
                   2795:     my $self = shift;
                   2796:     my $firstResource = $self->navhash('map_start_' .
1.320     albertel 2797:                      &Apache::lonnet::clutter($env{'request.course.uri'}));
1.133     bowersj2 2798:     return $self->getById($firstResource);
1.132     bowersj2 2799: }
1.133     bowersj2 2800: 
                   2801: =pod
                   2802: 
1.174     albertel 2803: =item * B<finishResource>():
                   2804: 
                   2805: Returns a resource object reference corresponding to the last resource
                   2806: in the navmap.
1.133     bowersj2 2807: 
                   2808: =cut
                   2809: 
                   2810: sub finishResource {
                   2811:     my $self = shift;
                   2812:     my $firstResource = $self->navhash('map_finish_' .
1.320     albertel 2813:                      &Apache::lonnet::clutter($env{'request.course.uri'}));
1.133     bowersj2 2814:     return $self->getById($firstResource);
1.132     bowersj2 2815: }
1.133     bowersj2 2816: 
                   2817: # Parmval reads the parm hash and cascades the lookups. parmval_real does
                   2818: # the actual lookup; parmval caches the results.
                   2819: sub parmval {
                   2820:     my $self = shift;
1.399     albertel 2821:     my ($what,$symb,$recurse)=@_;
1.133     bowersj2 2822:     my $hashkey = $what."|||".$symb;
1.459     foxr     2823:     my $cache = $self->{PARM_CACHE};
1.133     bowersj2 2824:     if (defined($self->{PARM_CACHE}->{$hashkey})) {
1.411     raeburn  2825:         if (ref($self->{PARM_CACHE}->{$hashkey}) eq 'ARRAY') { 
                   2826:             if (defined($self->{PARM_CACHE}->{$hashkey}->[0])) {
                   2827:                 if (wantarray) {
                   2828:                     return @{$self->{PARM_CACHE}->{$hashkey}};
                   2829:                 } else {
                   2830:                     return $self->{PARM_CACHE}->{$hashkey}->[0];
                   2831:                 }
                   2832:             }
                   2833:         } else {
                   2834:             return $self->{PARM_CACHE}->{$hashkey};
                   2835:         }
1.133     bowersj2 2836:     }
1.515     raeburn  2837: 
1.399     albertel 2838:     my $result = $self->parmval_real($what, $symb, $recurse);
1.133     bowersj2 2839:     $self->{PARM_CACHE}->{$hashkey} = $result;
1.405     albertel 2840:     if (wantarray) {
1.411     raeburn  2841:         return @{$result};
1.405     albertel 2842:     }
                   2843:     return $result->[0];
1.132     bowersj2 2844: }
                   2845: 
1.485     foxr     2846: 
1.133     bowersj2 2847: sub parmval_real {
                   2848:     my $self = shift;
1.219     albertel 2849:     my ($what,$symb,$recurse) = @_;
1.133     bowersj2 2850: 
1.485     foxr     2851: 
1.221     bowersj2 2852:     # Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated
                   2853:     $self->generate_course_user_opt();
                   2854: 
1.320     albertel 2855:     my $cid=$env{'request.course.id'};
                   2856:     my $csec=$env{'request.course.sec'};
1.350     raeburn  2857:     my $cgroup='';
                   2858:     my @cgrps=split(/:/,$env{'request.course.groups'});
                   2859:     if (@cgrps > 0) {
                   2860:         @cgrps = sort(@cgrps);
                   2861:         $cgroup = $cgrps[0];
                   2862:     } 
1.453     www      2863:     my $uname=$self->{USERNAME};
                   2864:     my $udom=$self->{DOMAIN};
1.133     bowersj2 2865: 
1.405     albertel 2866:     unless ($symb) { return ['']; }
1.133     bowersj2 2867:     my $result='';
                   2868: 
1.226     www      2869:     my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
1.383     albertel 2870:     $mapname = &Apache::lonnet::deversion($mapname);
1.537     raeburn  2871:     my $toolsymb = '';
                   2872:     if ($fn =~ /ext\.tool$/) {
                   2873:         $toolsymb = $symb;
                   2874:     }
1.515     raeburn  2875:     my ($recursed,@recurseup); 
                   2876:     
1.133     bowersj2 2877: # ----------------------------------------------------- Cascading lookup scheme
                   2878:     my $rwhat=$what;
                   2879:     $what=~s/^parameter\_//;
                   2880:     $what=~s/\_/\./;
                   2881: 
                   2882:     my $symbparm=$symb.'.'.$what;
1.515     raeburn  2883:     my $recurseparm=$mapname.'___(rec).'.$what;
1.133     bowersj2 2884:     my $mapparm=$mapname.'___(all).'.$what;
1.325     albertel 2885:     my $usercourseprefix=$cid;
1.515     raeburn  2886:     
1.485     foxr     2887: 
                   2888: 
1.350     raeburn  2889:     my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what;
                   2890:     my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm;
1.515     raeburn  2891:     my $grpleveli=$usercourseprefix.'.['.$cgroup.'].'.$recurseparm;
1.350     raeburn  2892:     my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm;
                   2893: 
1.485     foxr     2894: 
1.133     bowersj2 2895:     my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what;
                   2896:     my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm;
1.515     raeburn  2897:     my $secleveli=$usercourseprefix.'.['.$csec.'].'.$recurseparm;
1.133     bowersj2 2898:     my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm;
                   2899: 
1.485     foxr     2900: 
1.133     bowersj2 2901:     my $courselevel= $usercourseprefix.'.'.$what;
                   2902:     my $courselevelr=$usercourseprefix.'.'.$symbparm;
1.515     raeburn  2903:     my $courseleveli=$usercourseprefix.'.'.$recurseparm;
1.133     bowersj2 2904:     my $courselevelm=$usercourseprefix.'.'.$mapparm;
                   2905: 
1.485     foxr     2906: 
1.133     bowersj2 2907:     my $useropt = $self->{USER_OPT};
                   2908:     my $courseopt = $self->{COURSE_OPT};
                   2909:     my $parmhash = $self->{PARM_HASH};
                   2910: 
                   2911: # ---------------------------------------------------------- first, check user
                   2912:     if ($uname and defined($useropt)) {
1.405     albertel 2913:         if (defined($$useropt{$courselevelr})) { return [$$useropt{$courselevelr},'resource']; }
                   2914:         if (defined($$useropt{$courselevelm})) { return [$$useropt{$courselevelm},'map']; }
1.515     raeburn  2915:         if (defined($$useropt{$courseleveli})) { return [$$useropt{$courseleveli},'map']; }
                   2916:         unless ($recursed) {
                   2917:             @recurseup = $self->recurseup_maps($mapname);
                   2918:             $recursed = 1;
                   2919:         }
                   2920:         foreach my $item (@recurseup) {
                   2921:             my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what;
1.526     raeburn  2922:             if (defined($$useropt{$norecursechk})) {
                   2923:                 if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                   2924:                     return [$$useropt{$norecursechk},'map'];
                   2925:                 } else {
                   2926:                     last;
                   2927:                 }
                   2928:             }
1.515     raeburn  2929:             my $recursechk=$usercourseprefix.'.'.$item.'___(rec).'.$what;
                   2930:             if (defined($$useropt{$recursechk})) { return [$$useropt{$recursechk},'map']; } 
                   2931:         }
1.405     albertel 2932:         if (defined($$useropt{$courselevel})) { return [$$useropt{$courselevel},'course']; }
1.133     bowersj2 2933:     }
                   2934: 
                   2935: # ------------------------------------------------------- second, check course
1.350     raeburn  2936:     if ($cgroup ne '' and defined($courseopt)) {
1.405     albertel 2937:         if (defined($$courseopt{$grplevelr})) { return [$$courseopt{$grplevelr},'resource']; }
                   2938:         if (defined($$courseopt{$grplevelm})) { return [$$courseopt{$grplevelm},'map']; }
1.515     raeburn  2939:         if (defined($$courseopt{$grpleveli})) { return [$$courseopt{$grpleveli},'map']; } 
                   2940:         unless ($recursed) {
                   2941:             @recurseup = $self->recurseup_maps($mapname);
                   2942:             $recursed = 1;
                   2943:         }
                   2944:         foreach my $item (@recurseup) {
                   2945:             my $norecursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(all).'.$what;
1.526     raeburn  2946:             if (defined($$courseopt{$norecursechk})) {
                   2947:                 if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                   2948:                     return [$$courseopt{$norecursechk},'map'];
                   2949:                 } else {
                   2950:                    last;
                   2951:                 }
                   2952:             }
1.515     raeburn  2953:             my $recursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(rec).'.$what;
                   2954:             if (defined($$courseopt{$recursechk})) { return [$$courseopt{$recursechk},'map']; }      
                   2955:         }
1.405     albertel 2956:         if (defined($$courseopt{$grplevel})) { return [$$courseopt{$grplevel},'course']; }
1.350     raeburn  2957:     }
                   2958: 
1.515     raeburn  2959:     if ($csec ne '' and defined($courseopt)) {
1.405     albertel 2960:         if (defined($$courseopt{$seclevelr})) { return [$$courseopt{$seclevelr},'resource']; }
                   2961:         if (defined($$courseopt{$seclevelm})) { return [$$courseopt{$seclevelm},'map']; }
1.515     raeburn  2962:         if (defined($$courseopt{$secleveli})) { return [$$courseopt{$secleveli},'map']; } 
                   2963:         unless ($recursed) {
                   2964:             @recurseup = $self->recurseup_maps($mapname);
                   2965:             $recursed = 1;
                   2966:         }
                   2967:         foreach my $item (@recurseup) {
                   2968:             my $norecursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(all).'.$what;
1.526     raeburn  2969:             if (defined($$courseopt{$norecursechk})) {
                   2970:                 if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                   2971:                     return [$$courseopt{$norecursechk},'map'];
                   2972:                 } else {
                   2973:                     last;
                   2974:                 }
                   2975:             }
1.515     raeburn  2976:             my $recursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(rec).'.$what;
                   2977:             if (defined($$courseopt{$recursechk})) { return [$$courseopt{$recursechk},'map']; }
                   2978:         }
1.405     albertel 2979:         if (defined($$courseopt{$seclevel})) { return [$$courseopt{$seclevel},'course']; }
1.133     bowersj2 2980:     }
                   2981: 
                   2982:     if (defined($courseopt)) {
1.405     albertel 2983:         if (defined($$courseopt{$courselevelr})) { return [$$courseopt{$courselevelr},'resource']; }
1.133     bowersj2 2984:     }
                   2985: 
                   2986: # ----------------------------------------------------- third, check map parms
                   2987: 
                   2988:     my $thisparm=$$parmhash{$symbparm};
1.405     albertel 2989:     if (defined($thisparm)) { return [$thisparm,'map']; }
1.133     bowersj2 2990: 
                   2991: # ----------------------------------------------------- fourth , check default
                   2992: 
1.246     albertel 2993:     my $meta_rwhat=$rwhat;
                   2994:     $meta_rwhat=~s/\./_/g;
1.537     raeburn  2995:     my $default=&Apache::lonnet::metadata($fn,$meta_rwhat,$toolsymb);
1.405     albertel 2996:     if (defined($default)) { return [$default,'resource']}
1.537     raeburn  2997:     $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat,$toolsymb);
1.405     albertel 2998:     if (defined($default)) { return [$default,'resource']}
1.315     albertel 2999: # --------------------------------------------------- fifth, check more course
                   3000:     if (defined($courseopt)) {
1.405     albertel 3001:         if (defined($$courseopt{$courselevelm})) { return [$$courseopt{$courselevelm},'map']; }
1.515     raeburn  3002:         if (defined($$courseopt{$courseleveli})) { return [$$courseopt{$courseleveli},'map']; }
                   3003:         unless ($recursed) {
                   3004:             @recurseup = $self->recurseup_maps($mapname);
                   3005:             $recursed = 1;
                   3006:         }
                   3007:         foreach my $item (@recurseup) {
                   3008:             my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what;
1.526     raeburn  3009:             if (defined($$courseopt{$norecursechk})) {
                   3010:                 if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                   3011:                     return [$$courseopt{$norecursechk},'map'];
                   3012:                 } else {
                   3013:                     last;
                   3014:                 }
                   3015:             }
1.515     raeburn  3016:             my $recursechk=$usercourseprefix.'.'.$item.'___(rec).'.$what;
                   3017:             if (defined($$courseopt{$recursechk})) {
                   3018:                 return [$$courseopt{$recursechk},'map'];
                   3019:             }
                   3020:         }
1.405     albertel 3021:         if (defined($$courseopt{$courselevel})) {
                   3022:            my $ret = [$$courseopt{$courselevel},'course'];
                   3023:            return $ret;
                   3024:        }
1.315     albertel 3025:     }
                   3026: # --------------------------------------------------- sixth , cascade up parts
1.133     bowersj2 3027: 
                   3028:     my ($space,@qualifier)=split(/\./,$rwhat);
                   3029:     my $qualifier=join('.',@qualifier);
                   3030:     unless ($space eq '0') {
1.160     albertel 3031: 	my @parts=split(/_/,$space);
                   3032: 	my $id=pop(@parts);
                   3033: 	my $part=join('_',@parts);
                   3034: 	if ($part eq '') { $part='0'; }
1.405     albertel 3035:        my @partgeneral=$self->parmval($part.".$qualifier",$symb,1);
                   3036:        if (defined($partgeneral[0])) { return \@partgeneral; }
1.133     bowersj2 3037:     }
1.405     albertel 3038:     if ($recurse) { return []; }
1.537     raeburn  3039:     my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat,$toolsymb);
1.405     albertel 3040:     if (defined($pack_def)) { return [$pack_def,'resource']; }
                   3041:     return [''];
1.145     bowersj2 3042: }
1.515     raeburn  3043: 
                   3044: sub recurseup_maps {
                   3045:     my ($self,$mapname) = @_;
                   3046:     my @recurseup;
1.517     raeburn  3047:     if ($mapname) {
1.519     raeburn  3048:         my $res = $self->getResourceByUrl($mapname);
                   3049:         if (ref($res)) {
                   3050:             my @pcs = split(/,/,$res->map_hierarchy());
                   3051:             shift(@pcs);
                   3052:             if (@pcs) {
                   3053:                 @recurseup = map { &Apache::lonnet::declutter($self->getByMapPc($_)->src()); } reverse(@pcs);
                   3054:             }
1.517     raeburn  3055:         }
1.515     raeburn  3056:     }
                   3057:     return @recurseup;
                   3058: }
                   3059: 
1.533     raeburn  3060: sub recursed_crumbs {
1.535     raeburn  3061:     my ($self,$mapurl,$restitle) = @_;
1.533     raeburn  3062:     my (@revmapinfo,@revmapres);
                   3063:     my $mapres = $self->getResourceByUrl($mapurl);
                   3064:     if (ref($mapres)) {
1.534     raeburn  3065:         @revmapres = map { $self->getByMapPc($_); } split(/,/,$mapres->map_breadcrumbs());
1.533     raeburn  3066:         shift(@revmapres);
                   3067:     }
1.535     raeburn  3068:     my $allowedlength = 60;
                   3069:     my $minlength = 5;
                   3070:     my $allowedtitle = 30;
                   3071:     if (($env{'environment.icons'} eq 'iconsonly') && (!$env{'browser.mobile'})) {
                   3072:         $allowedlength = 100;
                   3073:         $allowedtitle = 70;
                   3074:     }
                   3075:     if (length($restitle) > $allowedtitle) {
                   3076:         $restitle = &truncate_crumb_text($restitle,$allowedtitle);
                   3077:     }
                   3078:     my $totallength = length($restitle);
                   3079:     my @links;
                   3080: 
1.533     raeburn  3081:     foreach my $map (@revmapres) {
                   3082:         my $pc = $map->map_pc();
                   3083:         next if ((!$pc) || ($pc == 1));
1.535     raeburn  3084:         push(@links,$map);
1.561     raeburn  3085:         my $text = $map->title();
                   3086:         if ($text eq '') {
                   3087:             $text = '...';
                   3088:         }
                   3089:         push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $text,'no_mt' => 1,});
                   3090:         $totallength += length($text);
1.535     raeburn  3091:     }
                   3092:     my $numlinks = scalar(@links);
                   3093:     if ($numlinks) {
                   3094:         if ($totallength - $allowedlength > 0) {
                   3095:             my $available = $allowedlength - length($restitle);
                   3096:             my $avg = POSIX::ceil($available/$numlinks);
                   3097:             if ($avg < $minlength) {
                   3098:                 $avg = $minlength;
                   3099:             }
                   3100:             @revmapinfo = ();
                   3101:             foreach my $map (@links) {
1.561     raeburn  3102:                 my $title = $map->title();
                   3103:                 if ($title eq '') {
                   3104:                     $title = '...';
                   3105:                 }
                   3106:                 my $showntitle = &truncate_crumb_text($title,$avg);
1.535     raeburn  3107:                 if ($showntitle ne '') {
1.545     raeburn  3108:                     push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $showntitle,'no_mt' => 1,});
1.535     raeburn  3109:                 }
                   3110:             }
                   3111:         }
                   3112:     }
                   3113:     if ($restitle ne '') {
                   3114:         push(@revmapinfo,{'text' => $restitle, 'no_mt' => 1});
1.533     raeburn  3115:     }
                   3116:     return @revmapinfo;
                   3117: }
                   3118: 
1.535     raeburn  3119: sub truncate_crumb_text {
                   3120:     my ($title,$limit) = @_;
                   3121:     my $showntitle = '';
                   3122:     if (length($title) > $limit) {
                   3123:         my @words = split(/\b\s*/,$title);
                   3124:         if (@words == 1) {
                   3125:             $showntitle = substr($title,0,$limit).' ...';
                   3126:         } else {
                   3127:             my $linklength = 0;
                   3128:             my $num = 0;
                   3129:             foreach my $word (@words) {
                   3130:                 $linklength += 1+length($word);
                   3131:                 if ($word eq '-') {
                   3132:                     $showntitle =~ s/ $//;
                   3133:                     $showntitle .= $word;
                   3134:                 } elsif ($linklength > $limit) {
                   3135:                     if ($num < @words) {
                   3136:                         $showntitle .= $word.' ...';
                   3137:                         last;
                   3138:                     } else {
                   3139:                         $showntitle .= $word;
                   3140:                     }
                   3141:                 } else {
                   3142:                     $showntitle .= $word.' ';
                   3143:                 }
                   3144:             }
                   3145:             $showntitle =~ s/ $//;
                   3146:         }
                   3147:         return $showntitle;
                   3148:     } else {
                   3149:         return $title;
                   3150:     }
                   3151: }
                   3152: 
1.485     foxr     3153: #
                   3154: #  Determines the open/close dates for printing a map that
                   3155: #  encloses a resource.
                   3156: #
                   3157: sub map_printdates {
                   3158:     my ($self, $res, $part) = @_;
                   3159: 
                   3160: 
                   3161: 
                   3162: 
                   3163: 
1.518     raeburn  3164:     my $opendate = $self->get_mapparam($res->symb(),'',"$part.printstartdate");
                   3165:     my $closedate= $self->get_mapparam($res->symb(),'',"$part.printenddate");
1.485     foxr     3166: 
                   3167: 
                   3168:     return ($opendate, $closedate);
                   3169: }
                   3170: 
                   3171: sub get_mapparam {
1.518     raeburn  3172:     my ($self, $symb, $mapname, $what) = @_;
1.485     foxr     3173: 
                   3174:     # Ensure the course option hash is populated:
                   3175: 
                   3176:     $self->generate_course_user_opt();
                   3177: 
                   3178:     # Get the course id and section if there is one.
                   3179: 
                   3180:     my $cid=$env{'request.course.id'};
                   3181:     my $csec=$env{'request.course.sec'};
                   3182:     my $cgroup='';
                   3183:     my @cgrps=split(/:/,$env{'request.course.groups'});
                   3184:     if (@cgrps > 0) {
                   3185:         @cgrps = sort(@cgrps);
                   3186:         $cgroup = $cgrps[0];
                   3187:     } 
                   3188:     my $uname=$self->{USERNAME};
                   3189:     my $udom=$self->{DOMAIN};
                   3190: 
1.518     raeburn  3191:     unless ($symb || $mapname) { return; }
1.485     foxr     3192:     my $result='';
1.516     raeburn  3193:     my ($recursed,@recurseup);
1.485     foxr     3194: 
                   3195: 
                   3196:     # Figure out which map we are in.
                   3197: 
1.518     raeburn  3198:     if ($symb && !$mapname) {
                   3199:         my ($id,$fn);
                   3200:         ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
                   3201:         $mapname = &Apache::lonnet::deversion($mapname);
                   3202:     }
1.485     foxr     3203: 
                   3204: 
                   3205:     my $rwhat=$what;
                   3206:     $what=~s/^parameter\_//;
                   3207:     $what=~s/\_/\./;
                   3208: 
                   3209:     # Build the hash keys for the lookup:
                   3210: 
                   3211:     my $mapparm=$mapname.'___(all).'.$what;
1.515     raeburn  3212:     my $recurseparm=$mapname.'___(rec).'.$what; 
1.485     foxr     3213:     my $usercourseprefix=$cid;
                   3214: 
                   3215: 
1.515     raeburn  3216:     my $grplevelm    = "$usercourseprefix.[$cgroup].$mapparm";
                   3217:     my $seclevelm    = "$usercourseprefix.[$csec].$mapparm";
                   3218:     my $courselevelm = "$usercourseprefix.$mapparm";
                   3219: 
                   3220:     my $grpleveli    = "$usercourseprefix.[$cgroup].$recurseparm";
                   3221:     my $secleveli    = "$usercourseprefix.[$csec].$recurseparm";
                   3222:     my $courseleveli = "$usercourseprefix.$recurseparm";
1.485     foxr     3223: 
                   3224:     # Get handy references to the hashes we need in $self:
                   3225: 
                   3226:     my $useropt = $self->{USER_OPT};
                   3227:     my $courseopt = $self->{COURSE_OPT};
                   3228:     my $parmhash = $self->{PARM_HASH};
                   3229: 
                   3230:     # Check per user 
                   3231: 
                   3232: 
                   3233: 
                   3234:     if ($uname and defined($useropt)) {
1.515     raeburn  3235: 	if (defined($$useropt{$courselevelm})) {
                   3236: 	    return $$useropt{$courselevelm};
1.485     foxr     3237: 	}
1.515     raeburn  3238:         if (defined($$useropt{$courseleveli})) {
                   3239:             return $$useropt{$courseleveli};
                   3240:         }
1.516     raeburn  3241:         unless ($recursed) {
                   3242:             @recurseup = $self->recurseup_maps($mapname);
                   3243:             $recursed = 1;
                   3244:         }
                   3245:         foreach my $item (@recurseup) {
                   3246:             my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what;
1.526     raeburn  3247:             if (defined($$useropt{$norecursechk})) {
                   3248:                 if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                   3249:                     return $$useropt{$norecursechk};
                   3250:                 } else {
                   3251:                     last;
                   3252:                 }
                   3253:             }
1.516     raeburn  3254:             my $recursechk=$usercourseprefix.'.'.$item.'___(rec).'.$what;
                   3255:             if (defined($$useropt{$recursechk})) {
                   3256:                 return $$useropt{$recursechk};
                   3257:             }
                   3258:         }
1.485     foxr     3259:     }
                   3260: 
                   3261:     # Check course -- group
                   3262: 
                   3263: 
                   3264: 
                   3265:     if ($cgroup ne '' and defined ($courseopt)) {
1.515     raeburn  3266: 	if (defined($$courseopt{$grplevelm})) {
                   3267: 	    return $$courseopt{$grplevelm};
1.485     foxr     3268: 	}
1.515     raeburn  3269:         if (defined($$courseopt{$grpleveli})) {
                   3270:             return $$courseopt{$grpleveli};
                   3271:         }
1.516     raeburn  3272:         unless ($recursed) {
                   3273:             @recurseup = $self->recurseup_maps($mapname);
                   3274:             $recursed = 1;
                   3275:         }
                   3276:         foreach my $item (@recurseup) {
                   3277:             my $norecursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(all).'.$what;
1.526     raeburn  3278:             if (defined($$courseopt{$norecursechk})) {
                   3279:                 if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                   3280:                     return $$courseopt{$norecursechk};
                   3281:                 } else {
                   3282:                     last;
                   3283:                 }
                   3284:             }
1.516     raeburn  3285:             my $recursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(rec).'.$what;
                   3286:             if (defined($$courseopt{$recursechk})) {
                   3287:                 return $$courseopt{$recursechk};
                   3288:             }
                   3289:         }
1.485     foxr     3290:     }
                   3291: 
                   3292:     # Check course -- section
                   3293: 
                   3294: 
1.516     raeburn  3295:     if ($csec ne '' and defined($courseopt)) {
1.515     raeburn  3296: 	if (defined($$courseopt{$seclevelm})) {
                   3297: 	    return $$courseopt{$seclevelm};
1.485     foxr     3298: 	}
1.515     raeburn  3299:         if (defined($$courseopt{$secleveli})) {
                   3300:             return $$courseopt{$secleveli};
                   3301:         }
1.516     raeburn  3302:         unless ($recursed) {
                   3303:             @recurseup = $self->recurseup_maps($mapname);
                   3304:             $recursed = 1;
                   3305:         }
                   3306:         foreach my $item (@recurseup) {
                   3307:             my $norecursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(all).'.$what;
1.526     raeburn  3308:             if (defined($$courseopt{$norecursechk})) {
                   3309:                 if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                   3310:                     return $$courseopt{$norecursechk};
                   3311:                 } else {
                   3312:                     last;
                   3313:                 }
                   3314:             }
1.516     raeburn  3315:             my $recursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(rec).'.$what;
                   3316:             if (defined($$courseopt{$recursechk})) {
                   3317:                 return $$courseopt{$recursechk};
                   3318:             }
                   3319:         }
1.485     foxr     3320:     }
                   3321:     # Check the map parameters themselves:
                   3322: 
1.518     raeburn  3323:     if ($symb) {
                   3324:         my $symbparm=$symb.'.'.$what;
                   3325:         my $thisparm = $$parmhash{$symbparm};
                   3326:         if (defined($thisparm)) {
                   3327: 	    return $thisparm;
                   3328:         }
1.485     foxr     3329:     }
                   3330: 
                   3331: 
                   3332:     # Additional course parameters:
                   3333: 
                   3334:     if (defined($courseopt)) {
1.515     raeburn  3335: 	if (defined($$courseopt{$courselevelm})) {
                   3336: 	    return $$courseopt{$courselevelm};
1.485     foxr     3337: 	}
1.552     raeburn  3338:         if (defined($$courseopt{$courseleveli})) {
                   3339:             return $$courseopt{$courseleveli};
                   3340:         }
1.516     raeburn  3341:         unless ($recursed) {
                   3342:             @recurseup = $self->recurseup_maps($mapname);
                   3343:             $recursed = 1;
                   3344:         }
                   3345:         if (@recurseup) {
                   3346:             foreach my $item (@recurseup) {
                   3347:                 my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what;
1.526     raeburn  3348:                 if (defined($$courseopt{$norecursechk})) {
                   3349:                     if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                   3350:                         return $$courseopt{$norecursechk};
                   3351:                     } else {
                   3352:                         last;
                   3353:                     }
                   3354:                 }
1.516     raeburn  3355:                 my $recursechk=$usercourseprefix.'.'.$item.'___(rec).'.$what;
                   3356:                 if (defined($$courseopt{$recursechk})) {
                   3357:                     return $$courseopt{$recursechk};
                   3358:                 }
                   3359:             }
                   3360:         }
1.485     foxr     3361:     }
1.516     raeburn  3362:     return undef;		# Undefined if we got here.
1.485     foxr     3363: }
                   3364: 
                   3365: sub course_printdates {
                   3366:     my ($self, $symb,  $part) = @_;
                   3367: 
                   3368: 
                   3369:     my $opendate  = $self->getcourseparam($symb, $part . '.printstartdate');
                   3370:     my $closedate = $self->getcourseparam($symb, $part . '.printenddate');
                   3371:     return ($opendate, $closedate);
                   3372: 
                   3373: }
                   3374: 
                   3375: sub getcourseparam {
                   3376:     my ($self, $symb, $what) = @_;
                   3377: 
                   3378:     $self->generate_course_user_opt(); # If necessary populate the hashes.
                   3379: 
                   3380:     my $uname = $self->{USERNAME};
                   3381:     my $udom  = $self->{DOMAIN};
                   3382:     
                   3383:     # Course, section, group ids come from the env:
                   3384: 
                   3385:     my $cid   = $env{'request.course.id'};
                   3386:     my $csec  = $env{'request.course.sec'};
                   3387:     my $cgroup = '';		# Assume no group
                   3388: 
                   3389:     my @cgroups = split(/:/, $env{'request.course.groups'});
                   3390:     if(@cgroups > 0) {
                   3391: 	@cgroups = sort(@cgroups);
                   3392: 	$cgroup  = $cgroups[0];	# There is a course group. 
                   3393:    }
                   3394:     my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
                   3395:     $mapname = &Apache::lonnet::deversion($mapname);
                   3396: 
                   3397:     #
                   3398:     # Make the various lookup keys:
                   3399:     #
                   3400: 
                   3401:     $what=~s/^parameter\_//;
                   3402:     $what=~s/\_/\./;
                   3403: 
                   3404:     # Local refs to the hashes we're going to look at:
                   3405: 
                   3406:     my $useropt   = $self->{USER_OPT};
                   3407:     my $courseopt = $self->{COURSE_OPT};
                   3408: 
                   3409:     # 
                   3410:     # We want the course level stuff from the way
                   3411:     # parmval_real operates 
1.513     raeburn  3412:     # TODO: Factor some of this stuff out of
1.485     foxr     3413:     # both parmval_real and here
                   3414:     #
                   3415:     my $courselevel = $cid . '.' .  $what;
                   3416:     my $grplevel    = $cid . '.[' . $cgroup   . ']' . $what;
                   3417:     my $seclevel    = $cid . '.[' . $csec     . ']' . $what;
                   3418: 
                   3419: 
                   3420:     # Try for the user's course level option:
                   3421: 
                   3422:     if ($uname and defined($useropt)) {
                   3423: 	if (defined($$useropt{$courselevel})) {
                   3424: 	    return $$useropt{$courselevel};
                   3425: 	}
                   3426:     }
                   3427:     # Try for the group's course level option:
                   3428: 
1.513     raeburn  3429:     if ($cgroup ne '' and defined($courseopt)) {
1.485     foxr     3430: 	if (defined($$courseopt{$grplevel})) {
                   3431: 	    return $$courseopt{$grplevel};
                   3432: 	}
                   3433:     }
                   3434: 
                   3435:     #  Try for section level parameters:
                   3436: 
1.513     raeburn  3437:     if ($csec ne '' and defined($courseopt)) {
1.485     foxr     3438: 	if (defined($$courseopt{$seclevel})) {
                   3439: 	    return $$courseopt{$seclevel};
                   3440: 	}
                   3441:     }
1.513     raeburn  3442:     # Try for 'additional' course parameters:
1.485     foxr     3443: 
                   3444:     if (defined($courseopt)) {
                   3445: 	if (defined($$courseopt{$courselevel})) {
                   3446: 	    return $$courseopt{$courselevel};
                   3447: 	}
                   3448:     }
                   3449:     return undef;
                   3450: 
                   3451: }
                   3452: 
1.145     bowersj2 3453: 
1.174     albertel 3454: =pod
1.145     bowersj2 3455: 
1.352     raeburn  3456: =item * B<getResourceByUrl>(url,multiple):
1.145     bowersj2 3457: 
1.352     raeburn  3458: Retrieves a resource object by URL of the resource, unless the optional
1.398     banghart 3459: multiple parameter is included in which case an array of resource 
1.352     raeburn  3460: objects is returned. If passed a resource object, it will simply return  
                   3461: it, so it is safe to use this method in code like
                   3462: "$res = $navmap->getResourceByUrl($res)"
                   3463: if you're not sure if $res is already an object, or just a URL. If the
                   3464: resource appears multiple times in the course, only the first instance 
                   3465: will be returned (useful for maps), unless the multiple parameter has
                   3466: been included, in which case all instances are returned in an array.
1.174     albertel 3467: 
1.505     raeburn  3468: =item * B<retrieveResources>(map, filterFunc, recursive, bailout, showall, noblockcheck):
1.174     albertel 3469: 
                   3470: The map is a specification of a map to retreive the resources from,
                   3471: either as a url or as an object. The filterFunc is a reference to a
                   3472: function that takes a resource object as its one argument and returns
                   3473: true if the resource should be included, or false if it should not
                   3474: be. If recursive is true, the map will be recursively examined,
                   3475: otherwise it will not be. If bailout is true, the function will return
1.314     albertel 3476: as soon as it finds a resource, if false it will finish. If showall is
1.505     raeburn  3477: true it will not hide maps that contain nothing but one other map. The 
                   3478: noblockcheck arg is propagated to become the sixth arg in the call to
                   3479: lonnet::allowed when checking a resource's availability during collection
                   3480: of resources using the iterator. noblockcheck needs to be true if 
                   3481: retrieveResources() was called by a routine that itself was called by 
                   3482: lonnet::allowed, in order to avoid recursion.  By default the map  
                   3483: is the top-level map of the course, filterFunc is a function that 
                   3484: always returns 1, recursive is true, bailout is false, showall is
                   3485: false. The resources will be returned in a list containing the
                   3486: resource objects for the corresponding resources, with B<no structure 
                   3487: information> in the list; regardless of branching, recursion, etc.,
                   3488: it will be a flat list.
1.174     albertel 3489: 
                   3490: Thus, this is suitable for cases where you don't want the structure,
                   3491: just a list of all resources. It is also suitable for finding out how
                   3492: many resources match a given description; for this use, if all you
                   3493: want to know is if I<any> resources match the description, the bailout
                   3494: parameter will allow you to avoid potentially expensive enumeration of
                   3495: all matching resources.
1.145     bowersj2 3496: 
1.318     albertel 3497: =item * B<hasResource>(map, filterFunc, recursive, showall):
1.145     bowersj2 3498: 
1.398     banghart 3499: Convenience method for
1.146     bowersj2 3500: 
1.318     albertel 3501:  scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0
1.146     bowersj2 3502: 
1.174     albertel 3503: which will tell whether the map has resources matching the description
                   3504: in the filter function.
1.146     bowersj2 3505: 
1.352     raeburn  3506: =item * B<usedVersion>(url):
                   3507: 
                   3508: Retrieves version infomation for a url. Returns the version (a number, or 
                   3509: the string "mostrecent") for resources which have version information in  
                   3510: the big hash.
1.448     raeburn  3511: 
1.145     bowersj2 3512: =cut
                   3513: 
1.277     matthew  3514: 
1.145     bowersj2 3515: sub getResourceByUrl {
                   3516:     my $self = shift;
                   3517:     my $resUrl = shift;
1.352     raeburn  3518:     my $multiple = shift;
1.145     bowersj2 3519: 
                   3520:     if (ref($resUrl)) { return $resUrl; }
                   3521: 
                   3522:     $resUrl = &Apache::lonnet::clutter($resUrl);
                   3523:     my $resId = $self->{NAV_HASH}->{'ids_' . $resUrl};
1.352     raeburn  3524:     if (!$resId) { return ''; }
                   3525:     if ($multiple) {
                   3526:         my @resources = ();
                   3527:         my @resIds = split (/,/, $resId);
                   3528:         foreach my $id (@resIds) {
1.353     raeburn  3529:             my $resourceId = $self->getById($id);
                   3530:             if ($resourceId) { 
                   3531:                 push(@resources,$resourceId);
1.352     raeburn  3532:             }
                   3533:         }
                   3534:         return @resources;
                   3535:     } else {
                   3536:         if ($resId =~ /,/) {
                   3537:             $resId = (split (/,/, $resId))[0];
                   3538:         }
                   3539:         return $self->getById($resId);
1.145     bowersj2 3540:     }
                   3541: }
                   3542: 
                   3543: sub retrieveResources {
                   3544:     my $self = shift;
                   3545:     my $map = shift;
                   3546:     my $filterFunc = shift;
                   3547:     if (!defined ($filterFunc)) {
                   3548:         $filterFunc = sub {return 1;};
                   3549:     }
                   3550:     my $recursive = shift;
                   3551:     if (!defined($recursive)) { $recursive = 1; }
                   3552:     my $bailout = shift;
                   3553:     if (!defined($bailout)) { $bailout = 0; }
1.314     albertel 3554:     my $showall = shift;
1.506     raeburn  3555:     my $noblockcheck = shift;
1.145     bowersj2 3556:     # Create the necessary iterator.
                   3557:     if (!ref($map)) { # assume it's a url of a map.
1.172     bowersj2 3558:         $map = $self->getResourceByUrl($map);
1.145     bowersj2 3559:     }
                   3560: 
1.213     bowersj2 3561:     # If nothing was passed, assume top-level map
                   3562:     if (!$map) {
                   3563: 	$map = $self->getById('0.0');
                   3564:     }
                   3565: 
1.145     bowersj2 3566:     # Check the map's validity.
1.213     bowersj2 3567:     if (!$map->is_map()) {
1.145     bowersj2 3568:         # Oh, to throw an exception.... how I'd love that!
                   3569:         return ();
                   3570:     }
                   3571: 
1.146     bowersj2 3572:     # Get an iterator.
                   3573:     my $it = $self->getIterator($map->map_start(), $map->map_finish(),
1.314     albertel 3574:                                 undef, $recursive, $showall);
1.146     bowersj2 3575: 
                   3576:     my @resources = ();
                   3577: 
1.400     albertel 3578:     if (&$filterFunc($map)) {
                   3579: 	push(@resources, $map);
                   3580:     }
                   3581: 
1.146     bowersj2 3582:     # Run down the iterator and collect the resources.
1.222     bowersj2 3583:     my $curRes;
                   3584: 
1.505     raeburn  3585:     while ($curRes = $it->next(undef,$noblockcheck)) {
1.146     bowersj2 3586:         if (ref($curRes)) {
                   3587:             if (!&$filterFunc($curRes)) {
                   3588:                 next;
                   3589:             }
                   3590: 
1.400     albertel 3591:             push(@resources, $curRes);
1.146     bowersj2 3592: 
                   3593:             if ($bailout) {
                   3594:                 return @resources;
                   3595:             }
                   3596:         }
                   3597: 
                   3598:     }
                   3599: 
                   3600:     return @resources;
                   3601: }
                   3602: 
                   3603: sub hasResource {
                   3604:     my $self = shift;
                   3605:     my $map = shift;
                   3606:     my $filterFunc = shift;
                   3607:     my $recursive = shift;
1.318     albertel 3608:     my $showall = shift;
1.146     bowersj2 3609:     
1.318     albertel 3610:     return scalar($self->retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0;
1.133     bowersj2 3611: }
1.51      bowersj2 3612: 
1.352     raeburn  3613: sub usedVersion {
                   3614:     my $self = shift;
                   3615:     my $linkurl = shift;
                   3616:     return $self->navhash("version_$linkurl");
                   3617: }
                   3618: 
1.542     raeburn  3619: sub isFirstResource {
                   3620:     my $self = shift;
                   3621:     my $map = shift;
                   3622:     my $symb = shift;
                   3623:     return unless (ref($map));
                   3624:     my $isfirst;
                   3625:     my $firstResource = $map->map_start();
                   3626:     if (ref($firstResource)) {
                   3627:         if ((!$firstResource->is_map()) && ($firstResource->src() ne ''))  {
                   3628:             if ($firstResource->symb() eq $symb) {
                   3629:                 $isfirst = 1;
                   3630:             } else {
                   3631:                 $isfirst = 0;
                   3632:             }
                   3633:         } else {
                   3634:             my $it = $self->getIterator($firstResource,undef,undef,1);
                   3635:             while ( my $res=$it->next()) {
                   3636:                 if ((ref($res)) && ($res->src() ne '') && (!$res->is_map())) {
                   3637:                     if ($res->symb() eq $symb) {
                   3638:                         $isfirst = 1;
                   3639:                     } else {
                   3640:                         $isfirst = 0;
                   3641:                     }
                   3642:                     last;
                   3643:                 }
                   3644:             }
                   3645:         }
                   3646:     }
                   3647:     return $isfirst;
                   3648: }
                   3649: 
                   3650: sub isLastResource {
                   3651:     my $self = shift;
                   3652:     my $map = shift;
                   3653:     my $symb = shift;
                   3654:     return unless (ref($map));
                   3655:     my $islast;
                   3656:     my $lastResource = $map->map_finish();
                   3657:     if (ref($lastResource)) {
                   3658:         if ((!$lastResource->is_map()) && ($lastResource->src() ne ''))  {
                   3659:             if ($lastResource->symb() eq $symb) {
                   3660:                 $islast = 1;
                   3661:             } else {
                   3662:                 $islast = 0;
                   3663:             }
                   3664:         } else {
                   3665:             my $currRes = $self->getBySymb($symb);
                   3666:             if (ref($currRes)) {
                   3667:                 my $it = $self->getIterator($currRes,undef,undef,1);
                   3668:                 while ( my $res=$it->next()) {
                   3669:                     if ((ref($res)) && ($res->src() ne '') && (!$res->is_map())) {
                   3670:                         if ($res->symb() eq $symb) {
                   3671:                             $islast = 1;
                   3672:                         } else {
                   3673:                             $islast = 0;
                   3674:                         }
                   3675:                         last;
                   3676:                     }
                   3677:                 }
                   3678:             }
                   3679:         }
                   3680:     }
                   3681:     return $islast;
                   3682: }
                   3683: 
1.51      bowersj2 3684: 1;
                   3685: 
                   3686: package Apache::lonnavmaps::iterator;
1.365     albertel 3687: use Scalar::Util qw(weaken);
1.320     albertel 3688: use Apache::lonnet;
                   3689: 
1.51      bowersj2 3690: =pod
                   3691: 
                   3692: =back
                   3693: 
1.174     albertel 3694: =head1 Object: navmap Iterator
1.51      bowersj2 3695: 
1.174     albertel 3696: An I<iterator> encapsulates the logic required to traverse a data
                   3697: structure. navmap uses an iterator to traverse the course map
                   3698: according to the criteria you wish to use.
                   3699: 
                   3700: To obtain an iterator, call the B<getIterator>() function of a
                   3701: B<navmap> object. (Do not instantiate Apache::lonnavmaps::iterator
                   3702: directly.) This will return a reference to the iterator:
1.51      bowersj2 3703: 
                   3704: C<my $resourceIterator = $navmap-E<gt>getIterator();>
                   3705: 
                   3706: To get the next thing from the iterator, call B<next>:
                   3707: 
                   3708: C<my $nextThing = $resourceIterator-E<gt>next()>
                   3709: 
                   3710: getIterator behaves as follows:
                   3711: 
                   3712: =over 4
                   3713: 
1.553     raeburn  3714: =item * B<getIterator>(firstResource, finishResource, filterHash, condition, forceTop, returnTopMap, $deeplinklisted):
1.174     albertel 3715: 
                   3716: All parameters are optional. firstResource is a resource reference
                   3717: corresponding to where the iterator should start. It defaults to
                   3718: navmap->firstResource() for the corresponding nav map. finishResource
                   3719: corresponds to where you want the iterator to end, defaulting to
                   3720: navmap->finishResource(). filterHash is a hash used as a set
                   3721: containing strings representing the resource IDs, defaulting to
                   3722: empty. Condition is a 1 or 0 that sets what to do with the filter
1.205     bowersj2 3723: hash: If a 0, then only resources that exist IN the filterHash will be
1.174     albertel 3724: recursed on. If it is a 1, only resources NOT in the filterHash will
                   3725: be recursed on. Defaults to 0. forceTop is a boolean value. If it is
                   3726: false (default), the iterator will only return the first level of map
                   3727: that is not just a single, 'redirecting' map. If true, the iterator
                   3728: will return all information, starting with the top-level map,
                   3729: regardless of content. returnTopMap, if true (default false), will
                   3730: cause the iterator to return the top-level map object (resource 0.0)
1.553     raeburn  3731: before anything else. deeplinklisted if true (default false), will
                   3732: check "listed" status of a resource with a deeplink, and unless "absent"
                   3733: will exclude deeplink checking when retrieving the browsePriv from
1.554     raeburn  3734: lonnet::allowed().
1.174     albertel 3735: 
                   3736: Thus, by default, only top-level resources will be shown. Change the
                   3737: condition to a 1 without changing the hash, and all resources will be
                   3738: shown. Changing the condition to 1 and including some values in the
                   3739: hash will allow you to selectively suppress parts of the navmap, while
                   3740: leaving it on 0 and adding things to the hash will allow you to
                   3741: selectively add parts of the nav map. See the handler code for
                   3742: examples.
                   3743: 
                   3744: The iterator will return either a reference to a resource object, or a
                   3745: token representing something in the map, such as the beginning of a
                   3746: new branch. The possible tokens are:
                   3747: 
                   3748: =over 4
1.51      bowersj2 3749: 
1.222     bowersj2 3750: =item * B<END_ITERATOR>:
                   3751: 
                   3752: The iterator has returned all that it's going to. Further calls to the
                   3753: iterator will just produce more of these. This is a "false" value, and
                   3754: is the only false value the iterator which will be returned, so it can
                   3755: be used as a loop sentinel.
                   3756: 
1.217     bowersj2 3757: =item * B<BEGIN_MAP>:
1.51      bowersj2 3758: 
1.174     albertel 3759: A new map is being recursed into. This is returned I<after> the map
                   3760: resource itself is returned.
1.51      bowersj2 3761: 
1.217     bowersj2 3762: =item * B<END_MAP>:
1.174     albertel 3763: 
                   3764: The map is now done.
1.51      bowersj2 3765: 
1.217     bowersj2 3766: =item * B<BEGIN_BRANCH>:
1.70      bowersj2 3767: 
1.174     albertel 3768: A branch is now starting. The next resource returned will be the first
                   3769: in that branch.
1.70      bowersj2 3770: 
1.217     bowersj2 3771: =item * B<END_BRANCH>:
1.70      bowersj2 3772: 
1.174     albertel 3773: The branch is now done.
1.51      bowersj2 3774: 
                   3775: =back
                   3776: 
1.174     albertel 3777: The tokens are retreivable via methods on the iterator object, i.e.,
                   3778: $iterator->END_MAP.
1.70      bowersj2 3779: 
1.174     albertel 3780: Maps can contain empty resources. The iterator will automatically skip
                   3781: over such resources, but will still treat the structure
                   3782: correctly. Thus, a complicated map with several branches, but
                   3783: consisting entirely of empty resources except for one beginning or
                   3784: ending resource, will cause a lot of BRANCH_STARTs and BRANCH_ENDs,
                   3785: but only one resource will be returned.
1.116     bowersj2 3786: 
1.242     matthew  3787: =back
                   3788: 
1.222     bowersj2 3789: =head2 Normal Usage
                   3790: 
                   3791: Normal usage of the iterator object is to do the following:
                   3792: 
                   3793:  my $it = $navmap->getIterator([your params here]);
                   3794:  my $curRes;
                   3795:  while ($curRes = $it->next()) {
                   3796:    [your logic here]
                   3797:  }
                   3798: 
                   3799: Note that inside of the loop, it's frequently useful to check if
                   3800: "$curRes" is a reference or not with the reference function; only
                   3801: resource objects will be references, and any non-references will 
                   3802: be the tokens described above.
                   3803: 
1.505     raeburn  3804: The next() routine can take two (optional) arguments:
                   3805: closeAllPages - if true will not recurse down a .page
                   3806: noblockcheck - passed to browsePriv() for passing as sixth arg to
                   3807: call to lonnet::allowed. This needs to be set if retrieveResources
                   3808: was already called from another routine called within lonnet::allowed, 
                   3809: so as to prevent recursion.
                   3810: 
1.506     raeburn  3811: Also note there is some old code floating around that tries to track
1.222     bowersj2 3812: the depth of the iterator to see when it's done; do not copy that 
1.398     banghart 3813: code. It is difficult to get right and harder to understand than
1.222     bowersj2 3814: this. They should be migrated to this new style.
1.51      bowersj2 3815: 
                   3816: =cut
                   3817: 
                   3818: # Here are the tokens for the iterator:
                   3819: 
1.222     bowersj2 3820: sub END_ITERATOR { return 0; }
1.51      bowersj2 3821: sub BEGIN_MAP { return 1; }    # begining of a new map
                   3822: sub END_MAP { return 2; }      # end of the map
                   3823: sub BEGIN_BRANCH { return 3; } # beginning of a branch
                   3824: sub END_BRANCH { return 4; }   # end of a branch
1.89      bowersj2 3825: sub FORWARD { return 1; }      # go forward
                   3826: sub BACKWARD { return 2; }
1.51      bowersj2 3827: 
1.96      bowersj2 3828: sub min {
                   3829:     (my $a, my $b) = @_;
                   3830:     if ($a < $b) { return $a; } else { return $b; }
                   3831: }
                   3832: 
1.94      bowersj2 3833: sub new {
                   3834:     # magic invocation to create a class instance
                   3835:     my $proto = shift;
                   3836:     my $class = ref($proto) || $proto;
                   3837:     my $self = {};
                   3838: 
1.300     albertel 3839:     weaken($self->{NAV_MAP} = shift);
1.94      bowersj2 3840:     return undef unless ($self->{NAV_MAP});
                   3841: 
1.454     www      3842:     $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
                   3843:     $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
                   3844: 
1.94      bowersj2 3845:     # Handle the parameters
                   3846:     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
                   3847:     $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
                   3848: 
                   3849:     # If the given resources are just the ID of the resource, get the
                   3850:     # objects
                   3851:     if (!ref($self->{FIRST_RESOURCE})) { $self->{FIRST_RESOURCE} = 
                   3852:              $self->{NAV_MAP}->getById($self->{FIRST_RESOURCE}); }
                   3853:     if (!ref($self->{FINISH_RESOURCE})) { $self->{FINISH_RESOURCE} = 
                   3854:              $self->{NAV_MAP}->getById($self->{FINISH_RESOURCE}); }
                   3855: 
                   3856:     $self->{FILTER} = shift;
                   3857: 
                   3858:     # A hash, used as a set, of resource already seen
                   3859:     $self->{ALREADY_SEEN} = shift;
                   3860:     if (!defined($self->{ALREADY_SEEN})) { $self->{ALREADY_SEEN} = {} };
                   3861:     $self->{CONDITION} = shift;
                   3862: 
1.116     bowersj2 3863:     # Do we want to automatically follow "redirection" maps?
                   3864:     $self->{FORCE_TOP} = shift;
                   3865: 
1.162     bowersj2 3866:     # Do we want to return the top-level map object (resource 0.0)?
                   3867:     $self->{RETURN_0} = shift;
                   3868:     # have we done that yet?
                   3869:     $self->{HAVE_RETURNED_0} = 0;
                   3870: 
1.553     raeburn  3871:     # Do we want to check the "listed" status for a resource for which
                   3872:     # deeplinking applies.
                   3873:     $self->{DEEPLINKLISTED} = shift;
                   3874: 
1.94      bowersj2 3875:     # Now, we need to pre-process the map, by walking forward and backward
                   3876:     # over the parts of the map we're going to look at.
1.96      bowersj2 3877: 
1.97      bowersj2 3878:     # The processing steps are exactly the same, except for a few small 
                   3879:     # changes, so I bundle those up in the following list of two elements:
                   3880:     # (direction_to_iterate, VAL_name, next_resource_method_to_call,
                   3881:     # first_resource).
                   3882:     # This prevents writing nearly-identical code twice.
                   3883:     my @iterations = ( [FORWARD(), 'TOP_DOWN_VAL', 'getNext', 
                   3884:                         'FIRST_RESOURCE'],
                   3885:                        [BACKWARD(), 'BOT_UP_VAL', 'getPrevious', 
                   3886:                         'FINISH_RESOURCE'] );
                   3887: 
1.98      bowersj2 3888:     my $maxDepth = 0; # tracks max depth
                   3889: 
1.116     bowersj2 3890:     # If there is only one resource in this map, and it's a map, we
                   3891:     # want to remember that, so the user can ask for the first map
                   3892:     # that isn't just a redirector.
                   3893:     my $resource; my $resourceCount = 0;
                   3894: 
1.209     bowersj2 3895:     # Documentation on this algorithm can be found in the CVS repository at 
                   3896:     # /docs/lonnavdocs; these "**#**" markers correspond to documentation
                   3897:     # in that file.
1.107     bowersj2 3898:     # **1**
                   3899: 
1.97      bowersj2 3900:     foreach my $pass (@iterations) {
                   3901:         my $direction = $pass->[0];
                   3902:         my $valName = $pass->[1];
                   3903:         my $nextResourceMethod = $pass->[2];
                   3904:         my $firstResourceName = $pass->[3];
                   3905: 
                   3906:         my $iterator = Apache::lonnavmaps::DFSiterator->new($self->{NAV_MAP}, 
                   3907:                                                             $self->{FIRST_RESOURCE},
                   3908:                                                             $self->{FINISH_RESOURCE},
                   3909:                                                             {}, undef, 0, $direction);
1.96      bowersj2 3910:     
1.97      bowersj2 3911:         # prime the recursion
                   3912:         $self->{$firstResourceName}->{DATA}->{$valName} = 0;
1.222     bowersj2 3913: 	$iterator->next();
1.97      bowersj2 3914:         my $curRes = $iterator->next();
1.222     bowersj2 3915: 	my $depth = 1;
                   3916:         while ($depth > 0) {
                   3917: 	    if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }
                   3918: 	    if ($curRes == $iterator->END_MAP()) { $depth--; }
                   3919: 
1.97      bowersj2 3920:             if (ref($curRes)) {
1.116     bowersj2 3921:                 # If there's only one resource, this will save it
1.117     bowersj2 3922:                 # we have to filter empty resources from consideration here,
                   3923:                 # or even "empty", redirecting maps have two (start & finish)
                   3924:                 # or three (start, finish, plus redirector)
                   3925:                 if($direction == FORWARD && $curRes->src()) { 
                   3926:                     $resource = $curRes; $resourceCount++; 
                   3927:                 }
1.97      bowersj2 3928:                 my $resultingVal = $curRes->{DATA}->{$valName};
                   3929:                 my $nextResources = $curRes->$nextResourceMethod();
1.116     bowersj2 3930:                 my $nextCount = scalar(@{$nextResources});
1.104     bowersj2 3931: 
1.116     bowersj2 3932:                 if ($nextCount == 1) { # **3**
1.97      bowersj2 3933:                     my $current = $nextResources->[0]->{DATA}->{$valName} || 999999999;
                   3934:                     $nextResources->[0]->{DATA}->{$valName} = min($resultingVal, $current);
                   3935:                 }
                   3936:                 
1.116     bowersj2 3937:                 if ($nextCount > 1) { # **4**
1.97      bowersj2 3938:                     foreach my $res (@{$nextResources}) {
                   3939:                         my $current = $res->{DATA}->{$valName} || 999999999;
                   3940:                         $res->{DATA}->{$valName} = min($current, $resultingVal + 1);
                   3941:                     }
                   3942:                 }
                   3943:             }
1.96      bowersj2 3944:             
1.107     bowersj2 3945:             # Assign the final val (**2**)
1.97      bowersj2 3946:             if (ref($curRes) && $direction == BACKWARD()) {
1.98      bowersj2 3947:                 my $finalDepth = min($curRes->{DATA}->{TOP_DOWN_VAL},
                   3948:                                      $curRes->{DATA}->{BOT_UP_VAL});
                   3949:                 
                   3950:                 $curRes->{DATA}->{DISPLAY_DEPTH} = $finalDepth;
                   3951:                 if ($finalDepth > $maxDepth) {$maxDepth = $finalDepth;}
1.190     bowersj2 3952:             }
1.222     bowersj2 3953: 
                   3954: 	    $curRes = $iterator->next();
1.96      bowersj2 3955:         }
                   3956:     }
1.94      bowersj2 3957: 
1.116     bowersj2 3958:     # Check: Was this only one resource, a map?
1.255     albertel 3959:     if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) { 
1.116     bowersj2 3960:         my $firstResource = $resource->map_start();
                   3961:         my $finishResource = $resource->map_finish();
1.500     raeburn  3962: 	return Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                   3963: 						 $finishResource, $self->{FILTER},
                   3964: 						 $self->{ALREADY_SEEN}, 
                   3965: 						 $self->{CONDITION},
1.553     raeburn  3966: 						 $self->{FORCE_TOP},
                   3967:                                                  undef,$self->{DEEPLINKLISTED});
1.116     bowersj2 3968:     }
                   3969: 
1.98      bowersj2 3970:     # Set up some bookkeeping information.
                   3971:     $self->{CURRENT_DEPTH} = 0;
                   3972:     $self->{MAX_DEPTH} = $maxDepth;
                   3973:     $self->{STACK} = [];
                   3974:     $self->{RECURSIVE_ITERATOR_FLAG} = 0;
1.222     bowersj2 3975:     $self->{FINISHED} = 0; # When true, the iterator has finished
1.98      bowersj2 3976: 
                   3977:     for (my $i = 0; $i <= $self->{MAX_DEPTH}; $i++) {
                   3978:         push @{$self->{STACK}}, [];
                   3979:     }
                   3980: 
1.107     bowersj2 3981:     # Prime the recursion w/ the first resource **5**
1.98      bowersj2 3982:     push @{$self->{STACK}->[0]}, $self->{FIRST_RESOURCE};
                   3983:     $self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1;
                   3984: 
                   3985:     bless ($self);
                   3986:     return $self;
                   3987: }
                   3988: 
                   3989: sub next {
                   3990:     my $self = shift;
1.256     albertel 3991:     my $closeAllPages=shift;
1.505     raeburn  3992:     my $noblockcheck = shift;
1.222     bowersj2 3993:     if ($self->{FINISHED}) {
                   3994: 	return END_ITERATOR();
                   3995:     }
                   3996: 
1.162     bowersj2 3997:     # If we want to return the top-level map object, and haven't yet,
                   3998:     # do so.
                   3999:     if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) {
                   4000:         $self->{HAVE_RETURNED_0} = 1;
1.470     foxr     4001: 	my $nextTopLevel = $self->{NAV_MAP}->getById('0.0');
1.162     bowersj2 4002:         return $self->{NAV_MAP}->getById('0.0');
                   4003:     }
1.401     albertel 4004:     if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0_BEGIN_MAP}) {
                   4005: 	$self->{HAVE_RETURNED_0_BEGIN_MAP} = 1;
                   4006: 	return $self->BEGIN_MAP();
                   4007:     }
1.98      bowersj2 4008: 
                   4009:     if ($self->{RECURSIVE_ITERATOR_FLAG}) {
                   4010:         # grab the next from the recursive iterator 
1.256     albertel 4011:         my $next = $self->{RECURSIVE_ITERATOR}->next($closeAllPages);
1.98      bowersj2 4012: 
                   4013:         # is it a begin or end map? If so, update the depth
                   4014:         if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; }
                   4015:         if ($next == END_MAP() ) { $self->{RECURSIVE_DEPTH}--; }
                   4016: 
                   4017:         # Are we back at depth 0? If so, stop recursing
                   4018:         if ($self->{RECURSIVE_DEPTH} == 0) {
                   4019:             $self->{RECURSIVE_ITERATOR_FLAG} = 0;
                   4020:         }
                   4021:         return $next;
                   4022:     }
                   4023: 
                   4024:     if (defined($self->{FORCE_NEXT})) {
                   4025:         my $tmp = $self->{FORCE_NEXT};
                   4026:         $self->{FORCE_NEXT} = undef;
                   4027:         return $tmp;
                   4028:     }
                   4029: 
                   4030:     # Have we not yet begun? If not, return BEGIN_MAP and
                   4031:     # remember we've started.
                   4032:     if ( !$self->{STARTED} ) { 
                   4033:         $self->{STARTED} = 1;
                   4034:         return $self->BEGIN_MAP();
                   4035:     }
                   4036: 
                   4037:     # Here's the guts of the iterator.
                   4038:     
                   4039:     # Find the next resource, if any.
                   4040:     my $found = 0;
                   4041:     my $i = $self->{MAX_DEPTH};
                   4042:     my $newDepth;
                   4043:     my $here;
                   4044:     while ( $i >= 0 && !$found ) {
1.107     bowersj2 4045:         if ( scalar(@{$self->{STACK}->[$i]}) > 0 ) { # **6**
                   4046:             $here = pop @{$self->{STACK}->[$i]}; # **7**
1.98      bowersj2 4047:             $found = 1;
                   4048:             $newDepth = $i;
                   4049:         }
                   4050:         $i--;
                   4051:     }
                   4052: 
                   4053:     # If we still didn't find anything, we're done.
                   4054:     if ( !$found ) {
                   4055:         # We need to get back down to the correct branch depth
                   4056:         if ( $self->{CURRENT_DEPTH} > 0 ) {
                   4057:             $self->{CURRENT_DEPTH}--;
                   4058:             return END_BRANCH();
                   4059:         } else {
1.222     bowersj2 4060: 	    $self->{FINISHED} = 1;
1.98      bowersj2 4061:             return END_MAP();
                   4062:         }
                   4063:     }
                   4064: 
1.104     bowersj2 4065:     # If this is not a resource, it must be an END_BRANCH marker we want
                   4066:     # to return directly.
1.107     bowersj2 4067:     if (!ref($here)) { # **8**
1.104     bowersj2 4068:         if ($here == END_BRANCH()) { # paranoia, in case of later extension
                   4069:             $self->{CURRENT_DEPTH}--;
                   4070:             return $here;
                   4071:         }
                   4072:     }
                   4073: 
                   4074:     # Otherwise, it is a resource and it's safe to store in $self->{HERE}
                   4075:     $self->{HERE} = $here;
                   4076: 
1.98      bowersj2 4077:     # Get to the right level
                   4078:     if ( $self->{CURRENT_DEPTH} > $newDepth ) {
                   4079:         push @{$self->{STACK}->[$newDepth]}, $here;
                   4080:         $self->{CURRENT_DEPTH}--;
                   4081:         return END_BRANCH();
                   4082:     }
                   4083:     if ( $self->{CURRENT_DEPTH} < $newDepth) {
                   4084:         push @{$self->{STACK}->[$newDepth]}, $here;
                   4085:         $self->{CURRENT_DEPTH}++;
                   4086:         return BEGIN_BRANCH();
                   4087:     }
                   4088: 
                   4089:     # If we made it here, we have the next resource, and we're at the
                   4090:     # right branch level. So let's examine the resource for where
                   4091:     # we can get to from here.
                   4092: 
                   4093:     # So we need to look at all the resources we can get to from here,
                   4094:     # categorize them if we haven't seen them, remember if we have a new
                   4095:     my $nextUnfiltered = $here->getNext();
1.470     foxr     4096: 
                   4097: 
1.104     bowersj2 4098:     my $maxDepthAdded = -1;
                   4099:     
1.98      bowersj2 4100:     for (@$nextUnfiltered) {
                   4101:         if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) {
1.104     bowersj2 4102:             my $depth = $_->{DATA}->{DISPLAY_DEPTH};
                   4103:             push @{$self->{STACK}->[$depth]}, $_;
1.98      bowersj2 4104:             $self->{ALREADY_SEEN}->{$_->{ID}} = 1;
1.104     bowersj2 4105:             if ($maxDepthAdded < $depth) { $maxDepthAdded = $depth; }
1.98      bowersj2 4106:         }
                   4107:     }
1.104     bowersj2 4108: 
                   4109:     # Is this the end of a branch? If so, all of the resources examined above
1.398     banghart 4110:     # led to lower levels than the one we are currently at, so we push a END_BRANCH
1.104     bowersj2 4111:     # marker onto the stack so we don't forget.
                   4112:     # Example: For the usual A(BC)(DE)F case, when the iterator goes down the
                   4113:     # BC branch and gets to C, it will see F as the only next resource, but it's
                   4114:     # one level lower. Thus, this is the end of the branch, since there are no
                   4115:     # more resources added to this level or above.
1.111     bowersj2 4116:     # We don't do this if the examined resource is the finish resource,
                   4117:     # because the condition given above is true, but the "END_MAP" will
                   4118:     # take care of things and we should already be at depth 0.
1.104     bowersj2 4119:     my $isEndOfBranch = $maxDepthAdded < $self->{CURRENT_DEPTH};
1.111     bowersj2 4120:     if ($isEndOfBranch && $here != $self->{FINISH_RESOURCE}) { # **9**
1.104     bowersj2 4121:         push @{$self->{STACK}->[$self->{CURRENT_DEPTH}]}, END_BRANCH();
                   4122:     }
                   4123: 
1.98      bowersj2 4124:     # That ends the main iterator logic. Now, do we want to recurse
                   4125:     # down this map (if this resource is a map)?
1.256     albertel 4126:     if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) &&
1.550     raeburn  4127:         (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION}) &&
                   4128:         ($env{'request.role.adv'} || !$self->{HERE}->randomout())) {
1.98      bowersj2 4129:         $self->{RECURSIVE_ITERATOR_FLAG} = 1;
                   4130:         my $firstResource = $self->{HERE}->map_start();
                   4131:         my $finishResource = $self->{HERE}->map_finish();
                   4132:         $self->{RECURSIVE_ITERATOR} = 
                   4133:             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                   4134:                                               $finishResource, $self->{FILTER},
1.314     albertel 4135:                                               $self->{ALREADY_SEEN},
                   4136: 					      $self->{CONDITION},
1.553     raeburn  4137: 					      $self->{FORCE_TOP},
                   4138:                                               undef,$self->{DEEPLINKLISTED});
1.98      bowersj2 4139:     }
                   4140: 
1.116     bowersj2 4141:     # If this is a blank resource, don't actually return it.
1.117     bowersj2 4142:     # Should you ever find you need it, make sure to add an option to the code
                   4143:     #  that you can use; other things depend on this behavior.
1.553     raeburn  4144:     my $browsePriv = $self->{HERE}->browsePriv($noblockcheck,$self->{DEEPLINKLISTED});
1.138     bowersj2 4145:     if (!$self->{HERE}->src() || 
                   4146:         (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {
1.256     albertel 4147:         return $self->next($closeAllPages);
1.116     bowersj2 4148:     }
                   4149: 
1.98      bowersj2 4150:     return $self->{HERE};
                   4151: 
                   4152: }
                   4153: 
                   4154: =pod
                   4155: 
1.174     albertel 4156: The other method available on the iterator is B<getStack>, which
                   4157: returns an array populated with the current 'stack' of maps, as
                   4158: references to the resource objects. Example: This is useful when
                   4159: making the navigation map, as we need to check whether we are under a
                   4160: page map to see if we need to link directly to the resource, or to the
                   4161: page. The first elements in the array will correspond to the top of
                   4162: the stack (most inclusive map).
1.98      bowersj2 4163: 
                   4164: =cut
                   4165: 
                   4166: sub getStack {
                   4167:     my $self=shift;
                   4168: 
                   4169:     my @stack;
                   4170: 
                   4171:     $self->populateStack(\@stack);
                   4172: 
                   4173:     return \@stack;
                   4174: }
                   4175: 
                   4176: # Private method: Calls the iterators recursively to populate the stack.
                   4177: sub populateStack {
                   4178:     my $self=shift;
                   4179:     my $stack = shift;
                   4180: 
                   4181:     push @$stack, $self->{HERE} if ($self->{HERE});
                   4182: 
                   4183:     if ($self->{RECURSIVE_ITERATOR_FLAG}) {
                   4184:         $self->{RECURSIVE_ITERATOR}->populateStack($stack);
                   4185:     }
1.94      bowersj2 4186: }
                   4187: 
                   4188: 1;
                   4189: 
                   4190: package Apache::lonnavmaps::DFSiterator;
1.365     albertel 4191: use Scalar::Util qw(weaken);
1.320     albertel 4192: use Apache::lonnet;
                   4193: 
1.100     bowersj2 4194: # Not documented in the perldoc: This is a simple iterator that just walks
                   4195: #  through the nav map and presents the resources in a depth-first search
                   4196: #  fashion, ignorant of conditionals, randomized resources, etc. It presents
                   4197: #  BEGIN_MAP and END_MAP, but does not understand branches at all. It is
                   4198: #  useful for pre-processing of some kind, and is in fact used by the main
                   4199: #  iterator that way, but that's about it.
                   4200: # One could imagine merging this into the init routine of the main iterator,
1.251     www      4201: #  but this might as well be left separate, since it is possible some other
1.100     bowersj2 4202: #  use might be found for it. - Jeremy
1.94      bowersj2 4203: 
1.117     bowersj2 4204: # Unlike the main iterator, this DOES return all resources, even blank ones.
                   4205: #  The main iterator needs them to correctly preprocess the map.
                   4206: 
1.94      bowersj2 4207: sub BEGIN_MAP { return 1; }    # begining of a new map
                   4208: sub END_MAP { return 2; }      # end of the map
                   4209: sub FORWARD { return 1; }      # go forward
                   4210: sub BACKWARD { return 2; }
                   4211: 
1.100     bowersj2 4212: # Params: Nav map ref, first resource id/ref, finish resource id/ref,
                   4213: #         filter hash ref (or undef), already seen hash or undef, condition
                   4214: #         (as in main iterator), direction FORWARD or BACKWARD (undef->forward).
1.51      bowersj2 4215: sub new {
                   4216:     # magic invocation to create a class instance
                   4217:     my $proto = shift;
                   4218:     my $class = ref($proto) || $proto;
                   4219:     my $self = {};
                   4220: 
1.300     albertel 4221:     weaken($self->{NAV_MAP} = shift);
1.51      bowersj2 4222:     return undef unless ($self->{NAV_MAP});
                   4223: 
1.454     www      4224:     $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
                   4225:     $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
                   4226: 
1.51      bowersj2 4227:     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
                   4228:     $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
                   4229: 
                   4230:     # If the given resources are just the ID of the resource, get the
                   4231:     # objects
                   4232:     if (!ref($self->{FIRST_RESOURCE})) { $self->{FIRST_RESOURCE} = 
                   4233:              $self->{NAV_MAP}->getById($self->{FIRST_RESOURCE}); }
                   4234:     if (!ref($self->{FINISH_RESOURCE})) { $self->{FINISH_RESOURCE} = 
                   4235:              $self->{NAV_MAP}->getById($self->{FINISH_RESOURCE}); }
                   4236: 
                   4237:     $self->{FILTER} = shift;
                   4238: 
                   4239:     # A hash, used as a set, of resource already seen
                   4240:     $self->{ALREADY_SEEN} = shift;
1.140     bowersj2 4241:      if (!defined($self->{ALREADY_SEEN})) { $self->{ALREADY_SEEN} = {} };
1.63      bowersj2 4242:     $self->{CONDITION} = shift;
1.89      bowersj2 4243:     $self->{DIRECTION} = shift || FORWARD();
1.51      bowersj2 4244: 
1.100     bowersj2 4245:     # Flag: Have we started yet?
1.51      bowersj2 4246:     $self->{STARTED} = 0;
                   4247: 
                   4248:     # Should we continue calling the recursive iterator, if any?
                   4249:     $self->{RECURSIVE_ITERATOR_FLAG} = 0;
                   4250:     # The recursive iterator, if any
                   4251:     $self->{RECURSIVE_ITERATOR} = undef;
                   4252:     # Are we recursing on a map, or a branch?
                   4253:     $self->{RECURSIVE_MAP} = 1; # we'll manually unset this when recursing on branches
                   4254:     # And the count of how deep it is, so that this iterator can keep track of
                   4255:     # when to pick back up again.
                   4256:     $self->{RECURSIVE_DEPTH} = 0;
                   4257: 
                   4258:     # For keeping track of our branches, we maintain our own stack
1.100     bowersj2 4259:     $self->{STACK} = [];
1.51      bowersj2 4260: 
                   4261:     # Start with the first resource
1.89      bowersj2 4262:     if ($self->{DIRECTION} == FORWARD) {
1.100     bowersj2 4263:         push @{$self->{STACK}}, $self->{FIRST_RESOURCE};
1.89      bowersj2 4264:     } else {
1.100     bowersj2 4265:         push @{$self->{STACK}}, $self->{FINISH_RESOURCE};
1.89      bowersj2 4266:     }
1.51      bowersj2 4267: 
                   4268:     bless($self);
                   4269:     return $self;
                   4270: }
                   4271: 
                   4272: sub next {
                   4273:     my $self = shift;
                   4274:     
                   4275:     # Are we using a recursive iterator? If so, pull from that and
                   4276:     # watch the depth; we want to resume our level at the correct time.
1.98      bowersj2 4277:     if ($self->{RECURSIVE_ITERATOR_FLAG}) {
1.51      bowersj2 4278:         # grab the next from the recursive iterator
                   4279:         my $next = $self->{RECURSIVE_ITERATOR}->next();
                   4280:         
                   4281:         # is it a begin or end map? Update depth if so
                   4282:         if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; }
                   4283:         if ($next == END_MAP() ) { $self->{RECURSIVE_DEPTH}--; }
                   4284: 
                   4285:         # Are we back at depth 0? If so, stop recursing.
                   4286:         if ($self->{RECURSIVE_DEPTH} == 0) {
                   4287:             $self->{RECURSIVE_ITERATOR_FLAG} = 0;
                   4288:         }
                   4289:         
                   4290:         return $next;
                   4291:     }
                   4292: 
                   4293:     # Is there a current resource to grab? If not, then return
1.100     bowersj2 4294:     # END_MAP, which will end the iterator.
                   4295:     if (scalar(@{$self->{STACK}}) == 0) {
                   4296:         return $self->END_MAP();
1.51      bowersj2 4297:     }
                   4298: 
                   4299:     # Have we not yet begun? If not, return BEGIN_MAP and 
                   4300:     # remember that we've started.
                   4301:     if ( !$self->{STARTED} ) {
                   4302:         $self->{STARTED} = 1;
                   4303:         return $self->BEGIN_MAP;
                   4304:     }
                   4305: 
                   4306:     # Get the next resource in the branch
1.100     bowersj2 4307:     $self->{HERE} = pop @{$self->{STACK}};
1.52      bowersj2 4308: 
1.100     bowersj2 4309:     # remember that we've seen this, so we don't return it again later
1.51      bowersj2 4310:     $self->{ALREADY_SEEN}->{$self->{HERE}->{ID}} = 1;
                   4311:     
                   4312:     # Get the next possible resources
1.90      bowersj2 4313:     my $nextUnfiltered;
1.89      bowersj2 4314:     if ($self->{DIRECTION} == FORWARD()) {
1.90      bowersj2 4315:         $nextUnfiltered = $self->{HERE}->getNext();
1.89      bowersj2 4316:     } else {
1.90      bowersj2 4317:         $nextUnfiltered = $self->{HERE}->getPrevious();
1.89      bowersj2 4318:     }
1.51      bowersj2 4319:     my $next = [];
                   4320: 
                   4321:     # filter the next possibilities to remove things we've 
1.100     bowersj2 4322:     # already seen.
1.394     raeburn  4323:     foreach my $item (@$nextUnfiltered) {
                   4324:         if (!defined($self->{ALREADY_SEEN}->{$item->{ID}})) {
                   4325:             push @$next, $item;
1.52      bowersj2 4326:         }
1.51      bowersj2 4327:     }
                   4328: 
                   4329:     while (@$next) {
1.100     bowersj2 4330:         # copy the next possibilities over to the stack
                   4331:         push @{$self->{STACK}}, shift @$next;
1.51      bowersj2 4332:     }
                   4333: 
                   4334:     # If this is a map and we want to recurse down it... (not filtered out)
1.70      bowersj2 4335:     if ($self->{HERE}->is_map() && 
1.63      bowersj2 4336:          (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) { 
1.51      bowersj2 4337:         $self->{RECURSIVE_ITERATOR_FLAG} = 1;
                   4338:         my $firstResource = $self->{HERE}->map_start();
                   4339:         my $finishResource = $self->{HERE}->map_finish();
                   4340: 
                   4341:         $self->{RECURSIVE_ITERATOR} =
1.94      bowersj2 4342:           Apache::lonnavmaps::DFSiterator->new ($self->{NAV_MAP}, $firstResource, 
1.63      bowersj2 4343:                      $finishResource, $self->{FILTER}, $self->{ALREADY_SEEN},
1.91      bowersj2 4344:                                              $self->{CONDITION}, $self->{DIRECTION});
1.51      bowersj2 4345:     }
                   4346: 
                   4347:     return $self->{HERE};
1.190     bowersj2 4348: }
                   4349: 
                   4350: # Identical to the full iterator methods of the same name. Hate to copy/paste
                   4351: # but I also hate to "inherit" either iterator from the other.
                   4352: 
                   4353: sub getStack {
                   4354:     my $self=shift;
                   4355: 
                   4356:     my @stack;
                   4357: 
                   4358:     $self->populateStack(\@stack);
                   4359: 
                   4360:     return \@stack;
                   4361: }
                   4362: 
                   4363: # Private method: Calls the iterators recursively to populate the stack.
                   4364: sub populateStack {
                   4365:     my $self=shift;
                   4366:     my $stack = shift;
                   4367: 
                   4368:     push @$stack, $self->{HERE} if ($self->{HERE});
                   4369: 
                   4370:     if ($self->{RECURSIVE_ITERATOR_FLAG}) {
                   4371:         $self->{RECURSIVE_ITERATOR}->populateStack($stack);
                   4372:     }
1.51      bowersj2 4373: }
                   4374: 
1.1       www      4375: 1;
1.2       www      4376: 
1.51      bowersj2 4377: package Apache::lonnavmaps::resource;
1.365     albertel 4378: use Scalar::Util qw(weaken);
1.51      bowersj2 4379: use Apache::lonnet;
                   4380: 
                   4381: =pod
                   4382: 
1.217     bowersj2 4383: =head1 Object: resource 
1.51      bowersj2 4384: 
1.217     bowersj2 4385: X<resource, navmap object>
1.174     albertel 4386: A resource object encapsulates a resource in a resource map, allowing
                   4387: easy manipulation of the resource, querying the properties of the
                   4388: resource (including user properties), and represents a reference that
                   4389: can be used as the canonical representation of the resource by
                   4390: lonnavmap clients like renderers.
                   4391: 
                   4392: A resource only makes sense in the context of a navmap, as some of the
                   4393: data is stored in the navmap object.
                   4394: 
                   4395: You will probably never need to instantiate this object directly. Use
                   4396: Apache::lonnavmaps::navmap, and use the "start" method to obtain the
                   4397: starting resource.
1.51      bowersj2 4398: 
1.188     bowersj2 4399: Resource objects respect the parameter_hiddenparts, which suppresses 
                   4400: various parts according to the wishes of the map author. As of this
                   4401: writing, there is no way to override this parameter, and suppressed
                   4402: parts will never be returned, nor will their response types or ids be
                   4403: stored.
                   4404: 
1.217     bowersj2 4405: =head2 Overview
1.51      bowersj2 4406: 
1.217     bowersj2 4407: A B<Resource> is the most granular type of object in LON-CAPA that can
                   4408: be included in a course. It can either be a particular resource, like
                   4409: an HTML page, external resource, problem, etc., or it can be a
                   4410: container sequence, such as a "page" or a "map".
                   4411: 
                   4412: To see a sequence from the user's point of view, please see the
                   4413: B<Creating a Course: Maps and Sequences> chapter of the Author's
                   4414: Manual.
                   4415: 
                   4416: A Resource Object, once obtained from a navmap object via a B<getBy*>
                   4417: method of the navmap, or from an iterator, allows you to query
                   4418: information about that resource.
                   4419: 
                   4420: Generally, you do not ever want to create a resource object yourself,
                   4421: so creation has been left undocumented. Always retrieve resources
                   4422: from navmap objects.
                   4423: 
                   4424: =head3 Identifying Resources
                   4425: 
                   4426: X<big hash>Every resource is identified by a Resource ID in the big hash that is
                   4427: unique to that resource for a given course. X<resource ID, in big hash>
                   4428: The Resource ID has the form #.#, where the first number is the same
                   4429: for every resource in a map, and the second is unique. For instance,
                   4430: for a course laid out like this:
                   4431: 
                   4432:  * Problem 1
                   4433:  * Map
                   4434:    * Resource 2
                   4435:    * Resource 3
                   4436: 
                   4437: C<Problem 1> and C<Map> will share a first number, and C<Resource 2>
                   4438: C<Resource 3> will share a first number. The second number may end up
                   4439: re-used between the two groups.
                   4440: 
                   4441: The resource ID is only used in the big hash, but can be used in the
                   4442: context of a course to identify a resource easily. (For instance, the
                   4443: printing system uses it to record which resources from a sequence you 
                   4444: wish to print.)
                   4445: 
                   4446: X<symb> X<resource, symb>
                   4447: All resources also have B<symb>s, which uniquely identify a resource
                   4448: in a course. Many internal LON-CAPA functions expect a symb. A symb
                   4449: carries along with it the URL of the resource, and the map it appears
1.398     banghart 4450: in. Symbs are much larger than resource IDs.
1.51      bowersj2 4451: 
                   4452: =cut
                   4453: 
                   4454: sub new {
                   4455:     # magic invocation to create a class instance
                   4456:     my $proto = shift;
                   4457:     my $class = ref($proto) || $proto;
                   4458:     my $self = {};
                   4459: 
1.300     albertel 4460:     weaken($self->{NAV_MAP} = shift);
1.51      bowersj2 4461:     $self->{ID} = shift;
                   4462: 
1.454     www      4463:     $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
                   4464:     $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
                   4465: 
1.51      bowersj2 4466:     # Store this new resource in the parent nav map's cache.
                   4467:     $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;
1.66      bowersj2 4468:     $self->{RESOURCE_ERROR} = 0;
1.51      bowersj2 4469: 
1.479     raeburn  4470:     $self->{DUEDATE_CACHE} = undef;
                   4471: 
1.51      bowersj2 4472:     # A hash that can be used by two-pass algorithms to store data
                   4473:     # about this resource in. Not used by the resource object
                   4474:     # directly.
                   4475:     $self->{DATA} = {};
1.508     damieng  4476:     
1.51      bowersj2 4477:     bless($self);
                   4478:     
1.508     damieng  4479:     # This is a speed optimization, to avoid calling symb() too often.
                   4480:     $self->{SYMB} = $self->symb();
1.515     raeburn  4481: 
1.51      bowersj2 4482:     return $self;
                   4483: }
                   4484: 
1.70      bowersj2 4485: # private function: simplify the NAV_HASH lookups we keep doing
                   4486: # pass the name, and to automatically append my ID, pass a true val on the
                   4487: # second param
                   4488: sub navHash {
                   4489:     my $self = shift;
                   4490:     my $param = shift;
                   4491:     my $id = shift;
1.441     raeburn  4492:     my $arg = $param . ($id?$self->{ID}:"");
1.451     raeburn  4493:     if (ref($self) && ref($self->{NAV_MAP}) && defined($arg)) {
1.441     raeburn  4494:         return $self->{NAV_MAP}->navhash($arg);
                   4495:     }
                   4496:     return;
1.70      bowersj2 4497: }
                   4498: 
1.51      bowersj2 4499: =pod
                   4500: 
1.217     bowersj2 4501: =head2 Methods
1.70      bowersj2 4502: 
1.217     bowersj2 4503: Once you have a resource object, here's what you can do with it:
                   4504: 
                   4505: =head3 Attribute Retrieval
                   4506: 
                   4507: Every resource has certain attributes that can be retrieved and used:
1.70      bowersj2 4508: 
                   4509: =over 4
                   4510: 
1.217     bowersj2 4511: =item * B<ID>: Every resource has an ID that is unique for that
                   4512:     resource in the course it is in. The ID is actually in the hash
                   4513:     representing the resource, so for a resource object $res, obtain
                   4514:     it via C<$res->{ID}).
                   4515: 
1.174     albertel 4516: =item * B<compTitle>:
                   4517: 
                   4518: Returns a "composite title", that is equal to $res->title() if the
                   4519: resource has a title, and is otherwise the last part of the URL (e.g.,
                   4520: "problem.problem").
                   4521: 
                   4522: =item * B<ext>:
                   4523: 
                   4524: Returns true if the resource is external.
                   4525: 
                   4526: =item * B<kind>:
                   4527: 
                   4528: Returns the kind of the resource from the compiled nav map.
                   4529: 
                   4530: =item * B<randomout>:
1.106     bowersj2 4531: 
1.174     albertel 4532: Returns true if this resource was chosen to NOT be shown to the user
                   4533: by the random map selection feature. In other words, this is usually
                   4534: false.
1.70      bowersj2 4535: 
1.174     albertel 4536: =item * B<randompick>:
1.70      bowersj2 4537: 
1.400     albertel 4538: Returns the number of randomly picked items for a map if the randompick
                   4539: feature is being used on the map. 
                   4540: 
                   4541: =item * B<randomorder>:
                   4542: 
                   4543: Returns true for a map if the randomorder feature is being used on the
                   4544: map.
1.70      bowersj2 4545: 
1.174     albertel 4546: =item * B<src>:
1.51      bowersj2 4547: 
1.174     albertel 4548: Returns the source for the resource.
1.70      bowersj2 4549: 
1.174     albertel 4550: =item * B<symb>:
1.70      bowersj2 4551: 
1.174     albertel 4552: Returns the symb for the resource.
1.70      bowersj2 4553: 
1.174     albertel 4554: =item * B<title>:
1.70      bowersj2 4555: 
1.174     albertel 4556: Returns the title of the resource.
                   4557: 
1.51      bowersj2 4558: =back
                   4559: 
                   4560: =cut
                   4561: 
                   4562: # These info functions can be used directly, as they don't return
                   4563: # resource information.
1.85      bowersj2 4564: sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); }
1.303     albertel 4565: sub encrypted { my $self=shift; return $self->navHash("encrypted_", 1); }
1.70      bowersj2 4566: sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; }
1.85      bowersj2 4567: sub from { my $self=shift; return $self->navHash("from_", 1); }
1.217     bowersj2 4568: # considered private and undocumented
1.51      bowersj2 4569: sub goesto { my $self=shift; return $self->navHash("goesto_", 1); }
                   4570: sub kind { my $self=shift; return $self->navHash("kind_", 1); }
1.68      bowersj2 4571: sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
1.553     raeburn  4572: sub deeplinkout { my $self=shift; return $self->navHash("deeplinkout_", 1); }
1.68      bowersj2 4573: sub randompick { 
                   4574:     my $self = shift;
1.410     raeburn  4575:     my $randompick = $self->parmval('randompick');
                   4576:     return $randompick;
1.68      bowersj2 4577: }
1.400     albertel 4578: sub randomorder { 
                   4579:     my $self = shift;
1.410     raeburn  4580:     my $randomorder = $self->parmval('randomorder');
                   4581:     return ($randomorder =~ /^yes$/i);
1.400     albertel 4582: }
1.303     albertel 4583: sub link {
                   4584:     my $self=shift;
                   4585:     if ($self->encrypted()) { return &Apache::lonenc::encrypted($self->src); }
                   4586:     return $self->src;
                   4587: }
1.51      bowersj2 4588: sub src { 
                   4589:     my $self=shift;
                   4590:     return $self->navHash("src_", 1);
                   4591: }
1.303     albertel 4592: sub shown_symb {
                   4593:     my $self=shift;
1.508     damieng  4594:     if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->{SYMB});}
                   4595:     return $self->{SYMB};
1.303     albertel 4596: }
1.328     www      4597: sub id {
                   4598:     my $self=shift;
                   4599:     return $self->{ID};
                   4600: }
                   4601: sub enclosing_map_src {
                   4602:     my $self=shift;
                   4603:     (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
                   4604:     return $self->navHash('map_id_'.$first);
                   4605: }
1.51      bowersj2 4606: sub symb {
                   4607:     my $self=shift;
1.523     damieng  4608:     if (defined $self->{SYMB}) { return $self->{SYMB}; }
1.51      bowersj2 4609:     (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
                   4610:     my $symbSrc = &Apache::lonnet::declutter($self->src());
1.223     albertel 4611:     my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first)) 
1.51      bowersj2 4612:         . '___' . $second . '___' . $symbSrc;
1.223     albertel 4613:     return &Apache::lonnet::symbclean($symb);
1.51      bowersj2 4614: }
1.321     raeburn  4615: sub wrap_symb {
                   4616:     my $self = shift;
1.508     damieng  4617:     return $self->{NAV_MAP}->wrap_symb($self->{SYMB});
1.321     raeburn  4618: }
1.213     bowersj2 4619: sub title { 
                   4620:     my $self=shift; 
                   4621:     if ($self->{ID} eq '0.0') {
                   4622: 	# If this is the top-level map, return the title of the course
                   4623: 	# since this map can not be titled otherwise.
1.320     albertel 4624: 	return $env{'course.'.$env{'request.course.id'}.'.description'};
1.213     bowersj2 4625:     }
                   4626:     return $self->navHash("title_", 1); }
1.217     bowersj2 4627: # considered private and undocumented
1.70      bowersj2 4628: sub to { my $self=shift; return $self->navHash("to_", 1); }
1.301     albertel 4629: sub condition {
                   4630:     my $self=shift;
                   4631:     my $undercond=$self->navHash("undercond_", 1);
                   4632:     if (!defined($undercond)) { return 1; };
                   4633:     my $condid=$self->navHash("condid_$undercond");
                   4634:     if (!defined($condid)) { return 1; };
                   4635:     my $condition=&Apache::lonnet::directcondval($condid);
                   4636:     return $condition;
                   4637: }
1.342     albertel 4638: sub condval {
                   4639:     my $self=shift;
1.359     albertel 4640:     my ($pathname,$filename) = 
                   4641: 	&Apache::lonnet::split_uri_for_cond($self->src());
1.342     albertel 4642: 
                   4643:     my $match=($env{'acc.res.'.$env{'request.course.id'}.'.'.$pathname}=~
                   4644: 	       /\&\Q$filename\E\:([\d\|]+)\&/);
                   4645:     if ($match) {
                   4646: 	return &Apache::lonnet::condval($1);
                   4647:     }
                   4648:     return 0;
                   4649: }
1.106     bowersj2 4650: sub compTitle {
                   4651:     my $self = shift;
                   4652:     my $title = $self->title();
1.176     www      4653:     $title=~s/\&colon\;/\:/gs;
1.106     bowersj2 4654:     if (!$title) {
                   4655:         $title = $self->src();
                   4656:         $title = substr($title, rindex($title, '/') + 1);
                   4657:     }
                   4658:     return $title;
                   4659: }
1.399     albertel 4660: 
1.70      bowersj2 4661: =pod
                   4662: 
                   4663: B<Predicate Testing the Resource>
                   4664: 
                   4665: These methods are shortcuts to deciding if a given resource has a given property.
                   4666: 
                   4667: =over 4
                   4668: 
1.174     albertel 4669: =item * B<is_map>:
                   4670: 
                   4671: Returns true if the resource is a map type.
                   4672: 
                   4673: =item * B<is_problem>:
1.70      bowersj2 4674: 
1.174     albertel 4675: Returns true if the resource is a problem type, false
                   4676: otherwise. (Looks at the extension on the src field; might need more
                   4677: to work correctly.)
1.70      bowersj2 4678: 
1.174     albertel 4679: =item * B<is_page>:
1.70      bowersj2 4680: 
1.174     albertel 4681: Returns true if the resource is a page.
                   4682: 
                   4683: =item * B<is_sequence>:
                   4684: 
                   4685: Returns true if the resource is a sequence.
1.70      bowersj2 4686: 
                   4687: =back
                   4688: 
                   4689: =cut
                   4690: 
1.256     albertel 4691: sub hasResource {
                   4692:    my $self = shift;
                   4693:    return $self->{NAV_MAP}->hasResource(@_);
                   4694: }
                   4695: 
                   4696: sub retrieveResources {
                   4697:    my $self = shift;
                   4698:    return $self->{NAV_MAP}->retrieveResources(@_);
                   4699: }
1.70      bowersj2 4700: 
1.369     albertel 4701: sub is_exam {
                   4702:     my ($self,$part) = @_;
1.410     raeburn  4703:     my $type = $self->parmval('type',$part);
                   4704:     if ($type eq 'exam') {
1.369     albertel 4705:         return 1;
                   4706:     }
                   4707:     if ($self->src() =~ /\.(exam)$/) {
                   4708:         return 1;
                   4709:     }
                   4710:     return 0;
                   4711: }
1.70      bowersj2 4712: sub is_html {
1.51      bowersj2 4713:     my $self=shift;
                   4714:     my $src = $self->src();
1.70      bowersj2 4715:     return ($src =~ /html$/);
1.51      bowersj2 4716: }
1.70      bowersj2 4717: sub is_map { my $self=shift; return defined($self->navHash("is_map_", 1)); }
                   4718: sub is_page {
1.51      bowersj2 4719:     my $self=shift;
                   4720:     my $src = $self->src();
1.205     bowersj2 4721:     return $self->navHash("is_map_", 1) && 
                   4722: 	$self->navHash("map_type_" . $self->map_pc()) eq 'page';
1.51      bowersj2 4723: }
1.361     albertel 4724: sub is_practice {
                   4725:     my $self=shift;
                   4726:     my ($part) = @_;
1.410     raeburn  4727:     my $type = $self->parmval('type',$part);
                   4728:     if ($type eq 'practice') {
1.361     albertel 4729:         return 1;
                   4730:     }
                   4731:     return 0;
                   4732: }
1.70      bowersj2 4733: sub is_problem {
1.51      bowersj2 4734:     my $self=shift;
                   4735:     my $src = $self->src();
1.466     www      4736:     if ($src =~ /$LONCAPA::assess_re/) {
1.361     albertel 4737: 	return !($self->is_practice());
                   4738:     }
                   4739:     return 0;
1.256     albertel 4740: }
1.537     raeburn  4741: sub is_tool {
                   4742:     my $self=shift;
                   4743:     my $src = $self->src();
                   4744:     return ($src =~ /ext\.tool$/);
                   4745: }
                   4746: sub is_gradable {
                   4747:     my $self=shift;
                   4748:     my $src = $self->src();
                   4749:     if (($src =~ /$LONCAPA::assess_re/) ||
                   4750:         (($self->is_tool()) && ($self->parmval('gradable',0) =~ /^yes$/i))) {
                   4751:         return !($self->is_practice());
                   4752:     }
                   4753: }
1.443     foxr     4754: #
                   4755: #  The has below is the set of status that are considered 'incomplete'
                   4756: #
                   4757: my %incomplete_hash = 
                   4758: (
                   4759:  TRIES_LEFT()     => 1,
                   4760:  OPEN()           => 1,
                   4761:  ATTEMPTED()      => 1
                   4762: 
                   4763:  );
                   4764: #
                   4765: #  Return tru if a problem is incomplete... for now incomplete means that
                   4766: #  any part of the problem is incomplete. 
                   4767: #  Note that if the resources is not a problem, 0 is returned.
                   4768: #
                   4769: sub is_incomplete {
                   4770:     my $self = shift;
                   4771:     if ($self->is_problem()) {
                   4772: 	foreach my $part (@{$self->parts()}) {
                   4773: 	    if (exists($incomplete_hash{$self->status($part)})) {
                   4774: 		return 1;
                   4775: 	    }
                   4776: 	}
                   4777:     }
                   4778:     return 0;
                   4779:        
                   4780: }
1.413     www      4781: sub is_raw_problem {
                   4782:     my $self=shift;
                   4783:     my $src = $self->src();
1.466     www      4784:     if ($src =~ /$LONCAPA::assess_re/) {
1.413     www      4785:         return 1;
                   4786:     }
                   4787:     return 0;
                   4788: }
                   4789: 
1.256     albertel 4790: sub contains_problem {
                   4791:     my $self=shift;
                   4792:     if ($self->is_page()) {
                   4793: 	my $hasProblem=$self->hasResource($self,sub { $_[0]->is_problem() },1);
                   4794: 	return $hasProblem;
                   4795:     }
                   4796:     return 0;
1.51      bowersj2 4797: }
1.401     albertel 4798: sub map_contains_problem {
                   4799:     my $self=shift;
                   4800:     if ($self->is_map()) {
                   4801: 	my $has_problem=
                   4802: 	    $self->hasResource($self,sub { $_[0]->is_problem() },1);
                   4803: 	return $has_problem;
                   4804:     }
                   4805:     return 0;
                   4806: }
1.70      bowersj2 4807: sub is_sequence {
1.51      bowersj2 4808:     my $self=shift;
1.205     bowersj2 4809:     return $self->navHash("is_map_", 1) && 
1.426     droeschl 4810:     $self->navHash("map_type_" . $self->map_pc()) eq 'sequence';
1.51      bowersj2 4811: }
1.562   ! raeburn  4812: sub is_missing_map {
        !          4813:     my $self=shift;
        !          4814:     return $self->navHash("is_map_", 1) &&
        !          4815:     $self->navHash("map_type_" . $self->map_pc()) eq 'none';
        !          4816: }
1.261     matthew  4817: sub is_survey {
                   4818:     my $self = shift();
                   4819:     my $part = shift();
1.410     raeburn  4820:     my $type = $self->parmval('type',$part);
1.444     raeburn  4821:     if (($type eq 'survey') || ($type eq 'surveycred')) {
1.261     matthew  4822:         return 1;
                   4823:     }
1.263     albertel 4824:     if ($self->src() =~ /\.(survey)$/) {
1.261     matthew  4825:         return 1;
                   4826:     }
                   4827:     return 0;
                   4828: }
1.444     raeburn  4829: sub is_anonsurvey {
                   4830:     my $self = shift();
                   4831:     my $part = shift();
                   4832:     my $type = $self->parmval('type',$part);
                   4833:     if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) {
                   4834:         return 1;
                   4835:     }
                   4836:     return 0;
                   4837: }
1.360     albertel 4838: sub is_task {
                   4839:     my $self=shift;
                   4840:     my $src = $self->src();
                   4841:     return ($src =~ /\.(task)$/)
                   4842: }
1.51      bowersj2 4843: 
1.266     raeburn  4844: sub is_empty_sequence {
                   4845:     my $self=shift;
                   4846:     return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc());
                   4847: }
                   4848: 
1.101     bowersj2 4849: # Private method: Shells out to the parmval in the nav map, handler parts.
1.51      bowersj2 4850: sub parmval {
                   4851:     my $self = shift;
                   4852:     my $what = shift;
1.185     bowersj2 4853:     my $part = shift;
                   4854:     if (!defined($part)) { 
                   4855:         $part = '0'; 
                   4856:     }
1.508     damieng  4857:     return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->{SYMB});
1.51      bowersj2 4858: }
                   4859: 
1.70      bowersj2 4860: =pod
                   4861: 
                   4862: B<Map Methods>
                   4863: 
1.174     albertel 4864: These methods are useful for getting information about the map
                   4865: properties of the resource, if the resource is a map (B<is_map>).
1.70      bowersj2 4866: 
                   4867: =over 4
                   4868: 
1.174     albertel 4869: =item * B<map_finish>:
                   4870: 
                   4871: Returns a reference to a resource object corresponding to the finish
                   4872: resource of the map.
1.70      bowersj2 4873: 
1.174     albertel 4874: =item * B<map_pc>:
1.70      bowersj2 4875: 
1.174     albertel 4876: Returns the pc value of the map, which is the first number that
                   4877: appears in the resource ID of the resources in the map, and is the
                   4878: number that appears around the middle of the symbs of the resources in
                   4879: that map.
1.70      bowersj2 4880: 
1.174     albertel 4881: =item * B<map_start>:
                   4882: 
                   4883: Returns a reference to a resource object corresponding to the start
                   4884: resource of the map.
                   4885: 
                   4886: =item * B<map_type>:
                   4887: 
                   4888: Returns a string with the type of the map in it.
1.70      bowersj2 4889: 
1.497     raeburn  4890: =item * B<map_hierarchy>:
1.448     raeburn  4891: 
                   4892: Returns a string with a comma-separated ordered list of map_pc IDs
                   4893: for the hierarchy of maps containing a map, with the top level
                   4894: map first, then descending to deeper levels, with the enclosing map last.
                   4895: 
1.534     raeburn  4896: =item * B<map_breadcrumbs>:
                   4897: 
                   4898: Same as map_hierarchy, except maps containing only a single itemm if
                   4899: it's a map, or containing no items are omitted, unless it's the top
                   4900: level map (map_pc = 1), which is always included.
                   4901: 
1.70      bowersj2 4902: =back
1.51      bowersj2 4903: 
1.70      bowersj2 4904: =cut
1.51      bowersj2 4905: 
                   4906: sub map_finish {
                   4907:     my $self = shift;
                   4908:     my $src = $self->src();
1.381     albertel 4909:     $src = &Apache::lonnet::clutter($src);
1.51      bowersj2 4910:     my $res = $self->navHash("map_finish_$src", 0);
                   4911:     $res = $self->{NAV_MAP}->getById($res);
                   4912:     return $res;
                   4913: }
1.70      bowersj2 4914: sub map_pc {
                   4915:     my $self = shift;
1.381     albertel 4916:     my $src = $self->src();
1.70      bowersj2 4917:     return $self->navHash("map_pc_$src", 0);
                   4918: }
1.51      bowersj2 4919: sub map_start {
                   4920:     my $self = shift;
                   4921:     my $src = $self->src();
1.381     albertel 4922:     $src = &Apache::lonnet::clutter($src);
1.51      bowersj2 4923:     my $res = $self->navHash("map_start_$src", 0);
                   4924:     $res = $self->{NAV_MAP}->getById($res);
                   4925:     return $res;
                   4926: }
                   4927: sub map_type {
                   4928:     my $self = shift;
                   4929:     my $pc = $self->map_pc();
                   4930:     return $self->navHash("map_type_$pc", 0);
                   4931: }
1.448     raeburn  4932: sub map_hierarchy {
                   4933:     my $self = shift;
                   4934:     my $pc = $self->map_pc();
                   4935:     return $self->navHash("map_hierarchy_$pc", 0);
                   4936: }
1.534     raeburn  4937: sub map_breadcrumbs {
                   4938:     my $self = shift;
                   4939:     my $pc = $self->map_pc();
                   4940:     return $self->navHash("map_breadcrumbs_$pc", 0);
                   4941: }
1.51      bowersj2 4942: 
                   4943: #####
                   4944: # Property queries
                   4945: #####
                   4946: 
                   4947: # These functions will be responsible for returning the CORRECT
                   4948: # VALUE for the parameter, no matter what. So while they may look
1.398     banghart 4949: # like direct calls to parmval, they can be more than that.
1.51      bowersj2 4950: # So, for instance, the duedate function should use the "duedatetype"
1.398     banghart 4951: # information, rather than the resource object user.
1.51      bowersj2 4952: 
                   4953: =pod
                   4954: 
                   4955: =head2 Resource Parameters
                   4956: 
1.174     albertel 4957: In order to use the resource parameters correctly, the nav map must
                   4958: have been instantiated with genCourseAndUserOptions set to true, so
                   4959: the courseopt and useropt is read correctly. Then, you can call these
                   4960: functions to get the relevant parameters for the resource. Each
                   4961: function defaults to part "0", but can be directed to another part by
                   4962: passing the part as the parameter.
                   4963: 
                   4964: These methods are responsible for getting the parameter correct, not
                   4965: merely reflecting the contents of the GDBM hashes. As we move towards
                   4966: dates relative to other dates, these methods should be updated to
                   4967: reflect that. (Then, anybody using these methods will not have to update
                   4968: their code.)
                   4969: 
                   4970: =over 4
                   4971: 
1.485     foxr     4972: 
1.459     foxr     4973: =item * B<printable>
                   4974: 
                   4975: returns true if the current date is such that the 
                   4976: specified resource part is printable.
                   4977: 
1.485     foxr     4978: 
1.459     foxr     4979: =item * B<resprintable>
                   4980: 
                   4981: Returns true if all parts in the resource are printable making the
                   4982: entire resource printable.
                   4983: 
                   4984: =item * B<acc>
1.174     albertel 4985: 
                   4986: Get the Client IP/Name Access Control information.
1.51      bowersj2 4987: 
1.174     albertel 4988: =item * B<answerdate>:
1.51      bowersj2 4989: 
1.174     albertel 4990: Get the answer-reveal date for the problem.
                   4991: 
1.211     bowersj2 4992: =item * B<awarded>: 
                   4993: 
                   4994: Gets the awarded value for the problem part. Requires genUserData set to
                   4995: true when the navmap object was created.
                   4996: 
1.174     albertel 4997: =item * B<duedate>:
                   4998: 
                   4999: Get the due date for the problem.
                   5000: 
                   5001: =item * B<tries>:
                   5002: 
                   5003: Get the number of tries the student has used on the problem.
                   5004: 
                   5005: =item * B<maxtries>:
                   5006: 
                   5007: Get the number of max tries allowed.
                   5008: 
                   5009: =item * B<opendate>:
1.51      bowersj2 5010: 
1.174     albertel 5011: Get the open date for the problem.
1.51      bowersj2 5012: 
1.174     albertel 5013: =item * B<sig>:
1.51      bowersj2 5014: 
1.174     albertel 5015: Get the significant figures setting.
1.51      bowersj2 5016: 
1.174     albertel 5017: =item * B<tol>:
1.51      bowersj2 5018: 
1.174     albertel 5019: Get the tolerance for the problem.
1.51      bowersj2 5020: 
1.174     albertel 5021: =item * B<tries>:
1.51      bowersj2 5022: 
1.174     albertel 5023: Get the number of tries the user has already used on the problem.
1.51      bowersj2 5024: 
1.174     albertel 5025: =item * B<type>:
1.51      bowersj2 5026: 
1.174     albertel 5027: Get the question type for the problem.
1.70      bowersj2 5028: 
1.174     albertel 5029: =item * B<weight>:
1.51      bowersj2 5030: 
1.174     albertel 5031: Get the weight for the problem.
1.51      bowersj2 5032: 
                   5033: =back
                   5034: 
                   5035: =cut
                   5036: 
1.485     foxr     5037: 
                   5038: 
                   5039: 
1.459     foxr     5040: sub printable {
                   5041: 
                   5042:     my ($self, $part) = @_;
                   5043: 
                   5044:     #  The following cases apply:
1.487     foxr     5045:     #  - If a start date is not set, it is replaced by the open date.
                   5046:     #  - Ditto for start/open replaced by content open.
                   5047:     #  - If neither start nor printdates are set the part is printable.
1.459     foxr     5048:     #  - Start date set but no end date: Printable if now >= start date.
                   5049:     #  - End date set but no start date: Printable if now <= end date.
                   5050:     #  - both defined: printable if start <= now <= end
                   5051:     #
1.487     foxr     5052: 
                   5053:     # Get the print open/close dates for the resource.
                   5054: 
                   5055:     my $start = $self->parmval("printstartdate", $part);
                   5056:     my $end   = $self->parmval("printenddate", $part);
                   5057: 
                   5058:     if (!$start) {
                   5059: 	$start = $self->parmval("opendate", $part);
                   5060:     }
                   5061:     if (!$start) {
                   5062: 	$start = $self->parmval("contentopen", $part);
                   5063:     }
                   5064: 
                   5065: 
1.459     foxr     5066:     my $now  = time();
                   5067: 
1.487     foxr     5068: 
1.459     foxr     5069:     my $startok = 1;
                   5070:     my $endok   = 1;
                   5071: 
1.460     foxr     5072:     if ((defined $start) && ($start ne '')) {
1.459     foxr     5073: 	$startok = $start <= $now;
                   5074:     }
1.460     foxr     5075:     if ((defined $end) && ($end != '')) {
1.459     foxr     5076: 	$endok = $end >= $now;
                   5077:     }
                   5078:     return $startok && $endok;
                   5079: }
                   5080: 
                   5081: sub resprintable {
                   5082:     my $self = shift;
                   5083: 
                   5084:     # get parts...or realize there are no parts.
                   5085: 
1.460     foxr     5086:     my $partsref = $self->parts();
                   5087:     my @parts    = @$partsref;
                   5088: 
1.489     raeburn  5089:     if (!@parts) {
1.459     foxr     5090: 	return $self->printable(0);
                   5091:     } else {
1.460     foxr     5092: 	foreach my $part  (@parts) {
                   5093: 	    if (!$self->printable($part)) { 
                   5094: 		return 0; 
                   5095: 	    }
1.459     foxr     5096: 	}
                   5097: 	return 1;
                   5098:     }
                   5099: }
                   5100: 
1.51      bowersj2 5101: sub acc {
                   5102:     (my $self, my $part) = @_;
1.410     raeburn  5103:     my $acc = $self->parmval("acc", $part);
                   5104:     return $acc;
1.51      bowersj2 5105: }
                   5106: sub answerdate {
                   5107:     (my $self, my $part) = @_;
                   5108:     # Handle intervals
1.410     raeburn  5109:     my $answerdatetype = $self->parmval("answerdate.type", $part);
                   5110:     my $answerdate = $self->parmval("answerdate", $part);
                   5111:     my $duedate = $self->parmval("duedate", $part);
                   5112:     if ($answerdatetype eq 'date_interval') {
                   5113:         $answerdate = $duedate + $answerdate; 
1.51      bowersj2 5114:     }
1.410     raeburn  5115:     return $answerdate;
1.106     bowersj2 5116: }
1.211     bowersj2 5117: sub awarded { 
                   5118:     my $self = shift; my $part = shift;
1.221     bowersj2 5119:     $self->{NAV_MAP}->get_user_data();
1.211     bowersj2 5120:     if (!defined($part)) { $part = '0'; }
1.508     damieng  5121:     return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.awarded'};
1.211     bowersj2 5122: }
1.432     raeburn  5123: sub taskversion {
                   5124:     my $self = shift; my $part = shift;
                   5125:     $self->{NAV_MAP}->get_user_data();
                   5126:     if (!defined($part)) { $part = '0'; }
1.508     damieng  5127:     return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.version'};
1.432     raeburn  5128: }
                   5129: sub taskstatus {
                   5130:     my $self = shift; my $part = shift;
                   5131:     $self->{NAV_MAP}->get_user_data();
                   5132:     if (!defined($part)) { $part = '0'; }
1.508     damieng  5133:     return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$self->taskversion($part).'.'.$part.'.status'};
1.432     raeburn  5134: }
                   5135: sub solved {
                   5136:     my $self = shift; my $part = shift;
                   5137:     $self->{NAV_MAP}->get_user_data();
                   5138:     if (!defined($part)) { $part = '0'; }
1.508     damieng  5139:     return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.solved'};
1.432     raeburn  5140: }
                   5141: sub checkedin {
                   5142:     my $self = shift; my $part = shift;
                   5143:     $self->{NAV_MAP}->get_user_data();
                   5144:     if (!defined($part)) { $part = '0'; }
                   5145:     if ($self->is_task()) {
                   5146:         my $version = $self->taskversion($part);
1.508     damieng  5147:         return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$version .'.'.$part.'.checkedin.slot'});
1.432     raeburn  5148:     } else {
1.508     damieng  5149:         return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.checkedin.slot'});
1.432     raeburn  5150:     }
                   5151: }
1.382     albertel 5152: # this should work exactly like the copy in lonhomework.pm
1.459     foxr     5153: # Why is there a copy in lonhomework?  Why not centralized?
                   5154: #
                   5155: #  TODO: Centralize duedate.
                   5156: #
                   5157: 
1.51      bowersj2 5158: sub duedate {
                   5159:     (my $self, my $part) = @_;
1.479     raeburn  5160:     if (defined ($self->{DUEDATE_CACHE}->{$part})) {
                   5161:         return $self->{DUEDATE_CACHE}->{$part};
                   5162:     }
1.382     albertel 5163:     my $date;
1.406     albertel 5164:     my @interval=$self->parmval("interval", $part);
1.382     albertel 5165:     my $due_date=$self->parmval("duedate", $part);
1.520     raeburn  5166:     if ($interval[0] =~ /^(\d+)/) {
                   5167:         my $timelimit = $1;
                   5168:         my $first_access=&Apache::lonnet::get_first_access($interval[1],
                   5169:                                                            $self->{SYMB});
1.382     albertel 5170: 	if (defined($first_access)) {
1.520     raeburn  5171:             my $interval = $first_access+$timelimit;
1.404     albertel 5172: 	    $date = (!$due_date || $interval < $due_date) ? $interval 
                   5173:                                                           : $due_date;
1.382     albertel 5174: 	} else {
                   5175: 	    $date = $due_date;
                   5176: 	}
                   5177:     } else {
                   5178: 	$date = $due_date;
1.260     albertel 5179:     }
1.479     raeburn  5180:     $self->{DUEDATE_CACHE}->{$part} = $date;
1.382     albertel 5181:     return $date;
1.51      bowersj2 5182: }
1.334     albertel 5183: sub handgrade {
                   5184:     (my $self, my $part) = @_;
1.399     albertel 5185:     my @response_ids = $self->responseIds($part);
                   5186:     if (@response_ids) {
                   5187: 	foreach my $response_id (@response_ids) {
1.410     raeburn  5188:             my $handgrade = $self->parmval("handgrade",$part.'_'.$response_id);
                   5189: 	    if (lc($handgrade) eq 'yes') {
1.399     albertel 5190: 		return 'yes';
                   5191: 	    }
                   5192: 	}
                   5193:     }
1.410     raeburn  5194:     my $handgrade = $self->parmval("handgrade", $part);
                   5195:     return $handgrade;
1.334     albertel 5196: }
1.51      bowersj2 5197: sub maxtries {
                   5198:     (my $self, my $part) = @_;
1.410     raeburn  5199:     my $maxtries = $self->parmval("maxtries", $part);
                   5200:     return $maxtries;
1.51      bowersj2 5201: }
                   5202: sub opendate {
                   5203:     (my $self, my $part) = @_;
1.410     raeburn  5204:     my $opendatetype = $self->parmval("opendate.type", $part);
                   5205:     my $opendate = $self->parmval("opendate", $part); 
                   5206:     if ($opendatetype eq 'date_interval') {
                   5207:         my $duedate = $self->duedate($part);
                   5208:         $opendate = $duedate - $opendate; 
1.51      bowersj2 5209:     }
1.410     raeburn  5210:     return $opendate;
1.51      bowersj2 5211: }
1.185     bowersj2 5212: sub problemstatus {
                   5213:     (my $self, my $part) = @_;
1.410     raeburn  5214:     my $problemstatus = $self->parmval("problemstatus", $part);
                   5215:     return lc($problemstatus);
1.185     bowersj2 5216: }
1.51      bowersj2 5217: sub sig {
                   5218:     (my $self, my $part) = @_;
1.410     raeburn  5219:     my $sig = $self->parmval("sig", $part);
                   5220:     return $sig;
1.51      bowersj2 5221: }
                   5222: sub tol {
                   5223:     (my $self, my $part) = @_;
1.410     raeburn  5224:     my $tol = $self->parmval("tol", $part);
                   5225:     return $tol;
1.51      bowersj2 5226: }
1.410     raeburn  5227: sub tries {
1.108     bowersj2 5228:     my $self = shift; 
                   5229:     my $tries = $self->queryRestoreHash('tries', shift);
                   5230:     if (!defined($tries)) { return '0';}
1.51      bowersj2 5231:     return $tries;
                   5232: }
1.70      bowersj2 5233: sub type {
                   5234:     (my $self, my $part) = @_;
1.410     raeburn  5235:     my $type = $self->parmval("type", $part);
                   5236:     return $type;
1.70      bowersj2 5237: }
1.109     bowersj2 5238: sub weight { 
                   5239:     my $self = shift; my $part = shift;
1.211     bowersj2 5240:     if (!defined($part)) { $part = '0'; }
1.409     raeburn  5241:     my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
1.508     damieng  5242:                                 $self->{SYMB}, $self->{DOMAIN},
1.453     www      5243:                                 $self->{USERNAME},
1.409     raeburn  5244:                                 $env{'request.course.sec'});
                   5245:     return $weight;
1.274     matthew  5246: }
                   5247: sub part_display {
                   5248:     my $self= shift(); my $partID = shift();
                   5249:     if (! defined($partID)) { $partID = '0'; }
                   5250:     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',
1.514     raeburn  5251:                                      $self->{SYMB});
1.274     matthew  5252:     if (! defined($display) || $display eq '') {
                   5253:         $display = $partID;
                   5254:     }
                   5255:     return $display;
1.70      bowersj2 5256: }
1.432     raeburn  5257: sub slot_control {
                   5258:     my $self=shift(); my $part = shift();
                   5259:     if (!defined($part)) { $part = '0'; }
                   5260:     my $useslots = $self->parmval("useslots", $part);
                   5261:     my $availablestudent = $self->parmval("availablestudent", $part);
                   5262:     my $available = $self->parmval("available", $part); 
                   5263:     return ($useslots,$availablestudent,$available);
                   5264: }
1.543     raeburn  5265: sub deeplink {
1.553     raeburn  5266:     my ($self,$caller,$action) = @_;
                   5267:     my $deeplink = $self->parmval("deeplink");
                   5268:     if ($deeplink) {
1.554     raeburn  5269:         my ($state,$others,$listed,$scope) = split(/,/,$deeplink);
1.553     raeburn  5270:         if ($action eq 'getlisted') {
                   5271:             return $listed;
                   5272:         }
                   5273:         if ($env{'request.deeplink.login'}) {
                   5274:             my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
                   5275:             my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   5276:             my $deeplink_symb = &Apache::loncommon::deeplink_login_symb($cnum,$cdom);
                   5277:             if ($deeplink_symb) {
                   5278:                 my ($loginmap,$mapname);
                   5279:                 if ($deeplink_symb =~ /\.(page|sequence)$/) {
                   5280:                     $mapname = $self->enclosing_map_src();
                   5281:                     $loginmap = &Apache::lonnet::clutter((&Apache::lonnet::decode_symb($deeplink_symb))[2]);
                   5282:                     return if ($mapname eq $loginmap);
                   5283:                 } else {
                   5284:                     return if ($deeplink_symb eq $self->symb());
                   5285:                     if (($scope eq 'map') || ($scope eq 'rec')) {
                   5286:                         $mapname = $self->enclosing_map_src();
                   5287:                         $loginmap = &Apache::lonnet::clutter((&Apache::lonnet::decode_symb($deeplink_symb))[0]);
                   5288:                         return if ($mapname eq $loginmap);
                   5289:                     }
                   5290:                 }
                   5291:                 if ($scope eq 'rec') {
                   5292:                     my $map_pc = $self->navHash('map_pc_'.$mapname);
                   5293:                     my @recurseup = split(/,/,$self->navHash('map_hierarchy_'.$map_pc));
                   5294:                     my $login_pc = $self->navHash('map_pc_'.$loginmap);
                   5295:                     return if (grep(/^\Q$login_pc\E$/,@recurseup));
                   5296:                 }
1.547     raeburn  5297:             }
1.543     raeburn  5298:         }
1.554     raeburn  5299:         unless (($caller eq 'sequence') || ($state eq 'both')) {
1.553     raeburn  5300:             return $listed;
                   5301:         }
1.543     raeburn  5302:     }
1.547     raeburn  5303:     return;
1.543     raeburn  5304: }
1.51      bowersj2 5305: 
                   5306: # Multiple things need this
                   5307: sub getReturnHash {
                   5308:     my $self = shift;
                   5309:     
                   5310:     if (!defined($self->{RETURN_HASH})) {
1.524     damieng  5311:         #my %tmpHash  = &Apache::lonnet::restore($self->{SYMB},undef,$self->{DOMAIN},$self->{USERNAME});
                   5312:         #$self->{RETURN_HASH} = \%tmpHash;
                   5313:         # When info is retrieved for several resources (as when rendering a directory),
                   5314:         # it is much faster to use the user profile dump and avoid repeated lonnet requests
                   5315:         # (especially since lonnet::currentdump is using Lond directly whenever possible,
                   5316:         # and lonnet::restore is not at this point).
                   5317:         $self->{NAV_MAP}->get_user_data();
                   5318:         $self->{RETURN_HASH} = $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}};
1.51      bowersj2 5319:     }
                   5320: }       
                   5321: 
                   5322: ######
                   5323: # Status queries
                   5324: ######
                   5325: 
                   5326: # These methods query the status of problems.
                   5327: 
                   5328: # If we need to count parts, this function determines the number of
                   5329: # parts from the metadata. When called, it returns a reference to a list
                   5330: # of strings corresponding to the parts. (Thus, using it in a scalar context
                   5331: # tells you how many parts you have in the problem:
                   5332: # $partcount = scalar($resource->countParts());
                   5333: # Don't use $self->{PARTS} directly because you don't know if it's been
                   5334: # computed yet.
                   5335: 
                   5336: =pod
                   5337: 
                   5338: =head2 Resource misc
                   5339: 
                   5340: Misc. functions for the resource.
                   5341: 
                   5342: =over 4
                   5343: 
1.174     albertel 5344: =item * B<hasDiscussion>:
1.59      bowersj2 5345: 
1.174     albertel 5346: Returns a false value if there has been discussion since the user last
                   5347: logged in, true if there has. Always returns false if the discussion
                   5348: data was not extracted when the nav map was constructed.
                   5349: 
1.366     albertel 5350: =item * B<last_post_time>:
                   5351: 
                   5352: Returns a false value if there hasn't been discussion otherwise returns
                   5353: unix timestamp of last time a discussion posting (or edit) was made.
                   5354: 
1.393     raeburn  5355: =item * B<discussion_info>:
1.366     albertel 5356: 
1.393     raeburn  5357: optional argument is a filter (currently can be 'unread');
                   5358: returns in scalar context the count of the number of discussion postings.
1.366     albertel 5359: 
                   5360: returns in list context both the count of postings and a hash ref
1.393     raeburn  5361: containing information about the postings (subject, id, timestamp) in a hash.
                   5362: 
                   5363: Default is to return counts for all postings.  However if called with a second argument set to 'unread', will return information about only unread postings.
1.366     albertel 5364: 
1.174     albertel 5365: =item * B<getFeedback>:
                   5366: 
                   5367: Gets the feedback for the resource and returns the raw feedback string
                   5368: for the resource, or the null string if there is no feedback or the
                   5369: email data was not extracted when the nav map was constructed. Usually
                   5370: used like this:
1.59      bowersj2 5371: 
1.394     raeburn  5372:  for my $url (split(/\,/, $res->getFeedback())) {
                   5373:     my $link = &escape($url);
1.59      bowersj2 5374:     ...
                   5375: 
                   5376: and use the link as appropriate.
                   5377: 
                   5378: =cut
                   5379: 
                   5380: sub hasDiscussion {
                   5381:     my $self = shift;
1.508     damieng  5382:     return $self->{NAV_MAP}->hasDiscussion($self->{SYMB});
1.59      bowersj2 5383: }
                   5384: 
1.366     albertel 5385: sub last_post_time {
                   5386:     my $self = shift;
1.508     damieng  5387:     return $self->{NAV_MAP}->last_post_time($self->{SYMB});
1.366     albertel 5388: }
                   5389: 
1.393     raeburn  5390: sub discussion_info {
                   5391:     my ($self,$filter) = @_;
1.508     damieng  5392:     return $self->{NAV_MAP}->discussion_info($self->{SYMB},$filter);
1.366     albertel 5393: }
                   5394: 
1.59      bowersj2 5395: sub getFeedback {
                   5396:     my $self = shift;
1.124     bowersj2 5397:     my $source = $self->src();
1.508     damieng  5398:     my $symb = $self->{SYMB};
1.125     bowersj2 5399:     if ($source =~ /^\/res\//) { $source = substr $source, 5; }
1.395     raeburn  5400:     return $self->{NAV_MAP}->getFeedback($symb,$source);
1.124     bowersj2 5401: }
                   5402: 
                   5403: sub getErrors {
                   5404:     my $self = shift;
                   5405:     my $source = $self->src();
1.508     damieng  5406:     my $symb = $self->{SYMB};
1.124     bowersj2 5407:     if ($source =~ /^\/res\//) { $source = substr $source, 5; }
1.395     raeburn  5408:     return $self->{NAV_MAP}->getErrors($symb,$source);
1.59      bowersj2 5409: }
                   5410: 
                   5411: =pod
                   5412: 
1.174     albertel 5413: =item * B<parts>():
1.51      bowersj2 5414: 
1.174     albertel 5415: Returns a list reference containing sorted strings corresponding to
1.193     bowersj2 5416: each part of the problem. Single part problems have only a part '0'.
                   5417: Multipart problems do not return their part '0', since they typically
                   5418: do not really matter. 
1.174     albertel 5419: 
                   5420: =item * B<countParts>():
                   5421: 
                   5422: Returns the number of parts of the problem a student can answer. Thus,
                   5423: for single part problems, returns 1. For multipart, it returns the
1.193     bowersj2 5424: number of parts in the problem, not including psuedo-part 0. 
1.51      bowersj2 5425: 
1.290     matthew  5426: =item * B<countResponses>():
                   5427: 
                   5428: Returns the total number of responses in the problem a student can answer.
                   5429: 
                   5430: =item * B<responseTypes>():
                   5431: 
                   5432: Returns a hash whose keys are the response types.  The values are the number 
                   5433: of times each response type is used.  This is for the I<entire> problem, not 
                   5434: just a single part.
                   5435: 
1.192     bowersj2 5436: =item * B<multipart>():
                   5437: 
1.193     bowersj2 5438: Returns true if the problem is multipart, false otherwise. Use this instead
                   5439: of countParts if all you want is multipart/not multipart.
1.192     bowersj2 5440: 
1.187     bowersj2 5441: =item * B<responseType>($part):
                   5442: 
                   5443: Returns the response type of the part, without the word "response" on the
                   5444: end. Example return values: 'string', 'essay', 'numeric', etc.
                   5445: 
1.193     bowersj2 5446: =item * B<responseIds>($part):
1.187     bowersj2 5447: 
1.193     bowersj2 5448: Retreives the response IDs for the given part as an array reference containing
                   5449: strings naming the response IDs. This may be empty.
1.187     bowersj2 5450: 
1.51      bowersj2 5451: =back
                   5452: 
                   5453: =cut
                   5454: 
                   5455: sub parts {
                   5456:     my $self = shift;
                   5457: 
1.192     bowersj2 5458:     if ($self->ext) { return []; }
1.537     raeburn  5459:     if (($self->is_tool()) &&
                   5460:         ($self->is_gradable())) { return ['0']; }
1.67      bowersj2 5461: 
1.51      bowersj2 5462:     $self->extractParts();
                   5463:     return $self->{PARTS};
                   5464: }
                   5465: 
                   5466: sub countParts {
                   5467:     my $self = shift;
                   5468:     
                   5469:     my $parts = $self->parts();
1.191     bowersj2 5470: 
                   5471:     # If I left this here, then it's not necessary.
                   5472:     #my $delta = 0;
                   5473:     #for my $part (@$parts) {
                   5474:     #    if ($part eq '0') { $delta--; }
                   5475:     #}
1.66      bowersj2 5476: 
                   5477:     if ($self->{RESOURCE_ERROR}) {
                   5478:         return 0;
                   5479:     }
                   5480: 
1.191     bowersj2 5481:     return scalar(@{$parts}); # + $delta;
1.192     bowersj2 5482: }
                   5483: 
1.290     matthew  5484: sub countResponses {
                   5485:     my $self = shift;
                   5486:     my $count;
1.293     matthew  5487:     foreach my $part (@{$self->parts()}) {
                   5488:         $count+= scalar($self->responseIds($part));
1.290     matthew  5489:     }
                   5490:     return $count;
                   5491: }
                   5492: 
                   5493: sub responseTypes {
                   5494:     my $self = shift;
1.291     albertel 5495:     my %responses;
1.368     raeburn  5496:     foreach my $part (@{$self->parts()}) {
1.290     matthew  5497:         foreach my $responsetype ($self->responseType($part)) {
1.291     albertel 5498:             $responses{$responsetype}++ if (defined($responsetype));
1.290     matthew  5499:         }
                   5500:     }
1.291     albertel 5501:     return %responses;
1.290     matthew  5502: }
                   5503: 
1.192     bowersj2 5504: sub multipart {
                   5505:     my $self = shift;
                   5506:     return $self->countParts() > 1;
1.51      bowersj2 5507: }
                   5508: 
1.225     bowersj2 5509: sub singlepart {
                   5510:     my $self = shift;
                   5511:     return $self->countParts() == 1;
                   5512: }
                   5513: 
1.187     bowersj2 5514: sub responseType {
1.184     bowersj2 5515:     my $self = shift;
                   5516:     my $part = shift;
                   5517: 
                   5518:     $self->extractParts();
1.235     albertel 5519:     if (defined($self->{RESPONSE_TYPES}->{$part})) {
                   5520: 	return @{$self->{RESPONSE_TYPES}->{$part}};
                   5521:     } else {
                   5522: 	return undef;
                   5523:     }
1.187     bowersj2 5524: }
                   5525: 
1.193     bowersj2 5526: sub responseIds {
1.187     bowersj2 5527:     my $self = shift;
                   5528:     my $part = shift;
                   5529: 
                   5530:     $self->extractParts();
1.235     albertel 5531:     if (defined($self->{RESPONSE_IDS}->{$part})) {
                   5532: 	return @{$self->{RESPONSE_IDS}->{$part}};
                   5533:     } else {
                   5534: 	return undef;
                   5535:     }
1.184     bowersj2 5536: }
                   5537: 
                   5538: # Private function: Extracts the parts information, both part names and
1.187     bowersj2 5539: # part types, and saves it. 
1.51      bowersj2 5540: sub extractParts { 
                   5541:     my $self = shift;
                   5542:     
1.153     bowersj2 5543:     return if (defined($self->{PARTS}));
1.67      bowersj2 5544:     return if ($self->ext);
1.51      bowersj2 5545: 
                   5546:     $self->{PARTS} = [];
                   5547: 
1.181     bowersj2 5548:     my %parts;
                   5549: 
1.82      bowersj2 5550:     # Retrieve part count, if this is a problem
1.531     raeburn  5551:     if ($self->is_raw_problem()) {
1.240     albertel 5552: 	my $partorder = &Apache::lonnet::metadata($self->src(), 'partorder');
1.152     matthew  5553:         my $metadata = &Apache::lonnet::metadata($self->src(), 'packages');
1.181     bowersj2 5554: 
1.239     bowersj2 5555: 	if ($partorder) {
                   5556: 	    my @parts;
                   5557: 	    for my $part (split (/,/,$partorder)) {
1.508     damieng  5558: 		if (!Apache::loncommon::check_if_partid_hidden($part, $self->{SYMB})) {
1.239     bowersj2 5559: 		    push @parts, $part;
1.241     albertel 5560: 		    $parts{$part} = 1;
1.239     bowersj2 5561: 		}
                   5562: 	    }
                   5563: 	    $self->{PARTS} = \@parts;
                   5564: 	} else {
                   5565: 	    if (!$metadata) {
                   5566: 		$self->{RESOURCE_ERROR} = 1;
                   5567: 		$self->{PARTS} = [];
                   5568: 		$self->{PART_TYPE} = {};
                   5569: 		return;
                   5570: 	    }
1.394     raeburn  5571: 	    foreach my $entry (split(/\,/,$metadata)) {
                   5572: 		if ($entry =~ /^(?:part|Task)_(.*)$/) {
1.239     bowersj2 5573: 		    my $part = $1;
                   5574: 		    # This floods the logs if it blows up
                   5575: 		    if (defined($parts{$part})) {
1.508     damieng  5576: 			&Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->{SYMB});
1.241     albertel 5577: 		    }
1.239     bowersj2 5578: 		    
                   5579: 		    # check to see if part is turned off.
                   5580: 		    
1.508     damieng  5581: 		    if (!Apache::loncommon::check_if_partid_hidden($part, $self->{SYMB})) {
1.239     bowersj2 5582: 			$parts{$part} = 1;
                   5583: 		    }
                   5584: 		}
                   5585: 	    }
1.504     raeburn  5586: 	    my @sortedParts = sort(keys(%parts));
1.239     bowersj2 5587: 	    $self->{PARTS} = \@sortedParts;
1.51      bowersj2 5588:         }
1.82      bowersj2 5589:         
1.187     bowersj2 5590: 
1.284     matthew  5591:         # These hashes probably do not need names that end with "Hash"....
1.187     bowersj2 5592:         my %responseIdHash;
                   5593:         my %responseTypeHash;
                   5594: 
1.189     bowersj2 5595: 
                   5596:         # Init the responseIdHash
1.394     raeburn  5597:         foreach my $part (@{$self->{PARTS}}) {
                   5598:             $responseIdHash{$part} = [];
1.189     bowersj2 5599:         }
                   5600: 
1.187     bowersj2 5601:         # Now, the unfortunate thing about this is that parts, part name, and
1.239     bowersj2 5602:         # response id are delimited by underscores, but both the part
1.187     bowersj2 5603:         # name and response id can themselves have underscores in them.
                   5604:         # So we have to use our knowlege of part names to figure out 
                   5605:         # where the part names begin and end, and even then, it is possible
                   5606:         # to construct ambiguous situations.
1.504     raeburn  5607:         foreach my $data (split(/,/, $metadata)) {
1.379     albertel 5608:             if ($data =~ /^([a-zA-Z]+)response_(.*)/
                   5609: 		|| $data =~ /^(Task)_(.*)/) {
1.187     bowersj2 5610:                 my $responseType = $1;
                   5611:                 my $partStuff = $2;
                   5612:                 my $partIdSoFar = '';
1.504     raeburn  5613:                 my @partChunks = split(/_/, $partStuff);
1.187     bowersj2 5614:                 my $i = 0;
                   5615:                 for ($i = 0; $i < scalar(@partChunks); $i++) {
                   5616:                     if ($partIdSoFar) { $partIdSoFar .= '_'; }
                   5617:                     $partIdSoFar .= $partChunks[$i];
                   5618:                     if ($parts{$partIdSoFar}) {
                   5619:                         my @otherChunks = @partChunks[$i+1..$#partChunks];
                   5620:                         my $responseId = join('_', @otherChunks);
1.379     albertel 5621: 			if ($self->is_task()) {
                   5622: 			    push(@{$responseIdHash{$partIdSoFar}},
                   5623: 				 $partIdSoFar);
                   5624: 			} else {
                   5625: 			    push(@{$responseIdHash{$partIdSoFar}},
                   5626: 				 $responseId);
                   5627: 			}
                   5628:                         push(@{$responseTypeHash{$partIdSoFar}},
                   5629: 			     $responseType);
1.187     bowersj2 5630:                     }
                   5631:                 }
                   5632:             }
                   5633:         }
1.264     albertel 5634: 	my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder');
1.284     matthew  5635:         #
                   5636:         # Reorder the arrays in the %responseIdHash and %responseTypeHash
1.264     albertel 5637: 	if ($resorder) {
                   5638: 	    my @resorder=split(/,/,$resorder);
                   5639: 	    foreach my $part (keys(%responseIdHash)) {
1.286     albertel 5640: 		my $i=0;
                   5641: 		my %resids = map { ($_,$i++) } @{ $responseIdHash{$part} };
1.264     albertel 5642: 		my @neworder;
                   5643: 		foreach my $possibleid (@resorder) {
                   5644: 		    if (exists($resids{$possibleid})) {
1.286     albertel 5645: 			push(@neworder,$resids{$possibleid});
1.264     albertel 5646: 		    }
                   5647: 		}
1.286     albertel 5648: 		my @ids;
                   5649: 		my @type;
                   5650: 		foreach my $element (@neworder) {
                   5651: 		    push (@ids,$responseIdHash{$part}->[$element]);
                   5652: 		    push (@type,$responseTypeHash{$part}->[$element]);
                   5653: 		}
                   5654: 		$responseIdHash{$part}=\@ids;
                   5655: 		$responseTypeHash{$part}=\@type;
1.264     albertel 5656: 	    }
                   5657: 	}
1.187     bowersj2 5658:         $self->{RESPONSE_IDS} = \%responseIdHash;
                   5659:         $self->{RESPONSE_TYPES} = \%responseTypeHash;
1.154     bowersj2 5660:     }
                   5661: 
1.51      bowersj2 5662:     return;
                   5663: }
                   5664: 
                   5665: =pod
                   5666: 
                   5667: =head2 Resource Status
                   5668: 
1.174     albertel 5669: Problem resources have status information, reflecting their various
                   5670: dates and completion statuses.
1.51      bowersj2 5671: 
1.174     albertel 5672: There are two aspects to the status: the date-related information and
                   5673: the completion information.
1.51      bowersj2 5674: 
1.174     albertel 5675: Idiomatic usage of these two methods would probably look something
                   5676: like
1.51      bowersj2 5677: 
1.394     raeburn  5678:  foreach my $part ($resource->parts()) {
                   5679:     my $dateStatus = $resource->getDateStatus($part);
                   5680:     my $completionStatus = $resource->getCompletionStatus($part);
1.51      bowersj2 5681: 
1.70      bowersj2 5682:     or
                   5683: 
1.394     raeburn  5684:     my $status = $resource->status($part);
1.70      bowersj2 5685: 
1.51      bowersj2 5686:     ... use it here ...
                   5687:  }
                   5688: 
1.174     albertel 5689: Which you use depends on exactly what you are looking for. The
                   5690: status() function has been optimized for the nav maps display and may
                   5691: not precisely match what you need elsewhere.
1.101     bowersj2 5692: 
1.174     albertel 5693: The symbolic constants shown below can be accessed through the
                   5694: resource object: C<$res->OPEN>.
1.101     bowersj2 5695: 
1.51      bowersj2 5696: =over 4
                   5697: 
1.174     albertel 5698: =item * B<getDateStatus>($part):
                   5699: 
                   5700: ($part defaults to 0). A convenience function that returns a symbolic
                   5701: constant telling you about the date status of the part. The possible
                   5702: return values are:
1.51      bowersj2 5703: 
                   5704: =back
                   5705: 
                   5706: B<Date Codes>
                   5707: 
                   5708: =over 4
                   5709: 
1.174     albertel 5710: =item * B<OPEN_LATER>:
                   5711: 
                   5712: The problem will be opened later.
                   5713: 
                   5714: =item * B<OPEN>:
                   5715: 
                   5716: Open and not yet due.
                   5717: 
1.51      bowersj2 5718: 
1.174     albertel 5719: =item * B<PAST_DUE_ANSWER_LATER>:
1.51      bowersj2 5720: 
1.174     albertel 5721: The due date has passed, but the answer date has not yet arrived.
1.59      bowersj2 5722: 
1.174     albertel 5723: =item * B<PAST_DUE_NO_ANSWER>:
1.54      bowersj2 5724: 
1.174     albertel 5725: The due date has passed and there is no answer opening date set.
1.51      bowersj2 5726: 
1.174     albertel 5727: =item * B<ANSWER_OPEN>:
1.51      bowersj2 5728: 
1.174     albertel 5729: The answer date is here.
                   5730: 
                   5731: =item * B<NETWORK_FAILURE>:
                   5732: 
                   5733: The information is unknown due to network failure.
1.51      bowersj2 5734: 
                   5735: =back
                   5736: 
                   5737: =cut
                   5738: 
                   5739: # Apparently the compiler optimizes these into constants automatically
1.54      bowersj2 5740: sub OPEN_LATER             { return 0; }
                   5741: sub OPEN                   { return 1; }
                   5742: sub PAST_DUE_NO_ANSWER     { return 2; }
                   5743: sub PAST_DUE_ANSWER_LATER  { return 3; }
                   5744: sub ANSWER_OPEN            { return 4; }
1.432     raeburn  5745: sub NOTHING_SET            { return 5; }
1.54      bowersj2 5746: sub NETWORK_FAILURE        { return 100; }
                   5747: 
                   5748: # getDateStatus gets the date status for a given problem part. 
                   5749: # Because answer date, due date, and open date are fully independent
                   5750: # (i.e., it is perfectly possible to *only* have an answer date), 
                   5751: # we have to completely cover the 3x3 maxtrix of (answer, due, open) x
                   5752: # (past, future, none given). This function handles this with a decision
                   5753: # tree. Read the comments to follow the decision tree.
1.51      bowersj2 5754: 
                   5755: sub getDateStatus {
                   5756:     my $self = shift;
                   5757:     my $part = shift;
                   5758:     $part = "0" if (!defined($part));
1.54      bowersj2 5759: 
                   5760:     # Always return network failure if there was one.
1.51      bowersj2 5761:     return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});
                   5762: 
                   5763:     my $now = time();
                   5764: 
1.53      bowersj2 5765:     my $open = $self->opendate($part);
                   5766:     my $due = $self->duedate($part);
                   5767:     my $answer = $self->answerdate($part);
                   5768: 
                   5769:     if (!$open && !$due && !$answer) {
                   5770:         # no data on the problem at all
                   5771:         # should this be the same as "open later"? think multipart.
                   5772:         return $self->NOTHING_SET;
                   5773:     }
1.59      bowersj2 5774:     if (!$open || $now < $open) {return $self->OPEN_LATER}
                   5775:     if (!$due || $now < $due) {return $self->OPEN}
                   5776:     if ($answer && $now < $answer) {return $self->PAST_DUE_ANSWER_LATER}
                   5777:     if ($answer) { return $self->ANSWER_OPEN; }
1.54      bowersj2 5778:     return PAST_DUE_NO_ANSWER;
1.51      bowersj2 5779: }
                   5780: 
                   5781: =pod
                   5782: 
                   5783: B<>
                   5784: 
                   5785: =over 4
                   5786: 
1.174     albertel 5787: =item * B<getCompletionStatus>($part):
1.51      bowersj2 5788: 
1.174     albertel 5789: ($part defaults to 0.) A convenience function that returns a symbolic
                   5790: constant telling you about the completion status of the part, with the
                   5791: following possible results:
                   5792: 
                   5793: =back
1.51      bowersj2 5794: 
                   5795: B<Completion Codes>
                   5796: 
                   5797: =over 4
                   5798: 
1.174     albertel 5799: =item * B<NOT_ATTEMPTED>:
                   5800: 
                   5801: Has not been attempted at all.
                   5802: 
                   5803: =item * B<INCORRECT>:
                   5804: 
                   5805: Attempted, but wrong by student.
1.51      bowersj2 5806: 
1.174     albertel 5807: =item * B<INCORRECT_BY_OVERRIDE>:
1.51      bowersj2 5808: 
1.174     albertel 5809: Attempted, but wrong by instructor override.
1.51      bowersj2 5810: 
1.174     albertel 5811: =item * B<CORRECT>:
1.51      bowersj2 5812: 
1.174     albertel 5813: Correct or correct by instructor.
1.51      bowersj2 5814: 
1.174     albertel 5815: =item * B<CORRECT_BY_OVERRIDE>:
1.51      bowersj2 5816: 
1.174     albertel 5817: Correct by instructor override.
1.51      bowersj2 5818: 
1.174     albertel 5819: =item * B<EXCUSED>:
                   5820: 
                   5821: Excused. Not yet implemented.
                   5822: 
                   5823: =item * B<NETWORK_FAILURE>:
                   5824: 
                   5825: Information not available due to network failure.
                   5826: 
                   5827: =item * B<ATTEMPTED>:
                   5828: 
                   5829: Attempted, and not yet graded.
1.69      bowersj2 5830: 
1.450     raeburn  5831: =item * B<CREDIT_ATTEMPTED>:
                   5832: 
                   5833: Attempted, and credit received for attempt (survey and anonymous survey only).
                   5834: 
1.537     raeburn  5835: =item * B<INCORRECT_BY_PASSBACK>:
                   5836: 
                   5837: Attempted, but wrong for LTI Tool Provider by passback of grade
                   5838: 
                   5839: =item * B<CORRECT_BY_PASSBACK>:
                   5840: 
                   5841: Correct for LTI Tool Provider by passback of grade
                   5842: 
1.51      bowersj2 5843: =back
                   5844: 
                   5845: =cut
                   5846: 
1.53      bowersj2 5847: sub NOT_ATTEMPTED         { return 10; }
                   5848: sub INCORRECT             { return 11; }
                   5849: sub INCORRECT_BY_OVERRIDE { return 12; }
                   5850: sub CORRECT               { return 13; }
                   5851: sub CORRECT_BY_OVERRIDE   { return 14; }
                   5852: sub EXCUSED               { return 15; }
1.69      bowersj2 5853: sub ATTEMPTED             { return 16; }
1.450     raeburn  5854: sub CREDIT_ATTEMPTED      { return 17; }
1.537     raeburn  5855: sub INCORRECT_BY_PASSBACK { return 18; }
                   5856: sub CORRECT_BY_PASSBACK   { return 19; }
1.51      bowersj2 5857: 
                   5858: sub getCompletionStatus {
                   5859:     my $self = shift;
1.332     albertel 5860:     my $part = shift;
1.51      bowersj2 5861:     return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});
                   5862: 
1.332     albertel 5863:     my $status = $self->queryRestoreHash('solved', $part);
1.51      bowersj2 5864: 
1.251     www      5865:     # Left as separate if statements in case we ever do more with this
1.51      bowersj2 5866:     if ($status eq 'correct_by_student') {return $self->CORRECT;}
1.288     albertel 5867:     if ($status eq 'correct_by_scantron') {return $self->CORRECT;}
1.332     albertel 5868:     if ($status eq 'correct_by_override') {
                   5869: 	return $self->CORRECT_BY_OVERRIDE;
                   5870:     }
1.537     raeburn  5871:     if ($status eq 'correct_by_passback') {
                   5872:         return $self->CORRECT_BY_PASSBACK;
                   5873:     }
1.51      bowersj2 5874:     if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }
                   5875:     if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }
1.537     raeburn  5876:     if ($status eq 'incorrect_by_passback') {return $self->INCORRECT_BY_PASSBACK; }
1.51      bowersj2 5877:     if ($status eq 'excused') {return $self->EXCUSED; }
1.69      bowersj2 5878:     if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; }
1.450     raeburn  5879:     if ($status eq 'credit_attempted') {
                   5880:         if ($self->is_anonsurvey($part) || $self->is_survey($part)) {
                   5881:             return $self->CREDIT_ATTEMPTED;
                   5882:         } else {
                   5883:             return $self->ATTEMPTED;
                   5884:         }
                   5885:     }
1.51      bowersj2 5886:     return $self->NOT_ATTEMPTED;
1.108     bowersj2 5887: }
                   5888: 
                   5889: sub queryRestoreHash {
                   5890:     my $self = shift;
                   5891:     my $hashentry = shift;
                   5892:     my $part = shift;
1.185     bowersj2 5893:     $part = "0" if (!defined($part) || $part eq '');
1.108     bowersj2 5894:     return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});
                   5895: 
                   5896:     $self->getReturnHash();
                   5897: 
                   5898:     return $self->{RETURN_HASH}->{'resource.'.$part.'.'.$hashentry};
1.51      bowersj2 5899: }
                   5900: 
                   5901: =pod
                   5902: 
                   5903: B<Composite Status>
                   5904: 
1.174     albertel 5905: Along with directly returning the date or completion status, the
                   5906: resource object includes a convenience function B<status>() that will
                   5907: combine the two status tidbits into one composite status that can
1.191     bowersj2 5908: represent the status of the resource as a whole. This method represents
                   5909: the concept of the thing we want to display to the user on the nav maps
                   5910: screen, which is a combination of completion and open status. The precise logic is
1.174     albertel 5911: documented in the comments of the status method. The following results
                   5912: may be returned, all available as methods on the resource object
1.185     bowersj2 5913: ($res->NETWORK_FAILURE): In addition to the return values that match
                   5914: the date or completion status, this function can return "ANSWER_SUBMITTED"
                   5915: if that problemstatus parameter value is set to No, suppressing the
                   5916: incorrect/correct feedback.
1.51      bowersj2 5917: 
                   5918: =over 4
                   5919: 
1.174     albertel 5920: =item * B<NETWORK_FAILURE>:
                   5921: 
                   5922: The network has failed and the information is not available.
1.51      bowersj2 5923: 
1.174     albertel 5924: =item * B<NOTHING_SET>:
1.53      bowersj2 5925: 
1.174     albertel 5926: No dates have been set for this problem (part) at all. (Because only
                   5927: certain parts of a multi-part problem may be assigned, this can not be
                   5928: collapsed into "open later", as we do not know a given part will EVER
                   5929: be opened. For single part, this is the same as "OPEN_LATER".)
1.51      bowersj2 5930: 
1.174     albertel 5931: =item * B<CORRECT>:
1.51      bowersj2 5932: 
1.174     albertel 5933: For any reason at all, the part is considered correct.
1.54      bowersj2 5934: 
1.174     albertel 5935: =item * B<EXCUSED>:
1.51      bowersj2 5936: 
1.174     albertel 5937: For any reason at all, the problem is excused.
1.51      bowersj2 5938: 
1.174     albertel 5939: =item * B<PAST_DUE_NO_ANSWER>:
1.51      bowersj2 5940: 
1.174     albertel 5941: The problem is past due, not considered correct, and no answer date is
                   5942: set.
1.51      bowersj2 5943: 
1.174     albertel 5944: =item * B<PAST_DUE_ANSWER_LATER>:
1.51      bowersj2 5945: 
1.174     albertel 5946: The problem is past due, not considered correct, and an answer date in
                   5947: the future is set.
1.51      bowersj2 5948: 
1.174     albertel 5949: =item * B<ANSWER_OPEN>:
                   5950: 
                   5951: The problem is past due, not correct, and the answer is now available.
                   5952: 
                   5953: =item * B<OPEN_LATER>:
                   5954: 
                   5955: The problem is not yet open.
                   5956: 
                   5957: =item * B<TRIES_LEFT>:
                   5958: 
                   5959: The problem is open, has been tried, is not correct, but there are
                   5960: tries left.
                   5961: 
                   5962: =item * B<INCORRECT>:
                   5963: 
                   5964: The problem is open, and all tries have been used without getting the
                   5965: correct answer.
                   5966: 
                   5967: =item * B<OPEN>:
                   5968: 
                   5969: The item is open and not yet tried.
                   5970: 
                   5971: =item * B<ATTEMPTED>:
                   5972: 
                   5973: The problem has been attempted.
1.69      bowersj2 5974: 
1.450     raeburn  5975: =item * B<CREDIT_ATTEMPTED>:
                   5976: 
                   5977: The problem has been attempted, and credit given for the attempt (survey and anonymous survey only).
                   5978: 
1.185     bowersj2 5979: =item * B<ANSWER_SUBMITTED>:
                   5980: 
                   5981: An answer has been submitted, but the student should not see it.
                   5982: 
1.51      bowersj2 5983: =back
                   5984: 
                   5985: =cut
                   5986: 
1.432     raeburn  5987: sub TRIES_LEFT        { return 20; }
                   5988: sub ANSWER_SUBMITTED  { return 21; }
                   5989: sub PARTIALLY_CORRECT { return 22; }
                   5990: 
                   5991: sub RESERVED_LATER    { return 30; }
                   5992: sub RESERVED          { return 31; }
                   5993: sub RESERVED_LOCATION { return 32; }
                   5994: sub RESERVABLE        { return 33; }
                   5995: sub RESERVABLE_LATER  { return 34; }
                   5996: sub NOTRESERVABLE     { return 35; }
                   5997: sub NOT_IN_A_SLOT     { return 36; }
                   5998: sub NEEDS_CHECKIN     { return 37; }
                   5999: sub WAITING_FOR_GRADE { return 38; }
                   6000: sub UNKNOWN           { return 39; }
1.51      bowersj2 6001: 
                   6002: sub status {
                   6003:     my $self = shift;
                   6004:     my $part = shift;
                   6005:     if (!defined($part)) { $part = "0"; }
                   6006:     my $completionStatus = $self->getCompletionStatus($part);
                   6007:     my $dateStatus = $self->getDateStatus($part);
                   6008: 
                   6009:     # What we have is a two-dimensional matrix with 4 entries on one
                   6010:     # dimension and 5 entries on the other, which we want to colorize,
1.54      bowersj2 6011:     # plus network failure and "no date data at all".
1.51      bowersj2 6012: 
1.222     bowersj2 6013:     #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }
1.53      bowersj2 6014:     if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }
1.51      bowersj2 6015: 
1.408     raeburn  6016:     my $suppressFeedback = 0;
                   6017:     if (($self->problemstatus($part) eq 'no') ||
                   6018:         ($self->problemstatus($part) eq 'no_feedback_ever')) {
                   6019:         $suppressFeedback = 1;
                   6020:     }
1.236     bowersj2 6021:     # If there's an answer date and we're past it, don't
                   6022:     # suppress the feedback; student should know
1.330     albertel 6023:     if ($self->duedate($part) && $self->duedate($part) < time() &&
                   6024: 	$self->answerdate($part) && $self->answerdate($part) < time()) {
1.236     bowersj2 6025: 	$suppressFeedback = 0;
                   6026:     }
1.185     bowersj2 6027: 
1.51      bowersj2 6028:     # There are a few whole rows we can dispose of:
1.53      bowersj2 6029:     if ($completionStatus == CORRECT ||
1.537     raeburn  6030:         $completionStatus == CORRECT_BY_OVERRIDE ||
                   6031:         $completionStatus == CORRECT_BY_PASSBACK ) {
1.332     albertel 6032: 	if ( $suppressFeedback ) { return ANSWER_SUBMITTED }
                   6033: 	my $awarded=$self->awarded($part);
                   6034: 	if ($awarded < 1 && $awarded > 0) {
                   6035:             return PARTIALLY_CORRECT;
                   6036: 	} elsif ($awarded<1) {
                   6037: 	    return INCORRECT;
                   6038: 	}
                   6039: 	return CORRECT; 
1.69      bowersj2 6040:     }
                   6041: 
1.343     albertel 6042:     # If it's WRONG... and not open
                   6043:     if ( ($completionStatus == INCORRECT || 
1.537     raeburn  6044: 	  $completionStatus == INCORRECT_BY_OVERRIDE ||
                   6045: 	  $completionStatus == INCORRECT_BY_PASSBACK)
1.343     albertel 6046: 	 && (!$self->opendate($part) ||  $self->opendate($part) > time()) ) {
                   6047: 	return INCORRECT;
                   6048:     }
                   6049: 
1.69      bowersj2 6050:     if ($completionStatus == ATTEMPTED) {
                   6051:         return ATTEMPTED;
1.53      bowersj2 6052:     }
                   6053: 
1.450     raeburn  6054:     if ($completionStatus == CREDIT_ATTEMPTED) {
                   6055:         return CREDIT_ATTEMPTED;
                   6056:     }
                   6057: 
1.53      bowersj2 6058:     # If it's EXCUSED, then return that no matter what
                   6059:     if ($completionStatus == EXCUSED) {
                   6060:         return EXCUSED; 
1.51      bowersj2 6061:     }
                   6062: 
1.53      bowersj2 6063:     if ($dateStatus == NOTHING_SET) {
                   6064:         return NOTHING_SET;
1.51      bowersj2 6065:     }
                   6066: 
1.69      bowersj2 6067:     # Now we're down to a 4 (incorrect, incorrect_override, not_attempted)
                   6068:     # by 4 matrix (date statuses).
1.51      bowersj2 6069: 
1.54      bowersj2 6070:     if ($dateStatus == PAST_DUE_ANSWER_LATER ||
1.59      bowersj2 6071:         $dateStatus == PAST_DUE_NO_ANSWER ) {
1.272     albertel 6072:         return $suppressFeedback ? ANSWER_SUBMITTED : $dateStatus; 
1.51      bowersj2 6073:     }
                   6074: 
1.53      bowersj2 6075:     if ($dateStatus == ANSWER_OPEN) {
                   6076:         return ANSWER_OPEN;
1.51      bowersj2 6077:     }
                   6078: 
                   6079:     # Now: (incorrect, incorrect_override, not_attempted) x 
                   6080:     # (open_later), (open)
                   6081:     
1.53      bowersj2 6082:     if ($dateStatus == OPEN_LATER) {
                   6083:         return OPEN_LATER;
1.51      bowersj2 6084:     }
                   6085: 
                   6086:     # If it's WRONG...
1.537     raeburn  6087:     if ($completionStatus == INCORRECT || $completionStatus == INCORRECT_BY_OVERRIDE ||
                   6088:         $completionStatus == INCORRECT_BY_PASSBACK) {
1.51      bowersj2 6089:         # and there are TRIES LEFT:
1.79      bowersj2 6090:         if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {
1.222     bowersj2 6091:             return $suppressFeedback ? ANSWER_SUBMITTED : TRIES_LEFT;
1.51      bowersj2 6092:         }
1.185     bowersj2 6093:         return $suppressFeedback ? ANSWER_SUBMITTED : INCORRECT; # otherwise, return orange; student can't fix this
1.51      bowersj2 6094:     }
                   6095: 
                   6096:     # Otherwise, it's untried and open
1.473     raeburn  6097:     return OPEN;
1.224     bowersj2 6098: }
                   6099: 
1.432     raeburn  6100: sub check_for_slot {
                   6101:     my $self = shift;
                   6102:     my $part = shift;
1.508     damieng  6103:     my $symb = $self->{SYMB};
1.432     raeburn  6104:     my ($use_slots,$available,$availablestudent) = $self->slot_control($part);
                   6105:     if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) {
                   6106:         my @slots = (split(/:/,$availablestudent),split(/:/,$available));
                   6107:         my $cid=$env{'request.course.id'};
                   6108:         my $cdom=$env{'course.'.$cid.'.domain'};
                   6109:         my $cnum=$env{'course.'.$cid.'.num'};
                   6110:         my $now = time;
1.473     raeburn  6111:         my $num_usable_slots = 0;
1.522     raeburn  6112:         my ($checkedin,$checkedinslot,%consumed_uniq,%slots);
1.432     raeburn  6113:         if (@slots > 0) {
1.522     raeburn  6114:             %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum);
1.432     raeburn  6115:             if (&Apache::lonnet::error(%slots)) {
                   6116:                 return (UNKNOWN);
                   6117:             }
1.473     raeburn  6118:             my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots,'starttime');
1.432     raeburn  6119:             foreach my $slot_name (@sorted_slots) {
1.473     raeburn  6120:                 next if (!defined($slots{$slot_name}) || !ref($slots{$slot_name}));
1.432     raeburn  6121:                 my $end = $slots{$slot_name}->{'endtime'};
                   6122:                 my $start = $slots{$slot_name}->{'starttime'};
                   6123:                 my $ip = $slots{$slot_name}->{'ip'};
                   6124:                 if ($self->simpleStatus() == OPEN) {
                   6125:                     if ($end > $now) {
1.473     raeburn  6126:                         if ($start > $now) {
                   6127:                             return (RESERVED_LATER,$start,$slot_name);
                   6128:                         } else {
                   6129:                             if ($ip ne '') {
                   6130:                                 if (!&Apache::loncommon::check_ip_acc($ip)) {
1.503     raeburn  6131:                                     return (RESERVED_LOCATION,$end,$slot_name);
1.432     raeburn  6132:                                 }
                   6133:                             }
1.473     raeburn  6134:                             my @proctors;
                   6135:                             if ($slots{$slot_name}->{'proctor'} ne '') {
                   6136:                                 @proctors = split(',',$slots{$slot_name}->{'proctor'});
                   6137:                             }
                   6138:                             if (@proctors > 0) {
                   6139:                                 ($checkedin,$checkedinslot) = $self->checkedin();
                   6140:                                 unless ((grep(/^\Q$checkedin\E/,@proctors)) &&
                   6141:                                         ($checkedinslot eq $slot_name)) {
1.538     raeburn  6142:                                     return (NEEDS_CHECKIN,$end,$slot_name); 
1.472     raeburn  6143:                                 }
1.432     raeburn  6144:                             }
1.473     raeburn  6145:                             return (RESERVED,$end,$slot_name);
1.432     raeburn  6146:                         }
                   6147:                     }
1.473     raeburn  6148:                 } elsif ($end > $now) {
                   6149:                     $num_usable_slots ++;
1.432     raeburn  6150:                 }
                   6151:             }
1.522     raeburn  6152:             my ($is_correct,$wait_for_grade);
1.432     raeburn  6153:             if ($self->is_task()) {
                   6154:                 my $taskstatus = $self->taskstatus();
                   6155:                 $is_correct = (($taskstatus eq 'pass') || 
                   6156:                                ($self->solved() =~ /^correct_/));
1.522     raeburn  6157:                 unless ($taskstatus =~ /^(?:pass|fail)$/) {
                   6158:                     $wait_for_grade = 1;
                   6159:                 }
1.432     raeburn  6160:             } else {
1.522     raeburn  6161:                 unless ($self->completable()) {
                   6162:                     $wait_for_grade = 1;
                   6163:                 }
                   6164:                 unless (($self->problemstatus($part) eq 'no') ||
                   6165:                         ($self->problemstatus($part) eq 'no_feedback_ever')) {
                   6166:                     $is_correct = ($self->solved($part) =~ /^correct_/);
                   6167:                     $wait_for_grade = 0;
                   6168:                 }
1.432     raeburn  6169:             }
                   6170:             ($checkedin,$checkedinslot) = $self->checkedin();
                   6171:             if ($checkedin) {
1.522     raeburn  6172:                 if (ref($slots{$checkedinslot}) eq 'HASH') {
                   6173:                     $consumed_uniq{$checkedinslot} = $slots{$checkedinslot}{'uniqueperiod'};
                   6174:                 }
                   6175:                 if ($wait_for_grade) {
1.432     raeburn  6176:                     return (WAITING_FOR_GRADE);
                   6177:                 } elsif ($is_correct) {
                   6178:                     return (CORRECT); 
                   6179:                 }
                   6180:             }
1.473     raeburn  6181:             if ($num_usable_slots) {
                   6182:                 return(NOT_IN_A_SLOT);
                   6183:             }
                   6184:         }
                   6185:         my $reservable = &Apache::lonnet::get_reservable_slots($cnum,$cdom,$env{'user.name'},
                   6186:                                                                $env{'user.domain'});
                   6187:         if (ref($reservable) eq 'HASH') {
1.511     raeburn  6188:             my ($map) = &Apache::lonnet::decode_symb($symb);
1.473     raeburn  6189:             if ((ref($reservable->{'now_order'}) eq 'ARRAY') && (ref($reservable->{'now'}) eq 'HASH')) {
1.474     raeburn  6190:                 foreach my $slot (reverse (@{$reservable->{'now_order'}})) {
1.510     raeburn  6191:                     my $canuse;
                   6192:                     if ($reservable->{'now'}{$slot}{'symb'} eq '') {
                   6193:                         $canuse = 1;
                   6194:                     } else {
                   6195:                         my %oksymbs;
                   6196:                         my @slotsymbs = split(/\s*,\s*/,$reservable->{'now'}{$slot}{'symb'});
                   6197:                         map { $oksymbs{$_} = 1; } @slotsymbs;
                   6198:                         if ($oksymbs{$symb}) {
                   6199:                             $canuse = 1;
                   6200:                         } else {
                   6201:                             foreach my $item (@slotsymbs) {
                   6202:                                 if ($item =~ /\.(page|sequence)$/) {
                   6203:                                     (undef,undef, my $sloturl) = &Apache::lonnet::decode_symb($item);
                   6204:                                     if (($map ne '') && ($map eq $sloturl)) {
                   6205:                                         $canuse = 1;
                   6206:                                         last;
                   6207:                                     }
                   6208:                                 }
                   6209:                             }
                   6210:                         }
                   6211:                     }
                   6212:                     if ($canuse) {
1.522     raeburn  6213:                         if ($checkedin) {
                   6214:                             if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') {
                   6215:                                 my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}};
                   6216:                                 if ($reservable->{'now'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) {
                   6217:                                     my ($new_uniq_start,$new_uniq_end) = ($1,$2);
                   6218:                                     next if (!
                   6219:                                         ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) ||
                   6220:                                         ($uniqstart > $new_uniq_end   &&  $uniqend > $new_uniq_end  ));
                   6221:                                 }
                   6222:                             }
                   6223:                         }
1.473     raeburn  6224:                         return(RESERVABLE,$reservable->{'now'}{$slot}{'endreserve'});
                   6225:                     }
                   6226:                 }
1.432     raeburn  6227:             }
1.473     raeburn  6228:             if ((ref($reservable->{'future_order'}) eq 'ARRAY') && (ref($reservable->{'future'}) eq 'HASH')) {
1.474     raeburn  6229:                 foreach my $slot (@{$reservable->{'future_order'}}) {
1.510     raeburn  6230:                     my $canuse;
                   6231:                     if ($reservable->{'future'}{$slot}{'symb'} eq '') {
                   6232:                         $canuse = 1;
                   6233:                     } elsif ($reservable->{'future'}{$slot}{'symb'} =~ /,/) {
                   6234:                         my %oksymbs;
                   6235:                         my @slotsymbs = split(/\s*,\s*/,$reservable->{'future'}{$slot}{'symb'});
                   6236:                         map { $oksymbs{$_} = 1; } @slotsymbs;
                   6237:                         if ($oksymbs{$symb}) {
                   6238:                             $canuse = 1;
                   6239:                         } else {
                   6240:                             foreach my $item (@slotsymbs) {
                   6241:                                 if ($item =~ /\.(page|sequence)$/) {
                   6242:                                     (undef,undef, my $sloturl) = &Apache::lonnet::decode_symb($item);
                   6243:                                     if (($map ne '') && ($map eq $sloturl)) {
                   6244:                                         $canuse = 1;
                   6245:                                         last;
                   6246:                                     }
                   6247:                                 }
                   6248:                             }
                   6249:                         }
                   6250:                     } elsif ($reservable->{'future'}{$slot}{'symb'} eq $symb) {
                   6251:                         $canuse = 1;
                   6252:                     }
                   6253:                     if ($canuse) {
1.522     raeburn  6254:                         if ($checkedin) {
                   6255:                             if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') {
                   6256:                                 my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}};
                   6257:                                 if ($reservable->{'future'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) {
                   6258:                                     my ($new_uniq_start,$new_uniq_end) = ($1,$2);
                   6259:                                     next if (!
                   6260:                                         ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) ||
                   6261:                                         ($uniqstart > $new_uniq_end   &&  $uniqend > $new_uniq_end  ));
                   6262:                                 }
                   6263:                             }
                   6264:                         }
1.473     raeburn  6265:                         return(RESERVABLE_LATER,$reservable->{'future'}{$slot}{'startreserve'});
                   6266:                     }
                   6267:                 }
1.432     raeburn  6268:             }
                   6269:         }
1.473     raeburn  6270:         return(NOTRESERVABLE);
1.432     raeburn  6271:     }
                   6272:     return;
                   6273: }
                   6274: 
1.224     bowersj2 6275: sub CLOSED { return 23; }
                   6276: sub ERROR { return 24; }
                   6277: 
                   6278: =pod
                   6279: 
                   6280: B<Simple Status>
                   6281: 
                   6282: Convenience method B<simpleStatus> provides a "simple status" for the resource.
                   6283: "Simple status" corresponds to "which icon is shown on the
                   6284: Navmaps". There are six "simple" statuses:
                   6285: 
                   6286: =over 4
                   6287: 
                   6288: =item * B<CLOSED>: The problem is currently closed. (No icon shown.)
                   6289: 
                   6290: =item * B<OPEN>: The problem is open and unattempted.
                   6291: 
                   6292: =item * B<CORRECT>: The problem is correct for any reason.
                   6293: 
                   6294: =item * B<INCORRECT>: The problem is incorrect and can still be
                   6295: completed successfully.
                   6296: 
                   6297: =item * B<ATTEMPTED>: The problem has been attempted, but the student
                   6298: does not know if they are correct. (The ellipsis icon.)
                   6299: 
                   6300: =item * B<ERROR>: There is an error retrieving information about this
                   6301: problem.
                   6302: 
                   6303: =back
                   6304: 
                   6305: =cut
                   6306: 
                   6307: # This hash maps the composite status to this simple status, and
                   6308: # can be used directly, if you like
                   6309: my %compositeToSimple = 
                   6310:     (
                   6311:       NETWORK_FAILURE()       => ERROR,
                   6312:       NOTHING_SET()           => CLOSED,
                   6313:       CORRECT()               => CORRECT,
1.332     albertel 6314:       PARTIALLY_CORRECT()     => PARTIALLY_CORRECT,
1.224     bowersj2 6315:       EXCUSED()               => CORRECT,
                   6316:       PAST_DUE_NO_ANSWER()    => INCORRECT,
                   6317:       PAST_DUE_ANSWER_LATER() => INCORRECT,
                   6318:       ANSWER_OPEN()           => INCORRECT,
                   6319:       OPEN_LATER()            => CLOSED,
                   6320:       TRIES_LEFT()            => OPEN,
                   6321:       INCORRECT()             => INCORRECT,
                   6322:       OPEN()                  => OPEN,
                   6323:       ATTEMPTED()             => ATTEMPTED,
1.450     raeburn  6324:       CREDIT_ATTEMPTED()      => CORRECT,
1.224     bowersj2 6325:       ANSWER_SUBMITTED()      => ATTEMPTED
                   6326:      );
                   6327: 
                   6328: sub simpleStatus {
                   6329:     my $self = shift;
                   6330:     my $part = shift;
                   6331:     my $status = $self->status($part);
                   6332:     return $compositeToSimple{$status};
1.225     bowersj2 6333: }
                   6334: 
                   6335: =pod
                   6336: 
                   6337: B<simpleStatusCount> will return an array reference containing, in
                   6338: this order, the number of OPEN, CLOSED, CORRECT, INCORRECT, ATTEMPTED,
                   6339: and ERROR parts the given problem has.
                   6340: 
                   6341: =cut
                   6342:     
                   6343: # This maps the status to the slot we want to increment
                   6344: my %statusToSlotMap = 
                   6345:     (
                   6346:      OPEN()      => 0,
                   6347:      CLOSED()    => 1,
                   6348:      CORRECT()   => 2,
                   6349:      INCORRECT() => 3,
                   6350:      ATTEMPTED() => 4,
                   6351:      ERROR()     => 5
                   6352:      );
                   6353: 
                   6354: sub statusToSlot { return $statusToSlotMap{shift()}; }
                   6355: 
                   6356: sub simpleStatusCount {
                   6357:     my $self = shift;
                   6358: 
                   6359:     my @counts = (0, 0, 0, 0, 0, 0, 0);
                   6360:     foreach my $part (@{$self->parts()}) {
                   6361: 	$counts[$statusToSlotMap{$self->simpleStatus($part)}]++;
                   6362:     }
                   6363: 
                   6364:     return \@counts;
1.191     bowersj2 6365: }
                   6366: 
                   6367: =pod
                   6368: 
                   6369: B<Completable>
                   6370: 
                   6371: The completable method represents the concept of I<whether the student can
                   6372: currently do the problem>. If the student can do the problem, which means
                   6373: that it is open, there are tries left, and if the problem is manually graded
                   6374: or the grade is suppressed via problemstatus, the student has not tried it
                   6375: yet, then the method returns 1. Otherwise, it returns 0, to indicate that 
                   6376: either the student has tried it and there is no feedback, or that for
                   6377: some reason it is no longer completable (not open yet, successfully completed,
                   6378: out of tries, etc.). As an example, this is used as the filter for the
                   6379: "Uncompleted Homework" option for the nav maps.
                   6380: 
                   6381: If this does not quite meet your needs, do not fiddle with it (unless you are
                   6382: fixing it to better match the student's conception of "completable" because
                   6383: it's broken somehow)... make a new method.
                   6384: 
                   6385: =cut
                   6386: 
                   6387: sub completable {
                   6388:     my $self = shift;
                   6389:     if (!$self->is_problem()) { return 0; }
                   6390:     my $partCount = $self->countParts();
                   6391: 
                   6392:     foreach my $part (@{$self->parts()}) {
                   6393:         if ($part eq '0' && $partCount != 1) { next; }
                   6394:         my $status = $self->status($part);
                   6395:         # "If any of the parts are open, or have tries left (implies open),
                   6396:         # and it is not "attempted" (manually graded problem), it is
                   6397:         # not "complete"
1.216     bowersj2 6398: 	if ($self->getCompletionStatus($part) == ATTEMPTED() ||
1.450     raeburn  6399:             $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() ||
1.216     bowersj2 6400: 	    $status == ANSWER_SUBMITTED() ) {
                   6401: 	    # did this part already, as well as we can
                   6402: 	    next;
                   6403: 	}
                   6404: 	if ($status == OPEN() || $status == TRIES_LEFT()) {
                   6405: 	    return 1;
                   6406: 	}
1.191     bowersj2 6407:     }
                   6408:         
                   6409:     # If all the parts were complete, so was this problem.
1.216     bowersj2 6410:     return 0;
1.51      bowersj2 6411: }
                   6412: 
                   6413: =pod
                   6414: 
1.507     raeburn  6415: B<Answerable>
                   6416: 
                   6417: The answerable method differs from the completable method in its handling of problem parts
                   6418: for which feedback on correctness is suppressed, but the student still has tries left, and
                   6419: the problem part is not past due, (i.e., the student could submit a different answer if
                   6420: he/she so chose). For that case completable will return 0, whereas answerable will return 1.
                   6421: 
                   6422: =cut
                   6423: 
                   6424: sub answerable {
                   6425:     my $self = shift;
                   6426:     if (!$self->is_problem()) { return 0; }
                   6427:     my $partCount = $self->countParts();
                   6428:     foreach my $part (@{$self->parts()}) {
                   6429:         if ($part eq '0' && $partCount != 1) { next; }
                   6430:         my $status = $self->status($part);
                   6431:         if ($self->getCompletionStatus($part) == ATTEMPTED() ||
                   6432:             $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() ||
                   6433:             $status == ANSWER_SUBMITTED() ) {
                   6434:             if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {
                   6435:                 return 1;
                   6436:             }
                   6437:         }
                   6438:         if ($status == OPEN() || $status == TRIES_LEFT() || $status == NETWORK_FAILURE()) {
                   6439:             return 1;
                   6440:         }
                   6441:     }
                   6442:     # None of the parts were answerable, so neither is this problem.
                   6443:     return 0;
                   6444: }
                   6445: 
                   6446: =pod
                   6447: 
1.51      bowersj2 6448: =head2 Resource/Nav Map Navigation
                   6449: 
                   6450: =over 4
                   6451: 
1.174     albertel 6452: =item * B<getNext>():
                   6453: 
                   6454: Retreive an array of the possible next resources after this
                   6455: one. Always returns an array, even in the one- or zero-element case.
                   6456: 
                   6457: =item * B<getPrevious>():
1.85      bowersj2 6458: 
1.174     albertel 6459: Retreive an array of the possible previous resources from this
                   6460: one. Always returns an array, even in the one- or zero-element case.
1.51      bowersj2 6461: 
                   6462: =cut
                   6463: 
                   6464: sub getNext {
                   6465:     my $self = shift;
                   6466:     my @branches;
                   6467:     my $to = $self->to();
1.85      bowersj2 6468:     foreach my $branch ( split(/,/, $to) ) {
1.51      bowersj2 6469:         my $choice = $self->{NAV_MAP}->getById($branch);
1.342     albertel 6470:         #if (!$choice->condition()) { next; }
1.51      bowersj2 6471:         my $next = $choice->goesto();
                   6472:         $next = $self->{NAV_MAP}->getById($next);
                   6473: 
1.131     bowersj2 6474:         push @branches, $next;
1.85      bowersj2 6475:     }
                   6476:     return \@branches;
                   6477: }
                   6478: 
                   6479: sub getPrevious {
                   6480:     my $self = shift;
                   6481:     my @branches;
                   6482:     my $from = $self->from();
1.504     raeburn  6483:     foreach my $branch ( split(/,/, $from)) {
1.85      bowersj2 6484:         my $choice = $self->{NAV_MAP}->getById($branch);
                   6485:         my $prev = $choice->comesfrom();
                   6486:         $prev = $self->{NAV_MAP}->getById($prev);
                   6487: 
1.131     bowersj2 6488:         push @branches, $prev;
1.51      bowersj2 6489:     }
                   6490:     return \@branches;
1.131     bowersj2 6491: }
                   6492: 
                   6493: sub browsePriv {
                   6494:     my $self = shift;
1.505     raeburn  6495:     my $noblockcheck = shift;
1.553     raeburn  6496:     my $deeplinklisted = shift;
1.131     bowersj2 6497:     if (defined($self->{BROWSE_PRIV})) {
                   6498:         return $self->{BROWSE_PRIV};
                   6499:     }
1.553     raeburn  6500:     my ($nodeeplinkcheck,$nodeeplinkout);
                   6501:     if ($deeplinklisted) {
                   6502:         my $deeplink = $self->deeplink(undef,'getlisted');
                   6503:         if (($deeplink) && ($deeplink ne 'absent')) {
                   6504:             $nodeeplinkcheck = 1;
                   6505:         }
                   6506:         $nodeeplinkout = 1;
                   6507:     }
1.311     albertel 6508:     $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(),
1.508     damieng  6509: 						    $self->{SYMB},undef,
1.553     raeburn  6510:                                                     undef,$noblockcheck,
                   6511:                                                     undef,$nodeeplinkcheck,
1.554     raeburn  6512:                                                     $nodeeplinkout);
1.51      bowersj2 6513: }
                   6514: 
                   6515: =pod
1.2       www      6516: 
1.51      bowersj2 6517: =back
1.2       www      6518: 
1.51      bowersj2 6519: =cut
1.2       www      6520: 
1.51      bowersj2 6521: 1;
1.2       www      6522: 
1.51      bowersj2 6523: __END__
1.2       www      6524: 
                   6525: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.