# The LearningOnline Network with CAPA
# Utility-routines for wishlist
#
# $Id: lonwishlist.pm,v 1.24 2014/12/20 15:35:40 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 = '';
&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 .= '';
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 $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 $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(< 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= 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(<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 .= '
';
# entry is a folder
if ($n->value()->path() eq '') {
$wishlistHTMLview .= '
';
}
# 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 .= '
';
$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).
'
'.
'
';
$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 .= '
';
# 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 .= '';
}
else {
$options .= '';
}
}
$curNode++;
# entry is a folder
if ($n->value()->path() eq '') {
$wishlistHTMLedit .= '
';
}
# entry is a link
else {
$wishlistHTMLedit .= '
'.
'
'.
''.
'
'.
'
';
}
# 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 .= '
';
$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).
'
'.
'
';
$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 ='
'.
'
'.&mt('Top level').'
';
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 .= '
'.
'
';
}
# highlight the title, if the folder was selected to be moved
else {
$wishlistHTMLmove .= '
'.
'
';
}
#arrow- and folder-image, all folders are open, and title
$wishlistHTMLmove .= ''.
''.
$n->value()->title().'
';
}
# 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 .= '
';
}
# 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 .= '
';
$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).
'
'.
'
'.
&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 .= '
';
$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 .= '
';
$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).
'
'.
'
';
$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();
# 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 = '."'loncapaclient'"}});
my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs(&mt('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(''.
' '.
''.$lt{'ed'}.'');
}
# icon for view-mode, display when in edit-mode
else {
$functions .= &Apache::lonhtmlcommon::add_item_funclist(''.
' '.
''.$lt{'vw'}.'');
}
# icon for adding a new link
$functions .= &Apache::lonhtmlcommon::add_item_funclist(''.
''.
''.$lt{'al'}.'');
# icon for adding a new folder
$functions .= &Apache::lonhtmlcommon::add_item_funclist(''.
''.
''.$lt{'af'}.'');
# icon for moving entries
$functions .= &Apache::lonhtmlcommon::add_item_funclist(''.
''.
''.$lt{'mv'}.'');
# icon for deleting entries
$functions .= &Apache::lonhtmlcommon::add_item_funclist(''.
''.
''.$lt{'dl'}.'');
# icon for saving changes
$functions .= &Apache::lonhtmlcommon::add_item_funclist(''.
''.
''.$lt{'sv'}.'');
# end funtionlist and generate subbox
$functions.= &Apache::lonhtmlcommon::end_funclist();
my $subbox = &Apache::loncommon::head_subbox($functions);
# start form
my $inner .= '';
# 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 = ''.$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://...');
my $inPageWishlistlink1 = '
'.&mt('Save to Stored Links').'
';
# 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 .= '';
$options = '';
my $endPageWishlistlink = &Apache::loncommon::end_page();
my $popUp = $startPageWishlistlink.
$inPageWishlistlink1.
''.
$inPageWishlistlink2.
''.
$inPageWishlistlink3;
# JavaScript-function to set title and path of ressource automatically
# and show warning, if no title was set or path is invalid
$popUp .= <