File:  [LON-CAPA] / loncom / interface / lonwishlist.pm
Revision 1.26: download - view: text, annotated - select for diffs
Mon May 22 19:18:41 2017 UTC (6 years, 11 months ago) by droeschl
Branches: MAIN
CVS tags: version_2_11_X, version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3, version_2_11_2_uiuc, version_2_11_2_msu, version_2_11_2_educog, version_2_11_2, HEAD
- distinguish between home in lonindexer and home in inline menu (requires different translations in German)
- avoid double translation of 'Stored Links' in lonwishlist

    1: # The LearningOnline Network with CAPA
    2: # Utility-routines for wishlist
    3: #
    4: # $Id: lonwishlist.pm,v 1.26 2017/05/22 19:18:41 droeschl Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: 
   29: =pod
   30: 
   31: =head1 NAME
   32: 
   33: Apache::lonwishlist - Wishlist-Module
   34:   
   35: =head1 SYNOPSIS
   36: 
   37: The wishlist offers a possibility to store links to resources from the resource-pool and external websites in a hierarchical list.
   38: It is only available for user with access to the resource-pool. The list can be structured by folders.
   39: 
   40: The wishlist-module uses the CPAN-module "Tree" for easily handling the directory-structure of the wishlist. Each node in the tree has an index to be referenced by.
   41: 
   42: =back
   43: 
   44: =cut
   45: 
   46: package Apache::lonwishlist;
   47: 
   48: use strict;
   49: use Apache::lonnet;
   50: use Apache::loncommon();
   51: use Apache::lonhtmlcommon;
   52: use Apache::lonlocal;
   53: use LONCAPA qw(:DEFAULT :match);
   54: use Tree;
   55: 
   56: 
   57: # Global variables
   58: my $root;
   59: my @childrenRt;
   60: my %TreeHash;
   61: my %TreeToHash;
   62: my @allFolders;
   63: my @allNodes;
   64: my $indentConst = 20;
   65: my $foldersOption;
   66: 
   67: =pod
   68: 
   69: =head2 Routines for getting and putting the wishlist data from and accordingly to users data.
   70: 
   71: =over 4
   72: 
   73: =item * &getWishlist()
   74: 
   75:      Get the wishlist-data via lonnet::getkeys() and lonnet::get() and returns the got data in a hash.
   76: 
   77: 
   78: =item * &putWishlist(wishlist)
   79: 
   80:      Parameter is a reference to a hash. Puts the wishlist-data contained in the given hash via lonnet::put() to user-data.
   81: 
   82: 
   83: =item * &deleteWishlist()
   84: 
   85:      Deletes all entries from the user-data for wishlist. Do this before putting in new data.
   86: 
   87: 
   88: =back
   89: 
   90: =cut
   91: 
   92: 
   93: # Read wishlist from user-data
   94: sub getWishlist {
   95:     my @keys = &Apache::lonnet::getkeys('wishlist');
   96:     my %wishlist = &Apache::lonnet::get('wishlist',\@keys);
   97:     foreach my $i (keys(%wishlist)) {
   98:         #File not found. This appears at the first time using the wishlist
   99:         #Create file and put 'root' into it
  100:        if ($i =~m/^\Qerror:No such file\E/) {
  101:            &Apache::lonnet::logthis($i.'! Create file by putting in the "root" of the directory tree.');
  102:            &Apache::lonnet::put('wishlist', {'root' => ''});
  103:            my $options = '<option value="" selected="selected">('.&mt('Top level').')</option>';
  104:            &Apache::lonnet::put('wishlist', {'folders' => $options});
  105:            @keys = &Apache::lonnet::getkeys('wishlist');
  106:            %wishlist = &Apache::lonnet::get('wishlist',\@keys);
  107:        }
  108:        elsif ($i =~ /^(con_lost|error|no_such_host)/i) {
  109:            &Apache::lonnet::logthis('ERROR while attempting to get wishlist: '.$i);
  110:            return 'error';
  111:        }
  112:     }
  113: 
  114:     # if we got no keys in hash returned by get(), return error.
  115:     # wishlist will not be loaded, instead the user will be asked to try again later
  116:     if ((keys(%wishlist)) == 0) {
  117:         &Apache::lonnet::logthis('ERROR while attempting to get wishlist: no keys retrieved!');
  118:         return 'error';
  119:     }
  120:     
  121:     return %wishlist;
  122: }
  123: 
  124: 
  125: # Write wishlist to user-data
  126: sub putWishlist {
  127:     my $wishlist = shift;
  128:     &Apache::lonnet::put('wishlist',$wishlist);
  129: }
  130: 
  131: 
  132: # Removes all existing entrys for wishlist in user-data
  133: sub deleteWishlist {
  134:     my @wishlistkeys = &Apache::lonnet::getkeys('wishlist');
  135:     my %wishlist = &Apache::lonnet::del('wishlist',\@wishlistkeys);
  136: }
  137: 
  138: 
  139: =pod
  140: 
  141: =head2 Routines for changing the directory struture of the wishlist.
  142: 
  143: =over 4
  144: 
  145: =item * &newEntry(title, path, note)
  146: 
  147:      Creates a new entry in the wishlist containing the given informations. Additionally saves the date of creation in the entry.  
  148: 
  149: 
  150: =item * &deleteEntries(marked)
  151: 
  152:      Parameter is a reference to an array containing the indices of all nodes that should be removed from the tree. 
  153: 
  154: 
  155: =item * &sortEntries(indexNode, at)
  156: 
  157:      Changes the position of a node given by indexNode within its siblings. New position is given by at.
  158: 
  159: 
  160: =item * &moveEntries(indexNodesToMove, indexParent)
  161: 
  162:      Parameter is a reference to an array containing the indices of all nodes that should be moved. indexParent specifies the node that will become the new Parent for these nodes. 
  163: 
  164: 
  165: =item * &setNewTitle(nodeindex, newTitle)
  166: 
  167:      Sets the title for the node given by nodeindex to newTitle.
  168: 
  169: 
  170: =item * &setNewPath(nodeindex, newPath)
  171: 
  172:      Sets the path for the node given by nodeindex to newPath.
  173: 
  174: 
  175: =item * &setNewNote(nodeindex, newNote)
  176: 
  177:      Sets the note for the node given by nodeindex to newNote.     
  178: 
  179: 
  180: =item * &saveChanges()
  181: 
  182:      Prepares the wishlist-hash to save it via &putWishlist(wishlist).   
  183: 
  184: 
  185: =back
  186: 
  187: =cut
  188: 
  189: 
  190: # Create a new entry
  191: sub newEntry() {
  192:     my ($rootgiven, $title, $path, $note) = @_;
  193: 
  194:     $root = $rootgiven;
  195:     @childrenRt = $root->children();
  196: 
  197:     my $date = gmtime();
  198:     # Create Entry-Object
  199:     my $entry = Entry->new(title => $title, path => $path, note => $note, date => $date);
  200:     # Create Tree-Object, this corresponds a node in the wishlist-tree
  201:     my $tree = Tree->new($entry);
  202:     # Add this node to wishlist-tree
  203:     my $folderIndex = $env{'form.folders'};
  204:     if ($folderIndex ne '') {
  205:         @allFolders = ();
  206:         &getFoldersToArray(\@childrenRt);
  207:         my $folderToInsertOn = &Apache::Tree::getNodeByIndex($folderIndex,\@allFolders);
  208:         $folderToInsertOn->add_child($tree);
  209:     }
  210:     else {
  211:         $root->add_child($tree);
  212:     }
  213:     return &saveChanges();
  214: }
  215: 
  216: 
  217: # Delete entries
  218: sub deleteEntries {
  219:     my $rootgiven = shift;
  220:     my $marked = shift;
  221: 
  222:     $root = $rootgiven;
  223:     @childrenRt = $root->children();
  224: 
  225:     &getNodesToArray(\@childrenRt);
  226:     foreach my $m (@$marked) {
  227:         my $found = &Apache::Tree::getNodeByIndex($m, \@allNodes);
  228:         # be sure, that entry exists (may have been deleted before, e.g. in an other browsertab)
  229:         if (defined $found) {
  230:             &Apache::Tree::removeNode($found);
  231:         }
  232:     }
  233:     @allNodes = ();
  234:     return &saveChanges();
  235: }
  236: 
  237: 
  238: # Sort entries
  239: sub sortEntries {
  240:     my $rootgiven = shift;
  241:     my $indexNode = shift;
  242:     my $at = shift;
  243: 
  244:     $root = $rootgiven;
  245:     @childrenRt = $root->children();
  246:     
  247:     &getNodesToArray(\@childrenRt);
  248:     my $foundNode = &Apache::Tree::getNodeByIndex($indexNode, \@allNodes);
  249: 
  250:     &Apache::Tree::moveNode($foundNode,$at,undef);
  251:     @allNodes = ();
  252:     return &saveChanges();
  253: }
  254: 
  255: 
  256: # Move entries
  257: sub moveEntries {
  258:     my $rootgiven = shift;
  259:     my $indexNodesToMove = shift;
  260:     my $indexParent = shift;
  261:     my @nodesToMove = ();
  262: 
  263:     $root = $rootgiven;
  264:     @childrenRt = $root->children();
  265: 
  266:     # get all nodes that should be moved
  267:     &getNodesToArray(\@childrenRt);
  268:     foreach my $index (@$indexNodesToMove) {
  269:         my $foundNode = &Apache::Tree::getNodeByIndex($index, \@allNodes);
  270:         push(@nodesToMove, $foundNode);
  271:     }
  272: 
  273:     foreach my $node (@nodesToMove) {
  274:         my $foundParent;
  275:         my $parentIsIn = 0;
  276:         foreach my $n (@nodesToMove) {
  277:             if ($node->parent()->value() ne "root") {
  278:                if ($node->parent()->value()->nindex() == $n->value()->nindex()) {
  279:                     $parentIsIn = 1;
  280:                 }
  281:             }
  282:         }
  283:         if (!$parentIsIn) {
  284:             if ($indexParent ne "root") {
  285:                 $foundParent = &Apache::Tree::getNodeByIndex($indexParent, \@allNodes);
  286:                 &Apache::Tree::moveNode($node,undef,$foundParent);
  287:             }
  288:             else {
  289:                 &Apache::Tree::moveNode($node,undef,$root);
  290:             }
  291:         }
  292:     }
  293:     @allNodes = ();
  294:     return &saveChanges();
  295: }
  296: 
  297: 
  298: # Set a new title for an entry
  299: sub setNewTitle {
  300:     my ($rootgiven, $nodeindex, $newTitle) = @_;
  301: 
  302:     $root = $rootgiven;
  303:     @childrenRt = $root->children();
  304: 
  305:     &getNodesToArray(\@childrenRt);
  306:     my $found = &Apache::Tree::getNodeByIndex($nodeindex, \@allNodes);
  307:     $found->value()->title($newTitle); 
  308:     @allNodes = ();
  309:     return &saveChanges();
  310: }
  311: 
  312: 
  313: # Set a new path for an entry
  314: sub setNewPath {
  315:     my ($rootgiven, $nodeindex, $newPath) = @_;
  316: 
  317:     $root = $rootgiven;
  318:     @childrenRt = $root->children();
  319: 
  320:     &getNodesToArray(\@childrenRt);
  321:     my $found = &Apache::Tree::getNodeByIndex($nodeindex, \@allNodes);
  322:     if ($found->value()->path()) {
  323:         $found->value()->path($newPath); 
  324:         return &saveChanges();
  325:     }
  326:     @allNodes = ();
  327:     return 0;
  328: }
  329: 
  330: 
  331: # Set a new note for an entry
  332: sub setNewNote {
  333:     my ($rootgiven, $nodeindex, $newNote) = @_;
  334: 
  335:     $root = $rootgiven;
  336:     @childrenRt = $root->children();
  337: 
  338:     &getNodesToArray(\@childrenRt);
  339:     my $found = &Apache::Tree::getNodeByIndex($nodeindex, \@allNodes);
  340:     $found->value()->note($newNote); 
  341:     @allNodes = ();
  342:     return &saveChanges();
  343: }
  344: 
  345: 
  346: # Save all changes
  347: sub saveChanges {
  348:     @childrenRt = $root->children();
  349:     &Apache::Tree::TreeIndex(\@childrenRt);
  350:     &Apache::Tree::setCountZero();
  351:     &Apache::Tree::RootToHash(\@childrenRt);
  352:     &Apache::Tree::TreeToHash(\@childrenRt);
  353:     &deleteWishlist();
  354:     &putWishlist(\%TreeToHash);
  355:     return $root;
  356: 
  357: }
  358: 
  359: 
  360: =pod
  361: 
  362: =head2 Routines for handling the directory structure
  363: 
  364: =over 4
  365: 
  366: =item * &getFoldersForOption(nodes)
  367: 
  368:      Return the titles for all exiting folders in an option-tag, used to offer the users a possibility to create a new link or folder in an existing folder.
  369:      Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level). 
  370: 
  371: 
  372: =item * &getFoldersToArray(children)
  373: 
  374:      Puts all nodes that represent folders in the wishlist into an array. 
  375:      Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level).     
  376: 
  377: 
  378: =item * &getNodesToArray(children)
  379: 
  380:      Puts all existing nodes into an array (apart from the root node, because this one does not represent an entry in the wishlist).
  381:      Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level).     
  382:  
  383: 
  384: =back
  385: 
  386: =cut
  387: 
  388: 
  389: # Return the names for all exiting folders in option-tags, so
  390: # a new link or a new folder can be created in an existing folder
  391: my $indent = 0;
  392: sub getFoldersForOption {
  393:     my $nodes = shift;
  394: 
  395:     foreach my $n (@$nodes) {
  396:         if ($n->value()->path() eq '') {
  397:             $foldersOption .= '<option value="'.$n->value()->nindex().'" style="margin-left:'.$indent.'px">'.
  398:                                    $n->value()->title().
  399:                                '</option>';
  400: 
  401:         my @children = $n->children();
  402:         if ($#children >=0) {
  403:             $indent += 10;
  404:             &getFoldersForOption(\@children);
  405:             $indent -= 10;
  406:             }
  407:         }
  408:     }
  409: }
  410: 
  411: 
  412: # Put all folder-nodes to an array
  413: sub getFoldersToArray {
  414:     my $children = shift;
  415:     foreach my $c (@$children) {
  416:         if ($c->value()->path() eq '') {
  417:             push(@allFolders,$c);
  418:         }
  419:         my @newchildren = $c->children();
  420:         if ($#newchildren >= 0) {
  421:             &getFoldersToArray(\@newchildren);
  422:         }
  423:     }
  424: }
  425: 
  426: 
  427: # Put all nodes to an array
  428: sub getNodesToArray {
  429:     my $children = shift;
  430:     foreach my $c (@$children) {
  431:         push(@allNodes,$c);
  432:         my @newchildren = $c->children();
  433:         if ($#newchildren >= 0) {
  434:             &getNodesToArray(\@newchildren);
  435:         }
  436:     }
  437: }
  438: 
  439: 
  440: =pod
  441: 
  442: =head2 Routines for the user-interface of the wishlist
  443: 
  444: =over 4
  445: 
  446: =item * &JSforWishlist()
  447: 
  448:      Returns JavaScript-functions needed for wishlist actions like open and close folders.
  449: 
  450: 
  451: =item * &wishlistView(nodes)
  452: 
  453:      Returns the table-HTML-markup for the wishlist in mode "view".
  454:      Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level).     
  455: 
  456: 
  457: =item * &wishlistEdit(nodes)
  458: 
  459:      Returns the table-HTML-markup for the wishlist in mode "edit".
  460:      Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level).     
  461: 
  462: 
  463: =item * &wishlistMove(nodes, marked)
  464: 
  465:      Returns the table-HTML-markup for the wishlist in mode "move". Highlights all entry "selected to move" contained in marked (reference to array).
  466:      Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level).     
  467: 
  468: 
  469: =item * &wishlistImport(nodes, numskipped)
  470: 
  471:      Returns the table-HTML-markup for the wishlist in mode "import".
  472:      Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level).
  473:      Side effect: increments the scalar ref: numskipped with a count of items in 
  474:      Stored Links unavailable for selection, (e.g., now marked obsolete or
  475:      inaccessible in Community context).
  476: 
  477: =item * &makePage(mode, marked)
  478: 
  479:      Returns the HTML-markup for the whole wishlist depending on mode. If mode is "move" we need the marked entries to be highlighted a "selected to move". 
  480:      Calls &wishlistView(nodes), &wishlistEdit(nodes) or &wishlistMove(nodes, marked).
  481:  
  482: 
  483: =item * &makePopUpNewLink(title, path)
  484: 
  485:      Returns the HTML-markup for the pop-up-window 'Add Link'. If this is called up from a browsed resource, the input-fields titel and path are pre-filled with the resources' meta-data-title and it's path. 
  486: 
  487: 
  488: =item * &makePopUpNewFolder()
  489: 
  490:      Returns the HTML-markup for the pop-up-window 'Add Folder'.
  491: 
  492: 
  493: =item * &makePageSet()
  494: 
  495:      Returns the HTML-Markup for the page shown when a link was set by using the icon when viewing a resource.
  496: 
  497: 
  498: =item * &makePageImport()
  499: 
  500:      Returns the HTML-Markup for the page shown when links should be imported into courses.
  501:  
  502: 
  503: =item * &makeErrorPage ()
  504: 
  505:      Returns the HTML-Markup for an error-page shown if the wishlist could not be loaded.
  506:  
  507: 
  508: =back
  509: 
  510: =cut
  511: 
  512: 
  513: # Return a script-tag containing Javascript-function
  514: # needed for wishlist actions like 'new link' ect.
  515: sub JSforWishlist {
  516:     my $startPagePopup = &Apache::loncommon::start_page('Stored Links',undef,
  517:                                                             {'only_body' => 1,
  518:                                                              'js_ready'  => 1,
  519:                                                              'bgcolor'   => '#FFFFFF',});
  520:     my $endPagePopup = &Apache::loncommon::end_page({'js_ready' => 1});
  521: 
  522:     @allFolders = ();
  523:     &getFoldersToArray(\@childrenRt);
  524:     &getFoldersForOption(\@childrenRt);
  525: 
  526:     # it is checked, wether a path links to a LON-CAPA-resource or an external website. links to course-contents are not allowed
  527:     # because they probably will return a kind of 'no access' (unless the user is already in the course, the path links to).
  528:     # also importing these kind of links into a course does not make much sense.
  529:     # to find out if a path (not starting with /res/...) links to course-contents, the same filter as in lonwrapper is used,
  530:     # that means that it is checked wether a path contains .problem, .quiz, .exam etc.
  531:     # this is good for most cases but crashes as soon as a real external website contains one of this pattern in its URL.
  532:     # so maybe there's a better way to find out wether a given URL belongs to a LON-CAPA-server or not ...?
  533:     my $warningLinkNotAllowed1 =
  534:         &mt('You can only insert links to LON-CAPA resources from the resource-pool'.
  535:             ' or to external websites.'.
  536:             ' Paths to LON-CAPA resources must be of the form /res/domain/user/...'.
  537:             ' Paths to external websites must contain the network protocol, e.g. http://...');
  538:     my $warningLinkNotAllowed2 = &mt('The following link is not allowed:').' ';
  539:     my $warningDelete = &mt('Are you sure you want to delete the selected entries? Deleting a folder also deletes all entries within this folder!');
  540:     my $warningSave = &mt('You have unsaved changes. You can either save these changes now by clicking "OK" or click "Cancel" if you do not want to save your changes.');
  541:     my $warningMoveS = &mt('You must select at minimum one entry to move!');
  542:     my $warningMoveD = &mt('You must select a destination folder!');
  543:     &js_escape(\$warningLinkNotAllowed1);
  544:     &js_escape(\$warningLinkNotAllowed2);
  545:     &js_escape(\$warningDelete);
  546:     &js_escape(\$warningSave);
  547:     &js_escape(\$warningMoveS);
  548:     &js_escape(\$warningMoveD);
  549:     $foldersOption = '';
  550: 
  551:     my $js = &Apache::lonhtmlcommon::scripttag(<<JAVASCRIPT);
  552:     function newLink() {
  553:         newlinkWin=window.open('/adm/wishlist?mode=newLink','newlinkWin','width=580,height=350, scrollbars=yes');
  554:     }
  555: 
  556:     function newFolder() {
  557:         newfolderWin=window.open('/adm/wishlist?mode=newFolder','newfolderWin','width=580,height=270, scrollbars=yes');
  558:     }
  559: 
  560:     function setFormAction(action,mode) {
  561:         var r = true;
  562:         setAction('');
  563:         if (action == 'delete') {
  564:             r = confirm("$warningDelete");
  565:             setAction('delete');
  566:         }
  567:         else if (action == 'save') {
  568:             var d = getDifferences();
  569:             if (d) {
  570:                 if (!confirm('$warningSave')) {
  571:                     setAction('noSave');
  572:                     r = true;
  573:                 }
  574:                 else {
  575:                     r = linksOK();
  576:                 }
  577:             }
  578:         }
  579:         else if (action == 'saveOK') {
  580:             r = linksOK();
  581:         }
  582:         else if (action == 'move') {
  583:             r = selectDestinationFolder(mode);
  584:         }
  585:         document.getElementsByName('list')[0].setAttribute("action", "/adm/wishlist?mode="+mode); 
  586:         if (r) {
  587:             document.getElementsByName('list')[0].submit(); 
  588:         }
  589:     }
  590: 
  591:     function setAction(action) {
  592:         document.getElementById('action').value = action; 
  593:     }
  594: 
  595:     function getDifferences() {
  596:         var newtitles = document.getElementsByName('newtitle');
  597:         var i = 0;
  598:         for (i=0;i<newtitles.length;i++) {
  599:             var newt = newtitles[i].value;
  600:             var oldt = newtitles[i].alt;
  601:             if (newt != oldt) {
  602:                 return true;
  603:             }
  604:         }
  605:         var newpath = document.getElementsByName('newpath');
  606:         var i = 0;
  607:         for (i=0;i<newpath.length;i++) {
  608:             var newp = newpath[i].value;
  609:             var oldp = newpath[i].alt;
  610:             if (newp != oldp) {
  611:                 return true;
  612:             }
  613:         }
  614:         var newnote = document.getElementsByName('newnote');
  615:         var i = 0;
  616:         for (i=0;i<newnote.length;i++) {
  617:             var newn = newnote[i].value;
  618:             var oldn = newnote[i].innerHTML;
  619:             if (newn != oldn) {
  620:                 return true;
  621:             }
  622:         }
  623:         return false;
  624:     }
  625: 
  626:     function linksOK() {
  627:         var newpath = document.getElementsByName('newpath');
  628:         var i = 0;
  629:         for (i=0;i<newpath.length;i++) {
  630:             var path = newpath[i].value;
  631:             var linkOK = (path.match(/^http:\\/\\//) || path.match(/^https:\\/\\//))
  632:                          && !(path.match(/\\.problem/) || path.match(/\\.exam/)
  633:                          || path.match(/\\.quiz/) || path.match(/\\.assess/)
  634:                          || path.match(/\\.survey/) || path.match(/\\.form/)
  635:                          || path.match(/\\.library/) || path.match(/\\.page/)
  636:                          || path.match(/\\.sequence/));
  637:             if (!path.match(/^(\\/res\\/)/) && !linkOK) {
  638:                 alert("$warningLinkNotAllowed1 $warningLinkNotAllowed2"+path);
  639:                 return false;
  640:             }
  641:          }
  642:         return true;
  643:     }
  644: 
  645:     function onLoadAction(mode) {
  646:         window.name = 'wishlist';
  647:         if (mode == "edit") {
  648:             var deepestRows = getDeepestRows();
  649:             setDisplaySelect(deepestRows, '');
  650:         }
  651:     }
  652: 
  653:     function folderAction(rowid) {
  654:         var row = document.getElementById(rowid);
  655:         var indent = getIndent(row);
  656:         var displ;
  657:         var status;
  658:         if (getImage(row) == 'closed') {
  659:             displ = '';
  660:             status = 'open';
  661:         }
  662:         else {
  663:             displ = 'LC_hidden';
  664:             status = 'closed';
  665:         }
  666:         setImage(row,status);
  667:         if (getNextRow(row) != null) {
  668:             var nextIndent = getIndent(getNextRow(row));
  669:             row = getNextRow(row);
  670:             while (nextIndent > indent) {
  671:                 if (displ == '') {
  672:                     row.className = (row.className).replace('LC_hidden','');
  673:                 }
  674:                 else if (displ != '' && !((row.className).match('LC_hidden'))) {
  675:                     var oldClass = row.className;
  676:                     row.className = oldClass+' LC_hidden';
  677:                     setDisplayNote(row.id.replace('row','note'),'LC_hidden');
  678:                 }
  679:                 if (status == 'open' && getImage(row).match('closed')) {
  680:                     row = getNextRowWithIndent(row, getIndent(row));
  681:                 }
  682:                 else {
  683:                     row = getNextRow(row);
  684:                 } 
  685:                 if (row != null) {
  686:                     nextIndent = getIndent(row);
  687:                 } 
  688:                 else {
  689:                     nextIndent = indent;
  690:                 }
  691:             }
  692:         }
  693:         setClasses();
  694:         var newtitles = document.getElementsByName('newtitle');
  695:         if (newtitles.length>0) {
  696:             var deepestRows = getDeepestRows();
  697:             var otherRows = getOtherRows(deepestRows);
  698:             setDisplaySelect(deepestRows,'');
  699:             setDisplaySelect(otherRows,'LC_hidden');
  700:         }
  701:     }
  702: 
  703:     function selectAction(rowid) {
  704:         var row = document.getElementById(rowid);
  705:         var indent = getIndent(row);
  706:         var checked = getChecked(row);
  707:         var previousFolderRows = new Array();
  708:         if (indent != 0) {
  709:             previousFolderRows = getPreviousFolderRows(row);
  710:         }
  711:         if (getNextRow(row) != null) {
  712:             var nextIndent = getIndent(getNextRow(row));
  713:             row = getNextRow(row);
  714:                 while (nextIndent > indent) {
  715:                     setChecked(row,checked);
  716:                     if (status == 'open' && getImage(row).match('closed')) {
  717:                         row = getNextRowWithIndent(row, getIndent(row));
  718:                     }
  719:                     else {
  720:                         row = getNextRow(row);
  721:                     }
  722:                     if (row != null) {
  723:                         nextIndent = getIndent(row);
  724:                     }
  725:                     else {
  726:                         nextIndent = indent;
  727:                     }
  728:                 }
  729:         }
  730:         if (!checked) {
  731:             var i = 0;
  732:             for (i=0;i<previousFolderRows.length;i++) {
  733:                 setChecked(previousFolderRows[i], false);
  734:             }
  735:         }
  736:     }
  737: 
  738:     function getNextNote(row) {
  739:         var rowId = row.id;
  740:         var nextRowId = parseInt(rowId.substr(3,rowId.length))+1;
  741:         nextRowId = "note"+nextRowId;
  742:         var nextRow = document.getElementById(nextRowId);
  743:         return nextRow;
  744:     }
  745: 
  746:     function getNextRow(row) {
  747:         var rowId = row.id;
  748:         var nextRowId = parseInt(rowId.substr(3,rowId.length))+1;
  749:         nextRowId = "row"+nextRowId;
  750:         var nextRow = document.getElementById(nextRowId);
  751:         return nextRow;
  752:     }
  753: 
  754:     function getPreviousRow(row) {
  755:         var rowId = row.id;
  756:         var previousRowId =  parseInt(rowId.substr(3,rowId.length))-1;
  757:         previousRowId = "row"+previousRowId;
  758:         var previousRow =document.getElementById(previousRowId);
  759:         return previousRow;
  760:     }
  761: 
  762:     function getIndent(row) {
  763:         var childPADD = document.getElementById(row.id.replace('row','padd'));
  764:         indent = childPADD.style.paddingLeft;
  765:         indent = parseInt(indent.substr(0,(indent.length-2)));
  766:  
  767:         if (getImage(row).match('link')) {
  768:             indent -= $indentConst;
  769:         }
  770:         return indent;
  771:     }
  772: 
  773:     function getNextRowWithIndent(row, indent) {
  774:         var nextRow = getNextRow(row);
  775:         if (nextRow != null) {
  776:         var nextIndent = getIndent(nextRow);
  777:         while (nextIndent >= indent) {
  778:             if (nextIndent == indent) {
  779:                 return nextRow;
  780:             }
  781:             nextRow = getNextRow(nextRow);
  782:             if (nextRow == null) {
  783:                 return null;
  784:             }
  785:             nextIndent = getIndent(nextRow);
  786:         }
  787:         }
  788:         return nextRow;
  789:     }
  790: 
  791:     function getImage(row) {
  792:         var childIMG = document.getElementById(row.id.replace('row','img'));
  793:         if ((childIMG.src).match('closed')) {
  794:             return 'closed';
  795:         }
  796:         else if ((childIMG.src).match('open')) {
  797:             return 'open;'
  798:         }
  799:         else {
  800:             return 'link';
  801:         }
  802:     } 
  803: 
  804:     function setImage(row, status) {
  805:         var childIMG = document.getElementById(row.id.replace('row','img'));
  806:         var childIMGFolder = document.getElementById(row.id.replace('row','imgFolder'));
  807:         childIMG.src = "/adm/lonIcons/arrow."+status+".gif";
  808:         childIMGFolder.src="/adm/lonIcons/navmap.folder."+status+".gif"; 
  809:     }
  810: 
  811:     function getChecked(row) {
  812:         var childCHECK = document.getElementById(row.id.replace('row','check'));
  813:         var checked = childCHECK.checked;
  814:         return checked;
  815:     }
  816: 
  817:     function setChecked(row,checked) {
  818:         var childCHECK = document.getElementById(row.id.replace('row','check'));
  819:         if (!childCHECK.disabled) {
  820:             childCHECK.checked = checked;
  821:         }
  822:     }
  823: 
  824:     function getPreviousFolderRows(row) {
  825:         var previousRow = getPreviousRow(row);
  826:         var indent = getIndent(previousRow);
  827:         var kindOfEntry = getImage(previousRow);
  828:         var rows = new Array();
  829:         if (kindOfEntry != 'link') {
  830:             rows.push(previousRow);
  831:         }
  832: 
  833:         while (indent >0) {
  834:             previousRow = getPreviousRow(previousRow);
  835:             if (previousRow != null) {
  836:                 indent = getIndent(previousRow);
  837:                 kindOfEntry = getImage(previousRow);
  838:                 if (kindOfEntry != 'link') {
  839:                     rows.push(previousRow);
  840:                 }
  841:             }
  842:             else {
  843:                 indent = 0; 
  844:             }
  845:         }
  846:         return rows;
  847:     }
  848: 
  849:     function getDeepestRows() {
  850:         var row = document.getElementById('row0');
  851:         var firstRow = row;
  852:         var indent = getIndent(row);
  853:         var maxIndent = indent;
  854:         while (getNextRow(row) != null) {
  855:             row = getNextRow(row);
  856:             indent = getIndent(row);
  857:             if (indent>maxIndent && !((row.className).match('LC_hidden'))) {
  858:                 maxIndent = indent;
  859:             }
  860:         }
  861:         var deepestRows = new Array();
  862:         row = firstRow;
  863:         var rowIndent;
  864:         while (getNextRow(row) != null) {
  865:             rowIndent = getIndent(row);
  866:             if (rowIndent == maxIndent) {
  867:                 deepestRows.push(row);
  868:             }
  869:             row = getNextRow(row);
  870:         }
  871:         rowIndent = getIndent(row);
  872:         if (rowIndent == maxIndent) {
  873:             deepestRows.push(row);
  874:         }
  875:         return deepestRows;
  876:     }
  877: 
  878:     function getOtherRows(deepestRows) {
  879:         var row = document.getElementById('row0');
  880:         var otherRows = new Array();
  881:         var isIn = false;
  882:         while (getNextRow(row) != null) {
  883:             var i = 0;
  884:             for (i=0; i < deepestRows.length; i++) {
  885:                 if (row.id == deepestRows[i].id) {
  886:                     isIn = true;
  887:                 }
  888:             }
  889:             if (!isIn) {
  890:                 otherRows.push(row);
  891:             }
  892:             row = getNextRow(row);
  893:             isIn = false;
  894:         }
  895:         for (i=0; i < deepestRows.length; i++) {
  896:             if (row.id == deepestRows[i].id) {
  897:                 isIn = true;
  898:             }
  899:         }
  900:         if (!isIn) {
  901:             otherRows.push(row);
  902:         }
  903:         return otherRows;
  904:     }
  905: 
  906:     function setDisplaySelect(deepestRows, displ) {
  907:         var i = 0;
  908:         for (i = 0; i < deepestRows.length; i++) {
  909:             var row = deepestRows[i];
  910:             var childSEL = document.getElementById(row.id.replace('row','sel'));
  911:             childSEL.className = displ;
  912:         } 
  913:     }
  914: 
  915:     function submitSelect() {
  916:        var list = document.getElementsByName('list')[0];
  917:        list.setAttribute("action","/adm/wishlist?mode=edit");
  918:        list.submit();
  919:     }
  920: 
  921:     function setDisplayNote(rowid, displ) {
  922:         var row = document.getElementById(rowid);
  923:         if (!displ) {
  924:             if ((row.className).match('LC_hidden')) {
  925:                 row.className = (row.className).replace('LC_hidden','');
  926:             }
  927:             else {
  928:                 var oldClass = row.className;
  929:                 row.className = oldClass+' LC_hidden';
  930:             }
  931:         }
  932:         else {
  933:             if (displ == '') {
  934:                 row.className = (row.className).replace('LC_hidden','');
  935:             }
  936:             else if (displ != '' && !((row.className).match('LC_hidden'))) {
  937:                 var oldClass = row.className;
  938:                 row.className = oldClass+' LC_hidden';
  939:             }
  940:         }
  941:         var noteText = document.getElementById(rowid.replace('note','noteText'));
  942:         var noteImg = document.getElementById(rowid.replace('note','noteImg'));
  943:         if (noteText.value) {
  944:             noteImg.src = "/res/adm/pages/anot2.png";
  945:         }
  946:         else {
  947:             noteImg.src = "/res/adm/pages/anot.png";
  948:         }
  949: 
  950:     }
  951: 
  952:     function setClasses() {
  953:         var row = document.getElementById("row0");
  954:         var note = document.getElementById("note0");
  955:         var LC_class = 0;
  956:         if (getNextRow(row) != null) {
  957:             while (getNextRow(row) != null) {
  958:                 if (!(row.className).match('LC_hidden')) {
  959:                     note.className = (note.className).replace('LC_even_row','');
  960:                     note.className = (note.className).replace('LC_odd_row','');
  961:                     if (LC_class) {
  962:                         row.className = 'LC_even_row';
  963:                         note.className = 'LC_even_row'+note.className;
  964:                     }
  965:                     else {
  966:                         row.className = 'LC_odd_row';
  967:                         note.className = 'LC_odd_row'+note.className;;
  968:                     }
  969:                     LC_class = !LC_class;
  970:                 }
  971:                 note = getNextNote(row);
  972:                 row = getNextRow(row);
  973:             }
  974:         }
  975:         if (!(row.className).match('LC_hidden')) {
  976:             note.className = (note.className).replace('LC_even_row','');
  977:             note.className = (note.className).replace('LC_odd_row','');
  978:             if (LC_class) {
  979:                 row.className = 'LC_even_row';
  980:                 note.className = 'LC_even_row'+note.className;
  981:             }
  982:             else {
  983:                 row.className = 'LC_odd_row';
  984:                 note.className = 'LC_odd_row'+note.className;
  985:             }
  986:         }
  987:     }
  988: 
  989:     function selectDestinationFolder(mode) {
  990:         var mark = document.getElementsByName('mark');
  991:         var i = 0;
  992:         for (i = 0; i < mark.length; i++) {
  993:             if (mark[i].checked) {
  994:                 document.getElementsByName('list')[0].submit();
  995:                 return true;
  996:             }
  997:         }
  998:         if (mode == 'move') {
  999:             alert('$warningMoveS');
 1000:         }
 1001:         else {
 1002:             alert('$warningMoveD');
 1003:         }
 1004:         return false;
 1005:     }
 1006: 
 1007:     function preview(url) {
 1008:        var newWin;
 1009:        if (!(url.match(/^http:\\/\\//) || url.match(/^https:\\/\\//))) {
 1010:            newWin = window.open(url+'?inhibitmenu=yes','preview','width=560,height=350,scrollbars=yes');
 1011:        }
 1012:        else {
 1013:            newWin = window.open(url,'preview','width=560,height=350,scrollbars=yes');
 1014:        }
 1015:        newWin.focus();
 1016:     }
 1017: 
 1018:     function checkAll() {
 1019:         var checkboxes = document.getElementsByName('check');
 1020:         for (var i = 0; i < checkboxes.length; i++) {
 1021:             if (!checkboxes[i].disabled) {
 1022:                 checkboxes[i].checked = "checked";
 1023:             }
 1024:         }
 1025:     }
 1026: 
 1027:     function uncheckAll() {
 1028:         var checkboxes = document.getElementsByName('check');
 1029:         for (var i = 0; i < checkboxes.length; i++) {
 1030:             if (!checkboxes[i].disabled) {
 1031:                 checkboxes[i].checked = "";
 1032:             }
 1033:         }
 1034:     }
 1035: 
 1036: JAVASCRIPT
 1037:    return $js;
 1038: }
 1039: 
 1040: sub JSforImport{
 1041:     my $rat = shift;
 1042: 
 1043:     my $js;
 1044:     if ($rat eq 'simple' || $rat eq '') {
 1045:         $js = &Apache::lonhtmlcommon::scripttag(<<JAVASCRIPT);
 1046:         function finish_import() {
 1047:             opener.document.forms.simpleedit.importdetail.value='';
 1048:             for (var num = 0; num < document.forms.groupsort.fnum.value; num++) {
 1049:                 try {
 1050:                     eval("document.forms.groupsort.filelink"+num+".value");
 1051:                 }
 1052:                 catch(err) {
 1053:                    continue;
 1054:                 }
 1055:                 if (eval("document.forms.groupsort.check"+num+".checked") && eval("document.forms.groupsort.filelink"+num+".value") != '') {
 1056:                     opener.document.forms.simpleedit.importdetail.value+='&'+
 1057:                     eval("document.forms.groupsort.title"+num+".value")+'='+
 1058:                     eval("document.forms.groupsort.filelink"+num+".value")+'='+
 1059:                     eval("document.forms.groupsort.id"+num+".value");
 1060:                 }
 1061:             }
 1062:             opener.document.forms.simpleedit.submit();
 1063:             self.close();
 1064:         }
 1065: JAVASCRIPT
 1066:     }
 1067:     else {
 1068:         $js = &Apache::lonhtmlcommon::scripttag(<<JAVASCRIPT);
 1069:         function finish_import() {
 1070:             var linkflag=false;
 1071:             for (var num=0; num<document.forms.groupsort.fnum.value; num++) {
 1072:                 if (eval("document.forms.groupsort.check"+num+".checked") && eval("document.forms.groupsort.filelink"+num+".value") != '') {
 1073:                     insertRowInLastRow();
 1074:                     placeResourceInLastRow(
 1075:                         eval("document.forms.groupsort.title"+num+".value"),
 1076:                         eval("document.forms.groupsort.filelink"+num+".value"),
 1077:                         eval("document.forms.groupsort.id"+num+".value"),
 1078:                         linkflag
 1079:                         );
 1080:                     linkflag=true;
 1081:                 }
 1082:             }
 1083:             opener.editmode=0;
 1084:             opener.notclear=0;
 1085:             opener.linkmode=0;
 1086:             opener.draw();
 1087:             self.close();
 1088:         }
 1089: 
 1090:         function insertRowInLastRow() {
 1091:             opener.insertrow(opener.maxrow);
 1092:             opener.addobj(opener.maxrow,'e&2');
 1093:         }
 1094: 
 1095:         function placeResourceInLastRow (title,url,id,linkflag) {
 1096:             opener.mostrecent=opener.newresource(opener.maxrow,2,opener.unescape(title),
 1097:                               opener.unescape(url),'false','normal',id);
 1098:             opener.save();
 1099:             if (linkflag) {
 1100:                 opener.joinres(opener.linkmode,opener.mostrecent,0);
 1101:             }
 1102:             opener.linkmode=opener.mostrecent;
 1103:         }
 1104: JAVASCRIPT
 1105:     }
 1106:     return $js;
 1107: }
 1108: 
 1109: # HTML-Markup for table if in view-mode
 1110: my $wishlistHTMLview;
 1111: my $indent_view = $indentConst;
 1112: sub wishlistView {
 1113:     my $nodes = shift;
 1114: 
 1115:     foreach my $n (@$nodes) {
 1116:         my $index = $n->value()->nindex();
 1117: 
 1118:         # start row, use data_table routines to set class to LC_even or LC_odd automatically. this row contains a checkbox, the title and the note-icon.
 1119:         # only display the top level entries on load
 1120:         $wishlistHTMLview .= ($n->parent()->value() eq 'root')?&Apache::loncommon::start_data_table_row('','row'.$index)
 1121:                                                               :&Apache::loncommon::continue_data_table_row('LC_hidden','row'.$index);
 1122: 
 1123:  
 1124:         # checkboxes
 1125:         $wishlistHTMLview .= '<td><input type="checkbox" name="mark" id="check'.$index.'" value="'.$index.'" '.
 1126:                              'onclick="selectAction('."'row".$index."'".')" /></td>';
 1127: 
 1128:         # entry is a folder
 1129:         if ($n->value()->path() eq '') {
 1130:             $wishlistHTMLview .= '<td id="padd'.$index.'" style="padding-left:'.(($indent_view-$indentConst)<0?0:($indent_view-$indentConst)).'px; min-width: 220px;">'.
 1131:                                  '<a href="javascript:;" onclick="folderAction('."'row".$index."'".')" style="vertical-align:top">'.
 1132:                                  '<img src="/adm/lonIcons/arrow.closed.gif" id="img'.$index.'" alt = "" class="LC_icon"/>'.
 1133:                                  '<img src="/adm/lonIcons/navmap.folder.closed.gif" id="imgFolder'.$index.'" alt="folder"/>'.
 1134:                                  $n->value()->title().'</a></td>';
 1135:         }
 1136:         # entry is a link
 1137:         else {
 1138:             my $quotable_link = &Apache::loncommon::escape_single($n->value()->path());
 1139:             $wishlistHTMLview .= '<td id="padd'.$index.'" style="padding-left:'.(($indent_view-$indentConst)<=0?$indentConst:$indent_view).'px; min-width: 220px;">'.
 1140:                                  '<a href="javascript:preview('."'".$quotable_link."'".');">'.
 1141:                                  '<img src="/res/adm/pages/wishlist-link.png" id="img'.$index.'" alt="link" />'.
 1142:                                  $n->value()->title().'</a></td>';
 1143:         }
 1144: 
 1145:         # note-icon, different icons for an entries with note and those without
 1146:         my $noteIMG = 'anot.png';
 1147: 
 1148:         if ($n->value()->note() ne '') {
 1149:             $noteIMG = 'anot2.png';
 1150:         }
 1151: 
 1152:         $wishlistHTMLview .= '<td style="padding-left:10px;"><a href="javascript:;" onclick="setDisplayNote('."'note".$index."'".')">'.
 1153:                              '<img id="noteImg'.$index.'" src="/res/adm/pages/'.$noteIMG.'" alt="'.&mt('Note').'" title="'.&mt('Note').'" '.
 1154:                              ' class="LC_icon"/></a></td>';
 1155: 
 1156:         $wishlistHTMLview .= &Apache::loncommon::end_data_table_row();
 1157: 
 1158:         # start row containing the textarea for the note, do not display note on default
 1159:         $wishlistHTMLview .= &Apache::loncommon::continue_data_table_row('LC_hidden','note'.$index).
 1160:                              '<td></td><td>'.
 1161:                              '<textarea id="noteText'.$index.'" cols="25" rows="3" style="width:100%" '.
 1162:                              'name="newnote" >'.
 1163:                              $n->value()->note().'</textarea></td><td></td>';
 1164:         $wishlistHTMLview .= &Apache::loncommon::end_data_table_row();
 1165: 
 1166:         # if the entry is a folder, it could have other entries as content. if it has, call wishlistView for those entries 
 1167:         my @children = $n->children();
 1168:         if ($#children >=0) {
 1169:             $indent_view += 20;
 1170:             &wishlistView(\@children);
 1171:             $indent_view -= 20;
 1172:         }
 1173:     }
 1174: }
 1175: 
 1176: 
 1177: # HTML-Markup for table if in edit-mode
 1178: my $wishlistHTMLedit;
 1179: my $indent_edit = $indentConst;
 1180: sub wishlistEdit {
 1181:     my $nodes = shift;
 1182:     my $curNode = 1;
 1183: 
 1184:     foreach my $n (@$nodes) {
 1185:         my $index = $n->value()->nindex();
 1186: 
 1187:         # start row, use data_table routines to set class to LC_even or LC_odd automatically.
 1188:         # this rows contains a checkbox, a select-field for sorting entries, the title in an input-field and the note-icon.
 1189:         # only display the top level entries on load
 1190:         $wishlistHTMLedit .= ($n->parent()->value() eq 'root')?&Apache::loncommon::start_data_table_row('','row'.$index)
 1191:                                                               :&Apache::loncommon::continue_data_table_row('LC_hidden','row'.$index);
 1192: 
 1193:         # checkboxes
 1194:         $wishlistHTMLedit .= '<td><input type="checkbox" name="mark" id="check'.$index.'" value="'.$index.'" '.
 1195:                              'onclick="selectAction('."'row".$index."'".')" /></td>';
 1196: 
 1197:         # option-tags for sorting entries. we need the numbers from 1 to n with n being the number of entries on the same level as the current entry.
 1198:         # set the number for the current entry into brackets 
 1199:         my $options;
 1200:         for (my $i = 1; $i < ((scalar @{$nodes})+1); $i++) {
 1201:            if ($i == $curNode) {
 1202:                $options .= '<option selected="selected" value="">('.$i.')</option>';
 1203:            }
 1204:            else {
 1205:                $options .= '<option value="'.$i.'">'.$i.'</option>';
 1206:            }
 1207:         }
 1208:         $curNode++;
 1209: 
 1210:         # entry is a folder
 1211:         if ($n->value()->path() eq '') {
 1212:             $wishlistHTMLedit .= '<td><select class="LC_hidden" name="sel" id="sel'.$index.'" onchange="submitSelect();">'.
 1213:                                  $options.'</select></td>'.
 1214:                                  '<td id="padd'.$index.'" style="padding-left:'.(($indent_edit-$indentConst)<0?0:($indent_edit-$indentConst)).'px;">'.
 1215:                                  '<a href="javascript:;" onclick="folderAction('."'row".$index."'".')" style="vertical-align:top" >'.
 1216:                                  '<img src="/adm/lonIcons/arrow.closed.gif" id="img'.$index.'" alt = ""  class="LC_icon"/>'.
 1217:                                  '<img src="/adm/lonIcons/navmap.folder.closed.gif" id="imgFolder'.$index.'" alt="folder"/></a>'.
 1218:                                  '<input type="text" name="newtitle" value="'.$n->value()->title().'" alt = "'.$n->value()->title().'" />'.
 1219:                                  '</td><td></td>';
 1220: 
 1221:         }
 1222:         # entry is a link
 1223:         else {
 1224:             $wishlistHTMLedit .= '<td><select class="LC_hidden" name="sel" id="sel'.$index.'" onchange="submitSelect();">'.
 1225:                                  $options.'</select></td>'.
 1226:                                  '<td id="padd'.$index.'" style="padding-left:'.(($indent_edit-$indentConst)<=0?$indentConst:$indent_edit).'px;">'.
 1227:                                  '<img src="/res/adm/pages/wishlist-link.png" id="img'.$index.'" alt="link"/>'.
 1228:                                  '<input type="text" name="newtitle" value="'.$n->value()->title().'" alt = "'.$n->value()->title().'" /></td>'.
 1229:                                  '<td><input type="text" name="newpath" value="'.$n->value()->path().'" alt = "'.$n->value()->path().'" /></td>';
 1230:         }
 1231:         
 1232:         # note-icon, different icons for an entries with note and those without
 1233:         my $noteIMG = 'anot.png';
 1234: 
 1235:         if ($n->value()->note() ne '') {
 1236:             $noteIMG = 'anot2.png';
 1237:         }
 1238: 
 1239:         $wishlistHTMLedit .= '<td style="padding-left:10px;"><a href="javascript:;" onclick="setDisplayNote('."'note".$index."'".')">'.
 1240:                              '<img id="noteImg'.$index.'" src="/res/adm/pages/'.$noteIMG.'" alt="'.&mt('Note').'" title="'.&mt('Note').'" '.
 1241:                              ' class="LC_icon"/></a></td>';
 1242: 
 1243:         $wishlistHTMLedit .= &Apache::loncommon::end_data_table_row();
 1244: 
 1245:         # start row containing the textarea for the note
 1246:         $wishlistHTMLedit .= &Apache::loncommon::continue_data_table_row('LC_hidden','note'.$index).
 1247:                              '<td></td><td></td><td colspan="2">'.
 1248:                              '<textarea id="noteText'.$index.'" cols="25" rows="3" style="width:100%" '.
 1249:                              'name="newnote">'.
 1250:                              $n->value()->note().'</textarea></td><td></td>';
 1251:         $wishlistHTMLedit .= &Apache::loncommon::end_data_table_row();
 1252: 
 1253:         # if the entry is a folder, it could have other entries as content. if it has, call wishlistEdit for those entries 
 1254:         my @children = $n->children();
 1255:         if ($#children >=0) {
 1256:             $indent_edit += 20;
 1257:             &wishlistEdit(\@children);
 1258:             $indent_edit -= 20;
 1259:         }
 1260:     }
 1261: }
 1262: 
 1263: 
 1264: 
 1265: # HTML-Markup for table if in move-mode
 1266: my $wishlistHTMLmove ='<tr id="root" class="LC_odd_row"><td><input type="radio" name="mark" id="radioRoot" value="root" /></td>'.
 1267:                       '<td>'.&mt('Top level').'</td><td></td></tr>';
 1268: my $indent_move = $indentConst;
 1269: sub wishlistMove {
 1270:     my $nodes = shift;
 1271:     my $marked = shift;
 1272: 
 1273:     foreach my $n (@$nodes) {
 1274:         my $index = $n->value()->nindex();
 1275: 
 1276:         #find out wether the current entry was marked to be moved.
 1277:         my $isIn = 0;
 1278:         foreach my $m (@$marked) {
 1279:             if ($index == $m) {
 1280:                $isIn = 1;
 1281:             }
 1282:         }
 1283:         # start row and set class for even or odd row. this rows contains the title and the note-icon and can contain a radio-button
 1284:         $wishlistHTMLmove .= &Apache::loncommon::start_data_table_row('','row'.$index);
 1285: 
 1286: 
 1287:         # entry is a folder
 1288:         if ($n->value()->path() eq '') {
 1289:             # display a radio-button, if the folder was not selected to be moved
 1290:             if (!$isIn) {
 1291:                 $wishlistHTMLmove .= '<td><input type="radio" name="mark" id="radio'.$index.'" value="'.$index.'" /></td>'.
 1292:                                      '<td id="padd'.$index.'" style="padding-left:'.(($indent_move-$indentConst)<0?0:($indent_move-$indentConst)).'px; min-width: 220px;">';
 1293:             }
 1294:             # highlight the title, if the folder was selected to be moved
 1295:             else {
 1296:                 $wishlistHTMLmove .= '<td></td>'.
 1297:                                      '<td id="padd'.$index.'" style="padding-left:'.(($indent_move-$indentConst)<0?0:($indent_move-$indentConst)).'px; min-width: 220px;'.
 1298:                                      'color:red;">';
 1299:             }
 1300:             #arrow- and folder-image, all folders are open, and title
 1301:             $wishlistHTMLmove .= '<img src="/adm/lonIcons/arrow.open.gif" id="img'.$index.'" alt = "" />'.
 1302:                                  '<img src="/adm/lonIcons/navmap.folder.open.gif" id="imgFolder'.$index.'" alt="folder"/>'.
 1303:                                  $n->value()->title().'</td>';
 1304:         }
 1305:         # entry is a link
 1306:         else {
 1307:             # higlight the title, if the link was selected to be moved
 1308:             my $highlight = '';
 1309:             if ($isIn) {
 1310:                $highlight = 'style="color:red;"';
 1311:             }
 1312:             # link-image and title
 1313:             my $quotable_link = &Apache::loncommon::escape_single($n->value()->path());
 1314:             $wishlistHTMLmove .= '<td></td>'.
 1315:                                  '<td id="padd'.$index.'" style="padding-left:'.(($indent_move-$indentConst)<=0?$indentConst:$indent_move).'px; min-width: 220px;">'.
 1316:                                  '<a href="javascript:preview('."'".$quotable_link."'".');" '.$highlight.'>'.
 1317:                                  '<img src="/res/adm/pages/wishlist-link.png" id="img'.$index.'" alt="link"/>'.
 1318:                                  $n->value()->title().'</a></td>';
 1319:         }
 1320: 
 1321:         # note-icon, different icons for an entries with note and those without
 1322:         my $noteIMG = 'anot.png';
 1323: 
 1324:         if ($n->value()->note() ne '') {
 1325:             $noteIMG = 'anot2.png';
 1326:         }
 1327: 
 1328:         $wishlistHTMLmove .= '<td style="padding-left:10px;"><a href="javascript:;" onclick="setDisplayNote('."'note".$index."'".')">'.
 1329:                              '<img id="noteImg'.$index.'" src="/res/adm/pages/'.$noteIMG.'" alt="'.&mt('Note').'" title="'.&mt('Note').'" '.
 1330:                              ' class="LC_icon"/></a></td>';
 1331: 
 1332:         $wishlistHTMLmove .= &Apache::loncommon::end_data_table_row();
 1333: 
 1334:         # start row containing the textarea for the note, readonly in move-mode
 1335:         $wishlistHTMLmove .= &Apache::loncommon::continue_data_table_row('LC_hidden','note'.$index).
 1336:                              '<td></td><td>'.
 1337:                              '<textarea id="noteText'.$index.'" cols="25" rows="3" style="width:100%" '.
 1338:                              'name="newnote" readonly="readonly">'.
 1339:                              $n->value()->note().'</textarea></td><td></td>'.
 1340:                              &Apache::loncommon::end_data_table_row();
 1341: 
 1342:         # if the entry is a folder, it could have other entries as content. if it has, call wishlistMove for those entries 
 1343:         my @children = $n->children();
 1344:         if ($#children >=0) {
 1345:             $indent_move += 20;
 1346:             &wishlistMove(\@children, $marked);
 1347:             $indent_move -= 20;
 1348:         }
 1349:     }
 1350: }
 1351: 
 1352: 
 1353: 
 1354: # HTML-Markup for table if in import-mode
 1355: my $wishlistHTMLimport;
 1356: my $indent_imp = $indentConst;
 1357: my $form = 1;
 1358: sub wishlistImport {
 1359:     my ($nodes,$numskipped) = @_;
 1360: 
 1361:     my ($is_community,%nopick);
 1362:     if ($env{'request.course.id'}) {
 1363:         if (&Apache::loncommon::course_type() eq 'Community') {
 1364:             $is_community = 1;
 1365:         }
 1366:     }
 1367: 
 1368:     foreach my $n (@$nodes) {
 1369:         my $index = $n->value()->nindex();
 1370: 
 1371:         #
 1372:         # Determine which resources in stored links may be imported into a course/community.
 1373:         # (a) Import of directories in /res space is not supported.
 1374:         # (b) Import of a resource into a community requires user has 'bro' privilege for resource
 1375:         #     (i.e., user has author or co-author role for corresponcding Authoring Space).
 1376:         # (c) Import of a resource into a course requires user has 'be' privilege for resource.
 1377:         #
 1378: 
 1379:         if ($n->value()->path() =~ m{^(/res/$match_domain/$match_username/)}) {
 1380:             if ($n->value()->path() =~ m{/$}) {
 1381:                 $nopick{$n->value()->path()} = $n->value()->title();
 1382:                 $$numskipped ++;
 1383:             } else {
 1384:                 if ($is_community) {
 1385:                     unless (&Apache::lonnet::allowed('bro',$n->value()->path())) {
 1386:                         $nopick{$n->value()->path()} = $n->value()->title();
 1387:                         $$numskipped ++;
 1388:                     }
 1389:                 } else {
 1390:                     unless (&Apache::lonnet::allowed('bre',$n->value()->path())) {
 1391:                         $nopick{$n->value()->path()} = $n->value()->title();
 1392:                         $$numskipped ++;
 1393:                     }
 1394:                 }
 1395:             }
 1396:         }
 1397: 
 1398:         # start row, use data_table routines to set class to LC_even or LC_odd automatically. this row contains a checkbox, the title and the note-icon.
 1399:         # only display the top level entries on load
 1400:         $wishlistHTMLimport .= ($n->parent()->value() eq 'root')?&Apache::loncommon::start_data_table_row('','row'.$index)
 1401:                                                                 :&Apache::loncommon::continue_data_table_row('LC_hidden','row'.$index);
 1402: 
 1403:  
 1404:         # checkboxes
 1405:         $wishlistHTMLimport .= '<td>';
 1406:         my ($disabled,$onclick,$image,$style);
 1407:         if ($nopick{$n->value()->path()}) {
 1408:             $disabled = ' disabled="disabled"';
 1409:             $image = 'wishlist-link-lighter.png';
 1410:             $style = 'style="color:#808080;"';
 1411:         } else {
 1412:             $onclick = ' onclick="selectAction('."'row".$index."'".')"';
 1413:             $image = 'wishlist-link.png';
 1414:         }
 1415:         $wishlistHTMLimport .= '<input type="checkbox" name="check" id="check'.$index.'" value="'.$index.'" '.
 1416:                                $disabled.$onclick.' />'.
 1417:                                '<input type="hidden" name="title'.$index.'" value="'.&escape($n->value()->title()).'" />'.
 1418:                                '<input type="hidden" name="filelink'.$index.'" value="'.&escape($n->value()->path()).'" />'.
 1419:                                '<input type="hidden" name="id'.$index.'" />';
 1420:         $wishlistHTMLimport .= '</td>';
 1421: 
 1422:         # entry is a folder
 1423:         if ($n->value()->path() eq '') {
 1424:             $wishlistHTMLimport .= '<td id="padd'.$index.'" style="padding-left:'.(($indent_imp-$indentConst)<0?0:($indent_imp-$indentConst)).'px; min-width: 220px;">'.
 1425:                                    '<a href="javascript:;" onclick="folderAction('."'row".$index."'".')" style="vertical-align:top">'.
 1426:                                    '<img src="/adm/lonIcons/arrow.closed.gif" id="img'.$index.'" alt = "" class="LC_icon"/>'.
 1427:                                    '<img src="/adm/lonIcons/navmap.folder.closed.gif" id="imgFolder'.$index.'" alt="folder"/>'.
 1428:                                    $n->value()->title().'</a></td>';
 1429:         }
 1430:         # entry is a link
 1431:         else {
 1432:             $wishlistHTMLimport .= '<td id="padd'.$index.'" style="padding-left:'.(($indent_imp-$indentConst)<=0?$indentConst:$indent_imp).'px; min-width: 220px;">';
 1433:             unless ($nopick{$n->value()->path()}) {
 1434:                 my $quotable_link = &Apache::loncommon::escape_single($n->value()->path());
 1435:                 $wishlistHTMLimport .= '<a href="javascript:preview('."'".$quotable_link."'".');">';
 1436:             }
 1437:             $wishlistHTMLimport .= '<img src="/res/adm/pages/'.$image.'" id="img'.$index.'" alt="link" />'.
 1438:                                    '<span '.$style.'>'.$n->value()->title().'</span></a></td>';
 1439:                                    $form++;
 1440:         }
 1441: 
 1442:         # note-icon, different icons for an entries with note and those without
 1443:         my $noteIMG = 'anot.png';
 1444: 
 1445:         if ($n->value()->note() ne '') {
 1446:             $noteIMG = 'anot2.png';
 1447:         }
 1448: 
 1449:         $wishlistHTMLimport .= '<td style="padding-left:10px;"><a href="javascript:;" onclick="setDisplayNote('."'note".$index."'".')">'.
 1450:                              '<img id="noteImg'.$index.'" src="/res/adm/pages/'.$noteIMG.'" alt="'.&mt('Note').'" title="'.&mt('Note').'" '.
 1451:                              ' class="LC_icon"/></a></td>';
 1452: 
 1453:         $wishlistHTMLimport .= &Apache::loncommon::end_data_table_row();
 1454: 
 1455:         # start row containing the textarea for the note, do not display note on default, readonly in import-mode
 1456:         $wishlistHTMLimport .= &Apache::loncommon::continue_data_table_row('LC_hidden','note'.$index).
 1457:                              '<td></td><td>'.
 1458:                              '<textarea id="noteText'.$index.'" cols="25" rows="3" style="width:100%" '.
 1459:                              'name="newnote" readonly="readonly">'.
 1460:                              $n->value()->note().'</textarea></td><td></td>';
 1461:         $wishlistHTMLimport .= &Apache::loncommon::end_data_table_row();
 1462: 
 1463:         # if the entry is a folder, it could have other entries as content. if it has, call wishlistImport for those entries 
 1464:         my @children = $n->children();
 1465:         if ($#children >=0) {
 1466:             $indent_imp += 20;
 1467:             &wishlistImport(\@children,$numskipped);
 1468:             $indent_imp -= 20;
 1469:         }
 1470:     }
 1471:     return;
 1472: }
 1473: 
 1474: # Returns the HTML-Markup for wishlist
 1475: sub makePage {
 1476:     my $rootgiven = shift;
 1477:     my $mode = shift;
 1478:     my $marked = shift;
 1479: 
 1480:     $root = $rootgiven;
 1481:     @childrenRt = $root->children();
 1482: 
 1483:     # breadcrumbs and start_page
 1484:     &Apache::lonhtmlcommon::clear_breadcrumbs();
 1485:     &Apache::lonhtmlcommon::add_breadcrumb(
 1486:               { href => '/adm/wishlist?mode='.$mode,
 1487:                 text => 'Stored Links'});
 1488:     my $startPage = &Apache::loncommon::start_page('Stored Links',undef,
 1489:                                                      {'add_entries' => {
 1490:                                                         'onload' => 'javascript:onLoadAction('."'".$mode."'".');',
 1491:                                                         'onunload' => 'javascript:window.name = '."'loncapaclient'"}});
 1492: 
 1493:     my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs('Stored Links','Wishlist');
 1494: 
 1495:     # get javascript-code for wishlist-interactions
 1496:     my $js = &JSforWishlist();
 1497: 
 1498:     # texthash for items in funtionlist
 1499:     my %lt = &Apache::lonlocal::texthash(
 1500:                  'ed' => 'Edit',
 1501:                  'vw' => 'View',
 1502:                  'al' => 'Add Link',
 1503:                  'af' => 'Add Folder',
 1504:                  'mv' => 'Move Selected',
 1505:                  'dl' => 'Delete Selected',
 1506:                  'sv' => 'Save');
 1507: 
 1508:     # start functionlist
 1509:     my $functions = &Apache::lonhtmlcommon::start_funclist();
 1510: 
 1511:     # icon for edit-mode, display when in view-mode
 1512:     if ($mode eq 'view') {
 1513:         $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
 1514:                           'onclick="setFormAction('."'save','edit'".');" class="LC_menubuttons_link">'.
 1515:                           '<img src="/res/adm/pages/edit-mode-22x22.png" alt="'.$lt{'ed'}.'" '.
 1516:                           'title="'.$lt{'ed'}.'" class="LC_icon"/> '.
 1517:                           '<span class="LC_menubuttons_inline_text">'.$lt{'ed'}.'</span></a>');
 1518:     }
 1519:     # icon for view-mode, display when in edit-mode
 1520:     else {
 1521:         $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
 1522:                           'onclick="setFormAction('."'save','view'".');" class="LC_menubuttons_link">'.
 1523:                           '<img src="/res/adm/pages/view-mode-22x22.png" alt="'.$lt{'vw'}.'" '.
 1524:                           'title="'.$lt{'vw'}.'" class="LC_icon"/> '.
 1525:                           '<span class="LC_menubuttons_inline_text">'.$lt{'vw'}.'</span></a>');
 1526:     }
 1527:     
 1528:     # icon for adding a new link
 1529:     $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
 1530:                       'onclick="newLink();" class="LC_menubuttons_link">'.
 1531:                       '<img src="/res/adm/pages/link-new-22x22.png" alt="'.$lt{'al'}.'" '.
 1532:                       'title="'.$lt{'al'}.'" class="LC_icon"/>'.
 1533:                       '<span class="LC_menubuttons_inline_text">'.$lt{'al'}.'</span></a>');
 1534: 
 1535:     # icon for adding a new folder
 1536:     $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
 1537:                       'onclick="newFolder();" class="LC_menubuttons_link">'.
 1538:                       '<img src="/res/adm/pages/folder-new-22x22.png" alt="'.$lt{'af'}.'" '.
 1539:                       'title="'.$lt{'af'}.'" class="LC_icon"/>'.
 1540:                       '<span class="LC_menubuttons_inline_text">'.$lt{'af'}.'</span></a>');
 1541: 
 1542:     # icon for moving entries
 1543:     $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
 1544:                       'onclick="setFormAction('."'move','move'".'); " class="LC_menubuttons_link">'.
 1545:                       '<img src="/res/adm/pages/move-22x22.png" alt="'.$lt{'mv'}.'" '.
 1546:                       'title="'.$lt{'mv'}.'" class="LC_icon" />'.
 1547:                       '<span class="LC_menubuttons_inline_text">'.$lt{'mv'}.'</span></a>');
 1548: 
 1549:     # icon for deleting entries
 1550:     $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
 1551:                       'onclick="setFormAction('."'delete','".$mode."'".'); " class="LC_menubuttons_link">'.
 1552:                       '<img src="/res/adm/pages/del.png" alt="'.$lt{'dl'}.'" '.
 1553:                       'title="'.$lt{'dl'}.'" class="LC_icon" />'.
 1554:                       '<span class="LC_menubuttons_inline_text">'.$lt{'dl'}.'</span></a>');
 1555: 
 1556:     # icon for saving changes
 1557:     $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
 1558:                       'onclick="setFormAction('."'saveOK','".$mode."'".'); " class="LC_menubuttons_link">'.
 1559:                       '<img src="/res/adm/pages/save-22x22.png" alt="'.$lt{'sv'}.'" '.
 1560:                       'title="'.$lt{'sv'}.'" class="LC_icon" />'.
 1561:                       '<span class="LC_menubuttons_inline_text">'.$lt{'sv'}.'</span></a>');
 1562: 
 1563:     # end funtionlist and generate subbox 
 1564:     $functions.= &Apache::lonhtmlcommon::end_funclist();
 1565:     my $subbox = &Apache::loncommon::head_subbox($functions);
 1566: 
 1567:     # start form 
 1568:     my $inner .= '<form name="list" action ="/adm/wishlist" method="post">'.
 1569:                  '<input type="hidden" id="action" name="action" value="" />';
 1570:  
 1571:     # only display subbox in view- or edit-mode
 1572:     if ($mode eq 'view' || $mode eq 'edit') {
 1573:         $inner .= $subbox;
 1574:     }
 1575: 
 1576:     # generate table-content depending on mode
 1577:     if ($mode eq 'edit') {
 1578:         &wishlistEdit(\@childrenRt);
 1579:         if ($wishlistHTMLedit ne '') {
 1580:             $inner .= &Apache::loncommon::start_data_table("LC_tableOfContent");
 1581:             $inner .= $wishlistHTMLedit;
 1582:             $inner .= &Apache::loncommon::end_data_table();
 1583:         }
 1584:         else {
 1585:             $inner .= '<span class="LC_info">'.&mt("Your Stored Links list is currently empty.").'</span>';
 1586:         }
 1587:         $wishlistHTMLedit = '';
 1588:     }
 1589:     elsif ($mode eq 'view') {
 1590:         &wishlistView(\@childrenRt);
 1591:         if ($wishlistHTMLview ne '') {
 1592:             $inner .= '<table class="LC_data_table LC_tableOfContent">'.$wishlistHTMLview.'</table>';
 1593:         }
 1594:         else {
 1595:             $inner .= '<span class="LC_info">'.&mt("Your Stored Links list is currently empty.").'</span>';
 1596:         }
 1597:         $wishlistHTMLview = '';
 1598:     }
 1599:     else {
 1600:         my $markStr = '';
 1601:         foreach my $m (@$marked) {
 1602:             $markStr .= $m.',';
 1603:         }
 1604:         if ($markStr) {
 1605:             $markStr = substr($markStr, 0, length($markStr)-1);
 1606:             $inner .= '<input type="hidden" value="'.$markStr.'" name="markedToMove" />';
 1607:             $inner .= '<p><span class="LC_info">'.&mt('You have selected the red marked entries to be moved to another folder. '.
 1608:                                                    'Now choose the new destination folder.').'</span></p>';
 1609:             &wishlistMove(\@childrenRt, $marked);
 1610:             $inner .= '<table class="LC_data_table LC_tableOfContent">'.$wishlistHTMLmove.'</table><br/><br/>';
 1611:             $inner .= '<input type="button" value="'.&mt('Move').'" onclick="setFormAction('."'move','view'".');" />'.
 1612:                       '<input type="button" value="'.&mt('Cancel').'" onclick="go('."'/adm/wishlist'".')" />';
 1613: 
 1614:             $wishlistHTMLmove ='<tr id="root" class="LC_odd_row"><td><input type="radio" name="mark" id="radioRoot" value="root" /></td>'.
 1615:                                '<td>'.&mt('Top level').'</td><td></td></tr>';
 1616:         }
 1617:         else {
 1618:             $inner .= '<p><span class="LC_info">'.&mt("You haven't marked any entry to move.").'</span></p>'.
 1619:                       '<input type="button" value="'.&mt('Back').'" onclick="go('."'/adm/wishlist'".')" />';
 1620:         }
 1621:     }
 1622:     
 1623:     # end form 
 1624:     $inner .= '</form>';
 1625: 
 1626:     # end_page 
 1627:     my $endPage =  &Apache::loncommon::end_page();
 1628: 
 1629:     # put all page-elements together
 1630:     my $page = $startPage.$breadcrumbs.$js.$inner.$endPage;
 1631: 
 1632:     return $page;
 1633: }
 1634: 
 1635: 
 1636: # Returns the HTML-Markup for the PopUp, shown when a new link should set, when NOT
 1637: # beeing in the wishlist-interface (method is called in lonmenu and lonsearchcat)
 1638: sub makePopUpNewLink {
 1639:     my ($title, $path) = @_;
 1640: 
 1641:     # Get all existing folders to offer posibility to set a new link
 1642:     # into a folder
 1643:     my %TreeHashLink = &Apache::lonwishlist::getWishlist();
 1644:     my $rootLink = &Apache::Tree::HashToTree(\%TreeHashLink);
 1645:     my @childrenRtLink = $rootLink->children();
 1646: 
 1647:     $foldersOption = '';
 1648:     @allFolders = ();
 1649:     &getFoldersToArray(\@childrenRtLink);
 1650:     &getFoldersForOption(\@childrenRtLink);
 1651: 
 1652:     my $options = '<option value="" selected="selected">('.&mt('Top level').')</option>'.$foldersOption;
 1653:     $foldersOption = '';
 1654:     @allFolders = ();
 1655: 
 1656:     # HTML-Markup for the Pop-Up-window 'Set a link for this resource to wishlist'
 1657:     my $startPageWishlistlink = 
 1658:         &Apache::loncommon::start_page('Save to Stored Links',undef,
 1659:                                       {'only_body' => 1,
 1660:                                        'bgcolor'   => '#FFFFFF',});
 1661: 
 1662:     my $warningLink = &mt('You must insert a title!');
 1663:     my $warningLinkNotAllowed1 =
 1664:         &mt('You can only insert links to LON-CAPA resources from the resource-pool'.
 1665:             ' or to external websites.'.
 1666:             ' Paths to LON-CAPA resources must be of the form /res/domain/user/...'.
 1667:             ' Paths to external websites must contain the network protocol, e.g. http://...');
 1668:     &js_escape(\$warningLink);
 1669:     &js_escape(\$warningLinkNotAllowed1);
 1670: 
 1671:     my $inPageWishlistlink1 = '<h1>'.&mt('Save to Stored Links').'</h1>';
 1672:     # If no title is delivered, 'New Link' is called up from the wishlist-interface, so after
 1673:     # submitting the window should close instead of offering a link to wishlist (like it should do
 1674:     # if we call 'Set New Link' from within a browsed ressource)
 1675:     if (!$title) {
 1676:         $inPageWishlistlink1 .= '<form method="post" name="newlink" action="/adm/wishlist" target="wishlist"'.
 1677:                                 'onsubmit="return newlinksubmit();" >';
 1678:     }
 1679:     else {
 1680:         $inPageWishlistlink1 .= '<form method="post" name="newlink" action="/adm/wishlist?mode=set" '.
 1681:                                 'onsubmit="return newlinksubmit();" >';
 1682:     }
 1683:     $inPageWishlistlink1 .= &Apache::lonhtmlcommon::start_pick_box().
 1684:                             &Apache::lonhtmlcommon::row_title(&mt('Link Title'));
 1685: 
 1686:     my $inPageWishlistlink2 = &Apache::lonhtmlcommon::row_closure().
 1687:                               &Apache::lonhtmlcommon::row_title(&mt('Path'));
 1688: 
 1689:     my $inPageWishlistlink3 = &Apache::lonhtmlcommon::row_closure().
 1690:                               &Apache::lonhtmlcommon::row_title(&mt('Note')).
 1691:                               '<textarea name="note" rows="3" cols="35" style="width:100%"></textarea>'.
 1692:                               &Apache::lonhtmlcommon::row_closure(1).
 1693:                               &Apache::lonhtmlcommon::end_pick_box().
 1694:                               '<br/><br/>'.
 1695:                               '<input type="submit" value="'.&mt('Save in').'" />'.
 1696:                               '<select name="folders">'.
 1697:                               $options.
 1698:                               '</select>'.
 1699:                               '<input type="button" value="'.&mt('Cancel').'" onclick="javascript:window.close();" />'.
 1700:                               '</form>';
 1701:     $options = '';
 1702: 
 1703:     my $endPageWishlistlink = &Apache::loncommon::end_page();
 1704: 
 1705:     my $popUp = $startPageWishlistlink.
 1706:     $inPageWishlistlink1.
 1707:     '<input type="text" name="title" size="45" value="" />'.
 1708:     $inPageWishlistlink2.
 1709:     '<input type="text" name="path" size="45" value="" />'.
 1710:     $inPageWishlistlink3;
 1711: 
 1712:     # JavaScript-function to set title and path of ressource automatically
 1713:     # and show warning, if no title was set or path is invalid
 1714:     $popUp .= <<SCRIPT;
 1715:     <script type="text\/javascript">
 1716:     document.getElementsByName("title")[0].value = '$title';
 1717:     document.getElementsByName("path")[0].value = '$path';
 1718:     var fromwishlist = false;
 1719:     var titleget = '$title';
 1720:     if (!titleget) {
 1721:         fromwishlist = true;
 1722:     } 
 1723:     function newlinksubmit(){
 1724:     var title = document.getElementsByName("title")[0].value;
 1725:     var path = document.getElementsByName("path")[0].value;
 1726:     if (!title) {
 1727:         alert("$warningLink");
 1728:         return false;}
 1729:     var linkOK = (path.match(/\^http:(\\\/\\\/)/) || path.match(/\^https:(\\\/\\\/)/))
 1730:     && !(path.match(/\\.problem/) || path.match(/\\.exam/)
 1731:     || path.match(/\\.quiz/) || path.match(/\\.assess/)
 1732:     || path.match(/\\.survey/) || path.match(/\\.form/)
 1733:     || path.match(/\\.library/) || path.match(/\\.page/)
 1734:     || path.match(/\\.sequence/));
 1735:     if (!path.match(/\^(\\\/res\\\/)/) && !linkOK) {
 1736:         alert("$warningLinkNotAllowed1");
 1737:         return false;}
 1738:     if (fromwishlist) {
 1739:         window.close();
 1740:     }
 1741:     return true;}
 1742:     <\/script>
 1743: SCRIPT
 1744: 
 1745:     $popUp .= $endPageWishlistlink;
 1746: 
 1747:     return $popUp;
 1748: }
 1749: 
 1750: sub makePopUpNewFolder {
 1751:     # Get all existing folders to offer posibility to create a new folder
 1752:     # into an existing folder
 1753:     my %TreeHashLink = &Apache::lonwishlist::getWishlist();
 1754:     my $rootLink = &Apache::Tree::HashToTree(\%TreeHashLink);
 1755:     my @childrenRtLink = $rootLink->children();
 1756: 
 1757:     $foldersOption = '';
 1758:     @allFolders = ();
 1759:     &getFoldersToArray(\@childrenRtLink);
 1760:     &getFoldersForOption(\@childrenRtLink);
 1761: 
 1762:     my $options = '<option value="" selected="selected">('.&mt('Top level').')</option>'.$foldersOption;
 1763:     $foldersOption = '';
 1764:     @allFolders = ();
 1765: 
 1766:     # HTML-Markup for the Pop-Up-window 'New Folder'
 1767:     my $startPageWishlistfolder = 
 1768:         &Apache::loncommon::start_page('New Folder',undef,
 1769:                                       {'only_body' => 1,
 1770:                                        'bgcolor'   => '#FFFFFF',});
 1771: 
 1772:     my $warningFolder = &mt('You must insert a title!');
 1773:     &js_escape(\$warningFolder);
 1774: 
 1775:     my $inPageNewFolder = '<h1>'.&mt('New Folder').'</h1>'.
 1776:                           '<form method="post" name="newfolder" action="/adm/wishlist" target="wishlist" '.
 1777:                           'onsubmit="return newfoldersubmit();" >'.
 1778:                           &Apache::lonhtmlcommon::start_pick_box().
 1779:                           &Apache::lonhtmlcommon::row_title(&mt('Folder title')).
 1780:                           '<input type="text" name="title" size="45" value="" /><br />'.
 1781:                           &Apache::lonhtmlcommon::row_closure().
 1782:                           &Apache::lonhtmlcommon::row_title(&mt('Note')).
 1783:                           '<textarea name="note" rows="3" cols="35" style="width:100%"></textarea><br />'.
 1784:                           &Apache::lonhtmlcommon::row_closure(1).
 1785:                           &Apache::lonhtmlcommon::end_pick_box().
 1786:                           '<br/><br/>'.
 1787:                           '<input type="submit" value="'.&mt('Save in').'" />'.
 1788:                           '<select name="folders">'.
 1789:                           $options.
 1790:                           '</select>'.
 1791:                           '<input type="button" value="'.&mt('Cancel').'" onclick="javascript:window.close();" />'.
 1792:                           '</form>';
 1793:     my $endPageWishlistfolder = &Apache::loncommon::end_page();
 1794: 
 1795:     my $popUp = $startPageWishlistfolder.
 1796:     $inPageNewFolder;
 1797: 
 1798:     $popUp .= <<SCRIPT;
 1799:     <script type="text\/javascript">
 1800:         function newfoldersubmit(){
 1801:             var title = document.getElementsByName("title")[0].value;
 1802:             if (!title) {
 1803:             alert("$warningFolder");
 1804:             return false;}
 1805:             else {
 1806:             window.close();
 1807:             return true;}}
 1808:     <\/script>
 1809: SCRIPT
 1810: 
 1811:     $popUp .= $endPageWishlistfolder;
 1812: 
 1813:     return $popUp;
 1814: }
 1815: 
 1816: # Returns the HTML-Markup for the page, shown when a link was set
 1817: sub makePageSet {
 1818:     my $title = 'Stored Links';
 1819: 
 1820:     # start_page
 1821:     my $output =
 1822:         &Apache::loncommon::start_page($title,undef,
 1823:                                        {'only_body' => 1})
 1824:        .'<h1>'.&mt($title).'</h1>';
 1825:     
 1826:     # confirm success and offer link to wishlist
 1827:     $output .=
 1828:         &Apache::loncommon::confirmwrapper(
 1829:             &Apache::lonhtmlcommon::confirm_success(
 1830:                 &mt('Link successfully saved!')))
 1831:        .&Apache::lonhtmlcommon::actionbox(
 1832:             ['<a href="javascript:;" onclick="opener.open('."'/adm/wishlist'".');window.close();">'.&mt('Go to Stored Links').'</a>',
 1833:              '<a href="javascript:;" onclick="window.close();">'.&mt('Close this window').'</a>'
 1834:             ]);
 1835: 
 1836:     # end_page 
 1837:     $output .= &Apache::loncommon::end_page();
 1838: 
 1839:     return $output;
 1840: }
 1841: 
 1842: 
 1843: # Returns the HTML-Markup for the page, shown when links should be imported into a course
 1844: sub makePageImport {
 1845:     my $rootgiven = shift;
 1846:     my $rat = shift;
 1847: 
 1848:     $root = $rootgiven;
 1849:     @childrenRt = $root->children();
 1850:     # start_page 
 1851:     my $startPage = &Apache::loncommon::start_page('Stored Links',undef,
 1852:                                                    {'only_body' => 1});
 1853:     
 1854:     # get javascript-code for wishlist-interactions
 1855:     my $js = &JSforWishlist();
 1856:     $js .= &JSforImport($rat);
 1857: 
 1858:     my $inner = '<h1>'.&mt('Import Resources from Stored Links').'</h1>';
 1859:     if (!$rat) {
 1860:         $inner .=
 1861:             '<ul>'.
 1862:             '<li class="LC_info">'.&mt('Use the checkboxes corresponding to a folder to '.
 1863:                 'easily check all links within the folder.').'</li>'.
 1864:             '<li class="LC_info">'.&mt('The folder structure itself cannot be imported.').'</li>'.
 1865:             '<li class="LC_info">'.&mt('All checked links will be imported into the current folder of your course.').'</li>'.
 1866:             '</ul>';
 1867:     }
 1868:     else {
 1869:         $inner .=
 1870:             '<ul>'.
 1871:             '<li class="LC_info">'.&mt('Use the checkboxes corresponding to a folder to '.
 1872:                 'easily check all links within this folder.').'</li>'.
 1873:             '<li class="LC_info">'.&mt('The folder structure itself cannot be imported.').'</li>'.
 1874:             '</ul>';
 1875:     }
 1876:     my %wishlist = &getWishlist();
 1877: 
 1878:     #FIXME Saved string containing all folders in wishlist.db-file (key 'folders') in first version of lonwishlist
 1879:     #After splitting lonwishlist into two modules, this is not necessary anymore. So, dependent from when the wishlist
 1880:     #was first called (i.e. when wishlist.db was created), there might be an entry 'folders' or not. Number of links in
 1881:     #wishlist.db depends on whether this entry exists or not...JW  
 1882:     my $fnum;
 1883:     if (defined $wishlist{'folders'}) {
 1884:         $fnum = (keys(%wishlist))-2;
 1885:     }
 1886:     else {
 1887:         $fnum = (keys(%wishlist))-1;
 1888:     }
 1889: 
 1890:     $inner .= '<form method="post" name="groupsort" action="">'.
 1891:               '<input type="hidden" value="'.$fnum.'" name="fnum" />'.
 1892:               '<input type="button" onclick="javascript:checkAll()" id="checkallbutton" value="'.&mt('Check All').'" />'.
 1893:               '<input type="button" onclick="javascript:uncheckAll()" id="uncheckallbutton" value="'.&mt('Uncheck All').'" />'.
 1894:               '<input type="button" value="'.&mt('Import Checked').'" onclick="finish_import();" />'.    
 1895:               '<input type="button" value="'.&mt('Cancel').'" onclick="window.close();" /><br/><br/>'; 
 1896: 
 1897:     
 1898:     # wishlist-table
 1899:     my $numskipped = 0;
 1900:     &wishlistImport(\@childrenRt,\$numskipped);
 1901:     if ($wishlistHTMLimport ne '') {
 1902:         $inner .= '<table class="LC_data_table LC_tableOfContent">'.$wishlistHTMLimport.'</table>';
 1903:     }
 1904:     else {
 1905:         $inner .= '<span class="LC_info">'.&mt("Your Stored Links list is currently empty.").'</span>';
 1906:     }
 1907:     if ($numskipped > 0) {
 1908:         $inner .= '<p class="LC_info">'.&mt('Note: where a Stored Link is unavailable for import in the current context it is grayed out.').'</p>';
 1909:     }
 1910:     $wishlistHTMLimport = '';
 1911: 
 1912:     $inner .= '</form>';
 1913: 
 1914:     # end_page 
 1915:     my $endPage =  &Apache::loncommon::end_page();
 1916: 
 1917:     # put all page-elements together
 1918:     my $page = $startPage.$js.$inner.$endPage;
 1919: 
 1920:     return $page;
 1921: }
 1922: 
 1923: 
 1924: # Returns the HTML-Markup for error-page
 1925: sub makeErrorPage {
 1926:     # breadcrumbs and start_page 
 1927:     &Apache::lonhtmlcommon::add_breadcrumb(
 1928:               { href => '/adm/wishlist',
 1929:                 text => 'Stored Links'});
 1930:     my $startPage = &Apache::loncommon::start_page('Stored Links');
 1931:     
 1932:     my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs(&mt('Stored Links'),'Wishlist');
 1933:     &Apache::lonhtmlcommon::clear_breadcrumbs();
 1934: 
 1935:     # error-message
 1936:     my $inner .= '<p class="LC_error">'.&mt('An error occurred! Please try again later.').'</p>';
 1937: 
 1938:     # end_page 
 1939:     my $endPage =  &Apache::loncommon::end_page();
 1940: 
 1941:     # put all page-elements together
 1942:     my $page = $startPage.$breadcrumbs.$inner.$endPage;
 1943: 
 1944:     return $page;
 1945: }
 1946: 
 1947: 
 1948: # ----------------------------------------------------- package Tree
 1949: # Extend CPAN-Module Tree by function like 'moveNode' or 'deleteNode'
 1950: package Apache::Tree;
 1951: 
 1952: =pod
 1953: 
 1954: =head2 Routines from package Tree
 1955: 
 1956: =over 4
 1957: 
 1958: =item * &getNodeByIndex(index, nodes)
 1959: 
 1960:      Searches for a node, specified by the index, in nodes (reference to array) and returns it. 
 1961:  
 1962: 
 1963: =item * &moveNode(node, at, newParent)
 1964: 
 1965:      Moves a given node to a new parent (if new parents is defined) or change the position from a node within its siblings (means sorting, at must be defined).
 1966: 
 1967: 
 1968: =item * &removeNode(node)
 1969: 
 1970:      Removes a node given by node from the tree.
 1971: 
 1972: 
 1973: =item * &TreeIndex(children)
 1974: 
 1975:      Sets an index for every node in the tree, beginning with 0.
 1976:      Recursive call starting with all children of the root of the tree (parameter children is reference to an array containing the nodes of the current level).     
 1977: 
 1978: 
 1979: =item * &setCountZero()
 1980: 
 1981:      Resets index counter.
 1982: 
 1983: 
 1984: =item * &RootToHash(childrenRt)
 1985: 
 1986:      Converts the root-node to a hash-entry: the key is root and values are just the indices of root's children.
 1987:    
 1988: 
 1989: =item * &TreeToHash(childrenRt)
 1990: 
 1991:      Converts all other nodes in the tree to hash. Each node is one hash-entry where the keys are the index of a node and the values are all other attributes (containing tile, path, note, date and indices for all direct children).
 1992:      Recursive call starting with all children of the root of the tree (parameter childrenRT is reference to an array containing the nodes of the current level).     
 1993: 
 1994: 
 1995: =item * &HashToTree()
 1996: 
 1997:      Converts the hash to a tree. Builds a tree-object for each entry in the hash. Afterwards call &buildTree(node, childrenIn, TreeNodes, TreeHash) to connect the tree-objects.
 1998: 
 1999: 
 2000: =item * &buildTree(node, childrenIn, TreeNodes, TreeHash)
 2001: 
 2002:      Joins the nodes to a tree.
 2003:      Recursive call starting with root and all children of root (parameter childrenIn is reference to an array containing the nodes indices of the current level).
 2004:    
 2005: 
 2006: =back
 2007: 
 2008: =cut
 2009: 
 2010: 
 2011: # returns the node with a given index from a list of nodes
 2012: sub getNodeByIndex {
 2013:     my $index = shift;
 2014:     my $nodes = shift;
 2015:     my $found;
 2016:     
 2017:     foreach my $n (@$nodes) {
 2018:         my $curIndex = $n->value()->nindex();
 2019:         if ($curIndex == $index) {
 2020:             $found = $n;
 2021:         }
 2022:     }
 2023:     return $found;
 2024: }
 2025: 
 2026: # moves a given node to a new parent or change the position from a node
 2027: # within its siblings (sorting)
 2028: sub moveNode {
 2029:     my $node = shift;
 2030:     my $at = shift;
 2031:     my $newParent = shift;
 2032: 
 2033: 
 2034:     if (!$newParent) {
 2035:         $newParent = $node->parent();
 2036:     }
 2037: 
 2038:     $node->parent()->remove_child($node);
 2039: 
 2040:     if (defined $at) {
 2041:         $newParent->add_child({at => $at},$node);
 2042:     }
 2043:     else {
 2044:         $newParent->add_child($node);
 2045:     }
 2046:     
 2047:     # updating root's children
 2048:     @childrenRt = $root->children();
 2049: }
 2050: 
 2051: # removes a given node
 2052: sub removeNode() {
 2053:     my $node = shift;
 2054:     my @children = $node->children();
 2055: 
 2056:     if ($#children >= 0) {
 2057:         foreach my $c (@children) {
 2058:             &removeNode($c);
 2059:         }
 2060:     }
 2061:     $node->parent()->remove_child($node);
 2062: 
 2063:     # updating root's children
 2064:     @childrenRt = $root->children();
 2065: }
 2066: 
 2067: 
 2068: # set an index for every node in the tree, beginning with 0
 2069: my $count = 0;
 2070: sub TreeIndex {
 2071:     my $children = shift;
 2072: 
 2073:     foreach my $n (@$children) {
 2074:         my @children = $n->children();
 2075:         $n->value()->nindex($count);$count++;
 2076: 
 2077:         if ($#children>=0) {
 2078:             &TreeIndex(\@children);
 2079:         }
 2080:     }
 2081: }
 2082: 
 2083: # reset index counter
 2084: sub setCountZero {
 2085:     $count = 0;
 2086: }
 2087: 
 2088: 
 2089: # convert the tree to a hash
 2090: # each node is one hash-entry
 2091: # keys are the indices, values are all other attributes
 2092: # (containing tile, path, note, date and indices for all direct children)
 2093: # except for root: the key is root and values are
 2094: # just the indices of root's children
 2095: sub RootToHash {
 2096:     my $childrenRt = shift;
 2097:     my @indexarr = ();
 2098: 
 2099:     foreach my $c (@$childrenRt) {
 2100:        push (@indexarr, $c->value()->nindex());
 2101:     }
 2102:     $TreeToHash{'root'} = [@indexarr];
 2103: }
 2104: 
 2105: sub TreeToHash {
 2106:     my $childrenRt = shift;
 2107: 
 2108:     foreach my $n (@$childrenRt) {
 2109:         my @arrtmp = ();
 2110:         $arrtmp[0] = $n->value()->title();
 2111:         $arrtmp[1] = $n->value()->path();
 2112:         $arrtmp[2] = $n->value()->note();
 2113:         $arrtmp[3] = $n->value()->date();
 2114:         my @childrenRt = $n->children();
 2115:         my $co = 4;
 2116:         foreach my $c (@childrenRt) {
 2117:             my $i = $c->value()->nindex();
 2118:             $arrtmp[$co] = $i;
 2119:             $co++;
 2120:         }
 2121:         $TreeToHash{$n->value()->nindex} = [ @arrtmp]; 
 2122:         if ($#childrenRt>=0) {
 2123:             &TreeToHash(\@childrenRt);
 2124:         }
 2125:     }
 2126: }
 2127: 
 2128: 
 2129: # convert the hash to a tree
 2130: # build a tree-object for each entry in the hash
 2131: # afterwards call &buildTree to connect the tree-objects
 2132: sub HashToTree {
 2133:     my $TreeHash = shift;
 2134:     my @TreeNodes = ();
 2135:     my $root;
 2136: 
 2137:     foreach my $key (keys(%$TreeHash)) {
 2138:         if ($key eq 'root') {
 2139:             $root = Tree->new("root");
 2140:         }
 2141:         elsif ($key ne 'folders') {
 2142:         my @attributes = @{ $$TreeHash{$key} };
 2143:         my $tmpNode;
 2144:             $tmpNode = Tree->new(Entry->new(title=>$attributes[0],
 2145:                                             path=>$attributes[1],
 2146:                                             note=>$attributes[2],
 2147:                                             date=>$attributes[3],
 2148:                                             nindex=>$key));
 2149:         push(@TreeNodes, $tmpNode);
 2150:         # shift all attributes except for
 2151:         # the indices representing the children of a node
 2152:         shift(@attributes);
 2153:         shift(@attributes);
 2154:         shift(@attributes);
 2155:         shift(@attributes);
 2156:         $$TreeHash{$key} = [ @attributes ];
 2157:         }
 2158:     }
 2159:     # if there are nodes, build up the tree-structure
 2160:     if (defined $$TreeHash{'root'} && $$TreeHash{'root'} ne '') {
 2161:         my @childrenRtIn = @{ $$TreeHash{'root'} };
 2162:         &buildTree(\$root, \@childrenRtIn,\@TreeNodes,$TreeHash);
 2163:     }
 2164:     return $root; 
 2165: }
 2166: 
 2167: 
 2168: # join the nodes to a tree
 2169: sub buildTree {
 2170:     my ($node, $childrenIn, $TreeNodes, $TreeHash) = @_;
 2171:     bless($node, 'Tree');
 2172:     foreach my $c (@$childrenIn) {
 2173:         my $tmpNode =  &getNodeByIndex($c,$TreeNodes);
 2174:         $$node->add_child($tmpNode);
 2175:         my @childrenIn = @{ $$TreeHash{$tmpNode->value()->nindex()} };
 2176:         &buildTree(\$tmpNode,\@childrenIn,$TreeNodes,$TreeHash);
 2177:     }
 2178: 
 2179: }
 2180: 
 2181: 
 2182: # ----------------------------------------------------- package Entry
 2183: # package that defines the entrys a wishlist could have
 2184: # i.e. folders and links
 2185: package Entry;
 2186: 
 2187: # constructor
 2188: sub new {
 2189:     my $invocant = shift;
 2190:     my $class = ref($invocant) || $invocant;
 2191:     my $self = { @_ }; #set attributes
 2192:     bless($self, $class);
 2193:     return $self;    
 2194: }
 2195: 
 2196: # getter and setter
 2197: sub title {
 2198:     my $self = shift;
 2199:     if ( @_ ) { $self->{title} = shift}
 2200:     return $self->{title};
 2201: }
 2202: 
 2203: sub date {
 2204:     my $self = shift;
 2205:     if ( @_ ) { $self->{date} = shift}
 2206:     return $self->{date};
 2207: }
 2208: 
 2209: sub note {
 2210:     my $self = shift;
 2211:     if ( @_ ) { $self->{note} = shift}
 2212:     return $self->{note};
 2213: }
 2214: 
 2215: sub path {
 2216:     my $self = shift;
 2217:     if ( @_ ) { $self->{path} = shift}
 2218:     return $self->{path};
 2219: }
 2220: 
 2221: sub nindex {
 2222:     my $self = shift;
 2223:     if ( @_ ) { $self->{nindex} = shift}
 2224:     return $self->{nindex};
 2225: }
 2226: 
 2227: 
 2228: 1;
 2229: __END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>