Example #1
0
/**
 * Given an object containing all the necessary data,
 * (defined by the form in mod_form.php) this function
 * will create a new instance and return the id number
 * of the new instance.
 *
 * @param object $opencast Moodle {opencast} table DB record
 *
 * @return int newly created instance ID
 */
function opencast_add_instance($opencast)
{
    global $DB, $USER;
    $opencast->timemodified = time();
    $scast = new mod_opencast_series();
    if (isset($opencast->newchannelname)) {
        $scast->setChannelName($opencast->newchannelname);
    }
    //$scast->setCourseId();
    //    $scast->setLicense($opencast->license);
    //    $scast->setDepartment($opencast->department);
    $scast->setAllowAnnotations($opencast->allow_annotations == OPENCAST_ANNOTATIONS);
    //    if (isset($opencast->template_id)) { // not set if creating new instance with existing channel
    //        $scast->setTemplateId($opencast->template_id);
    //    }
    $scast->setIvt($opencast->is_ivt);
    if (isset($opencast->inviting)) {
        $scast->setInvitingPossible($opencast->inviting);
    }
    $scast->setOrganizationDomain(mod_opencast_series::getOrganizationByEmail($USER->email));
    $opencast->organization_domain = $scast->getOrganization();
    if ($opencast->channelnew == OPENCAST_CHANNEL_NEW) {
        // New channel
        $scast->setProducer(mod_opencast_user::getExtIdFromMoodleUserId($USER->id));
        $scast->doCreate();
        $opencast->ext_id = $scast->getExtId();
    } else {
        // Existing channel
        $scast->setExtId($opencast->ext_id);
        $scast->update();
    }
    if (empty($opencast->timerestrict)) {
        $opencast->timeopen = 0;
        $opencast->timeclose = 0;
    }
    $opencast->id = $DB->insert_record('opencast', $opencast);
    return $opencast->id;
}
// Moodle 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 Moodle.  If not, see <http://www.gnu.org/licenses/>.
/**
 * Version information
 *
 * @package    mod
 * @subpackage opencast
 * @copyright  2013-2015 Université de Lausanne
 * @author     Nicolas.Dunand@unil.ch
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
require_once '../../config.php';
require_once $CFG->dirroot . '/mod/opencast/lib.php';
$seriesExtId = required_param('ext_id', PARAM_RAW_TRIMMED);
$sc_user = new mod_opencast_user();
$url = '/series/' . $seriesExtId;
if ($sc_user->getExternalAccount() != '') {
    $runas = true;
} else {
    $runas = false;
}
$series = new mod_opencast_series();
$series->fetch($seriesExtId, false);
//$series = mod_opencast_apicall::sendRequest($url, 'GET', null, null, null, null, $runas);
$channel_details = ['title' => $series->title];
echo json_encode($channel_details);
Example #3
0
 /**
  * Displays a user selector
  *
  * @param bool   $withproducers    shall the producers be included ?
  * @param string $action_url       where the form shall be posted
  * @param string $buttonlabel      value attribute of the submit button
  * @param bool   $switchaaionly    display users with ExternalAccount only
  * @param bool   $with_emtpyoption display 'remove user' option or not
  * @param bool   $selectonly       display HTML SELECT element only
  * @param int    $selected_id      if not zero, select OPTION with this index
  */
 function display_user_selector($withproducers = false, $action_url = '', $buttonlabel = 'OK', $switchaaionly = false, $with_emtpyoption = false, $selectonly = false, $selected_id = 0)
 {
     global $context, $url, $course;
     if ($withproducers === false) {
         $producers = get_users_by_capability($context, 'mod/opencast:isproducer', 'u.id');
     }
     $possible_users = get_users_by_capability($context, 'mod/opencast:use', 'u.id, u.lastname, u.firstname, u.maildisplay, u.email', 'u.lastname, u.firstname');
     $options = [];
     if ($with_emtpyoption) {
         $options[-1] = '(' . get_string('removeowner', 'opencast') . ')';
     }
     foreach ($possible_users as $possible_user_id => $possible_user) {
         if (in_array($possible_user_id, $this->displayed_userids)) {
             continue;
         }
         if ($withproducers === false && array_key_exists($possible_user_id, $producers)) {
             continue;
         }
         if ($switchaaionly && !mod_opencast_user::getExtIdFromMoodleUserId($possible_user_id)) {
             continue;
         }
         $option_text = $possible_user->lastname . ', ' . $possible_user->firstname;
         if ($possible_user->maildisplay == 1 or $possible_user->maildisplay == 2 and $course->id != SITEID and !isguestuser() or has_capability('moodle/course:viewhiddenuserfields', $context)) {
             $option_text .= ' (' . $possible_user->email . ')';
         }
         $options[$possible_user_id] = $option_text;
     }
     if (count($options)) {
         if (!$selectonly) {
             echo html_writer::start_tag('form', ['method' => 'post', 'action' => $action_url, 'onsubmit' => 'return document.getElementById(\'menuuserid\').selectedIndex != 0;']);
             echo html_writer::input_hidden_params($url, ['action', 'userid']);
             echo html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()]);
             echo html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'action', 'value' => 'add']);
         }
         echo html_writer::select($options, 'userid', $selected_id);
         if (!$selectonly) {
             echo html_writer::empty_tag('input', ['type' => 'submit', 'value' => $buttonlabel]);
             echo html_writer::end_tag('form');
         }
     } else {
         if (!$selectonly) {
             echo html_writer::start_tag('form');
         }
         echo html_writer::select($options, 'userid', null, null, ['disabled' => 'disabled']);
         if (!$selectonly) {
             echo html_writer::empty_tag('input', ['type' => 'submit', 'value' => $buttonlabel, 'disabled' => 'disabled']);
             echo html_writer::tag('div', get_string('nomoreusers', 'opencast'));
             echo html_writer::end_tag('form');
         }
     }
 }
Example #4
0
 /**
  * Sends an API request to the NEW Matterhorn server
  *
  * @param         $url
  * @param string  $request_type   request type
  * @param array   $data           input data for POST/PUT
  * @param boolean $return_rawdata return raw XML?
  * @param boolean $usecache       try to use cache?
  * @param string  $file           video file to upload
  * @param bool    $runas          run the API request as the current logged in user?
  * @param bool    $haltonerror    halt on error, or just return FALSE
  *
  * @return bool|stdClass|array result object or false if error
  * @throws moodle_exception
  */
 static function sendRequest($url, $request_type, $data = null, $return_rawdata = false, $usecache = true, $file = null, $runas = true, $haltonerror = true)
 {
     global $CFG, $USER;
     $request_url = mod_opencast_series::getValueForKey('switch_api_host') . $url;
     $cache_time = mod_opencast_series::getValueForKey('local_cache_time');
     $cache_dir = $CFG->dataroot . '/cache/mod_opencast';
     if ($request_type !== 'GET') {
         // a modification has been made, clear the cache for consistency
         $reason = $request_type . ' ' . $request_url;
         $matches = false;
         if (preg_match('/\\/series\\/([0-9a-zA-Z]+)/', $request_url, $matches)) {
             // TODO Check because not working when deleting an event
             mod_opencast_log::write("CACHE : destroying cache for series " . $matches[1] . " because " . $reason);
             self::clear_cache($cache_dir, $matches[1]);
         } else {
             // No need to destroy the cache, the requests not containning "/series/xyzxyz" have no effect on clip/series metadata
             // TODO yes I think we do! BUT NOT ALWAYS! e.g. not needed when /sign
             mod_opencast_log::write("CACHE : destroying entire cache because " . $reason);
             self::clear_cache($cache_dir);
         }
     }
     if (!file_exists($cache_dir)) {
         mod_opencast_log::write("CACHE : initializing empty cache");
         mkdir($cache_dir);
     }
     if (is_array($data) || is_object($data)) {
         $data_json = json_encode($data);
     }
     mod_opencast_log::write("REQUEST " . $request_type . " " . $request_url);
     mod_opencast_log::write("INPUT " . print_r($data, true));
     $cache_filename = $cache_dir . '/' . self::hashfilename($request_url);
     if ($usecache && (string) $request_type === 'GET' && $cache_time && $cache_dir && file_exists($cache_filename) && time() - filemtime($cache_filename) < $cache_time) {
         // use the appropriate cached file
         mod_opencast_log::write("CACHE : using cached file " . $cache_filename);
         $output = file_get_contents($cache_filename);
     } else {
         // no cache for this request
         mod_opencast_log::write("CACHE : no cached file");
         libxml_use_internal_errors(true);
         $curl_request = curl_init();
         curl_setopt($curl_request, CURLOPT_SAFE_UPLOAD, true);
         curl_setopt($curl_request, CURLOPT_RETURNTRANSFER, true);
         curl_setopt($curl_request, CURLOPT_CUSTOMREQUEST, $request_type);
         if ($runas) {
             $role_user = '******' . mod_opencast_user::getExtIdFromMoodleUserId($USER->id);
             curl_setopt($curl_request, CURLOPT_HTTPHEADER, ['X-RUN-WITH-ROLES: ' . $role_user]);
         } else {
             curl_setopt($curl_request, CURLOPT_HTTPHEADER, ['X-RUN-WITH-ROLES: ROLE_EXTERNAL_APPLICATION']);
         }
         if (isset($file)) {
             if (!filesize($file) || !is_readable($file)) {
                 mod_opencast_log::write("CURL UPLOAD ERROR : empty or unreadable file");
                 if ($haltonerror) {
                     throw new moodle_exception('uploaderror', 'opencast');
                 }
                 return false;
             }
             $curl_file = new CURLFile($file);
             curl_setopt($curl_request, CURLOPT_TIMEOUT_MS, 300000);
             //                curl_setopt($curl_request, CURLOPT_PUT, true); // must be set, elsewise the multipart info will also be sent
             //                curl_setopt($curl_request, CURLOPT_BINARYTRANSFER, 1);
             //                curl_setopt($curl_request, CURLOPT_VERBOSE, (bool)mod_opencast_obj::getValueForKey('logging_enabled'));
         } else {
             curl_setopt($curl_request, CURLOPT_TIMEOUT_MS, (int) mod_opencast_series::getValueForKey('curl_timeout') * 1000);
         }
         curl_setopt($curl_request, CURLOPT_SSL_VERIFYPEER, false);
         // TODO WAIT FOR SWITCH : no signing for now (test phase)
         curl_setopt($curl_request, CURLOPT_USERPWD, mod_opencast_series::getValueForKey('switch_api_username') . ':' . mod_opencast_series::getValueForKey('switch_api_password'));
         curl_setopt($curl_request, CURLOPT_URL, $request_url);
         if (mod_opencast_series::getValueForKey('curl_proxy')) {
             curl_setopt($curl_request, CURLOPT_PROXY, mod_opencast_series::getValueForKey('curl_proxy'));
         }
         if (is_array($data)) {
             $postfields = [];
             if ($file) {
                 $postfields['presentation'] = $curl_file;
             }
             foreach ($data as $key => $value) {
                 $postfields[$key] = $value;
                 // htmlentities($value, ENT_NOQUOTES); // not necessary anymore
             }
             curl_setopt($curl_request, CURLOPT_POSTFIELDS, $postfields);
             curl_setopt($curl_request, CURLOPT_HTTPHEADER, ['Accept: application/v1.0.0+json']);
         }
         $output = curl_exec($curl_request);
         $curl_errno = curl_errno($curl_request);
         // 0 if fine
         $response_details = curl_getinfo($curl_request);
         if (isset($file)) {
             if ($curl_errno) {
                 curl_close($curl_request);
                 mod_opencast_log::write("CURL UPLOAD ERROR : no. " . $curl_errno);
                 mod_opencast_log::write("                  : " . $output);
                 if ($haltonerror) {
                     throw new moodle_exception('uploaderror', 'opencast');
                 }
                 return false;
             }
         }
         curl_close($curl_request);
         if ($output && (string) $request_type === 'GET' && (isset($response_details) && $response_details['http_code'] < 400) && ($cache_time && $cache_dir && is_writable($cache_dir))) {
             // write cache to file, only if response is not an error
             mod_opencast_log::write("CACHE : writing output to cache file " . $cache_filename);
             $fh_w = fopen($cache_filename, 'w');
             fwrite($fh_w, $output);
             fclose($fh_w);
             // TODO SOMETIME : here, see if we can do sth like the following (i.e. cache all events from 1 API call)
             //                if (strstr($request_url, 'clips.xml?full=true') !== false) {
             //                    // we're getting full clip matadata (woohoo!), so let's fill in the cache
             //                    // on these clips, before making any further API calls.
             //                    $channelfull = new SimpleXMLElement($output);
             //                    foreach ($channelfull as $clipxml) {
             //                        $clip_request_url =
             //                                preg_replace('/^(.*)clips\.xml\?full=true.*/', '\1clips/' . $clipxml->ext_id . '.xml',
             //                                        $request_url);
             //                        $cache_clip_filename = $cache_dir . '/' . self::hashfilename($clip_request_url);
             //                        $fh_w = fopen($cache_clip_filename, 'w');
             //                        fwrite($fh_w, $clipxml->asXML());
             //                        fclose($fh_w);
             //                    }
             //                }
         }
     }
     if ($return_rawdata) {
         return $output;
     }
     if ($output === false) {
         if ($curl_errno) {
             mod_opencast_log::write("CURL REQUEST ERROR : no. " . $curl_errno);
         }
         if ($haltonerror) {
             print_error('switch_api_down', 'opencast');
         }
         return false;
     }
     mod_opencast_log::write("OUTPUT " . $output);
     //        $output = html_entity_decode($output, ENT_NOQUOTES); // not necessary anymore
     try {
         $return = json_decode($output);
     } catch (Exception $e) {
         if ($haltonerror) {
             print_error('api_fail', 'opencast', null, $e->getMessage() . $e->getCode());
         }
         return false;
     }
     if (isset($response_details) && $response_details['http_code'] >= 400) {
         if ($response_details['http_code'] == 404) {
             if ($haltonerror) {
                 print_error('api_404', 'opencast', null, $response_details['http_code']);
             }
         }
         if ($haltonerror) {
             print_error('api_fail', 'opencast', null, $response_details['http_code']);
         }
         return false;
     }
     return $return;
 }
Example #5
0
 /**
  * Checks the current USER's permission on the event
  *
  * @param string $perm_type permission : 'read' or 'write'
  *
  * @return bool true if permission granted
  */
 public function isAllowed($perm_type)
 {
     global $DB, $USER, $context;
     if (!has_capability('mod/opencast:use', $context)) {
         return false;
     }
     $mod_opencast_user = new mod_opencast_user();
     $user_uploaded_events = $DB->get_records('opencast_uploadedclip', ['userid' => $USER->id]);
     $user_uploaded_events_extids = [];
     if (is_array($user_uploaded_events)) {
         foreach ($user_uploaded_events as $user_uploaded_event) {
             $user_uploaded_events_extids[] = $user_uploaded_event->ext_id;
         }
     }
     if ($perm_type == 'write') {
         if (has_capability('mod/opencast:isproducer', $context) || $mod_opencast_user->getExternalAccount() == $this->getOwner() && $this->getOwner() !== '' || in_array($this->getExtId(), $user_uploaded_events_extids)) {
             /*
              * the current $USER is channel producer
              * OR the current $USER is the clip owner
              * OR the current $USER is the user who uploaded the clip
              */
             return true;
         }
     } else {
         if ($perm_type == 'read') {
             if (has_capability('mod/opencast:isproducer', $context) || has_capability('mod/opencast:seeallclips', $context) || $this->series->getIvt() && $this->getOwner() !== '' && $mod_opencast_user->getExternalAccount() == $this->getOwner() || $this->series->getIvt() == false || $this->series->getIvt() == true && $this->series->getInvitingPossible() == true && is_numeric(array_search($USER->id, $this->getMembers())) || mod_opencast_user::checkSameGroup(mod_opencast_user::getMoodleUserIdFromExtId($this->getOwner()), $USER->id) || in_array($this->getExtId(), $user_uploaded_events_extids)) {
                 /*
                  * the current $USER is channel producer
                  * the current $USER has the mod/opencast:seeallclips capability
                  * OR activity is set in individual mode AND the current $USER is the clip owner
                  * OR there are no individual clip permissions set for this activity
                  * OR activity is set in individual mode AND $USER is an invited member of a clip
                  * OR is in the same user group as the clip owner
                  * OR the current $USER is the user who uploaded the clip
                  */
                 return true;
             }
         }
     }
     return false;
 }
if (in_array($action, ['edit']) && confirm_sesskey() && has_capability('mod/opencast:isproducer', $context)) {
    /*
     * $confirm
     * AND sesskey() ok
     * AND has producer rights
     */
    if ($action === 'edit') {
        $sc_clip->setTitle(optional_param('title', $sc_clip->getTitle(), PARAM_RAW_TRIMMED));
        $sc_clip->setSubtitle(optional_param('subtitle', $sc_clip->getSubtitle(), PARAM_RAW_TRIMMED));
        $sc_clip->setPresenter(optional_param('presenter', $sc_clip->getPresenter(), PARAM_RAW_TRIMMED));
        $sc_clip->setLocation(optional_param('location', $sc_clip->getLocation(), PARAM_RAW_TRIMMED));
        if ($userid !== 0) {
            if ($userid == -1) {
                $sc_clip->setOwner('');
            } else {
                $sc_user = new mod_opencast_user(null, $userid);
                $newowner_aaiUniqueId = $sc_user->getExternalAccount();
                if ($newowner_aaiUniqueId) {
                    $newowner = new mod_opencast_user($newowner_aaiUniqueId);
                    $sc_clip->setOwner($newowner_aaiUniqueId);
                    $sc_clip->update();
                } else {
                    print_error('owner_no_switch_account', 'opencast', $url, $setuser->lastname . ', ' . $setuser->firstname);
                }
            }
        }
        $sc_clip->update();
        $eventparams = ['context' => $context, 'objectid' => $opencast->id];
        $event = \mod_opencast\event\clip_editdetails::create($eventparams);
        $event->add_record_snapshot('course_modules', $cm);
        $event->add_record_snapshot('course', $course);
}
if (!($course = $DB->get_record("course", ["id" => $cm->course]))) {
    print_error('coursemisconf');
}
$return_course = new moodle_url('/course/view.php', ['id' => $course->id]);
require_course_login($course, false, $cm);
if (!($opencast = opencast_get_opencast($cm->instance))) {
    print_error('invalidcoursemodule', null, $return_course);
}
if (!($context = context_module::instance($cm->id))) {
    print_error('badcontext', null, $return_course);
}
$allclips = [];
$sc_obj = new mod_opencast_series();
$sc_obj->fetch($opencast->id, true);
$sc_user = new mod_opencast_user();
$arr_filter = [];
$filters = explode('&', urldecode($filterstr));
foreach ($filters as $filter) {
    $parts = explode('=', $filter);
    if (count($parts) == 2) {
        $arr_filter[$parts[0]] = $parts[1];
    }
}
$xml_clips = $sc_obj->getEvents($arr_filter);
$xml_clips_access_allowed = $sc_obj->checkAccess($xml_clips);
$clips = [];
foreach ($xml_clips_access_allowed as $xml_clip) {
    $clips[] = (array) $xml_clip;
}
if (mod_opencast_series::getValueForKey('display_select_columns')) {
Example #8
0
 /**
  * To create a channel we need an aai account that is allowed to register a new channel.
  * Thus the first choice is the aai account of the current user, if he doesn't have an
  * account we use the system account.
  */
 function doCreate()
 {
     global $USER;
     $scuser = new mod_opencast_user();
     // if the current USER has no switchaai account, prevent channel creation
     if ($scuser->getExternalAccount() == '') {
         print_error('user_notaai', 'opencast');
     }
     if ($this->getExtId() == '') {
         // No ext_id: that's a new channel to be created at SWITCHcast server
         $url = '/series';
         $data = ['metadata' => json_encode([['label' => 'Opencast Series DublinCore', 'flavor' => 'dublincore/series', 'fields' => [['id' => 'title', 'value' => $this->title]]]], JSON_UNESCAPED_SLASHES), 'acl' => json_encode([['allow' => true, 'action' => 'read', 'role' => 'ROLE_ORG_PRODUCER'], ['allow' => true, 'action' => 'write', 'role' => 'ROLE_ORG_PRODUCER'], ['allow' => true, 'action' => 'read', 'role' => 'ROLE_EXTERNAL_APPLICATION'], ['allow' => true, 'action' => 'write', 'role' => 'ROLE_EXTERNAL_APPLICATION'], ['allow' => true, 'action' => 'read', 'role' => 'ROLE_AAI_USER_' . $scuser->getExternalAccount()], ['allow' => true, 'action' => 'write', 'role' => 'ROLE_AAI_USER_' . $scuser->getExternalAccount()]], JSON_UNESCAPED_SLASHES)];
         $new_series = mod_opencast_apicall::sendRequest($url, 'POST', $data);
         // Check ext_id
         if ($new_series->identifier) {
             $this->setExtId($new_series->identifier);
         } else {
             print_error('errorchannelcreation', 'opencast');
         }
     } else {
         // existing channel at SWITCHcast server, to be updated
         // basically, we only add our sysAccount as producer at the SWITCHcast server
         $this->update();
     }
 }
                print_error('fileis_notavideo', 'opencast', $url, $file->get_mimetype());
            }
            $filename = $file->get_filename();
            preg_match('/\\.([^.]+)$/', $filename, $extension);
            if (!in_array(strtolower($extension[1]), mod_opencast_series::getAllowedFileExtensions())) {
                $file->delete();
                $a = new stdClass();
                $a->yours = $extension[1];
                $a->allowed = implode(', ', mod_opencast_series::getAllowedFileExtensions());
                print_error('fileis_notextensionallowed', 'opencast', $url, $a);
            }
            $filetoupload = $CFG->dataroot . '/temp/files/mod_opencast_' . md5(microtime()) . '.' . $extension[1];
            $a_file = $file->copy_content_to_temp();
            rename($a_file, $filetoupload);
            try {
                $result = $sc_obj->createClip(['title' => $formdata->cliptitle, 'subtitle' => $formdata->clipsubtitle, 'presenter' => $formdata->clippresenter, 'location' => $formdata->cliplocation, 'ivt__owner' => mod_opencast_user::getExtIdFromMoodleUserId($USER->id), 'filename' => $filetoupload]);
            } catch (Exception $e) {
                unlink($filetoupload);
                $file->delete();
                $retryurl = new moodle_url($url, ['formdata' => serialize($formdata)]);
                print_error('userupload_error', 'opencast', $retryurl);
            }
            unlink($filetoupload);
            $file->delete();
        }
    }
}
if (isset($formdata) && isset($result)) {
    // data submitted: record file upload
    $uploaded_clip = new stdClass();
    $uploaded_clip->userid = $USER->id;
Example #10
0
 function validation($data, $files)
 {
     global $DB;
     $errors = parent::validation($data, $files);
     $scuser = new mod_opencast_user();
     if ($data['channelnew'] == OPENCAST_CHANNEL_NEW) {
         if ($scuser->getExternalAccount() == '') {
             $errors['channelnew'] = get_string('user_notaai', 'opencast');
         }
         if (!$data['newchannelname']) {
             $errors['newchannelname'] = get_string('required');
         }
     }
     if ($data['channelnew'] == OPENCAST_CHANNEL_EXISTING) {
         // make sure we can be external_authority for this channel
         $scobj = new mod_opencast_series();
         $ext_id = isset($data['ext_id']) ? $data['ext_id'] : $this->current->ext_id;
         $scobj->setExtId($ext_id);
         $sysaccount_extid = mod_opencast_series::getSysAccountOfUser();
         // we must explicitly set $USER as a producer in $scobj or we won't be allowed to add his system_user
         $scobj->setOrganizationDomain(mod_opencast_series::getOrganizationByEmail($sysaccount_extid));
         $scobj->setProducer($scuser->getExternalAccount());
         // first, add SysAccount as producer (using $USER account), so we can use SysAccount later to make API calls
         //            $scobj->addProducer($sysaccount_extid, false);
         $channelid = empty($this->_instance) ? $ext_id : $this->current->id;
         // if there already is one instance we must refer to it by its Moodle ID otherwise there could
         // be several records!
         $thechannel = $scobj->fetch($channelid, !empty($this->_instance), true);
     } else {
         if ($data['groupmode'] != NOGROUPS && !$data['is_ivt']) {
             $errors['groupmode'] = get_string('nogroups_withoutivt', 'opencast');
         }
     }
     return $errors;
 }