File:  [LON-CAPA] / loncom / interface / lonwishlist.pm
Revision 1.27: download - view: text, annotated - select for diffs
Sat Apr 14 02:29:44 2018 UTC (6 years ago) by raeburn
Branches: MAIN
CVS tags: version_2_12_X, HEAD
- Bug 6754 LON-CAPA as LTI Provider
  - Original LTI launch of LON-CAPA is for a resource, a map, or an entire
    course, and display is for iframe, tab or window; retain this in %env.

# The LearningOnline Network with CAPA
# Utility-routines for wishlist
#
# $Id: lonwishlist.pm,v 1.27 2018/04/14 02:29:44 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# 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::lonnet;
use Apache::loncommon();
use Apache::lonhtmlcommon;
use Apache::lonlocal;
use LONCAPA qw(:DEFAULT :match);
use Tree;


# Global variables
my $root;
my @childrenRt;
my %TreeHash;
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 @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/^\Qerror:No such file\E/) {
           &Apache::lonnet::logthis($i.'! Create file by putting in the "root" of the directory tree.');
           &Apache::lonnet::put('wishlist', {'root' => ''});
           my $options = '<option value="" selected="selected">('.&mt('Top level').')</option>';
           &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);
           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!');
        return 'error';
    }
    
    return %wishlist;
}


# Write wishlist to user-data
sub putWishlist {
    my $wishlist = shift;
    &Apache::lonnet::put('wishlist',$wishlist);
}


# Removes all existing entrys for wishlist in user-data
sub deleteWishlist {
    my @wishlistkeys = &Apache::lonnet::getkeys('wishlist');
    my %wishlist = &Apache::lonnet::del('wishlist',\@wishlistkeys);
}


=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 ($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 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 = &Apache::Tree::getNodeByIndex($folderIndex,\@allFolders);
        $folderToInsertOn->add_child($tree);
    }
    else {
        $root->add_child($tree);
    }
    return &saveChanges();
}


# Delete entries
sub deleteEntries {
    my $rootgiven = shift;
    my $marked = shift;

    $root = $rootgiven;
    @childrenRt = $root->children();

    &getNodesToArray(\@childrenRt);
    foreach my $m (@$marked) {
        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 = ();
    return &saveChanges();
}


# Sort entries
sub sortEntries {
    my $rootgiven = shift;
    my $indexNode = shift;
    my $at = shift;

    $root = $rootgiven;
    @childrenRt = $root->children();
    
    &getNodesToArray(\@childrenRt);
    my $foundNode = &Apache::Tree::getNodeByIndex($indexNode, \@allNodes);

    &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 = &Apache::Tree::getNodeByIndex($index, \@allNodes);
        push(@nodesToMove, $foundNode);
    }

    foreach my $node (@nodesToMove) {
        my $foundParent;
        my $parentIsIn = 0;
        foreach my $n (@nodesToMove) {
            if ($node->parent()->value() ne "root") {
               if ($node->parent()->value()->nindex() == $n->value()->nindex()) {
                    $parentIsIn = 1;
                }
            }
        }
        if (!$parentIsIn) {
            if ($indexParent ne "root") {
                $foundParent = &Apache::Tree::getNodeByIndex($indexParent, \@allNodes);
                &Apache::Tree::moveNode($node,undef,$foundParent);
            }
            else {
                &Apache::Tree::moveNode($node,undef,$root);
            }
        }
    }
    @allNodes = ();
    return &saveChanges();
}


# Set a new title for an entry
sub setNewTitle {
    my ($rootgiven, $nodeindex, $newTitle) = @_;

    $root = $rootgiven;
    @childrenRt = $root->children();

    &getNodesToArray(\@childrenRt);
    my $found = &Apache::Tree::getNodeByIndex($nodeindex, \@allNodes);
    $found->value()->title($newTitle); 
    @allNodes = ();
    return &saveChanges();
}


# Set a new path for an entry
sub setNewPath {
    my ($rootgiven, $nodeindex, $newPath) = @_;

    $root = $rootgiven;
    @childrenRt = $root->children();

    &getNodesToArray(\@childrenRt);
    my $found = &Apache::Tree::getNodeByIndex($nodeindex, \@allNodes);
    if ($found->value()->path()) {
        $found->value()->path($newPath); 
        return &saveChanges();
    }
    @allNodes = ();
    return 0;
}


# Set a new note for an entry
sub setNewNote {
    my ($rootgiven, $nodeindex, $newNote) = @_;

    $root = $rootgiven;
    @childrenRt = $root->children();

    &getNodesToArray(\@childrenRt);
    my $found = &Apache::Tree::getNodeByIndex($nodeindex, \@allNodes);
    $found->value()->note($newNote); 
    @allNodes = ();
    return &saveChanges();
}


# Save all changes
sub saveChanges {
    @childrenRt = $root->children();
    &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;
sub getFoldersForOption {
    my $nodes = shift;

    foreach my $n (@$nodes) {
        if ($n->value()->path() eq '') {
            $foldersOption .= '<option value="'.$n->value()->nindex().'" style="margin-left:'.$indent.'px">'.
                                   $n->value()->title().
                               '</option>';

        my @children = $n->children();
        if ($#children >=0) {
            $indent += 10;
            &getFoldersForOption(\@children);
            $indent -= 10;
            }
        }
    }
}


# Put all folder-nodes to an array
sub getFoldersToArray {
    my $children = shift;
    foreach my $c (@$children) {
        if ($c->value()->path() eq '') {
            push(@allFolders,$c);
        }
        my @newchildren = $c->children();
        if ($#newchildren >= 0) {
            &getFoldersToArray(\@newchildren);
        }
    }
}


# Put all nodes to an array
sub getNodesToArray {
    my $children = shift;
    foreach my $c (@$children) {
        push(@allNodes,$c);
        my @newchildren = $c->children();
        if ($#newchildren >= 0) {
            &getNodesToArray(\@newchildren);
        }
    }
}


=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('Stored Links',undef,
                                                            {'only_body' => 1,
                                                             'js_ready'  => 1,
                                                             'bgcolor'   => '#FFFFFF',});
    my $endPagePopup = &Apache::loncommon::end_page({'js_ready' => 1});

    @allFolders = ();
    &getFoldersToArray(\@childrenRt);
    &getFoldersForOption(\@childrenRt);

    # 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.
    # to find out if a path (not starting with /res/...) links to course-contents, the same filter as in lonwrapper is used,
    # 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/domain/user/...'.
            ' Paths to external websites must contain the network protocol, e.g. http://...');
    my $warningLinkNotAllowed2 = &mt('The following link is not allowed:').' ';
    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 $warningMoveS = &mt('You must select at minimum one entry to move!');
    my $warningMoveD = &mt('You must select a destination folder!');
    &js_escape(\$warningLinkNotAllowed1);
    &js_escape(\$warningLinkNotAllowed2);
    &js_escape(\$warningDelete);
    &js_escape(\$warningSave);
    &js_escape(\$warningMoveS);
    &js_escape(\$warningMoveD);
    $foldersOption = '';

    my $js = &Apache::lonhtmlcommon::scripttag(<<JAVASCRIPT);
    function newLink() {
        newlinkWin=window.open('/adm/wishlist?mode=newLink','newlinkWin','width=580,height=350, scrollbars=yes');
    }

    function newFolder() {
        newfolderWin=window.open('/adm/wishlist?mode=newFolder','newfolderWin','width=580,height=270, scrollbars=yes');
    }

    function setFormAction(action,mode) {
        var r = true;
        setAction('');
        if (action == 'delete') {
            r = confirm("$warningDelete");
            setAction('delete');
        }
        else if (action == 'save') {
            var d = getDifferences();
            if (d) {
                if (!confirm('$warningSave')) {
                    setAction('noSave');
                    r = true;
                }
                else {
                    r = linksOK();
                }
            }
        }
        else if (action == 'saveOK') {
            r = linksOK();
        }
        else if (action == 'move') {
            r = selectDestinationFolder(mode);
        }
        document.getElementsByName('list')[0].setAttribute("action", "/adm/wishlist?mode="+mode); 
        if (r) {
            document.getElementsByName('list')[0].submit(); 
        }
    }

    function setAction(action) {
        document.getElementById('action').value = action; 
    }

    function getDifferences() {
        var newtitles = document.getElementsByName('newtitle');
        var i = 0;
        for (i=0;i<newtitles.length;i++) {
            var newt = newtitles[i].value;
            var oldt = newtitles[i].alt;
            if (newt != oldt) {
                return true;
            }
        }
        var newpath = document.getElementsByName('newpath');
        var i = 0;
        for (i=0;i<newpath.length;i++) {
            var newp = newpath[i].value;
            var oldp = newpath[i].alt;
            if (newp != oldp) {
                return true;
            }
        }
        var newnote = document.getElementsByName('newnote');
        var i = 0;
        for (i=0;i<newnote.length;i++) {
            var newn = newnote[i].value;
            var oldn = newnote[i].innerHTML;
            if (newn != oldn) {
                return true;
            }
        }
        return false;
    }

    function linksOK() {
        var newpath = document.getElementsByName('newpath');
        var i = 0;
        for (i=0;i<newpath.length;i++) {
            var path = newpath[i].value;
            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 $warningLinkNotAllowed2"+path);
                return false;
            }
         }
        return true;
    }

    function onLoadAction(mode) {
        window.name = 'wishlist';
        if (mode == "edit") {
            var deepestRows = getDeepestRows();
            setDisplaySelect(deepestRows, '');
        }
    }

    function folderAction(rowid) {
        var row = document.getElementById(rowid);
        var indent = getIndent(row);
        var displ;
        var status;
        if (getImage(row) == 'closed') {
            displ = '';
            status = 'open';
        }
        else {
            displ = 'LC_hidden';
            status = 'closed';
        }
        setImage(row,status);
        if (getNextRow(row) != null) {
            var nextIndent = getIndent(getNextRow(row));
            row = getNextRow(row);
            while (nextIndent > indent) {
                if (displ == '') {
                    row.className = (row.className).replace('LC_hidden','');
                }
                else if (displ != '' && !((row.className).match('LC_hidden'))) {
                    var oldClass = row.className;
                    row.className = oldClass+' LC_hidden';
                    setDisplayNote(row.id.replace('row','note'),'LC_hidden');
                }
                if (status == 'open' && getImage(row).match('closed')) {
                    row = getNextRowWithIndent(row, getIndent(row));
                }
                else {
                    row = getNextRow(row);
                } 
                if (row != null) {
                    nextIndent = getIndent(row);
                } 
                else {
                    nextIndent = indent;
                }
            }
        }
        setClasses();
        var newtitles = document.getElementsByName('newtitle');
        if (newtitles.length>0) {
            var deepestRows = getDeepestRows();
            var otherRows = getOtherRows(deepestRows);
            setDisplaySelect(deepestRows,'');
            setDisplaySelect(otherRows,'LC_hidden');
        }
    }

    function selectAction(rowid) {
        var row = document.getElementById(rowid);
        var indent = getIndent(row);
        var checked = getChecked(row);
        var previousFolderRows = new Array();
        if (indent != 0) {
            previousFolderRows = getPreviousFolderRows(row);
        }
        if (getNextRow(row) != null) {
            var nextIndent = getIndent(getNextRow(row));
            row = getNextRow(row);
                while (nextIndent > indent) {
                    setChecked(row,checked);
                    if (status == 'open' && getImage(row).match('closed')) {
                        row = getNextRowWithIndent(row, getIndent(row));
                    }
                    else {
                        row = getNextRow(row);
                    }
                    if (row != null) {
                        nextIndent = getIndent(row);
                    }
                    else {
                        nextIndent = indent;
                    }
                }
        }
        if (!checked) {
            var i = 0;
            for (i=0;i<previousFolderRows.length;i++) {
                setChecked(previousFolderRows[i], false);
            }
        }
    }

    function getNextNote(row) {
        var rowId = row.id;
        var nextRowId = parseInt(rowId.substr(3,rowId.length))+1;
        nextRowId = "note"+nextRowId;
        var nextRow = document.getElementById(nextRowId);
        return nextRow;
    }

    function getNextRow(row) {
        var rowId = row.id;
        var nextRowId = parseInt(rowId.substr(3,rowId.length))+1;
        nextRowId = "row"+nextRowId;
        var nextRow = document.getElementById(nextRowId);
        return nextRow;
    }

    function getPreviousRow(row) {
        var rowId = row.id;
        var previousRowId =  parseInt(rowId.substr(3,rowId.length))-1;
        previousRowId = "row"+previousRowId;
        var previousRow =document.getElementById(previousRowId);
        return previousRow;
    }

    function getIndent(row) {
        var childPADD = document.getElementById(row.id.replace('row','padd'));
        indent = childPADD.style.paddingLeft;
        indent = parseInt(indent.substr(0,(indent.length-2)));
 
        if (getImage(row).match('link')) {
            indent -= $indentConst;
        }
        return indent;
    }

    function getNextRowWithIndent(row, indent) {
        var nextRow = getNextRow(row);
        if (nextRow != null) {
        var nextIndent = getIndent(nextRow);
        while (nextIndent >= indent) {
            if (nextIndent == indent) {
                return nextRow;
            }
            nextRow = getNextRow(nextRow);
            if (nextRow == null) {
                return null;
            }
            nextIndent = getIndent(nextRow);
        }
        }
        return nextRow;
    }

    function getImage(row) {
        var childIMG = document.getElementById(row.id.replace('row','img'));
        if ((childIMG.src).match('closed')) {
            return 'closed';
        }
        else if ((childIMG.src).match('open')) {
            return 'open;'
        }
        else {
            return 'link';
        }
    } 

    function setImage(row, status) {
        var childIMG = document.getElementById(row.id.replace('row','img'));
        var childIMGFolder = document.getElementById(row.id.replace('row','imgFolder'));
        childIMG.src = "/adm/lonIcons/arrow."+status+".gif";
        childIMGFolder.src="/adm/lonIcons/navmap.folder."+status+".gif"; 
    }

    function getChecked(row) {
        var childCHECK = document.getElementById(row.id.replace('row','check'));
        var checked = childCHECK.checked;
        return checked;
    }

    function setChecked(row,checked) {
        var childCHECK = document.getElementById(row.id.replace('row','check'));
        if (!childCHECK.disabled) {
            childCHECK.checked = checked;
        }
    }

    function getPreviousFolderRows(row) {
        var previousRow = getPreviousRow(row);
        var indent = getIndent(previousRow);
        var kindOfEntry = getImage(previousRow);
        var rows = new Array();
        if (kindOfEntry != 'link') {
            rows.push(previousRow);
        }

        while (indent >0) {
            previousRow = getPreviousRow(previousRow);
            if (previousRow != null) {
                indent = getIndent(previousRow);
                kindOfEntry = getImage(previousRow);
                if (kindOfEntry != 'link') {
                    rows.push(previousRow);
                }
            }
            else {
                indent = 0; 
            }
        }
        return rows;
    }

    function getDeepestRows() {
        var row = document.getElementById('row0');
        var firstRow = row;
        var indent = getIndent(row);
        var maxIndent = indent;
        while (getNextRow(row) != null) {
            row = getNextRow(row);
            indent = getIndent(row);
            if (indent>maxIndent && !((row.className).match('LC_hidden'))) {
                maxIndent = indent;
            }
        }
        var deepestRows = new Array();
        row = firstRow;
        var rowIndent;
        while (getNextRow(row) != null) {
            rowIndent = getIndent(row);
            if (rowIndent == maxIndent) {
                deepestRows.push(row);
            }
            row = getNextRow(row);
        }
        rowIndent = getIndent(row);
        if (rowIndent == maxIndent) {
            deepestRows.push(row);
        }
        return deepestRows;
    }

    function getOtherRows(deepestRows) {
        var row = document.getElementById('row0');
        var otherRows = new Array();
        var isIn = false;
        while (getNextRow(row) != null) {
            var i = 0;
            for (i=0; i < deepestRows.length; i++) {
                if (row.id == deepestRows[i].id) {
                    isIn = true;
                }
            }
            if (!isIn) {
                otherRows.push(row);
            }
            row = getNextRow(row);
            isIn = false;
        }
        for (i=0; i < deepestRows.length; i++) {
            if (row.id == deepestRows[i].id) {
                isIn = true;
            }
        }
        if (!isIn) {
            otherRows.push(row);
        }
        return otherRows;
    }

    function setDisplaySelect(deepestRows, displ) {
        var i = 0;
        for (i = 0; i < deepestRows.length; i++) {
            var row = deepestRows[i];
            var childSEL = document.getElementById(row.id.replace('row','sel'));
            childSEL.className = displ;
        } 
    }

    function submitSelect() {
       var list = document.getElementsByName('list')[0];
       list.setAttribute("action","/adm/wishlist?mode=edit");
       list.submit();
    }

    function setDisplayNote(rowid, displ) {
        var row = document.getElementById(rowid);
        if (!displ) {
            if ((row.className).match('LC_hidden')) {
                row.className = (row.className).replace('LC_hidden','');
            }
            else {
                var oldClass = row.className;
                row.className = oldClass+' LC_hidden';
            }
        }
        else {
            if (displ == '') {
                row.className = (row.className).replace('LC_hidden','');
            }
            else if (displ != '' && !((row.className).match('LC_hidden'))) {
                var oldClass = row.className;
                row.className = oldClass+' LC_hidden';
            }
        }
        var noteText = document.getElementById(rowid.replace('note','noteText'));
        var noteImg = document.getElementById(rowid.replace('note','noteImg'));
        if (noteText.value) {
            noteImg.src = "/res/adm/pages/anot2.png";
        }
        else {
            noteImg.src = "/res/adm/pages/anot.png";
        }

    }

    function setClasses() {
        var row = document.getElementById("row0");
        var note = document.getElementById("note0");
        var LC_class = 0;
        if (getNextRow(row) != null) {
            while (getNextRow(row) != null) {
                if (!(row.className).match('LC_hidden')) {
                    note.className = (note.className).replace('LC_even_row','');
                    note.className = (note.className).replace('LC_odd_row','');
                    if (LC_class) {
                        row.className = 'LC_even_row';
                        note.className = 'LC_even_row'+note.className;
                    }
                    else {
                        row.className = 'LC_odd_row';
                        note.className = 'LC_odd_row'+note.className;;
                    }
                    LC_class = !LC_class;
                }
                note = getNextNote(row);
                row = getNextRow(row);
            }
        }
        if (!(row.className).match('LC_hidden')) {
            note.className = (note.className).replace('LC_even_row','');
            note.className = (note.className).replace('LC_odd_row','');
            if (LC_class) {
                row.className = 'LC_even_row';
                note.className = 'LC_even_row'+note.className;
            }
            else {
                row.className = 'LC_odd_row';
                note.className = 'LC_odd_row'+note.className;
            }
        }
    }

    function selectDestinationFolder(mode) {
        var mark = document.getElementsByName('mark');
        var i = 0;
        for (i = 0; i < mark.length; i++) {
            if (mark[i].checked) {
                document.getElementsByName('list')[0].submit();
                return true;
            }
        }
        if (mode == 'move') {
            alert('$warningMoveS');
        }
        else {
            alert('$warningMoveD');
        }
        return false;
    }

    function preview(url) {
       var newWin;
       if (!(url.match(/^http:\\/\\//) || url.match(/^https:\\/\\//))) {
           newWin = window.open(url+'?inhibitmenu=yes','preview','width=560,height=350,scrollbars=yes');
       }
       else {
           newWin = window.open(url,'preview','width=560,height=350,scrollbars=yes');
       }
       newWin.focus();
    }

    function checkAll() {
        var checkboxes = document.getElementsByName('check');
        for (var i = 0; i < checkboxes.length; i++) {
            if (!checkboxes[i].disabled) {
                checkboxes[i].checked = "checked";
            }
        }
    }

    function uncheckAll() {
        var checkboxes = document.getElementsByName('check');
        for (var i = 0; i < checkboxes.length; i++) {
            if (!checkboxes[i].disabled) {
                checkboxes[i].checked = "";
            }
        }
    }

JAVASCRIPT
   return $js;
}

sub JSforImport{
    my $rat = shift;

    my $js;
    if ($rat eq 'simple' || $rat eq '') {
        $js = &Apache::lonhtmlcommon::scripttag(<<JAVASCRIPT);
        function finish_import() {
            opener.document.forms.simpleedit.importdetail.value='';
            for (var num = 0; num < document.forms.groupsort.fnum.value; num++) {
                try {
                    eval("document.forms.groupsort.filelink"+num+".value");
                }
                catch(err) {
                   continue;
                }
                if (eval("document.forms.groupsort.check"+num+".checked") && eval("document.forms.groupsort.filelink"+num+".value") != '') {
                    opener.document.forms.simpleedit.importdetail.value+='&'+
                    eval("document.forms.groupsort.title"+num+".value")+'='+
                    eval("document.forms.groupsort.filelink"+num+".value")+'='+
                    eval("document.forms.groupsort.id"+num+".value");
                }
            }
            opener.document.forms.simpleedit.submit();
            self.close();
        }
JAVASCRIPT
    }
    else {
        $js = &Apache::lonhtmlcommon::scripttag(<<JAVASCRIPT);
        function finish_import() {
            var linkflag=false;
            for (var num=0; num<document.forms.groupsort.fnum.value; num++) {
                if (eval("document.forms.groupsort.check"+num+".checked") && eval("document.forms.groupsort.filelink"+num+".value") != '') {
                    insertRowInLastRow();
                    placeResourceInLastRow(
                        eval("document.forms.groupsort.title"+num+".value"),
                        eval("document.forms.groupsort.filelink"+num+".value"),
                        eval("document.forms.groupsort.id"+num+".value"),
                        linkflag
                        );
                    linkflag=true;
                }
            }
            opener.editmode=0;
            opener.notclear=0;
            opener.linkmode=0;
            opener.draw();
            self.close();
        }

        function insertRowInLastRow() {
            opener.insertrow(opener.maxrow);
            opener.addobj(opener.maxrow,'e&2');
        }

        function placeResourceInLastRow (title,url,id,linkflag) {
            opener.mostrecent=opener.newresource(opener.maxrow,2,opener.unescape(title),
                              opener.unescape(url),'false','normal',id);
            opener.save();
            if (linkflag) {
                opener.joinres(opener.linkmode,opener.mostrecent,0);
            }
            opener.linkmode=opener.mostrecent;
        }
JAVASCRIPT
    }
    return $js;
}

# HTML-Markup for table if in view-mode
my $wishlistHTMLview;
my $indent_view = $indentConst;
sub wishlistView {
    my $nodes = shift;

    foreach my $n (@$nodes) {
        my $index = $n->value()->nindex();

        # 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.
        # only display the top level entries on load
        $wishlistHTMLview .= ($n->parent()->value() eq 'root')?&Apache::loncommon::start_data_table_row('','row'.$index)
                                                              :&Apache::loncommon::continue_data_table_row('LC_hidden','row'.$index);

 
        # checkboxes
        $wishlistHTMLview .= '<td><input type="checkbox" name="mark" id="check'.$index.'" value="'.$index.'" '.
                             'onclick="selectAction('."'row".$index."'".')" /></td>';

        # entry is a folder
        if ($n->value()->path() eq '') {
            $wishlistHTMLview .= '<td id="padd'.$index.'" style="padding-left:'.(($indent_view-$indentConst)<0?0:($indent_view-$indentConst)).'px; min-width: 220px;">'.
                                 '<a href="javascript:;" onclick="folderAction('."'row".$index."'".')" style="vertical-align:top">'.
                                 '<img src="/adm/lonIcons/arrow.closed.gif" id="img'.$index.'" alt = "" class="LC_icon"/>'.
                                 '<img src="/adm/lonIcons/navmap.folder.closed.gif" id="imgFolder'.$index.'" alt="folder"/>'.
                                 $n->value()->title().'</a></td>';
        }
        # entry is a link
        else {
            my $quotable_link = &Apache::loncommon::escape_single($n->value()->path());
            $wishlistHTMLview .= '<td id="padd'.$index.'" style="padding-left:'.(($indent_view-$indentConst)<=0?$indentConst:$indent_view).'px; min-width: 220px;">'.
                                 '<a href="javascript:preview('."'".$quotable_link."'".');">'.
                                 '<img src="/res/adm/pages/wishlist-link.png" id="img'.$index.'" alt="link" />'.
                                 $n->value()->title().'</a></td>';
        }

        # note-icon, different icons for an entries with note and those without
        my $noteIMG = 'anot.png';

        if ($n->value()->note() ne '') {
            $noteIMG = 'anot2.png';
        }

        $wishlistHTMLview .= '<td style="padding-left:10px;"><a href="javascript:;" onclick="setDisplayNote('."'note".$index."'".')">'.
                             '<img id="noteImg'.$index.'" src="/res/adm/pages/'.$noteIMG.'" alt="'.&mt('Note').'" title="'.&mt('Note').'" '.
                             ' class="LC_icon"/></a></td>';

        $wishlistHTMLview .= &Apache::loncommon::end_data_table_row();

        # start row containing the textarea for the note, do not display note on default
        $wishlistHTMLview .= &Apache::loncommon::continue_data_table_row('LC_hidden','note'.$index).
                             '<td></td><td>'.
                             '<textarea id="noteText'.$index.'" cols="25" rows="3" style="width:100%" '.
                             'name="newnote" >'.
                             $n->value()->note().'</textarea></td><td></td>';
        $wishlistHTMLview .= &Apache::loncommon::end_data_table_row();

        # if the entry is a folder, it could have other entries as content. if it has, call wishlistView for those entries 
        my @children = $n->children();
        if ($#children >=0) {
            $indent_view += 20;
            &wishlistView(\@children);
            $indent_view -= 20;
        }
    }
}


# HTML-Markup for table if in edit-mode
my $wishlistHTMLedit;
my $indent_edit = $indentConst;
sub wishlistEdit {
    my $nodes = shift;
    my $curNode = 1;

    foreach my $n (@$nodes) {
        my $index = $n->value()->nindex();

        # start row, use data_table routines to set class to LC_even or LC_odd automatically.
        # this rows contains a checkbox, a select-field for sorting entries, the title in an input-field and the note-icon.
        # only display the top level entries on load
        $wishlistHTMLedit .= ($n->parent()->value() eq 'root')?&Apache::loncommon::start_data_table_row('','row'.$index)
                                                              :&Apache::loncommon::continue_data_table_row('LC_hidden','row'.$index);

        # checkboxes
        $wishlistHTMLedit .= '<td><input type="checkbox" name="mark" id="check'.$index.'" value="'.$index.'" '.
                             'onclick="selectAction('."'row".$index."'".')" /></td>';

        # 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.
        # set the number for the current entry into brackets 
        my $options;
        for (my $i = 1; $i < ((scalar @{$nodes})+1); $i++) {
           if ($i == $curNode) {
               $options .= '<option selected="selected" value="">('.$i.')</option>';
           }
           else {
               $options .= '<option value="'.$i.'">'.$i.'</option>';
           }
        }
        $curNode++;

        # entry is a folder
        if ($n->value()->path() eq '') {
            $wishlistHTMLedit .= '<td><select class="LC_hidden" name="sel" id="sel'.$index.'" onchange="submitSelect();">'.
                                 $options.'</select></td>'.
                                 '<td id="padd'.$index.'" style="padding-left:'.(($indent_edit-$indentConst)<0?0:($indent_edit-$indentConst)).'px;">'.
                                 '<a href="javascript:;" onclick="folderAction('."'row".$index."'".')" style="vertical-align:top" >'.
                                 '<img src="/adm/lonIcons/arrow.closed.gif" id="img'.$index.'" alt = ""  class="LC_icon"/>'.
                                 '<img src="/adm/lonIcons/navmap.folder.closed.gif" id="imgFolder'.$index.'" alt="folder"/></a>'.
                                 '<input type="text" name="newtitle" value="'.$n->value()->title().'" alt = "'.$n->value()->title().'" />'.
                                 '</td><td></td>';

        }
        # entry is a link
        else {
            $wishlistHTMLedit .= '<td><select class="LC_hidden" name="sel" id="sel'.$index.'" onchange="submitSelect();">'.
                                 $options.'</select></td>'.
                                 '<td id="padd'.$index.'" style="padding-left:'.(($indent_edit-$indentConst)<=0?$indentConst:$indent_edit).'px;">'.
                                 '<img src="/res/adm/pages/wishlist-link.png" id="img'.$index.'" alt="link"/>'.
                                 '<input type="text" name="newtitle" value="'.$n->value()->title().'" alt = "'.$n->value()->title().'" /></td>'.
                                 '<td><input type="text" name="newpath" value="'.$n->value()->path().'" alt = "'.$n->value()->path().'" /></td>';
        }
        
        # note-icon, different icons for an entries with note and those without
        my $noteIMG = 'anot.png';

        if ($n->value()->note() ne '') {
            $noteIMG = 'anot2.png';
        }

        $wishlistHTMLedit .= '<td style="padding-left:10px;"><a href="javascript:;" onclick="setDisplayNote('."'note".$index."'".')">'.
                             '<img id="noteImg'.$index.'" src="/res/adm/pages/'.$noteIMG.'" alt="'.&mt('Note').'" title="'.&mt('Note').'" '.
                             ' class="LC_icon"/></a></td>';

        $wishlistHTMLedit .= &Apache::loncommon::end_data_table_row();

        # start row containing the textarea for the note
        $wishlistHTMLedit .= &Apache::loncommon::continue_data_table_row('LC_hidden','note'.$index).
                             '<td></td><td></td><td colspan="2">'.
                             '<textarea id="noteText'.$index.'" cols="25" rows="3" style="width:100%" '.
                             'name="newnote">'.
                             $n->value()->note().'</textarea></td><td></td>';
        $wishlistHTMLedit .= &Apache::loncommon::end_data_table_row();

        # if the entry is a folder, it could have other entries as content. if it has, call wishlistEdit for those entries 
        my @children = $n->children();
        if ($#children >=0) {
            $indent_edit += 20;
            &wishlistEdit(\@children);
            $indent_edit -= 20;
        }
    }
}



# HTML-Markup for table if in move-mode
my $wishlistHTMLmove ='<tr id="root" class="LC_odd_row"><td><input type="radio" name="mark" id="radioRoot" value="root" /></td>'.
                      '<td>'.&mt('Top level').'</td><td></td></tr>';
my $indent_move = $indentConst;
sub wishlistMove {
    my $nodes = shift;
    my $marked = shift;

    foreach my $n (@$nodes) {
        my $index = $n->value()->nindex();

        #find out wether the current entry was marked to be moved.
        my $isIn = 0;
        foreach my $m (@$marked) {
            if ($index == $m) {
               $isIn = 1;
            }
        }
        # 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
        $wishlistHTMLmove .= &Apache::loncommon::start_data_table_row('','row'.$index);


        # entry is a folder
        if ($n->value()->path() eq '') {
            # display a radio-button, if the folder was not selected to be moved
            if (!$isIn) {
                $wishlistHTMLmove .= '<td><input type="radio" name="mark" id="radio'.$index.'" value="'.$index.'" /></td>'.
                                     '<td id="padd'.$index.'" style="padding-left:'.(($indent_move-$indentConst)<0?0:($indent_move-$indentConst)).'px; min-width: 220px;">';
            }
            # highlight the title, if the folder was selected to be moved
            else {
                $wishlistHTMLmove .= '<td></td>'.
                                     '<td id="padd'.$index.'" style="padding-left:'.(($indent_move-$indentConst)<0?0:($indent_move-$indentConst)).'px; min-width: 220px;'.
                                     'color:red;">';
            }
            #arrow- and folder-image, all folders are open, and title
            $wishlistHTMLmove .= '<img src="/adm/lonIcons/arrow.open.gif" id="img'.$index.'" alt = "" />'.
                                 '<img src="/adm/lonIcons/navmap.folder.open.gif" id="imgFolder'.$index.'" alt="folder"/>'.
                                 $n->value()->title().'</td>';
        }
        # entry is a link
        else {
            # higlight the title, if the link was selected to be moved
            my $highlight = '';
            if ($isIn) {
               $highlight = 'style="color:red;"';
            }
            # link-image and title
            my $quotable_link = &Apache::loncommon::escape_single($n->value()->path());
            $wishlistHTMLmove .= '<td></td>'.
                                 '<td id="padd'.$index.'" style="padding-left:'.(($indent_move-$indentConst)<=0?$indentConst:$indent_move).'px; min-width: 220px;">'.
                                 '<a href="javascript:preview('."'".$quotable_link."'".');" '.$highlight.'>'.
                                 '<img src="/res/adm/pages/wishlist-link.png" id="img'.$index.'" alt="link"/>'.
                                 $n->value()->title().'</a></td>';
        }

        # note-icon, different icons for an entries with note and those without
        my $noteIMG = 'anot.png';

        if ($n->value()->note() ne '') {
            $noteIMG = 'anot2.png';
        }

        $wishlistHTMLmove .= '<td style="padding-left:10px;"><a href="javascript:;" onclick="setDisplayNote('."'note".$index."'".')">'.
                             '<img id="noteImg'.$index.'" src="/res/adm/pages/'.$noteIMG.'" alt="'.&mt('Note').'" title="'.&mt('Note').'" '.
                             ' class="LC_icon"/></a></td>';

        $wishlistHTMLmove .= &Apache::loncommon::end_data_table_row();

        # start row containing the textarea for the note, readonly in move-mode
        $wishlistHTMLmove .= &Apache::loncommon::continue_data_table_row('LC_hidden','note'.$index).
                             '<td></td><td>'.
                             '<textarea id="noteText'.$index.'" cols="25" rows="3" style="width:100%" '.
                             'name="newnote" readonly="readonly">'.
                             $n->value()->note().'</textarea></td><td></td>'.
                             &Apache::loncommon::end_data_table_row();

        # if the entry is a folder, it could have other entries as content. if it has, call wishlistMove for those entries 
        my @children = $n->children();
        if ($#children >=0) {
            $indent_move += 20;
            &wishlistMove(\@children, $marked);
            $indent_move -= 20;
        }
    }
}



# HTML-Markup for table if in import-mode
my $wishlistHTMLimport;
my $indent_imp = $indentConst;
my $form = 1;
sub wishlistImport {
    my ($nodes,$numskipped) = @_;

    my ($is_community,%nopick);
    if ($env{'request.course.id'}) {
        if (&Apache::loncommon::course_type() eq 'Community') {
            $is_community = 1;
        }
    }

    foreach my $n (@$nodes) {
        my $index = $n->value()->nindex();

        #
        # Determine which resources in stored links may be imported into a course/community.
        # (a) Import of directories in /res space is not supported.
        # (b) Import of a resource into a community requires user has 'bro' privilege for resource
        #     (i.e., user has author or co-author role for corresponcding Authoring Space).
        # (c) Import of a resource into a course requires user has 'be' privilege for resource.
        #

        if ($n->value()->path() =~ m{^(/res/$match_domain/$match_username/)}) {
            if ($n->value()->path() =~ m{/$}) {
                $nopick{$n->value()->path()} = $n->value()->title();
                $$numskipped ++;
            } else {
                if ($is_community) {
                    unless (&Apache::lonnet::allowed('bro',$n->value()->path())) {
                        $nopick{$n->value()->path()} = $n->value()->title();
                        $$numskipped ++;
                    }
                } else {
                    unless (&Apache::lonnet::allowed('bre',$n->value()->path())) {
                        $nopick{$n->value()->path()} = $n->value()->title();
                        $$numskipped ++;
                    }
                }
            }
        }

        # 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.
        # only display the top level entries on load
        $wishlistHTMLimport .= ($n->parent()->value() eq 'root')?&Apache::loncommon::start_data_table_row('','row'.$index)
                                                                :&Apache::loncommon::continue_data_table_row('LC_hidden','row'.$index);

 
        # checkboxes
        $wishlistHTMLimport .= '<td>';
        my ($disabled,$onclick,$image,$style);
        if ($nopick{$n->value()->path()}) {
            $disabled = ' disabled="disabled"';
            $image = 'wishlist-link-lighter.png';
            $style = 'style="color:#808080;"';
        } else {
            $onclick = ' onclick="selectAction('."'row".$index."'".')"';
            $image = 'wishlist-link.png';
        }
        $wishlistHTMLimport .= '<input type="checkbox" name="check" id="check'.$index.'" value="'.$index.'" '.
                               $disabled.$onclick.' />'.
                               '<input type="hidden" name="title'.$index.'" value="'.&escape($n->value()->title()).'" />'.
                               '<input type="hidden" name="filelink'.$index.'" value="'.&escape($n->value()->path()).'" />'.
                               '<input type="hidden" name="id'.$index.'" />';
        $wishlistHTMLimport .= '</td>';

        # entry is a folder
        if ($n->value()->path() eq '') {
            $wishlistHTMLimport .= '<td id="padd'.$index.'" style="padding-left:'.(($indent_imp-$indentConst)<0?0:($indent_imp-$indentConst)).'px; min-width: 220px;">'.
                                   '<a href="javascript:;" onclick="folderAction('."'row".$index."'".')" style="vertical-align:top">'.
                                   '<img src="/adm/lonIcons/arrow.closed.gif" id="img'.$index.'" alt = "" class="LC_icon"/>'.
                                   '<img src="/adm/lonIcons/navmap.folder.closed.gif" id="imgFolder'.$index.'" alt="folder"/>'.
                                   $n->value()->title().'</a></td>';
        }
        # entry is a link
        else {
            $wishlistHTMLimport .= '<td id="padd'.$index.'" style="padding-left:'.(($indent_imp-$indentConst)<=0?$indentConst:$indent_imp).'px; min-width: 220px;">';
            unless ($nopick{$n->value()->path()}) {
                my $quotable_link = &Apache::loncommon::escape_single($n->value()->path());
                $wishlistHTMLimport .= '<a href="javascript:preview('."'".$quotable_link."'".');">';
            }
            $wishlistHTMLimport .= '<img src="/res/adm/pages/'.$image.'" id="img'.$index.'" alt="link" />'.
                                   '<span '.$style.'>'.$n->value()->title().'</span></a></td>';
                                   $form++;
        }

        # note-icon, different icons for an entries with note and those without
        my $noteIMG = 'anot.png';

        if ($n->value()->note() ne '') {
            $noteIMG = 'anot2.png';
        }

        $wishlistHTMLimport .= '<td style="padding-left:10px;"><a href="javascript:;" onclick="setDisplayNote('."'note".$index."'".')">'.
                             '<img id="noteImg'.$index.'" src="/res/adm/pages/'.$noteIMG.'" alt="'.&mt('Note').'" title="'.&mt('Note').'" '.
                             ' class="LC_icon"/></a></td>';

        $wishlistHTMLimport .= &Apache::loncommon::end_data_table_row();

        # start row containing the textarea for the note, do not display note on default, readonly in import-mode
        $wishlistHTMLimport .= &Apache::loncommon::continue_data_table_row('LC_hidden','note'.$index).
                             '<td></td><td>'.
                             '<textarea id="noteText'.$index.'" cols="25" rows="3" style="width:100%" '.
                             'name="newnote" readonly="readonly">'.
                             $n->value()->note().'</textarea></td><td></td>';
        $wishlistHTMLimport .= &Apache::loncommon::end_data_table_row();

        # if the entry is a folder, it could have other entries as content. if it has, call wishlistImport for those entries 
        my @children = $n->children();
        if ($#children >=0) {
            $indent_imp += 20;
            &wishlistImport(\@children,$numskipped);
            $indent_imp -= 20;
        }
    }
    return;
}

# Returns the HTML-Markup for wishlist
sub makePage {
    my $rootgiven = shift;
    my $mode = shift;
    my $marked = shift;

    $root = $rootgiven;
    @childrenRt = $root->children();

    my $windowname = 'loncapaclient';
    if ($env{'request.lti.login'}) {
        $windowname .= 'lti';
    }

    # breadcrumbs and start_page
    &Apache::lonhtmlcommon::clear_breadcrumbs();
    &Apache::lonhtmlcommon::add_breadcrumb(
              { href => '/adm/wishlist?mode='.$mode,
                text => 'Stored Links'});
    my $startPage = &Apache::loncommon::start_page('Stored Links',undef,
                                                     {'add_entries' => {
                                                        'onload' => 'javascript:onLoadAction('."'".$mode."'".');',
                                                        'onunload' => 'javascript:window.name = '."'$windowname'"}});

    my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs('Stored Links','Wishlist');

    # get javascript-code for wishlist-interactions
    my $js = &JSforWishlist();

    # texthash for items in funtionlist
    my %lt = &Apache::lonlocal::texthash(
                 'ed' => 'Edit',
                 'vw' => 'View',
                 'al' => 'Add Link',
                 'af' => 'Add Folder',
                 'mv' => 'Move Selected',
                 'dl' => 'Delete Selected',
                 'sv' => 'Save');

    # start functionlist
    my $functions = &Apache::lonhtmlcommon::start_funclist();

    # icon for edit-mode, display when in view-mode
    if ($mode eq 'view') {
        $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
                          'onclick="setFormAction('."'save','edit'".');" class="LC_menubuttons_link">'.
                          '<img src="/res/adm/pages/edit-mode-22x22.png" alt="'.$lt{'ed'}.'" '.
                          'title="'.$lt{'ed'}.'" class="LC_icon"/> '.
                          '<span class="LC_menubuttons_inline_text">'.$lt{'ed'}.'</span></a>');
    }
    # icon for view-mode, display when in edit-mode
    else {
        $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
                          'onclick="setFormAction('."'save','view'".');" class="LC_menubuttons_link">'.
                          '<img src="/res/adm/pages/view-mode-22x22.png" alt="'.$lt{'vw'}.'" '.
                          'title="'.$lt{'vw'}.'" class="LC_icon"/> '.
                          '<span class="LC_menubuttons_inline_text">'.$lt{'vw'}.'</span></a>');
    }
    
    # icon for adding a new link
    $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
                      'onclick="newLink();" class="LC_menubuttons_link">'.
                      '<img src="/res/adm/pages/link-new-22x22.png" alt="'.$lt{'al'}.'" '.
                      'title="'.$lt{'al'}.'" class="LC_icon"/>'.
                      '<span class="LC_menubuttons_inline_text">'.$lt{'al'}.'</span></a>');

    # icon for adding a new folder
    $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
                      'onclick="newFolder();" class="LC_menubuttons_link">'.
                      '<img src="/res/adm/pages/folder-new-22x22.png" alt="'.$lt{'af'}.'" '.
                      'title="'.$lt{'af'}.'" class="LC_icon"/>'.
                      '<span class="LC_menubuttons_inline_text">'.$lt{'af'}.'</span></a>');

    # icon for moving entries
    $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
                      'onclick="setFormAction('."'move','move'".'); " class="LC_menubuttons_link">'.
                      '<img src="/res/adm/pages/move-22x22.png" alt="'.$lt{'mv'}.'" '.
                      'title="'.$lt{'mv'}.'" class="LC_icon" />'.
                      '<span class="LC_menubuttons_inline_text">'.$lt{'mv'}.'</span></a>');

    # icon for deleting entries
    $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
                      'onclick="setFormAction('."'delete','".$mode."'".'); " class="LC_menubuttons_link">'.
                      '<img src="/res/adm/pages/del.png" alt="'.$lt{'dl'}.'" '.
                      'title="'.$lt{'dl'}.'" class="LC_icon" />'.
                      '<span class="LC_menubuttons_inline_text">'.$lt{'dl'}.'</span></a>');

    # icon for saving changes
    $functions .= &Apache::lonhtmlcommon::add_item_funclist('<a href="javascript:;" '.
                      'onclick="setFormAction('."'saveOK','".$mode."'".'); " class="LC_menubuttons_link">'.
                      '<img src="/res/adm/pages/save-22x22.png" alt="'.$lt{'sv'}.'" '.
                      'title="'.$lt{'sv'}.'" class="LC_icon" />'.
                      '<span class="LC_menubuttons_inline_text">'.$lt{'sv'}.'</span></a>');

    # end funtionlist and generate subbox 
    $functions.= &Apache::lonhtmlcommon::end_funclist();
    my $subbox = &Apache::loncommon::head_subbox($functions);

    # start form 
    my $inner .= '<form name="list" action ="/adm/wishlist" method="post">'.
                 '<input type="hidden" id="action" name="action" value="" />';
 
    # only display subbox in view- or edit-mode
    if ($mode eq 'view' || $mode eq 'edit') {
        $inner .= $subbox;
    }

    # generate table-content depending on mode
    if ($mode eq 'edit') {
        &wishlistEdit(\@childrenRt);
        if ($wishlistHTMLedit ne '') {
            $inner .= &Apache::loncommon::start_data_table("LC_tableOfContent");
            $inner .= $wishlistHTMLedit;
            $inner .= &Apache::loncommon::end_data_table();
        }
        else {
            $inner .= '<span class="LC_info">'.&mt("Your Stored Links list is currently empty.").'</span>';
        }
        $wishlistHTMLedit = '';
    }
    elsif ($mode eq 'view') {
        &wishlistView(\@childrenRt);
        if ($wishlistHTMLview ne '') {
            $inner .= '<table class="LC_data_table LC_tableOfContent">'.$wishlistHTMLview.'</table>';
        }
        else {
            $inner .= '<span class="LC_info">'.&mt("Your Stored Links list is currently empty.").'</span>';
        }
        $wishlistHTMLview = '';
    }
    else {
        my $markStr = '';
        foreach my $m (@$marked) {
            $markStr .= $m.',';
        }
        if ($markStr) {
            $markStr = substr($markStr, 0, length($markStr)-1);
            $inner .= '<input type="hidden" value="'.$markStr.'" name="markedToMove" />';
            $inner .= '<p><span class="LC_info">'.&mt('You have selected the red marked entries to be moved to another folder. '.
                                                   'Now choose the new destination folder.').'</span></p>';
            &wishlistMove(\@childrenRt, $marked);
            $inner .= '<table class="LC_data_table LC_tableOfContent">'.$wishlistHTMLmove.'</table><br/><br/>';
            $inner .= '<input type="button" value="'.&mt('Move').'" onclick="setFormAction('."'move','view'".');" />'.
                      '<input type="button" value="'.&mt('Cancel').'" onclick="go('."'/adm/wishlist'".')" />';

            $wishlistHTMLmove ='<tr id="root" class="LC_odd_row"><td><input type="radio" name="mark" id="radioRoot" value="root" /></td>'.
                               '<td>'.&mt('Top level').'</td><td></td></tr>';
        }
        else {
            $inner .= '<p><span class="LC_info">'.&mt("You haven't marked any entry to move.").'</span></p>'.
                      '<input type="button" value="'.&mt('Back').'" onclick="go('."'/adm/wishlist'".')" />';
        }
    }
    
    # end form 
    $inner .= '</form>';

    # end_page 
    my $endPage =  &Apache::loncommon::end_page();

    # put all page-elements together
    my $page = $startPage.$breadcrumbs.$js.$inner.$endPage;

    return $page;
}


# Returns the HTML-Markup for the PopUp, shown when a new link should set, when NOT
# beeing in the wishlist-interface (method is called in lonmenu and lonsearchcat)
sub makePopUpNewLink {
    my ($title, $path) = @_;

    # Get all existing folders to offer posibility to set a new link
    # into a folder
    my %TreeHashLink = &Apache::lonwishlist::getWishlist();
    my $rootLink = &Apache::Tree::HashToTree(\%TreeHashLink);
    my @childrenRtLink = $rootLink->children();

    $foldersOption = '';
    @allFolders = ();
    &getFoldersToArray(\@childrenRtLink);
    &getFoldersForOption(\@childrenRtLink);

    my $options = '<option value="" selected="selected">('.&mt('Top level').')</option>'.$foldersOption;
    $foldersOption = '';
    @allFolders = ();

    # HTML-Markup for the Pop-Up-window 'Set a link for this resource to wishlist'
    my $startPageWishlistlink = 
        &Apache::loncommon::start_page('Save to Stored Links',undef,
                                      {'only_body' => 1,
                                       'bgcolor'   => '#FFFFFF',});

    my $warningLink = &mt('You must insert a title!');
    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://...');
    &js_escape(\$warningLink);
    &js_escape(\$warningLinkNotAllowed1);

    my $inPageWishlistlink1 = '<h1>'.&mt('Save to Stored Links').'</h1>';
    # If no title is delivered, 'New Link' is called up from the wishlist-interface, so after
    # submitting the window should close instead of offering a link to wishlist (like it should do
    # if we call 'Set New Link' from within a browsed ressource)
    if (!$title) {
        $inPageWishlistlink1 .= '<form method="post" name="newlink" action="/adm/wishlist" target="wishlist"'.
                                'onsubmit="return newlinksubmit();" >';
    }
    else {
        $inPageWishlistlink1 .= '<form method="post" name="newlink" action="/adm/wishlist?mode=set" '.
                                'onsubmit="return newlinksubmit();" >';
    }
    $inPageWishlistlink1 .= &Apache::lonhtmlcommon::start_pick_box().
                            &Apache::lonhtmlcommon::row_title(&mt('Link Title'));

    my $inPageWishlistlink2 = &Apache::lonhtmlcommon::row_closure().
                              &Apache::lonhtmlcommon::row_title(&mt('Path'));

    my $inPageWishlistlink3 = &Apache::lonhtmlcommon::row_closure().
                              &Apache::lonhtmlcommon::row_title(&mt('Note')).
                              '<textarea name="note" rows="3" cols="35" style="width:100%"></textarea>'.
                              &Apache::lonhtmlcommon::row_closure(1).
                              &Apache::lonhtmlcommon::end_pick_box().
                              '<br/><br/>'.
                              '<input type="submit" value="'.&mt('Save in').'" />'.
                              '<select name="folders">'.
                              $options.
                              '</select>'.
                              '<input type="button" value="'.&mt('Cancel').'" onclick="javascript:window.close();" />'.
                              '</form>';
    $options = '';

    my $endPageWishlistlink = &Apache::loncommon::end_page();

    my $popUp = $startPageWishlistlink.
    $inPageWishlistlink1.
    '<input type="text" name="title" size="45" value="" />'.
    $inPageWishlistlink2.
    '<input type="text" name="path" size="45" value="" />'.
    $inPageWishlistlink3;

    # JavaScript-function to set title and path of ressource automatically
    # and show warning, if no title was set or path is invalid
    $popUp .= <<SCRIPT;
    <script type="text\/javascript">
    document.getElementsByName("title")[0].value = '$title';
    document.getElementsByName("path")[0].value = '$path';
    var fromwishlist = false;
    var titleget = '$title';
    if (!titleget) {
        fromwishlist = true;
    } 
    function newlinksubmit(){
    var title = document.getElementsByName("title")[0].value;
    var path = document.getElementsByName("path")[0].value;
    if (!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;}
    if (fromwishlist) {
        window.close();
    }
    return true;}
    <\/script>
SCRIPT

    $popUp .= $endPageWishlistlink;

    return $popUp;
}

sub makePopUpNewFolder {
    # Get all existing folders to offer posibility to create a new folder
    # into an existing folder
    my %TreeHashLink = &Apache::lonwishlist::getWishlist();
    my $rootLink = &Apache::Tree::HashToTree(\%TreeHashLink);
    my @childrenRtLink = $rootLink->children();

    $foldersOption = '';
    @allFolders = ();
    &getFoldersToArray(\@childrenRtLink);
    &getFoldersForOption(\@childrenRtLink);

    my $options = '<option value="" selected="selected">('.&mt('Top level').')</option>'.$foldersOption;
    $foldersOption = '';
    @allFolders = ();

    # HTML-Markup for the Pop-Up-window 'New Folder'
    my $startPageWishlistfolder = 
        &Apache::loncommon::start_page('New Folder',undef,
                                      {'only_body' => 1,
                                       'bgcolor'   => '#FFFFFF',});

    my $warningFolder = &mt('You must insert a title!');
    &js_escape(\$warningFolder);

    my $inPageNewFolder = '<h1>'.&mt('New Folder').'</h1>'.
                          '<form method="post" name="newfolder" action="/adm/wishlist" target="wishlist" '.
                          'onsubmit="return newfoldersubmit();" >'.
                          &Apache::lonhtmlcommon::start_pick_box().
                          &Apache::lonhtmlcommon::row_title(&mt('Folder title')).
                          '<input type="text" name="title" size="45" value="" /><br />'.
                          &Apache::lonhtmlcommon::row_closure().
                          &Apache::lonhtmlcommon::row_title(&mt('Note')).
                          '<textarea name="note" rows="3" cols="35" style="width:100%"></textarea><br />'.
                          &Apache::lonhtmlcommon::row_closure(1).
                          &Apache::lonhtmlcommon::end_pick_box().
                          '<br/><br/>'.
                          '<input type="submit" value="'.&mt('Save in').'" />'.
                          '<select name="folders">'.
                          $options.
                          '</select>'.
                          '<input type="button" value="'.&mt('Cancel').'" onclick="javascript:window.close();" />'.
                          '</form>';
    my $endPageWishlistfolder = &Apache::loncommon::end_page();

    my $popUp = $startPageWishlistfolder.
    $inPageNewFolder;

    $popUp .= <<SCRIPT;
    <script type="text\/javascript">
        function newfoldersubmit(){
            var title = document.getElementsByName("title")[0].value;
            if (!title) {
            alert("$warningFolder");
            return false;}
            else {
            window.close();
            return true;}}
    <\/script>
SCRIPT

    $popUp .= $endPageWishlistfolder;

    return $popUp;
}

# Returns the HTML-Markup for the page, shown when a link was set
sub makePageSet {
    my $title = 'Stored Links';

    # start_page
    my $output =
        &Apache::loncommon::start_page($title,undef,
                                       {'only_body' => 1})
       .'<h1>'.&mt($title).'</h1>';
    
    # confirm success and offer link to wishlist
    $output .=
        &Apache::loncommon::confirmwrapper(
            &Apache::lonhtmlcommon::confirm_success(
                &mt('Link successfully saved!')))
       .&Apache::lonhtmlcommon::actionbox(
            ['<a href="javascript:;" onclick="opener.open('."'/adm/wishlist'".');window.close();">'.&mt('Go to Stored Links').'</a>',
             '<a href="javascript:;" onclick="window.close();">'.&mt('Close this window').'</a>'
            ]);

    # end_page 
    $output .= &Apache::loncommon::end_page();

    return $output;
}


# Returns the HTML-Markup for the page, shown when links should be imported into a course
sub makePageImport {
    my $rootgiven = shift;
    my $rat = shift;

    $root = $rootgiven;
    @childrenRt = $root->children();
    # start_page 
    my $startPage = &Apache::loncommon::start_page('Stored Links',undef,
                                                   {'only_body' => 1});
    
    # get javascript-code for wishlist-interactions
    my $js = &JSforWishlist();
    $js .= &JSforImport($rat);

    my $inner = '<h1>'.&mt('Import Resources from Stored Links').'</h1>';
    if (!$rat) {
        $inner .=
            '<ul>'.
            '<li class="LC_info">'.&mt('Use the checkboxes corresponding to a folder to '.
                'easily check all links within the folder.').'</li>'.
            '<li class="LC_info">'.&mt('The folder structure itself cannot be imported.').'</li>'.
            '<li class="LC_info">'.&mt('All checked links will be imported into the current folder of your course.').'</li>'.
            '</ul>';
    }
    else {
        $inner .=
            '<ul>'.
            '<li class="LC_info">'.&mt('Use the checkboxes corresponding to a folder to '.
                'easily check all links within this folder.').'</li>'.
            '<li class="LC_info">'.&mt('The folder structure itself cannot be imported.').'</li>'.
            '</ul>';
    }
    my %wishlist = &getWishlist();

    #FIXME Saved string containing all folders in wishlist.db-file (key 'folders') in first version of lonwishlist
    #After splitting lonwishlist into two modules, this is not necessary anymore. So, dependent from when the wishlist
    #was first called (i.e. when wishlist.db was created), there might be an entry 'folders' or not. Number of links in
    #wishlist.db depends on whether this entry exists or not...JW  
    my $fnum;
    if (defined $wishlist{'folders'}) {
        $fnum = (keys(%wishlist))-2;
    }
    else {
        $fnum = (keys(%wishlist))-1;
    }

    $inner .= '<form method="post" name="groupsort" action="">'.
              '<input type="hidden" value="'.$fnum.'" name="fnum" />'.
              '<input type="button" onclick="javascript:checkAll()" id="checkallbutton" value="'.&mt('Check All').'" />'.
              '<input type="button" onclick="javascript:uncheckAll()" id="uncheckallbutton" value="'.&mt('Uncheck All').'" />'.
              '<input type="button" value="'.&mt('Import Checked').'" onclick="finish_import();" />'.    
              '<input type="button" value="'.&mt('Cancel').'" onclick="window.close();" /><br/><br/>'; 

    
    # wishlist-table
    my $numskipped = 0;
    &wishlistImport(\@childrenRt,\$numskipped);
    if ($wishlistHTMLimport ne '') {
        $inner .= '<table class="LC_data_table LC_tableOfContent">'.$wishlistHTMLimport.'</table>';
    }
    else {
        $inner .= '<span class="LC_info">'.&mt("Your Stored Links list is currently empty.").'</span>';
    }
    if ($numskipped > 0) {
        $inner .= '<p class="LC_info">'.&mt('Note: where a Stored Link is unavailable for import in the current context it is grayed out.').'</p>';
    }
    $wishlistHTMLimport = '';

    $inner .= '</form>';

    # end_page 
    my $endPage =  &Apache::loncommon::end_page();

    # put all page-elements together
    my $page = $startPage.$js.$inner.$endPage;

    return $page;
}


# Returns the HTML-Markup for error-page
sub makeErrorPage {
    # breadcrumbs and start_page 
    &Apache::lonhtmlcommon::add_breadcrumb(
              { href => '/adm/wishlist',
                text => 'Stored Links'});
    my $startPage = &Apache::loncommon::start_page('Stored Links');
    
    my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs(&mt('Stored Links'),'Wishlist');
    &Apache::lonhtmlcommon::clear_breadcrumbs();

    # error-message
    my $inner .= '<p class="LC_error">'.&mt('An error occurred! Please try again later.').'</p>';

    # end_page 
    my $endPage =  &Apache::loncommon::end_page();

    # put all page-elements together
    my $page = $startPage.$breadcrumbs.$inner.$endPage;

    return $page;
}


# ----------------------------------------------------- package Tree
# Extend CPAN-Module Tree by function like 'moveNode' or 'deleteNode'
package Apache::Tree;

=pod

=head2 Routines from package Tree

=over 4

=item * &getNodeByIndex(index, nodes)

     Searches for a node, specified by the index, in nodes (reference to array) and returns it. 
 

=item * &moveNode(node, at, newParent)

     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).


=item * &removeNode(node)

     Removes a node given by node from the tree.


=item * &TreeIndex(children)

     Sets an index for every node in the tree, beginning with 0.
     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).     


=item * &setCountZero()

     Resets index counter.


=item * &RootToHash(childrenRt)

     Converts the root-node to a hash-entry: the key is root and values are just the indices of root's children.
   

=item * &TreeToHash(childrenRt)

     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).
     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).     


=item * &HashToTree()

     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.


=item * &buildTree(node, childrenIn, TreeNodes, TreeHash)

     Joins the nodes to a tree.
     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).
   

=back

=cut


# returns the node with a given index from a list of nodes
sub getNodeByIndex {
    my $index = shift;
    my $nodes = shift;
    my $found;
    
    foreach my $n (@$nodes) {
        my $curIndex = $n->value()->nindex();
        if ($curIndex == $index) {
            $found = $n;
        }
    }
    return $found;
}

# moves a given node to a new parent or change the position from a node
# within its siblings (sorting)
sub moveNode {
    my $node = shift;
    my $at = shift;
    my $newParent = shift;


    if (!$newParent) {
        $newParent = $node->parent();
    }

    $node->parent()->remove_child($node);

    if (defined $at) {
        $newParent->add_child({at => $at},$node);
    }
    else {
        $newParent->add_child($node);
    }
    
    # updating root's children
    @childrenRt = $root->children();
}

# removes a given node
sub removeNode() {
    my $node = shift;
    my @children = $node->children();

    if ($#children >= 0) {
        foreach my $c (@children) {
            &removeNode($c);
        }
    }
    $node->parent()->remove_child($node);

    # updating root's children
    @childrenRt = $root->children();
}


# set an index for every node in the tree, beginning with 0
my $count = 0;
sub TreeIndex {
    my $children = shift;

    foreach my $n (@$children) {
        my @children = $n->children();
        $n->value()->nindex($count);$count++;

        if ($#children>=0) {
            &TreeIndex(\@children);
        }
    }
}

# reset index counter
sub setCountZero {
    $count = 0;
}


# convert the tree to a hash
# each node is one hash-entry
# keys are the indices, values are all other attributes
# (containing tile, path, note, date and indices for all direct children)
# except for root: the key is root and values are
# just the indices of root's children
sub RootToHash {
    my $childrenRt = shift;
    my @indexarr = ();

    foreach my $c (@$childrenRt) {
       push (@indexarr, $c->value()->nindex());
    }
    $TreeToHash{'root'} = [@indexarr];
}

sub TreeToHash {
    my $childrenRt = shift;

    foreach my $n (@$childrenRt) {
        my @arrtmp = ();
        $arrtmp[0] = $n->value()->title();
        $arrtmp[1] = $n->value()->path();
        $arrtmp[2] = $n->value()->note();
        $arrtmp[3] = $n->value()->date();
        my @childrenRt = $n->children();
        my $co = 4;
        foreach my $c (@childrenRt) {
            my $i = $c->value()->nindex();
            $arrtmp[$co] = $i;
            $co++;
        }
        $TreeToHash{$n->value()->nindex} = [ @arrtmp]; 
        if ($#childrenRt>=0) {
            &TreeToHash(\@childrenRt);
        }
    }
}


# convert the hash to a tree
# build a tree-object for each entry in the hash
# afterwards call &buildTree to connect the tree-objects
sub HashToTree {
    my $TreeHash = shift;
    my @TreeNodes = ();
    my $root;

    foreach my $key (keys(%$TreeHash)) {
        if ($key eq 'root') {
            $root = Tree->new("root");
        }
        elsif ($key ne 'folders') {
        my @attributes = @{ $$TreeHash{$key} };
        my $tmpNode;
            $tmpNode = Tree->new(Entry->new(title=>$attributes[0],
                                            path=>$attributes[1],
                                            note=>$attributes[2],
                                            date=>$attributes[3],
                                            nindex=>$key));
        push(@TreeNodes, $tmpNode);
        # shift all attributes except for
        # the indices representing the children of a node
        shift(@attributes);
        shift(@attributes);
        shift(@attributes);
        shift(@attributes);
        $$TreeHash{$key} = [ @attributes ];
        }
    }
    # if there are nodes, build up the tree-structure
    if (defined $$TreeHash{'root'} && $$TreeHash{'root'} ne '') {
        my @childrenRtIn = @{ $$TreeHash{'root'} };
        &buildTree(\$root, \@childrenRtIn,\@TreeNodes,$TreeHash);
    }
    return $root; 
}


# join the nodes to a tree
sub buildTree {
    my ($node, $childrenIn, $TreeNodes, $TreeHash) = @_;
    bless($node, 'Tree');
    foreach my $c (@$childrenIn) {
        my $tmpNode =  &getNodeByIndex($c,$TreeNodes);
        $$node->add_child($tmpNode);
        my @childrenIn = @{ $$TreeHash{$tmpNode->value()->nindex()} };
        &buildTree(\$tmpNode,\@childrenIn,$TreeNodes,$TreeHash);
    }

}


# ----------------------------------------------------- package Entry
# package that defines the entrys a wishlist could have
# i.e. folders and links
package Entry;

# constructor
sub new {
    my $invocant = shift;
    my $class = ref($invocant) || $invocant;
    my $self = { @_ }; #set attributes
    bless($self, $class);
    return $self;    
}

# getter and setter
sub title {
    my $self = shift;
    if ( @_ ) { $self->{title} = shift}
    return $self->{title};
}

sub date {
    my $self = shift;
    if ( @_ ) { $self->{date} = shift}
    return $self->{date};
}

sub note {
    my $self = shift;
    if ( @_ ) { $self->{note} = shift}
    return $self->{note};
}

sub path {
    my $self = shift;
    if ( @_ ) { $self->{path} = shift}
    return $self->{path};
}

sub nindex {
    my $self = shift;
    if ( @_ ) { $self->{nindex} = shift}
    return $self->{nindex};
}


1;
__END__

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