/** * Create method for this handler * * @return array of assets created **/ public function create() { // Include needed files require_once dirname(dirname(__DIR__)) . DS . 'tables' . DS . 'asset.association.php'; require_once dirname(dirname(__DIR__)) . DS . 'tables' . DS . 'asset.php'; require_once dirname(__DIR__) . DS . 'asset.php'; // Get the file if (isset($_FILES['files'])) { $file = $_FILES['files']['name'][0]; $size = (int) $_FILES['files']['size']; // Get the file extension $pathinfo = pathinfo($file); $filename = $pathinfo['filename']; $ext = $pathinfo['extension']; } else { return array('error' => 'No files provided'); } // @FIXME: should these come from the global settings, or should they be courses specific // Get config $config = Component::params('com_media'); // Max upload size $sizeLimit = (int) $config->get('upload_maxsize'); $sizeLimit = $sizeLimit * 1024 * 1024; // Check to make sure we have a file and its not too big if ($size == 0) { return array('error' => 'File is empty'); } if ($size > $sizeLimit) { $max = preg_replace('/<abbr \\w+=\\"\\w+\\">(\\w{1,3})<\\/abbr>/', '$1', \Hubzero\Utility\Number::formatBytes($sizeLimit)); return array('error' => "File is too large. Max file upload size is {$max}"); } // Create our asset table object $assetObj = new Tables\Asset($this->db); $this->asset['title'] = $filename; $this->asset['type'] = !empty($this->asset['type']) ? $this->asset['type'] : 'file'; $this->asset['subtype'] = !empty($this->asset['subtype']) ? $this->asset['subtype'] : 'file'; $this->asset['url'] = $file; $this->asset['created'] = Date::toSql(); $this->asset['created_by'] = App::get('authn')['user_id']; $this->asset['course_id'] = Request::getInt('course_id', 0); // Save the asset if (!$assetObj->save($this->asset)) { return array('error' => 'Asset save failed'); } // Create asset assoc object $assocObj = new Tables\AssetAssociation($this->db); $this->assoc['asset_id'] = $assetObj->get('id'); $this->assoc['scope'] = Request::getCmd('scope', 'asset_group'); $this->assoc['scope_id'] = Request::getInt('scope_id', 0); // Save the asset association if (!$assocObj->save($this->assoc)) { return array('error' => 'Asset association save failed'); } // Get courses config $cconfig = Component::params('com_courses'); // Build the upload path if it doesn't exist $uploadDirectory = PATH_APP . DS . trim($cconfig->get('uploadpath', '/site/courses'), DS) . DS . $this->asset['course_id'] . DS . $this->assoc['asset_id'] . DS; // Make sure upload directory exists and is writable if (!is_dir($uploadDirectory)) { if (!Filesystem::makeDirectory($uploadDirectory, 0755, true)) { return array('error' => 'Server error. Unable to create upload directory'); } } if (!is_writable($uploadDirectory)) { return array('error' => 'Server error. Upload directory isn\'t writable'); } // Get the final file path $target_path = $uploadDirectory . $filename . '.' . $ext; // Move the file to the site folder set_time_limit(60); // Scan for viruses if (!Filesystem::isSafe($_FILES['files']['tmp_name'][0])) { // Scan failed, delete asset and association and return an error $assetObj->delete(); $assocObj->delete(); Filesystem::deleteDirectory($uploadDirectory); return array('error' => 'File rejected because the anti-virus scan failed.'); } if (!($move = move_uploaded_file($_FILES['files']['tmp_name'][0], $target_path))) { // Move failed, delete asset and association and return an error $assetObj->delete(); $assocObj->delete(); Filesystem::deleteDirectory($uploadDirectory); return array('error' => 'Move file failed'); } // Get the url to return to the page $course_id = Request::getInt('course_id', 0); $offering_alias = Request::getCmd('offering', ''); $course = new \Components\Courses\Models\Course($course_id); $url = Route::url('index.php?option=com_courses&controller=offering&gid=' . $course->get('alias') . '&offering=' . $offering_alias . '&asset=' . $assetObj->get('id')); $url = rtrim(str_replace('/api', '', Request::root()), '/') . '/' . ltrim($url, '/'); $return_info = array('asset_id' => $this->assoc['asset_id'], 'asset_title' => $this->asset['title'], 'asset_type' => $this->asset['type'], 'asset_subtype' => $this->asset['subtype'], 'asset_url' => $url, 'course_id' => $this->asset['course_id'], 'offering_alias' => Request::getCmd('offering', ''), 'scope_id' => $this->assoc['scope_id'], 'asset_ext' => $ext, 'upload_path' => $uploadDirectory, 'target_path' => $target_path); // Return info return array('assets' => $return_info); }
/** * Check whether or not the student is passing the course and has completed all items * * @param int $member_id * @return bool **/ public function isEligibleForRecognition($member_id = null) { static $assets = null; static $grades = null; // Get a grade policy object $gradePolicy = new GradePolicies($this->course->offering()->section()->get('grade_policy_id'), $this->course->offering()->section()->get('id')); if (!isset($assets)) { // Get the graded assets $asset = new Tables\Asset(\App::get('db')); $assets = $asset->find(array('w' => array('course_id' => $this->course->get('id'), 'section_id' => $this->course->offering()->section()->get('id'), 'offering_id' => $this->course->offering()->get('id'), 'graded' => true, 'state' => 1, 'asset_scope' => 'asset_group'), 'order_by' => 'title', 'order_dir' => 'ASC')); // Get gradebook auxiliary assets $auxiliary = $asset->findByScope('offering', $this->course->offering()->get('id'), array('asset_type' => 'gradebook', 'asset_subtype' => 'auxiliary', 'graded' => true, 'state' => 1)); $assets = array_merge($assets, $auxiliary); } // Get totals by type $totals = array('exam' => 0, 'quiz' => 0, 'homework' => 0); $counts = array(); if ($assets && count($assets) > 0) { foreach ($assets as $asset) { ++$totals[$asset->grade_weight]; } } if (!isset($grades)) { // Get count of graded items taken $filters = array('member_id' => $member_id, 'scope' => 'asset', 'graded' => true); $grades = $this->_grades($filters); } // Restructure data foreach ($grades as $g) { if (!is_null($g->score) || !is_null($g->override)) { if (isset($counts[$g->member_id][$g->grade_weight])) { ++$counts[$g->member_id][$g->grade_weight]; } else { $counts[$g->member_id][$g->grade_weight] = 1; } } } // Get weights to determine what counts toward the final grade $exam_weight = $gradePolicy->get('exam_weight'); $quiz_weight = $gradePolicy->get('quiz_weight'); $homework_weight = $gradePolicy->get('homework_weight'); $return = false; if (isset($counts)) { if (!is_null($member_id) && !is_array($member_id)) { $member_id = (array) $member_id; } else { $member_id = array(); foreach ($this->course->offering()->section()->members() as $m) { $member_id[] = $m->get('id'); } } // Loop though the users foreach ($member_id as $m) { $passing = $this->passing(true, $m); // Now make sure they've taken all required exams/quizzes/homeworks, and that they passed if (($exam_weight == 0 || $exam_weight > 0 && isset($counts[$m]['exam']) && $totals['exam'] == $counts[$m]['exam']) && ($quiz_weight == 0 || $quiz_weight > 0 && isset($counts[$m]['quiz']) && $totals['quiz'] == $counts[$m]['quiz']) && ($homework_weight == 0 || $homework_weight > 0 && isset($counts[$m]['homework']) && $totals['homework'] == $counts[$m]['homework']) && $passing[$m]) { $return[] = $m; } } } return $return; }
/** * Get a list of assets for a unit * Accepts an array of filters to apply to the list of assets * * @param array $filters Filters to apply * @return object \Components\Courses\Models\Iterator */ public function assets($filters = array()) { if (!isset($this->_assets) || !$this->_assets instanceof Iterator) { if (!isset($filters['asset_scope_id'])) { $filters['asset_scope_id'] = (int) $this->get('id'); } if (!isset($filters['asset_scope'])) { $filters['asset_scope'] = 'unit'; } if (!isset($filters['section_id'])) { $filters['section_id'] = (int) $this->get('section_id'); } $tbl = new Tables\Asset($this->_db); if ($results = $tbl->find(array('w' => $filters))) { foreach ($results as $key => $result) { $results[$key] = new Asset($result); } } else { $results = array(); } $this->_assets = new Iterator($results); } return $this->_assets; }
/** * Deletes an asset * * @apiMethod POST * @apiUri /courses/asset/delete * @apiParameter { * "name": "asset_id", * "description": "ID of asset to delete", * "type": "integer", * "required": true, * "default": null * } * @apiParameter { * "name": "scope", * "description": "Asset scope", * "type": "string", * "required": true, * "default": null * } * @apiParameter { * "name": "scope_id", * "description": "Asset scope ID", * "type": "integer", * "required": true, * "default": null * } * @return void */ public function deleteTask() { // Require authentication and authorization $this->authorizeOrFail(); // First, delete the asset association $database = App::get('db'); $assocObj = new AssetAssociation($database); // Get vars $asset_id = Request::getInt('asset_id', 0); $scope = Request::getCmd('scope', 'asset_group'); $scope_id = Request::getInt('scope_id', 0); // Make sure we're not missing anything if (!$asset_id || !$scope || !$scope_id) { // Missing needed variables to identify asset association App::abort(404, 'Missing one of asset id, scope, or scope id'); } else { // Try to load the association if (!$assocObj->loadByAssetScope($asset_id, $scope_id, $scope)) { App::abort(500, 'Loading asset association failed'); } else { // Delete the association if (!$assocObj->delete()) { App::abort(500, $assocObj->getError()); } } } // Then, lookup whether or not there are other assocations connected to this asset $assetObj = new AssetTbl($database); if (!$assetObj->load($asset_id)) { App::abort(500, "Loading asset {$id} failed"); } // See if the asset is orphaned if (!$assetObj->isOrphaned()) { // Asset isn't an orphan (i.e. it's still being used elsewhere), so we're done $this->send(['asset_id' => $assetObj->id]); return; } // If no other associations exist, we'll delete the asset file and folder on the file system $deleted = []; $params = Component::params('com_courses'); $path = DS . trim($params->get('uploadpath', '/site/courses'), DS) . DS . $this->course_id . DS . $assetObj->id; // If the path exists, delete it! if (Filesystem::exists($path)) { $deleted = Filesystem::listFolderTree($path); Filesystem::deleteDirectory($path); } // Then we'll delete the asset entry itself if (!$assetObj->delete()) { App::abort(500, $assetObj->getError()); } // Return message $this->send(['asset_id' => $assetObj->id, 'deleted' => $deleted]); }
/** * Save a course page * * @return void */ public function saveTask() { // Check for request forgeries Request::checkToken(); // load the request vars $fields = Request::getVar('fields', array(), 'post', 'none', 2); $tmpl = Request::getVar('tmpl', ''); // instatiate course page object for saving $row = new Tables\Asset($this->database); if (!$row->bind($fields)) { $this->setError($row->getError()); $this->editTask($row); return; } if (!$row->check()) { $this->setError($row->getError()); $this->editTask($row); return; } if (!$row->store()) { $this->setError($row->getError()); $this->editTask($row); return; } $fields['asset_id'] = $row->get('id'); $row2 = new Tables\AssetAssociation($this->database); $row2->loadByAssetScope($fields['asset_id'], $fields['scope_id'], $fields['scope']); if (!$row2->id) { if (!$row2->bind($fields)) { $this->setError($row2->getError()); $this->editTask($row); return; } if (!$row2->check()) { $this->setError($row2->getError()); $this->editTask($row); return; } if (!$row2->store()) { $this->setError($row2->getError()); $this->editTask($row); return; } } // Rename the temporary upload directory if it exist $lid = $fields['lid']; if ($lid != $row->get('id')) { $path = PATH_APP . DS . trim($this->config->get('uploadpath', '/site/courses'), DS) . DS . $fields['course_id']; if (is_dir($path . DS . $lid)) { if (!Filesystem::move($path . DS . $lid, $path . DS . $row->get('id'))) { $this->setError(Lang::txt('UNABLE_TO_MOVE_PATH')); } } } // Incoming file /*$file = Request::getVar('upload', '', 'files', 'array'); if ($file['name']) { $path = PATH_APP . DS . trim($this->config->get('uploadpath', '/site/courses'), DS) . DS . $fields['course_id'] . DS . $row->id; // Make sure the upload path exist if (!is_dir($path)) { if (!\Filesystem::makeDirectory($path)) { $this->setError(Lang::txt('UNABLE_TO_CREATE_UPLOAD_PATH').' '.$path); $this->editTask($row); return; } } // Make the filename safe $file['name'] = Filesystem::clean($file['name']); // Ensure file names fit. $ext = Filesystem::extension($file['name']); $file['name'] = str_replace(' ', '_', $file['name']); if (strlen($file['name']) > 230) { $file['name'] = substr($file['name'], 0, 230); $file['name'] .= '.' . $ext; } // Perform the upload if (!\Filesystem::upload($file['tmp_name'], $path . DS . $file['name'])) { $this->setError(Lang::txt('ERROR_UPLOADING')); } else { if (strtolower($ext) == 'zip') { require_once(PATH_CORE . DS . 'includes' . DS . 'pcl' . DS . 'pclzip.lib.php'); if (!extension_loaded('zlib')) { $this->setError(Lang::txt('ZLIB_PACKAGE_REQUIRED')); } else { $zip = new PclZip($path . DS . $file['name']); // unzip the file if (!($do = $zip->extract($path))) { $this->setError(Lang::txt('UNABLE_TO_EXTRACT_PACKAGE')); } else { @unlink($path . DS . $file['name']); $file['name'] = 'presentation.json'; } } } // Set the url $row->set('url', $file['name']); $row->store(); } }*/ if ($tmpl == 'component') { if ($this->getError()) { echo '<p class="error">' . $this->getError() . '</p>'; } else { echo '<p class="message">' . Lang::txt('COM_COURSES_ITEM_SAVED') . '</p>'; } return; } App::redirect(Route::url('index.php?option=' . $this->_option . '&controller=' . $this->_controller . '&tmpl=' . $tmpl . '&scope=' . $fields['scope'] . '&scope_id=' . $fields['scope_id'] . '&course_id=' . $fields['course_id'], false)); }
/** * Create method for this handler * * @return array of assets created **/ public function create() { // Include needed files require_once dirname(dirname(__DIR__)) . DS . 'tables' . DS . 'asset.association.php'; require_once dirname(dirname(__DIR__)) . DS . 'tables' . DS . 'asset.php'; require_once dirname(__DIR__) . DS . 'asset.php'; // Get the file if (isset($_FILES['files'])) { $file = $_FILES['files']['name'][0]; $size = (int) $_FILES['files']['size']; // Get the file extension $pathinfo = pathinfo($file); $filename = $pathinfo['filename']; $ext = $pathinfo['extension']; } else { return array('error' => 'No files provided'); } // @FIXME: should these come from the global settings, or should they be courses specific // Get config $config = Component::params('com_media'); // Max upload size $sizeLimit = (int) $config->get('upload_maxsize'); $sizeLimit = $sizeLimit * 1024 * 1024; // Check to make sure we have a file and its not too big if ($size == 0) { return array('error' => 'File is empty'); } if ($size > $sizeLimit) { $max = preg_replace('/<abbr \\w+=\\"\\w+\\">(\\w{1,3})<\\/abbr>/', '$1', \Hubzero\Utility\Number::formatBytes($sizeLimit)); return array('error' => "File is too large. Max file upload size is {$max}"); } // get request vars $course_id = Request::getInt('course_id', 0); $offering_alias = Request::getCmd('offering', ''); $scope = Request::getCmd('scope', 'asset_group'); $scope_id = Request::getInt('scope_id', 0); // get all assets in group $assetGroup = new \Components\Courses\Models\Assetgroup($scope_id); $assets = $assetGroup->assets(); // check to see if any of our assets are html5? $hubpresenter = null; foreach ($assets as $asset) { if ($asset->get('type') == 'video' && $asset->get('subtype') == 'video' && in_array($asset->get('state'), array(0, 1)) && strpos($asset->get('url'), 'zip')) { $hubpresenter = $asset; break; } } // make sure we have asset if ($hubpresenter === null) { return array('error' => 'Unable to locate html5 video or hubpresenter asset to attach subtitle file to.'); } // build path to asset $pathToAsset = $hubpresenter->path($course_id); $pathToAssetFolder = trim(dirname($pathToAsset), DS); // build target path $target_path = PATH_APP . DS . $pathToAssetFolder . DS . $filename . '.' . $ext; // Move the file to the site folder set_time_limit(60); // Scan for viruses if (!Filesystem::isSafe($_FILES['files']['tmp_name'][0])) { // Scan failed, return an error return array('error' => 'File rejected because the anti-virus scan failed.'); } // move file if (!($move = move_uploaded_file($_FILES['files']['tmp_name'][0], $target_path))) { // Move failed, delete asset and association and return an error return array('error' => 'Move file failed'); } // get json file $jsonFile = $pathToAssetFolder . DS . 'presentation.json'; // get manifest $manifest = file_get_contents(PATH_APP . DS . $jsonFile); $manifest = json_decode($manifest); // make sure we have a subtitles section $currentSubtitles = array(); if (!isset($manifest->presentation->subtitles)) { $manifest->presentation->subtitles = array(); } else { foreach ($manifest->presentation->subtitles as $subtitle) { $currentSubtitles[] = $subtitle->source; } } // create subtitle details based on filename $info = pathinfo($file); $name = str_replace('-auto', '', $info['filename']); $autoplay = strstr($info['filename'], '-auto') ? 1 : 0; $source = $file; // use only the last segment from name (ex. ThisIsATest.English => English) $nameParts = explode('.', $name); $name = array_pop($nameParts); // add subtitle $subtitle = new stdClass(); $subtitle->type = 'SRT'; $subtitle->name = ucfirst($name); $subtitle->source = $source; $subtitle->autoplay = $autoplay; // only add sub if we dont already have it if (!in_array($subtitle->source, $currentSubtitles)) { $manifest->presentation->subtitles[] = $subtitle; } // update json file file_put_contents(PATH_APP . DS . $jsonFile, json_encode($manifest, JSON_PRETTY_PRINT)); //parse subtitle file $lines = self::_parseSubtitleFile($target_path); // make transcript file $transcript = ''; foreach ($lines as $line) { $transcript .= ' ' . trim($line->text); } // trim transcript and replace add slide markers $transcript = str_replace(array("\r\n", "\n"), array('', ''), $transcript); $transcript = preg_replace("/\\[([^\\]]*)\\]/ux", "\n\n[\$1]", $transcript); // add title to transcript $transcript = $manifest->presentation->title . PHP_EOL . str_repeat('==', 20) . PHP_EOL . ltrim($transcript, PHP_EOL); // Create our asset table object $assetObj = new Tables\Asset($this->db); $this->asset['title'] = 'Video Transcript'; $this->asset['type'] = 'file'; $this->asset['subtype'] = 'file'; $this->asset['url'] = $info['filename'] . '.txt'; $this->asset['created'] = Date::toSql(); $this->asset['created_by'] = App::get('authn')['user_id']; $this->asset['course_id'] = $course_id; // Save the asset if (!$assetObj->save($this->asset)) { return array('error' => 'Asset save failed'); } // Create asset assoc object $assocObj = new Tables\AssetAssociation($this->db); $this->assoc['asset_id'] = $assetObj->get('id'); $this->assoc['scope'] = $scope; $this->assoc['scope_id'] = $scope_id; // Save the asset association if (!$assocObj->save($this->assoc)) { return array('error' => 'Asset association save failed'); } // Get courses config $cconfig = Component::params('com_courses'); // Build the upload path if it doesn't exist $uploadDirectory = PATH_APP . DS . trim($cconfig->get('uploadpath', '/site/courses'), DS) . DS . $this->asset['course_id'] . DS . $this->assoc['asset_id'] . DS; // Make sure upload directory exists and is writable if (!is_dir($uploadDirectory)) { if (!Filesystem::makeDirectory($uploadDirectory)) { return array('error' => 'Server error. Unable to create upload directory'); } } if (!is_writable($uploadDirectory)) { return array('error' => 'Server error. Upload directory isn\'t writable'); } // Get the final file path $transcript_target_path = $uploadDirectory . $this->asset['url']; // make transcript file file_put_contents($transcript_target_path, $transcript); // Get the url to return to the page $course_id = Request::getInt('course_id', 0); $offering_alias = Request::getCmd('offering', ''); $course = new \Components\Courses\Models\Course($course_id); $url = Route::url('index.php?option=com_courses&controller=offering&gid=' . $course->get('alias') . '&offering=' . $offering_alias . '&asset=' . $assetObj->get('id')); // build return info $return_info = array('asset_id' => $this->assoc['asset_id'], 'asset_title' => $this->asset['title'], 'asset_type' => $this->asset['type'], 'asset_subtype' => $this->asset['subtype'], 'asset_url' => $url, 'course_id' => $this->asset['course_id'], 'offering_alias' => Request::getCmd('offering', ''), 'scope_id' => $this->assoc['scope_id'], 'asset_ext' => 'txt', 'upload_path' => $uploadDirectory, 'target_path' => $transcript_target_path); // Return info return array('assets' => $return_info); }