* @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 * * * * 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); } } } ?>