--- loncom/interface/lonwishlist.pm 2010/08/16 13:37:41 1.3 +++ loncom/interface/lonwishlist.pm 2014/05/31 16:01:33 1.22 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA -# Routines to control the wishlist +# Utility-routines for wishlist # -# $Id: lonwishlist.pm,v 1.3 2010/08/16 13:37:41 wenzelju Exp $ +# $Id: lonwishlist.pm,v 1.22 2014/05/31 16:01:33 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,17 +26,31 @@ # http://www.lon-capa.org/ # +=pod +=head1 NAME + +Apache::lonwishlist - Wishlist-Module + +=head1 SYNOPSIS + +The wishlist offers a possibility to store links to resources from the resource-pool and external websites in a hierarchical list. +It is only available for user with access to the resource-pool. The list can be structured by folders. + +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. + +=back + +=cut package Apache::lonwishlist; use strict; -use Apache::Constants qw(:common); use Apache::lonnet; use Apache::loncommon(); use Apache::lonhtmlcommon; use Apache::lonlocal; -use LONCAPA; +use LONCAPA qw(:DEFAULT :match); use Tree; @@ -48,18 +62,48 @@ my %TreeToHash; my @allFolders; my @allNodes; my $indentConst = 20; +my $foldersOption; + +=pod + +=head2 Routines for getting and putting the wishlist data from and accordingly to users data. + +=over 4 + +=item * &getWishlist() + + Get the wishlist-data via lonnet::getkeys() and lonnet::get() and returns the got data in a hash. + + +=item * &putWishlist(wishlist) + + Parameter is a reference to a hash. Puts the wishlist-data contained in the given hash via lonnet::put() to user-data. + + +=item * &deleteWishlist() + + Deletes all entries from the user-data for wishlist. Do this before putting in new data. + + +=back + +=cut # Read wishlist from user-data sub getWishlist { - my %wishlist = &Apache::lonnet::dump('wishlist'); + my @keys = &Apache::lonnet::getkeys('wishlist'); + my %wishlist = &Apache::lonnet::get('wishlist',\@keys); foreach my $i ( keys %wishlist) { #File not found. This appears at the first time using the wishlist #Create file and put 'root' into it if ($i =~m/^error:No such file/) { &Apache::lonnet::logthis($i.'! Create file by putting in the "root" of the directory tree.'); &Apache::lonnet::put('wishlist', {'root' => ''}); - %wishlist = &Apache::lonnet::dump('wishlist'); + my $options = ''; + &Apache::lonnet::put('wishlist', {'folders' => $options}); + @keys = &Apache::lonnet::getkeys('wishlist'); + %wishlist = &Apache::lonnet::get('wishlist',\@keys); } elsif ($i =~ /^(con_lost|error|no_such_host)/i) { &Apache::lonnet::logthis('ERROR while attempting to get wishlist: '.$i); @@ -67,7 +111,7 @@ sub getWishlist { } } - # if we got no keys in hash returned by dump(), return error. + # if we got no keys in hash returned by get(), return error. # wishlist will not be loaded, instead the user will be asked to try again later if ((keys %wishlist) == 0) { &Apache::lonnet::logthis('ERROR while attempting to get wishlist: no keys retrieved!'); @@ -92,66 +136,137 @@ sub deleteWishlist { } +=pod + +=head2 Routines for changing the directory struture of the wishlist. + +=over 4 + +=item * &newEntry(title, path, note) + + Creates a new entry in the wishlist containing the given informations. Additionally saves the date of creation in the entry. + + +=item * &deleteEntries(marked) + + Parameter is a reference to an array containing the indices of all nodes that should be removed from the tree. + + +=item * &sortEntries(indexNode, at) + + Changes the position of a node given by indexNode within its siblings. New position is given by at. + + +=item * &moveEntries(indexNodesToMove, indexParent) + + 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. + + +=item * &setNewTitle(nodeindex, newTitle) + + Sets the title for the node given by nodeindex to newTitle. + + +=item * &setNewPath(nodeindex, newPath) + + Sets the path for the node given by nodeindex to newPath. + + +=item * &setNewNote(nodeindex, newNote) + + Sets the note for the node given by nodeindex to newNote. + + +=item * &saveChanges() + + Prepares the wishlist-hash to save it via &putWishlist(wishlist). + + +=back + +=cut + + # Create a new entry sub newEntry() { - my ($title, $path, $note) = @_; + my ($rootgiven, $title, $path, $note) = @_; + + $root = $rootgiven; + @childrenRt = $root->children(); + my $date = gmtime(); # Create Entry-Object my $entry = Entry->new(title => $title, path => $path, note => $note, date => $date); - # Create Tree-Object, this correspones a node in the wishlist-tree + # Create Tree-Object, this corresponds a node in the wishlist-tree my $tree = Tree->new($entry); # Add this node to wishlist-tree my $folderIndex = $env{'form.folders'}; if ($folderIndex ne '') { @allFolders = (); &getFoldersToArray(\@childrenRt); - my $folderToInsertOn = &Tree::getNodeByIndex($folderIndex,\@allFolders); + my $folderToInsertOn = &Apache::Tree::getNodeByIndex($folderIndex,\@allFolders); $folderToInsertOn->add_child($tree); } else { $root->add_child($tree); } - &saveChanges(); + return &saveChanges(); } # Delete entries sub deleteEntries { + my $rootgiven = shift; my $marked = shift; - &getNodesToArray(\@childrenRt); + $root = $rootgiven; + @childrenRt = $root->children(); + + &getNodesToArray(\@childrenRt); foreach my $m (@$marked) { - my $found = &Tree::getNodeByIndex($m, \@allNodes); - &Tree::removeNode($found); + my $found = &Apache::Tree::getNodeByIndex($m, \@allNodes); + # be sure, that entry exists (may have been deleted before, e.g. in an other browsertab) + if (defined $found) { + &Apache::Tree::removeNode($found); + } } @allNodes = (); - &saveChanges(); + return &saveChanges(); } # Sort entries sub sortEntries { + my $rootgiven = shift; my $indexNode = shift; my $at = shift; + + $root = $rootgiven; + @childrenRt = $root->children(); &getNodesToArray(\@childrenRt); - my $foundNode = &Tree::getNodeByIndex($indexNode, \@allNodes); + my $foundNode = &Apache::Tree::getNodeByIndex($indexNode, \@allNodes); - &Tree::moveNode($foundNode,$at,undef); + &Apache::Tree::moveNode($foundNode,$at,undef); @allNodes = (); + return &saveChanges(); } # Move entries sub moveEntries { + my $rootgiven = shift; my $indexNodesToMove = shift; my $indexParent = shift; my @nodesToMove = (); + $root = $rootgiven; + @childrenRt = $root->children(); + # get all nodes that should be moved &getNodesToArray(\@childrenRt); foreach my $index (@$indexNodesToMove) { - my $foundNode = &Tree::getNodeByIndex($index, \@allNodes); + my $foundNode = &Apache::Tree::getNodeByIndex($index, \@allNodes); push(@nodesToMove, $foundNode); } @@ -167,36 +282,46 @@ sub moveEntries { } if (!$parentIsIn) { if ($indexParent ne "root") { - $foundParent = &Tree::getNodeByIndex($indexParent, \@allNodes); - &Tree::moveNode($node,undef,$foundParent); + $foundParent = &Apache::Tree::getNodeByIndex($indexParent, \@allNodes); + &Apache::Tree::moveNode($node,undef,$foundParent); } else { - &Tree::moveNode($node,undef,$root); + &Apache::Tree::moveNode($node,undef,$root); } } } @allNodes = (); + return &saveChanges(); } # Set a new title for an entry sub setNewTitle { - my ($nodeindex, $newTitle) = @_; + my ($rootgiven, $nodeindex, $newTitle) = @_; + + $root = $rootgiven; + @childrenRt = $root->children(); + &getNodesToArray(\@childrenRt); - my $found = &Tree::getNodeByIndex($nodeindex, \@allNodes); + my $found = &Apache::Tree::getNodeByIndex($nodeindex, \@allNodes); $found->value()->title($newTitle); @allNodes = (); + return &saveChanges(); } # Set a new path for an entry sub setNewPath { - my ($nodeindex, $newPath) = @_; + my ($rootgiven, $nodeindex, $newPath) = @_; + + $root = $rootgiven; + @childrenRt = $root->children(); + &getNodesToArray(\@childrenRt); - my $found = &Tree::getNodeByIndex($nodeindex, \@allNodes); + my $found = &Apache::Tree::getNodeByIndex($nodeindex, \@allNodes); if ($found->value()->path()) { $found->value()->path($newPath); - return 1; + return &saveChanges(); } @allNodes = (); return 0; @@ -205,31 +330,65 @@ sub setNewPath { # Set a new note for an entry sub setNewNote { - my ($nodeindex, $newNote) = @_; + my ($rootgiven, $nodeindex, $newNote) = @_; + + $root = $rootgiven; + @childrenRt = $root->children(); + &getNodesToArray(\@childrenRt); - my $found = &Tree::getNodeByIndex($nodeindex, \@allNodes); + my $found = &Apache::Tree::getNodeByIndex($nodeindex, \@allNodes); $found->value()->note($newNote); @allNodes = (); + return &saveChanges(); } # Save all changes sub saveChanges { @childrenRt = $root->children(); - &Tree::TreeIndex(\@childrenRt); - &Tree::setCountZero(); - &Tree::RootToHash(\@childrenRt); - &Tree::TreeToHash(\@childrenRt); + &Apache::Tree::TreeIndex(\@childrenRt); + &Apache::Tree::setCountZero(); + &Apache::Tree::RootToHash(\@childrenRt); + &Apache::Tree::TreeToHash(\@childrenRt); &deleteWishlist(); &putWishlist(\%TreeToHash); + return $root; } +=pod + +=head2 Routines for handling the directory structure + +=over 4 + +=item * &getFoldersForOption(nodes) + + 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. + 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). + + +=item * &getFoldersToArray(children) + + Puts all nodes that represent folders in the wishlist into an array. + 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). + + +=item * &getNodesToArray(children) + + Puts all existing nodes into an array (apart from the root node, because this one does not represent an entry in the wishlist). + 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). + + +=back + +=cut + + # Return the names for all exiting folders in option-tags, so # a new link or a new folder can be created in an existing folder my $indent = 0; -my $foldersOption; sub getFoldersForOption { my $nodes = shift; @@ -250,22 +409,6 @@ sub getFoldersForOption { } -sub getfoldersOption { - if (&getWishlist ne 'error') { - %TreeHash = &getWishlist(); - $root = &Tree::HashToTree(); - @childrenRt = $root->children(); - &getFoldersForOption(\@childrenRt); - my $options = ''.$foldersOption; - $foldersOption = ''; - return $options; - } - else { - return ''; - } -} - - # Put all folder-nodes to an array sub getFoldersToArray { my $children = shift; @@ -294,10 +437,83 @@ sub getNodesToArray { } +=pod + +=head2 Routines for the user-interface of the wishlist + +=over 4 + +=item * &JSforWishlist() + + Returns JavaScript-functions needed for wishlist actions like open and close folders. + + +=item * &wishlistView(nodes) + + Returns the table-HTML-markup for the wishlist in mode "view". + 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). + + +=item * &wishlistEdit(nodes) + + Returns the table-HTML-markup for the wishlist in mode "edit". + 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). + + +=item * &wishlistMove(nodes, marked) + + Returns the table-HTML-markup for the wishlist in mode "move". Highlights all entry "selected to move" contained in marked (reference to array). + 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). + + +=item * &wishlistImport(nodes, numskipped) + + Returns the table-HTML-markup for the wishlist in mode "import". + 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). + Side effect: increments the scalar ref: numskipped with a count of items in + Stored Links unavailable for selection, (e.g., now marked obsolete or + inaccessible in Community context). + +=item * &makePage(mode, marked) + + 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". + Calls &wishlistView(nodes), &wishlistEdit(nodes) or &wishlistMove(nodes, marked). + + +=item * &makePopUpNewLink(title, path) + + 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. + + +=item * &makePopUpNewFolder() + + Returns the HTML-markup for the pop-up-window 'Add Folder'. + + +=item * &makePageSet() + + Returns the HTML-Markup for the page shown when a link was set by using the icon when viewing a resource. + + +=item * &makePageImport() + + Returns the HTML-Markup for the page shown when links should be imported into courses. + + +=item * &makeErrorPage () + + Returns the HTML-Markup for an error-page shown if the wishlist could not be loaded. + + +=back + +=cut + + # Return a script-tag containing Javascript-function # needed for wishlist actions like 'new link' ect. sub JSforWishlist { - my $startPagePopup = &Apache::loncommon::start_page('Wishlist',undef, + my $startPagePopup = &Apache::loncommon::start_page('Stored Links',undef, {'only_body' => 1, 'js_ready' => 1, 'bgcolor' => '#FFFFFF',}); @@ -307,65 +523,6 @@ sub JSforWishlist { &getFoldersToArray(\@childrenRt); &getFoldersForOption(\@childrenRt); - # texthash - my %lt = &Apache::lonlocal::texthash( - 'nl' => 'New Link', - 'nf' => 'New Folder', - 'lt' => 'Link Title', - 'ft' => 'Folder Title', - 'pa' => 'Path', - 'nt' => 'Note', - 'si' => 'Save in', - 'cl' => 'Cancel'); - - - my $inPageNewLink = '

'.$lt{'nl'}.'

'. - '
'. - &Apache::lonhtmlcommon::start_pick_box(). - &Apache::lonhtmlcommon::row_title($lt{'lt'}). - ''. - &Apache::lonhtmlcommon::row_closure(). - &Apache::lonhtmlcommon::row_title($lt{'pa'}). - ''. - &Apache::lonhtmlcommon::row_closure(). - &Apache::lonhtmlcommon::row_title($lt{'nt'}). - ''. - &Apache::lonhtmlcommon::row_closure(1). - &Apache::lonhtmlcommon::end_pick_box(). - '

'. - ''. - ''. - ''. - '
'; - - my $inPageNewFolder = '

'.$lt{'nf'}.'

'. - '
'. - &Apache::lonhtmlcommon::start_pick_box(). - &Apache::lonhtmlcommon::row_title($lt{'ft'}). - '
'. - &Apache::lonhtmlcommon::row_closure(). - &Apache::lonhtmlcommon::row_title($lt{'nt'}). - '
'. - &Apache::lonhtmlcommon::row_closure(1). - &Apache::lonhtmlcommon::end_pick_box(). - '

'. - ''. - ''. - ''. - '
'; - - # Remove all \n for inserting on javascript document.write - $inPageNewLink =~ s/\n//g; - $inPageNewFolder =~ s/\n//g; - # it is checked, wether a path links to a LON-CAPA-resource or an external website. links to course-contents are not allowed # because they probably will return a kind of 'no access' (unless the user is already in the course, the path links to). # also importing these kind of links into a course does not make much sense. @@ -373,62 +530,27 @@ sub JSforWishlist { # that means that it is checked wether a path contains .problem, .quiz, .exam etc. # this is good for most cases but crashes as soon as a real external website contains one of this pattern in its URL. # so maybe there's a better way to find out wether a given URL belongs to a LON-CAPA-server or not ...? - my $warningLinkNotAllowed1 = &mt('You can only insert links to LON-CAPA resources from the resource-pool '. - 'or to external websites. Paths to LON-CAPA resources must be of the form /res/dom/usr... . '. - 'Paths to external websites must contain the network protocol (e.g. http://...).'); - my $warningLinkNotAllowed2 = &mt('The following link is not allowed: '); + my $warningLinkNotAllowed1 = + &mt('You can only insert links to LON-CAPA resources from the resource-pool'. + ' or to external websites.'. + ' Paths to LON-CAPA resources must be of the form /res/domain/user/...'. + ' Paths to external websites must contain the network protocol, e.g. http://...'); + my $warningLinkNotAllowed2 = &mt('The following link is not allowed:').' '; my $warningLink = &mt('You must insert a title and a path!'); my $warningFolder = &mt('You must insert a title!'); my $warningDelete = &mt('Are you sure you want to delete the selected entries? Deleting a folder also deletes all entries within this folder!'); - 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.'); - my $warningMove = &mt('You must select a destination folder!'); + 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.'); + my $warningMoveS = &mt('You must select at minimum one entry to move!'); + my $warningMoveD = &mt('You must select a destination folder!'); $foldersOption = ''; my $js = &Apache::lonhtmlcommon::scripttag(<' - +'function newlinksubmit(){' - +'var path = document.getElementsByName("path")[0].value;' - +'var title = document.getElementsByName("title")[0].value;' - +'if (!path || !title) {' - +'alert("$warningLink");' - +'return false;}' - +'var linkOK = (path.match(/^http:(\\\\/\\\\/)/) || path.match(/^https:(\\\\/\\\\/)/))' - +'&& !(path.match(/\\.problem/) || path.match(/\\.exam/)' - +'|| path.match(/\\.quiz/) || path.match(/\\.assess/)' - +'|| path.match(/\\.survey/) || path.match(/\\.form/)' - +'|| path.match(/\\.library/) || path.match(/\\.page/)' - +'|| path.match(/\\.sequence/));' - +'if (!path.match(/^(\\\\/res\\\\/)/) && !linkOK) {' - +'alert("$warningLinkNotAllowed1");' - +'return false;}' - +'else {' - +'window.close();' - +'return true;}}' - +'<\/scr'+'ipt>' - +'$inPageNewLink' - +'$endPagePopup'); - newlinkWin.document.close(); + newlinkWin=window.open('/adm/wishlist?mode=newLink','newlinkWin','width=580,height=350, scrollbars=yes'); } function newFolder() { - newfolderWin=window.open('','newfolderWin','width=580,height=270, scrollbars=yes'); - newfolderWin.document.write('$startPagePopup' - +'