* @copyright 2001-2007 VIKO team and contributors
* @license http://www.gnu.org/licenses/gpl.html GPL 2.0
*/
require_once 'Module/Course.php';
require_once 'HTML/QuickForm.php';
require_once 'HTML/QuickForm/Renderer/VIKO.php';
/**
* HTML form elements for materials
*/
require_once 'MaterialForm.php';
/**
* Files, folders and links
*/
require_once 'MaterialFactory.php';
/**
* Showing, editing, adding, deleting of course materials: files, folders and links
*
*
* - /#course_id/materials - view contents of main folder
* - /#course_id/materials/view/#folder_id - view contents of folder with ID #folder_id
* - /#course_id/materials/download/#file_id - download file
* - /#course_id/materials/edit/#material_id - edit material
* - /#course_id/materials/delete/#material_id - delete material
* - /#course_id/materials/add-file/#folder_id - upload file to folder with ID #folder_id
* - /#course_id/materials/add-folder/#folder_id - add folder to folder with ID #folder_id
* - /#course_id/materials/add-link/#folder_id - add link to folder with ID #folder_id
*
*
* The #folder_id parameter is optional - when this is missing,
* then the action refers to the root directory.
* For example /materials and /materials/view both trigger the showing of root folder contents.
*/
class Module_Course_Materials extends Module_Course {
/**
* The action to perform with the material
*
* @access private
* @var string
*/
var $_action = "view";
/**
* The material to view/edit/delete/...
*
* @access private
* @var Material
*/
var $_material = null;
/**
* Constructs new Materials Module
*/
function Module_Course_Materials( $course, $parameters )
{
$possible_actions = array(
"view" => true,
"download" => true,
"edit" => true,
"delete" => true,
"add-file" => true,
"add-folder" => true,
"add-link" => true
);
// the first parameter is the name of action
if ( isset( $parameters[0] ) ) {
$this->_action = $parameters[0];
}
else {
$this->_action = "view"; // default
}
// if the action is one of allowed ones
if ( isset( $possible_actions[ $this->_action ] ) ) {
// take the [optional] second parameter and turn it into Material object
if ( isset( $parameters[1] ) && (int)$parameters[1] > 0 ) {
$this->_material = MaterialFactory::materialByID( (int)$parameters[1] );
// if the loading failid, _material will be false - that is checked in toHTML()
}
}
else {
trigger_error( "Unknown action '{$this->_action}'.", E_USER_ERROR );
}
parent::Module_Course( $course, $parameters );
}
/**
* Returns the module identifier string
*
* @access public
* @static
* @return string Identificator of module
*/
function getID()
{
return "materials";
}
/**
* Returns the short name of the module, this is used in navigation menu.
*
* @access public
* @static
* @return string short name
*/
function getName()
{
return _("Materials");
}
/**
* Returns short description of module
*
* @access public
* @static
* @return string module description
*/
function getDescription()
{
return _("Manage materials");
}
/**
* Returns Materials page in HTML
*
* @return string HTML fragment
*/
function toHTML()
{
if ( isset( $this->_material ) ) {
// ensure, that the material exists
if ( $this->_material === false ) {
return $this->errorPage( _("The selected material does not exist.") );
}
// ensure, that material belongs to current course
$material_course = $this->_material->getCourse();
if ( $material_course->getID() != $this->_course->getID() ) {
return $this->errorPage( _("The selected material does not belong to this course.") );
}
}
switch ( $this->_action ) {
case "view":
return $this->_showFolder( $this->_material );
break;
case "download":
return $this->_downloadFile( $this->_material );
break;
case "delete":
return $this->_deleteMaterial( $this->_material );
break;
case "add-folder":
return $this->_addFolder( $this->_material );
break;
case "add-link":
return $this->_addLink( $this->_material );
break;
case "add-file":
return $this->_addFile( $this->_material );
break;
case "edit":
return $this->_editMaterial( $this->_material );
break;
default:
trigger_error( "Unknown action '{$this->_action}'.", E_USER_ERROR );
}
}
/**
* Returns page with table listing all files in folder
*
* When the $folder parameter is not specified, the contents of main folder is shown.
*
* @access private
* @param File $folder
* @return string HTML fragment
*/
function _showFolder( $folder = null )
{
// page title
$html = $this->title();
// table with files
$html.= $this->_getMaterialsTable( $folder );
// links to add files/folders/links
if ( isset($folder) ) {
$add_file_uri = $this->relativeURI('add-file', $folder->getID() );
$add_link_uri = $this->relativeURI('add-link', $folder->getID() );
$add_folder_uri = $this->relativeURI('add-folder', $folder->getID() );
}
else {
$add_file_uri = $this->relativeURI('add-file' );
$add_link_uri = $this->relativeURI('add-link' );
$add_folder_uri = $this->relativeURI('add-folder' );
}
$add_file_link = HTML::newLink( $add_file_uri, _("Add file") );
$add_link_link = HTML::newLink( $add_link_uri, _("Add link") );
$add_folder_link = HTML::newFolderLink( $add_folder_uri, _("Add folder") );
// only teachers can add folders
if ( $this->_userCanEditThisCourse() ) {
$html.= HTML::actionList( $add_file_link, $add_link_link, $add_folder_link );
}
else {
// both the adding of links and uploading files are allowed/disallowed in configuration
if ( Configuration::getStudentCanUploadFiles() && Configuration::getStudentCanAddLinks() ) {
$html.= HTML::actionList( $add_file_link, $add_link_link );
}
elseif ( Configuration::getStudentCanUploadFiles() ) {
$html.= HTML::actionList( $add_file_link );
}
elseif ( Configuration::getStudentCanAddLinks() ) {
$html.= HTML::actionList( $add_link_link );
}
}
return $html;
}
/**
* Returns table listing all files in folder
*
* When the $folder parameter is not specified, the contents of main folder are listed.
*
* @access private
* @param File $folder
* @return string HTML table
*/
function _getMaterialsTable( $folder = null )
{
$table =& new VikoTable();
$header = array(
_("File name"),
_("File type"),
_("Size")
);
if ( $this->_userCanEditThisCourse() ) {
$header[] = _("Edit");
$header[] = HTML::abbr('X', _("Delete material") );
}
$table->addHeader( $header );
if ( isset( $folder ) ) {
// get list of materials inside folder
$material_records = $this->_getFolderRecords( $folder );
// because we display a subfolder, we need to provide a backlink to parent folder
$table->addRow(
$this->_linkToParentFolder( $folder ),
_("Parent folder")
);
}
else {
// get materials inside root folder
$material_records = $this->_getRootFolderRecords();
}
$total_filesize = 0;
// If course has no materials, show notice about it
if ( count($material_records) == 0 && !isset($folder) ) {
return HTML::notice( _("There are no materials on this course. To add new materials use the links below.") );
}
foreach ( $material_records as $record ) {
$material = MaterialFactory::material( $record['material_type'], $record['material_id'] );
$material->readFromDatabaseRecord( $record );
$delete_title = sprintf( _("Delete %s"), $material->getHTMLName() );
if ( $material->getType() == 'FILE' ) {
$filesize = $material->getHumanReadableSize();
$total_filesize += $material->getSize();
}
else {
$filesize = "";
}
// only create delete- and edit-links when user is allowed to edit the file
if ( $this->_userCanEditThisCourse() ) {
$edit_link = HTML::a( $this->relativeURI( 'edit', $material->getID() ), _("Edit") );
$delete_link = HTML::a( $this->relativeURI( 'delete', $material->getID() ), "X", $delete_title );
}
// only add description, when it exists
if ( $material->getDescription() == "" ) {
$description = "";
}
else {
// place description inside
$description = HTML::element(
'div',
$material->getHTMLDescription(),
array( "class" => "material-description" )
);
}
// if the the file was added by someone else than the teacher,
// display the name of the adder in description.
$material_owner = $material->getOwner();
$course_teacher = $this->_course->getTeacher();
if ( $material_owner->getID() != $course_teacher->getID() ) {
$description .= HTML::p( $material_owner->getFullName() );
}
$row = array(
$material->getLink() . $description,
$material->getTypeName(),
$filesize
);
if ( $this->_userCanEditThisCourse() ) {
$row[] = $edit_link;
$row[] = $delete_link;
}
$table->addRow( $row );
}
$table->addFooter(
_("Total"),
"",
File::getHumanReadableSize( $total_filesize )
);
$table->setColumnsToNumeric( array(2), TABLE_BODY_AND_FOOTER );
if ( $this->_userCanEditThisCourse() ) {
$table->setColumnToDelete( 4 );
}
return $table->toHTML();
}
/**
* Returns array with files and folders inside root folder
*
* @access private
* @return array file records
*/
function _getRootFolderRecords()
{
$db = DBInstance::get();
$order_by = DBUtils::createOrderBy( array('material_name') );
// get list of folders
$folder_records = $db->getAll(
"SELECT * FROM Materials WHERE course_id=? AND parent_id is NULL AND material_type='FOLDER' $order_by",
array( $this->_course->getID() )
);
if ( PEAR::isError( $folder_records ) ) {
trigger_error( $folder_records->getMessage(), E_USER_ERROR );
}
// get list of other files/links
$file_records = $db->getAll(
"SELECT * FROM Materials WHERE course_id=? AND parent_id is NULL AND material_type<>'FOLDER' $order_by",
array( $this->_course->getID() )
);
if ( PEAR::isError( $file_records ) ) {
trigger_error( $file_records->getMessage(), E_USER_ERROR );
}
return array_merge( $folder_records, $file_records );
}
/**
* Returns array with files and folders inside specified folder
*
* @access private
* @param Material $folder
* @return array file records
*/
function _getFolderRecords( $folder )
{
$db = DBInstance::get();
$order_by = DBUtils::createOrderBy( array('material_name') );
// get list of folders
$folder_records = $db->getAll(
"SELECT * FROM Materials WHERE parent_id = ? AND material_type='FOLDER' $order_by",
array( $folder->getID() )
);
if ( PEAR::isError( $folder_records ) ) {
trigger_error( $folder_records->getMessage(), E_USER_ERROR );
}
// get list of other files/links
$file_records = $db->getAll(
"SELECT * FROM Materials WHERE parent_id = ? AND material_type<>'FOLDER' $order_by",
array( $folder->getID() )
);
if ( PEAR::isError( $file_records ) ) {
trigger_error( $file_records->getMessage(), E_USER_ERROR );
}
return array_merge( $folder_records, $file_records );
}
/**
* Returns link to parent folder of given folder
*
* @access private
* @param Material $current_folder
* @return string HTML a element
*/
function _linkToParentFolder( $current_folder )
{
$parent = $current_folder->getParent();
if ( isset( $parent ) && $parent->getID() > 0 ) {
// if parent folder is not root folder, link to the parent folder id
$parent_uri = $this->relativeURI( 'view', $parent->getID() );
}
else {
// if parent folder is root folder, link to the root dir of course module
$parent_uri = $this->relativeURI();
}
return HTML::element( "a", "..", array( "href" => $parent_uri, "class" => "folder" ) );
}
/**
* Retrieves the file contents from database and sends to client
*
* @access private
* @param Material $file the file to download
* @return bool
*/
function _downloadFile( $file )
{
// $file must not be null
if ( !isset($file) ) {
trigger_error( "The file_id to download must be specified.", E_USER_ERROR );
}
// $file must be of correct type
if ( $file->getType() != 'FILE' ) {
trigger_error( "Only files can be downloaded.", E_USER_ERROR );
}
// set headers
header("Content-Type: " . $file->getMimeType() );
header("Content-Length: " . $file->getSize() );
header("Content-Disposition: attachment; filename=" . $file->getDownloadName() );
// output data
$file->outputContent();
// exit
return false;
}
/**
* Deletes the material
*
* On successful delete sends user back to previous page
*
* @access private
* @param Material $material the material to delete
* @return mixed
*/
function _deleteMaterial( $material )
{
// must not be null
if ( !isset($material) ) {
trigger_error( "The material to delete must be specified.", E_USER_ERROR );
}
// ensure, that user is teacher of the course
if ( !$this->_userCanEditThisCourse() ) {
return $this->accessDeniedPage( _("You are not allowed to delete this material.") );
}
// delete the file
if ( !$material->delete() ) {
return $this->errorPage( _("The deleting of material did not succeed.") );
}
// Send user back to previous page
if ( isset($_SERVER['HTTP_REFERER']) ) {
header("Location: $_SERVER[HTTP_REFERER]");
}
else {
$this->redirectInsideCourseModule();
}
// exit
return false;
}
/**
* Shows form for adding new folder, when form is submitted, adds the folder
*
* Only the teacher of the course is allowed to add folders, this is mostly
* to prevent situation where student creates a folder, other users add files
* to that folder and then the user who created the folder deletes it, deleting
* also all the files inside that folder.
*
* @access private
* @return mixed
*/
function _addFolder( $parent=null )
{
// only teacher can add folders
if ( !$this->_userCanEditThisCourse() ) {
return $this->accessDeniedPage( _("Only teacher can add folders.") );
}
// if the folder is specified, check that it is a correct one
if ( isset($parent) && $parent->getType() != 'FOLDER' ) {
return $this->errorPage( _("You can only add folders inside folders.") );
}
// construct HTML form for adding new folder
$form =& new HTML_QuickForm();
// folder name
MaterialForm::addFolderNameField( $form );
// folder description
MaterialForm::addDescriptionField( $form );
// submit button
$form->addElement(
'submit',
'submit',
_("Add folder")
);
// validate the form
if ( $form->validate() ) {
// if validation succeeded, create new Folder object and save it to database
$this->_saveNewFolder( $form, $parent );
// Send user back to the parent folder
if ( isset($parent) ) {
$this->redirectInsideCourseModule( 'view', $parent->getID() );
}
else {
$this->redirectInsideCourseModule();
}
return false;
}
// change the menu item to be clickable link
$this->_selected_menu_item_status='HIGHLIGHTED';
// display the form
$html = $this->title( _("Add folder") );
$html .= HTML_QuickForm_Renderer_VIKO::render( $form );
$html .= HTML::backLink( $this->relativeURI(), _("Return to the list of materials"), STANDALONE );
return $html;
}
/**
* Saves the info about new folder from HTML form to database
*
* @access private
* @param HTML_QuickForm $form
* @param File $parent
*/
function _saveNewFolder( $form, $parent=null ) {
$folder = MaterialFactory::folder();
$folder->setCourse( $this->_course );
$folder->setParent( $parent );
$folder->setOwner( $_SESSION['user'] );
$folder->setName( $form->exportValue("material-name") );
$folder->setDescription( $form->exportValue("material-description") );
$folder->setAddTime( new Date() );
$folder->save();
}
/**
* Shows form for adding new link, when form is submitted, adds the link
*
* @access private
* @return mixed
*/
function _addLink( $parent=null )
{
// if adding links is disabled for students - error
if ( !( $this->_userCanEditThisCourse() || Configuration::getStudentCanAddLinks() ) ) {
return $this->accessDeniedPage( _("Only teacher can add links.") );
}
// if the parent folder is specified, check that it is a correct one
if ( isset($parent) && $parent->getType() != 'FOLDER' ) {
return $this->errorPage( _("You can only add links inside folders.") );
}
// construct HTML form for adding new URI
$form =& new HTML_QuickForm();
// link URI
MaterialForm::addURIField( $form );
// link name
MaterialForm::addLinkNameField( $form );
// link description
MaterialForm::addDescriptionField( $form );
// submit button
$form->addElement(
'submit',
'submit',
_("Add link")
);
// validate the form
if ( $form->validate() ) {
// if validation succeeded, create new Folder object and save it to database
$this->_saveNewLink( $form, $parent );
// Send user back to the parent folder
if ( isset($parent) ) {
$this->redirectInsideCourseModule( 'view', $parent->getID() );
}
else {
$this->redirectInsideCourseModule();
}
return false;
}
// change the menu item to be clickable link
$this->_selected_menu_item_status='HIGHLIGHTED';
// display the form
$html = $this->title( _("Add link") );
$html .= HTML_QuickForm_Renderer_VIKO::render( $form );
$html .= HTML::backLink( $this->relativeURI(), _("Return to the list of materials"), STANDALONE );
return $html;
}
/**
* Saves the info about new link from HTML form to database
*
* @access private
* @param HTML_QuickForm $form
* @param File $parent
*/
function _saveNewLink( $form, $parent=null ) {
$link = MaterialFactory::link();
$link->setCourse( $this->_course );
$link->setParent( $parent );
$link->setOwner( $_SESSION['user'] );
$link->setDescription( $form->exportValue("material-description") );
$link->setURI( MaterialForm::normalizeURI( $form->exportValue("link-uri") ) );
$link->setAddTime( new Date() );
// if no link name is given, use the URI as a name
if ( trim( $form->exportValue("material-name") ) == "" ) {
$link->setName( $form->exportValue("link-uri") );
}
else {
$link->setName( $form->exportValue("material-name") );
}
$link->save();
}
/**
* Shows form for adding new file, when form is submitted, adds the file
*
* If parent folder is specified, the file is added inside that.
*
* @access private
* @param Material $parent parent folder
* @return mixed
*/
function _addFile( $parent=null )
{
// if uploading is disabled for students - error
if ( !( $this->_userCanEditThisCourse() || Configuration::getStudentCanUploadFiles() ) ) {
return $this->accessDeniedPage( _("Only teacher can upload files.") );
}
// if the parent folder is specified, check that it is a correct one
if ( isset($parent) && $parent->getType() != 'FOLDER' ) {
return $this->errorPage( _("You can only add files inside folders.") );
}
// construct HTML form for adding new file
$form =& new HTML_QuickForm();
// file upload (true marks, that the file is required)
MaterialForm::addFileUploadField( $form, true );
// file name
MaterialForm::addFileNameField( $form );
// file description
MaterialForm::addDescriptionField( $form );
// submit button
$form->addElement(
'submit',
'submit',
_("Add file")
);
// validate the form
if ( $form->validate() ) {
// if validation succeeded, create new Folder object and save it to database
$this->_saveNewFile( $form, $parent );
// Send user back to the parent folder
if ( isset($parent) ) {
$this->redirectInsideCourseModule( 'view', $parent->getID() );
}
else {
$this->redirectInsideCourseModule();
}
return false;
}
// change the menu item to be clickable link
$this->_selected_menu_item_status='HIGHLIGHTED';
// display the form
$html = $this->title( _("Add file") );
$html .= HTML_QuickForm_Renderer_VIKO::render( $form );
$html .= HTML::backLink( $this->relativeURI(), _("Return to the list of materials"), STANDALONE );
return $html;
}
/**
* Saves the info about new file from HTML form to database
*
* @access private
* @param HTML_QuickForm $form
* @param File $parent
*/
function _saveNewFile( $form, $parent=null ) {
$file = MaterialFactory::file();
$file->setCourse( $this->_course );
$file->setParent( $parent );
$file->setOwner( $_SESSION['user'] );
$file->setDescription( $form->exportValue("material-description") );
$file->setAddTime( new Date() );
// get array with uploaded file info
$file_element = $form->getElement("file");
$uploaded_file = $file_element->getValue();
// get the size of the file
$file->setSize( $uploaded_file['size'] );
// determine the MIME type of the file from the name of uploaded file
$mime_type = MimeType::determineFromFilename( $uploaded_file['name'] );
$file->setMimeType( $mime_type );
// if no file name is given, use the original file name
if ( trim( $form->exportValue("material-name") ) == "" ) {
$file->setName( $uploaded_file['name'] );
}
else {
$file->setName( $form->exportValue("material-name") );
}
$file->save();
// now the file contents also have to be saved
$file->loadContentFromFile( $uploaded_file['tmp_name'] );
// remove the uploaded file
unlink( $uploaded_file['tmp_name'] );
}
/**
* Shows form with material info and allows user to modify it
*
* @access private
* @param Material $material the material to be edited
* @return mixed HTML fragment with form
*/
function _editMaterial( $material )
{
// must not be null
if ( !isset($material) ) {
trigger_error( "The material to edit must be specified.", E_USER_ERROR );
}
// ensure, that user is teacher of the course
if ( !$this->_userCanEditThisCourse() ) {
return $this->accessDeniedPage( _("You are not allowed to edit this material.") );
}
// construct HTML form for editing material
$form = $this->_editMaterialForm( $material );
// validate the form
if ( $form->validate() ) {
// if validation succeeded, apply the new values from form to material, and save it
$this->_changeMaterial( $form, $material );
// Send user back to the parent folder
$parent = $material->getParent();
if ( isset($parent) ) {
$this->redirectInsideCourseModule( 'view', $parent->getID() );
}
else {
$this->redirectInsideCourseModule();
}
return false;
}
// change the menu item to be clickable link
$this->_selected_menu_item_status='HIGHLIGHTED';
// display the form
$html = $this->title( $this->_editFormSubmitTitle( $material->getType() ) );
$html .= HTML_QuickForm_Renderer_VIKO::render( $form );
$html .= HTML::backLink( $this->relativeURI(), _("Return to the list of materials"), STANDALONE );
return $html;
}
/**
* Applies new values from submitted form to material and saves the material
*
* @access private
* @param HTML_QuickForm $form form with new values
* @param Material $material the material to change
* @return mixed
*/
function _changeMaterial( $form, $material )
{
// all materials have name and description fields
$material->setName( $form->exportValue("material-name") );
$material->setDescription( $form->exportValue("material-description") );
if ( $material->getType() == 'LINK' ) {
// link has URI field
$material->setURI( MaterialForm::normalizeURI( $form->exportValue("link-uri") ) );
// if no link name is given, use the URI as link name
if ( trim( $form->exportValue("material-name") ) == "" ) {
$material->setName( $form->exportValue("link-uri") );
}
}
elseif ( $material->getType() == 'FILE' ) {
// if new file was uploaded, replace the current file in database with that
$file_element = $form->getElement("file");
if ( $file_element->isUploadedFile() ) {
$uploaded_file = $file_element->getValue();
// get the size of the file
$material->setSize( $uploaded_file['size'] );
// determine the MIME type of the file from the name of uploaded file
$mime_type = MimeType::determineFromFilename( $uploaded_file['name'] );
$material->setMimeType( $mime_type );
// if no file name is given, use the original file name
if ( trim( $form->exportValue("material-name") ) == "" ) {
$material->setName( $uploaded_file['name'] );
}
// change file contents
$material->loadContentFromFile( $uploaded_file['tmp_name'] );
// remove the uploaded file
unlink( $uploaded_file['tmp_name'] );
}
}
$material->save();
}
/**
* Creates HTML form object for editing material
*
* @access private
* @param Material $material the material to edit
* @return HTML_QuickForm
*/
function _editMaterialForm( $material )
{
// construct HTML form for editing
$form =& new HTML_QuickForm();
if ( $material->getType() == 'FILE' ) {
// file upload (false marks, that the file is NOT required)
MaterialForm::addFileUploadField( $form, false );
// file name
MaterialForm::addFileNameField( $form, $material->getName() );
}
elseif ( $material->getType() == 'FOLDER' ) {
// folder name
MaterialForm::addFolderNameField( $form, $material->getName() );
}
elseif ( $material->getType() == 'LINK' ) {
// link URI
MaterialForm::addURIField( $form, $material->getURI() );
// link name
MaterialForm::addLinkNameField( $form, $material->getName() );
}
// all materials have a description field and submit button
MaterialForm::addDescriptionField( $form, $material->getDescription() );
$form->addElement(
'submit',
'submit',
$this->_editFormSubmitTitle( $material->getType() )
);
return $form;
}
/**
* Returns appropriate title for the form, based on material type
*
* @access private
* @param string $material_type either FILE, FOLDER or LINK
* @return string
*/
function _editFormSubmitTitle( $material_type )
{
switch ( $material_type ) {
case 'FILE': return _("Change file"); break;
case 'FOLDER': return _("Change folder"); break;
case 'LINK': return _("Change link"); break;
default: trigger_error("Unknown material type.", E_USER_ERROR);
}
}
}
?>