Actualización

This commit is contained in:
Xes
2025-04-10 12:49:05 +02:00
parent 4aff98e77b
commit 1cdd00920f
9151 changed files with 1800913 additions and 0 deletions

View File

@@ -0,0 +1,379 @@
<?php
/* For licensing terms, see /license.txt */
class CcBase
{
public const CC_TYPE_FORUM = 'imsdt_xmlv1p3';
public const CC_TYPE_QUIZ = 'imsqti_xmlv1p3/imscc_xmlv1p3/assessment';
public const CC_TYPE_QUESTION_BANK = 'imsqti_xmlv1p3/imscc_xmlv1p3/question-bank';
public const CC_TYPE_WEBLINK = 'imswl_xmlv1p3';
public const CC_TYPE_WEBCONTENT = 'webcontent';
public const CC_TYPE_ASSOCIATED_CONTENT = 'associatedcontent/imscc_xmlv1p3/learning-application-resource';
public const CC_TYPE_EMPTY = '';
public static $restypes = ['associatedcontent/imscc_xmlv1p0/learning-application-resource', 'webcontent'];
public static $forumns = ['dt' => 'http://www.imsglobal.org/xsd/imsdt_v1p0'];
public static $quizns = ['xmlns' => 'http://www.imsglobal.org/xsd/ims_qtiasiv1p2'];
public static $resourcens = ['wl' => 'http://www.imsglobal.org/xsd/imswl_v1p0'];
public static $instances = [];
public static $manifest;
public static $pathToManifestFolder;
public static $namespaces = ['imscc' => 'http://www.imsglobal.org/xsd/imscc/imscp_v1p1',
'lomimscc' => 'http://ltsc.ieee.org/xsd/imscc/LOM',
'lom' => 'http://ltsc.ieee.org/xsd/LOM',
'voc' => 'http://ltsc.ieee.org/xsd/LOM/vocab',
'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'cc' => 'http://www.imsglobal.org/xsd/imsccauth_v1p0', ];
public function __construct($path_to_manifest)
{
static::$manifest = new DOMDocument();
static::$manifest->validateOnParse = false;
static::$pathToManifestFolder = dirname($path_to_manifest);
static::logAction('Proccess start');
static::logAction('Load the manifest file: '.$path_to_manifest);
if (!static::$manifest->load($path_to_manifest, LIBXML_NONET)) {
static::logAction('Cannot load the manifest file: '.$path_to_manifest, true);
}
}
/**
* @return array
*/
public static function getquizns()
{
return static::$quizns;
}
/**
* @return array
*/
public static function getforumns()
{
return static::$forumns;
}
/**
* @return array
*/
public static function getresourcens()
{
return static::$resourcens;
}
/**
* Find the imsmanifest.xml file inside the given folder and return its path.
*
* @param string $folder Full path name of the folder in which we expect to find imsmanifest.xml
*
* @return false|string
*/
public static function getManifest(string $folder)
{
if (!is_dir($folder)) {
return false;
}
// Before iterate over directories, try to find one manifest at top level
if (file_exists($folder.'/imsmanifest.xml')) {
return $folder.'/imsmanifest.xml';
}
$result = false;
try {
$dirIter = new RecursiveDirectoryIterator($folder, RecursiveDirectoryIterator::KEY_AS_PATHNAME);
$recIter = new RecursiveIteratorIterator($dirIter, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($recIter as $info) {
if ($info->isFile() && ($info->getFilename() == 'imsmanifest.xml')) {
$result = $info->getPathname();
break;
}
}
} catch (Exception $e) {
}
return $result;
}
public function isAuth()
{
$xpath = static::newxPath(static::$manifest, static::$namespaces);
$count_auth = $xpath->evaluate('count(/imscc:manifest/cc:authorizations)');
if ($count_auth > 0) {
$response = true;
} else {
$response = false;
}
return $response;
}
public function getNodesByCriteria($key, $value)
{
$response = [];
if (array_key_exists('index', static::$instances)) {
foreach (static::$instances['index'] as $item) {
if ($item[$key] == $value) {
$response[] = $item;
}
}
}
return $response;
}
public function countInstances($type)
{
$quantity = 0;
if (array_key_exists('index', static::$instances)) {
if (static::$instances['index'] && $type) {
foreach (static::$instances['index'] as $instance) {
if (!empty($instance['tool_type'])) {
$types[] = $instance['tool_type'];
}
}
$quantityInstances = array_count_values($types);
$quantity = array_key_exists($type, $quantityInstances) ? $quantityInstances[$type] : 0;
}
}
return $quantity;
}
public function getItemCcType($identifier)
{
$xpath = static::newxPath(static::$manifest, static::$namespaces);
$nodes = $xpath->query('/imscc:manifest/imscc:resources/imscc:resource[@identifier="'.$identifier.'"]/@type');
if ($nodes && !empty($nodes->item(0)->nodeValue)) {
return $nodes->item(0)->nodeValue;
} else {
return '';
}
}
public function getItemHref($identifier)
{
$xpath = static::newxPath(static::$manifest, static::$namespaces);
$nodes = $xpath->query('/imscc:manifest/imscc:resources/imscc:resource[@identifier="'.$identifier.'"]/imscc:file/@href');
if ($nodes && !empty($nodes->item(0)->nodeValue)) {
return $nodes->item(0)->nodeValue;
} else {
return '';
}
}
public static function newxPath(DOMDocument $manifest, $namespaces = '')
{
$xpath = new DOMXPath($manifest);
if (!empty($namespaces)) {
foreach ($namespaces as $prefix => $ns) {
if (!$xpath->registerNamespace($prefix, $ns)) {
static::logAction('Cannot register the namespace: '.$prefix.':'.$ns, true);
}
}
}
return $xpath;
}
public static function logFile()
{
return static::$pathToManifestFolder.DIRECTORY_SEPARATOR.'cc_import.log';
}
public static function logAction($text, $criticalError = false)
{
$full_message = strtoupper(date("j/n/Y g:i:s a"))." - ".$text."\r";
file_put_contents(static::logFile(), $full_message, FILE_APPEND);
if ($criticalError) {
static::criticalError($text);
}
}
public function convertToToolType($ccType)
{
$type = TYPE_UNKNOWN;
if ($ccType == static::CC_TYPE_FORUM) {
$type = TOOL_TYPE_FORUM;
}
if ($ccType == static::CC_TYPE_QUIZ) {
$type = TOOL_TYPE_QUIZ;
}
if ($ccType == static::CC_TYPE_WEBLINK) {
$type = TOOL_TYPE_WEBLINK;
}
if ($ccType == static::CC_TYPE_WEBCONTENT) {
$type = TOOL_TYPE_DOCUMENT;
}
return $type;
}
protected function getMetadata($section, $key)
{
$xpath = static::newxPath(static::$manifest, static::$namespaces);
$metadata = $xpath->query('/imscc:manifest/imscc:metadata/lomimscc:lom/lomimscc:'.$section.'/lomimscc:'.$key.'/lomimscc:string');
$value = !empty($metadata->item(0)->nodeValue) ? $metadata->item(0)->nodeValue : '';
return $value;
}
/**
* Is activity visible or not.
*
* @param string $identifier
*
* @return number
*/
protected function getModuleVisible($identifier)
{
//Should item be hidden or not
$mod_visible = 1;
if (!empty($identifier)) {
$xpath = static::newxPath(static::$manifest, static::$namespaces);
$query = '/imscc:manifest/imscc:resources/imscc:resource[@identifier="'.$identifier.'"]';
$query .= '//lom:intendedEndUserRole/voc:vocabulary/lom:value';
$intendeduserrole = $xpath->query($query);
if (!empty($intendeduserrole) && ($intendeduserrole->length > 0)) {
$role = trim($intendeduserrole->item(0)->nodeValue);
if (strcasecmp('Instructor', $role) == 0) {
$mod_visible = 0;
}
}
}
return $mod_visible;
}
protected function createInstances($items, $level = 0, &$array_index = 0, $index_root = 0)
{
$level++;
$i = 1;
if ($items) {
$xpath = self::newxPath(static::$manifest, static::$namespaces);
foreach ($items as $item) {
$array_index++;
$title = $path = $tool_type = $identifierref = '';
if ($item->nodeName == 'item') {
if ($item->hasAttribute('identifierref')) {
$identifierref = $item->getAttribute('identifierref');
}
$titles = $xpath->query('imscc:title', $item);
if ($titles->length > 0) {
$title = $titles->item(0)->nodeValue;
}
$ccType = $this->getItemCcType($identifierref);
$tool_type = $this->convertToToolType($ccType);
//Fix the label issue - MDL-33523
if (empty($identifierref) && empty($title)) {
$tool_type = TYPE_UNKNOWN;
}
} elseif ($item->nodeName == 'resource') {
$identifierref = $xpath->query('@identifier', $item);
$identifierref = !empty($identifierref->item(0)->nodeValue) ? $identifierref->item(0)->nodeValue : '';
$ccType = $this->getItemCcType($identifierref);
$tool_type = $this->convertToToolType($ccType);
if (self::CC_TYPE_WEBCONTENT == $ccType) {
$path = $this->getItemHref($identifierref);
$title = basename($path);
} else { // A resource but not a file... we assume it's a quiz bank and its assigned identifier is irrelevant to its name
$title = 'Quiz Bank '.($this->countInstances($tool_type) + 1);
}
}
if ($level == ROOT_DEEP) {
$index_root = $array_index;
}
static::$instances['index'][$array_index] = [
'common_cartridge_type' => $ccType,
'tool_type' => $tool_type,
'title' => $title ? $title : '',
'root_parent' => $index_root,
'index' => $array_index,
'deep' => $level,
'instance' => $this->countInstances($tool_type),
'resource_identifier' => $identifierref,
];
static::$instances['instances'][$tool_type][] = [
'title' => $title,
'instance' => static::$instances['index'][$array_index]['instance'],
'common_cartridge_type' => $ccType,
'resource_identifier' => $identifierref,
'deep' => $level,
'src' => $path,
];
$more_items = $xpath->query('imscc:item', $item);
if ($more_items->length > 0) {
$this->createInstances($more_items, $level, $array_index, $index_root);
}
$i++;
}
}
}
protected static function criticalError($text)
{
$path_to_log = static::logFile();
echo '
<p>
<hr />A critical error has been found!
<p>'.$text.'</p>
<p>
The process has been stopped. Please see the <a href="'.$path_to_log.'">log file</a> for more information.</p>
<p>Log: '.$path_to_log.'</p>
<hr />
</p>
';
exit();
}
protected function createCourseCode($title)
{
//Making sure that text of the short name does not go over the DB limit.
//and leaving the space to add additional characters by the platform
$code = substr(strtoupper(str_replace(' ', '', trim($title))), 0, 94);
return $code;
}
}

View File

@@ -0,0 +1,10 @@
<?php
/* Source: https://github.com/moodle/moodle/blob/MOODLE_310_STABLE/backup/cc/validator.php under GNU/GPL license */
class AssesmentValidator extends CcValidateType
{
public function __construct($location)
{
parent::__construct(self::ASSESMENT_VALIDATOR13, $location);
}
}

View File

@@ -0,0 +1,10 @@
<?php
/* Source: https://github.com/moodle/moodle/blob/MOODLE_310_STABLE/backup/cc/validator.php under GNU/GPL license */
class BltiValidator extends CcValidateType
{
public function __construct($location)
{
parent::__construct(self::BLTI_VALIDATOR13, $location);
}
}

View File

@@ -0,0 +1,62 @@
<?php
/* Source: https://github.com/moodle/moodle/blob/MOODLE_310_STABLE/backup/cc/validator.php under GNU/GPL license */
class CcValidateType
{
public const MANIFEST_VALIDATOR1 = 'cclibxml2validator.xsd';
public const ASSESMENT_VALIDATOR1 = '/domainProfile_4/ims_qtiasiv1p2_localised.xsd';
public const DISCUSSION_VALIDATOR1 = '/domainProfile_6/imsdt_v1p0_localised.xsd';
public const WEBLINK_VALIDATOR1 = '/domainProfile_5/imswl_v1p0_localised.xsd';
public const MANIFEST_VALIDATOR11 = 'cc11libxml2validator.xsd';
public const BLTI_VALIDATOR11 = 'imslticc_v1p0p1.xsd';
public const ASSESMENT_VALIDATOR11 = 'ccv1p1_qtiasiv1p2p1_v1p0.xsd';
public const DISCUSSION_VALIDATOR11 = 'ccv1p1_imsdt_v1p1.xsd';
public const WEBLINK_VALIDATOR11 = 'ccv1p1_imswl_v1p1.xsd';
public const MANIFEST_VALIDATOR13 = 'cc13libxml2validator.xsd';
public const BLTI_VALIDATOR13 = 'imslticc_v1p3.xsd';
public const ASSESMENT_VALIDATOR13 = 'ccv1p3_qtiasiv1p2p1_v1p0.xsd';
public const DISCUSSION_VALIDATOR13 = 'ccv1p3_imsdt_v1p3.xsd';
public const WEBLINK_VALIDATOR13 = 'ccv1p3_imswl_v1p3.xsd';
/**
* @var string
*/
protected $type = null;
/**
* @var string
*/
protected $location = null;
public function __construct($type, $location)
{
$this->type = $type;
$this->location = $location;
}
/**
* Validates the item.
*
* @param string $element - File path for the xml
*
* @return bool
*/
public function validate($element)
{
$this->last_error = null;
$celement = realpath($element);
$cvalidator = realpath($this->location.DIRECTORY_SEPARATOR.$this->type);
$result = (empty($celement) || empty($cvalidator));
if (!$result) {
$xml_error = new LibxmlErrorsMgr();
$doc = new DOMDocument();
$doc->validateOnParse = false;
$result = $doc->load($celement, LIBXML_NONET) &&
$doc->schemaValidate($cvalidator);
}
return $result;
}
}

View File

@@ -0,0 +1,10 @@
<?php
/* Source: https://github.com/moodle/moodle/blob/MOODLE_310_STABLE/backup/cc/validator.php under GNU/GPL license */
class DiscussionValidator extends CcValidateType
{
public function __construct($location)
{
parent::__construct(self::DISCUSSION_VALIDATOR13, $location);
}
}

View File

@@ -0,0 +1,101 @@
<?php
/* Source: https://github.com/moodle/moodle/blob/MOODLE_310_STABLE/backup/cc/validator.php under GNU/GPL license */
final class ErrorMessages
{
/**
* @static ErrorMessages
*/
private static $instance = null;
/**
* @var array
*/
private $items = [];
private function __construct()
{
}
private function __clone()
{
}
/**
* Casting to string method.
*
* @return string
*/
public function __toString()
{
return $this->toString(false);
}
/**
* @return ErrorMessages
*/
public static function instance()
{
if (empty(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c();
}
return self::$instance;
}
/**
* @param string $msg
*/
public function add($msg)
{
if (!empty($msg)) {
$this->items[] = $msg;
}
}
/**
* @return array
*/
public function errors()
{
$this->items;
}
/**
* Empties the error content.
*/
public function reset()
{
$this->items = [];
}
/**
* @param bool $web
*
* @return string
*/
public function toString($web = false)
{
$result = '';
if ($web) {
$result .= '<ol>'.PHP_EOL;
}
foreach ($this->items as $error) {
if ($web) {
$result .= '<li>';
}
$result .= $error.PHP_EOL;
if ($web) {
$result .= '</li>'.PHP_EOL;
}
}
if ($web) {
$result .= '</ol>'.PHP_EOL;
}
return $result;
}
}

View File

@@ -0,0 +1,61 @@
<?php
/* Source: https://github.com/moodle/moodle/blob/MOODLE_310_STABLE/backup/cc/validator.php under GNU/GPL license */
final class LibxmlErrorsMgr
{
/**
* @var bool
*/
private $previous = false;
/**
* @param bool $reset
*/
public function __construct($reset = false)
{
if ($reset) {
ErrorMessages::instance()->reset();
}
$this->previous = libxml_use_internal_errors(true);
libxml_clear_errors();
}
public function __destruct()
{
$this->collectErrors();
if (!$this->previous) {
libxml_use_internal_errors($this->previous);
}
}
public function collect()
{
$this->collectErrors();
}
private function collectErrors($filename = '')
{
$errors = libxml_get_errors();
static $error_types = [
LIBXML_ERR_ERROR => 'Error', LIBXML_ERR_FATAL => 'Fatal Error', LIBXML_ERR_WARNING => 'Warning',
];
$result = [];
foreach ($errors as $error) {
$add = '';
if (!empty($filename)) {
$add = " in {$filename}";
} elseif (!empty($error->file)) {
$add = " in {$error->file}";
}
$line = '';
if (!empty($error->line)) {
$line = " at line {$error->line}";
}
$err = "{$error_types[$error->level]}{$add}: {$error->message}{$line}";
ErrorMessages::instance()->add($err);
}
libxml_clear_errors();
return $result;
}
}

View File

@@ -0,0 +1,10 @@
<?php
/* Source: https://github.com/moodle/moodle/blob/MOODLE_310_STABLE/backup/cc/validator.php under GNU/GPL license */
class Manifest10Validator extends CcValidateType
{
public function __construct($location)
{
parent::__construct(self::MANIFEST_VALIDATOR1, $location);
}
}

View File

@@ -0,0 +1,10 @@
<?php
/* Source: https://github.com/moodle/moodle/blob/MOODLE_310_STABLE/backup/cc/validator.php under GNU/GPL license */
class ManifestValidator extends CcValidateType
{
public function __construct($location)
{
parent::__construct(self::MANIFEST_VALIDATOR13, $location);
}
}

View File

@@ -0,0 +1,10 @@
<?php
/* Source: https://github.com/moodle/moodle/blob/MOODLE_310_STABLE/backup/cc/validator.php under GNU/GPL license */
class WeblinkValidator extends CcValidateType
{
public function __construct($location)
{
parent::__construct(self::WEBLINK_VALIDATOR13, $location);
}
}

View File

@@ -0,0 +1,57 @@
<?php
/* For licensing terms, see /license.txt */
class Cc13Entities extends CcEntities
{
public function getExternalXml($identifier)
{
$xpath = Cc1p3Convert::newxPath(Cc1p3Convert::$manifest, Cc1p3Convert::$namespaces);
$files = $xpath->query('/imscc:manifest/imscc:resources/imscc:resource[@identifier="'.
$identifier.'"]/imscc:file/@href');
$response = empty($files) || ($files->length == 0) ? '' : $files->item(0)->nodeValue;
return $response;
}
protected function getAllFiles()
{
$permDirs = api_get_permissions_for_new_directories();
$allFiles = [];
$xpath = Cc1p3Convert::newxPath(Cc1p3Convert::$manifest, Cc1p3Convert::$namespaces);
foreach (Cc1p3Convert::$restypes as $type) {
$files = $xpath->query('/imscc:manifest/imscc:resources/imscc:resource[@type="'.
$type.'"]/imscc:file/@href');
if (empty($files) || ($files->length == 0)) {
continue;
}
foreach ($files as $file) {
//omit html files
//this is a bit too simplistic
$ext = strtolower(pathinfo($file->nodeValue, PATHINFO_EXTENSION));
if (in_array($ext, ['html', 'htm', 'xhtml'])) {
continue;
}
$allFiles[] = $file->nodeValue;
}
unset($files);
}
//are there any labels?
$xquery = "//imscc:item/imscc:item/imscc:item[imscc:title][not(@identifierref)]";
$labels = $xpath->query($xquery);
if (!empty($labels) && ($labels->length > 0)) {
$tname = 'course_files';
$dpath = Cc1p3Convert::$pathToManifestFolder.DIRECTORY_SEPARATOR.$tname;
$rfpath = 'files.gif';
$fpath = $dpath.DIRECTORY_SEPARATOR.'files.gif';
if (!file_exists($dpath)) {
mkdir($dpath, $permDirs, true);
}
$allFiles[] = $rfpath;
}
$allFiles = empty($allFiles) ? '' : $allFiles;
return $allFiles;
}
}

View File

@@ -0,0 +1,166 @@
<?php
/* For licensing terms, see /license.txt */
require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
class Cc13Forum extends Cc13Entities
{
public function fullPath($path, $dir_sep = DIRECTORY_SEPARATOR)
{
$token = '/\$(?:IMS|1EdTech)[-_]CC[-_]FILEBASE\$/';
$path = preg_replace($token, '', $path);
if (is_string($path) && ($path != '')) {
$dot_dir = '.';
$up_dir = '..';
$length = strlen($path);
$rtemp = trim($path);
$start = strrpos($path, $dir_sep);
$can_continue = ($start !== false);
$result = $can_continue ? '' : $path;
$rcount = 0;
while ($can_continue) {
$dir_part = ($start !== false) ? substr($rtemp, $start + 1, $length - $start) : $rtemp;
$can_continue = ($dir_part !== false);
if ($can_continue) {
if ($dir_part != $dot_dir) {
if ($dir_part == $up_dir) {
$rcount++;
} else {
if ($rcount > 0) {
$rcount--;
} else {
$result = ($result == '') ? $dir_part : $dir_part.$dir_sep.$result;
}
}
}
$rtemp = substr($path, 0, $start);
$start = strrpos($rtemp, $dir_sep);
$can_continue = (($start !== false) || (strlen($rtemp) > 0));
}
}
}
return $result;
}
public function generateData()
{
$data = [];
if (!empty(Cc1p3Convert::$instances['instances']['forum'])) {
foreach (Cc1p3Convert::$instances['instances']['forum'] as $instance) {
$data[] = $this->getForumData($instance);
}
}
return $data;
}
public function getForumData($instance)
{
$topic_data = $this->getTopicData($instance);
$values = [];
if (!empty($topic_data)) {
$values = [
'instance' => $instance['instance'],
'title' => self::safexml($topic_data['title']),
'description' => self::safexml($topic_data['description']),
];
}
return $values;
}
public function storeForums($forums)
{
// Create a Forum category for the import CC 1.3.
$courseInfo = api_get_course_info();
$catForumValues['forum_category_title'] = 'CC1p3';
$catForumValues['forum_category_comment'] = '';
$catId = store_forumcategory(
$catForumValues,
$courseInfo,
false
);
foreach ($forums as $forum) {
$forumValues = [];
$forumValues['forum_title'] = $forum['title'];
$forumValues['forum_image'] = '';
$forumValues['forum_comment'] = strip_tags($forum['description']);
$forumValues['forum_category'] = $catId;
$forumValues['moderated'] = 0;
store_forum($forumValues, $courseInfo);
}
return true;
}
public function getTopicData($instance)
{
$topic_data = [];
$topic_file = $this->getExternalXml($instance['resource_identifier']);
if (!empty($topic_file)) {
$topic_file_path = Cc1p3Convert::$pathToManifestFolder.DIRECTORY_SEPARATOR.$topic_file;
$topic_file_dir = dirname($topic_file_path);
$topic = $this->loadXmlResource($topic_file_path);
if (!empty($topic)) {
$xpath = Cc1p3Convert::newxPath($topic, Cc1p3Convert::$forumns);
$topic_title = $xpath->query('/dt:topic/dt:title');
if ($topic_title->length > 0 && !empty($topic_title->item(0)->nodeValue)) {
$topic_title = $topic_title->item(0)->nodeValue;
} else {
$topic_title = 'Untitled Topic';
}
$topic_text = $xpath->query('/dt:topic/dt:text');
$topic_text = !empty($topic_text->item(0)->nodeValue) ? $this->updateSources($topic_text->item(0)->nodeValue, dirname($topic_file)) : '';
$topic_text = !empty($topic_text) ? str_replace("%24", "\$", $this->includeTitles($topic_text)) : '';
if (!empty($topic_title)) {
$topic_data['title'] = $topic_title;
$topic_data['description'] = $topic_text;
}
}
$topic_attachments = $xpath->query('/dt:topic/dt:attachments/dt:attachment/@href');
if ($topic_attachments->length > 0) {
$attachment_html = '';
foreach ($topic_attachments as $file) {
$attachment_html .= $this->generateAttachmentHtml($this->fullPath($file->nodeValue, '/'));
}
$topic_data['description'] = !empty($attachment_html) ? $topic_text.'<p>Attachments:</p>'.$attachment_html : $topic_text;
}
}
return $topic_data;
}
private function generateAttachmentHtml(string $filename, ?string $rootPath)
{
$images_extensions = ['gif', 'jpeg', 'jpg', 'jif', 'jfif', 'png', 'bmp', 'webp'];
$fileinfo = pathinfo($filename);
if (empty($rootPath)) {
$rootPath = '';
}
if (in_array($fileinfo['extension'], $images_extensions)) {
return '<img src="'.$rootPath.$filename.'" title="'.$fileinfo['basename'].'" alt="'.$fileinfo['basename'].'" /><br />';
} else {
return '<a href="'.$rootPath.$filename.'" title="'.$fileinfo['basename'].'" alt="'.$fileinfo['basename'].'">'.$fileinfo['basename'].'</a><br />';
}
return '';
}
}

View File

@@ -0,0 +1,763 @@
<?php
/* For licensing terms, see /license.txt */
class Cc13Quiz extends Cc13Entities
{
/**
* Get all data from the object instance (coming from the xml file) into a clean array.
*
* @return array
*/
public function generateData()
{
$data = [];
$instances = $this->generateInstances();
if (!empty($instances)) {
foreach ($instances as $instance) {
if ($instance['is_question_bank'] == 0) {
$data[] = $this->getQuizData($instance);
}
}
}
return $data;
}
/**
* Create a quiz based on the information available in the assessment structure.
*
* @param $quiz
*
* @return void
*/
public function storeQuiz($quiz)
{
$token = '/\$(?:IMS|1EdTech)[-_]CC[-_]FILEBASE\$\.\.\//';
$courseInfo = api_get_course_info();
// Replace by the path in documents in which we place all relevant CC files
$replacementPath = '/courses/'.$courseInfo['directory'].'/document/commoncartridge/';
$exercise = new Exercise($courseInfo['real_id']);
$title = Exercise::format_title_variable($quiz['title']);
$exercise->updateTitle($title);
$description = preg_replace($token, $replacementPath, $quiz['description']);
$exercise->updateDescription($description);
$exercise->updateAttempts($quiz['max_attempts']);
$exercise->updateFeedbackType(0);
$exercise->setRandom(0);
$exercise->updateRandomAnswers(!empty($moduleValues['shuffleanswers']));
$exercise->updateExpiredTime((int) $quiz['timelimit']);
$exercise->updateType(1);
// Create the new Quiz
$exercise->save();
if (!empty($quiz['questions'])) {
foreach ($quiz['questions'] as $question) {
$qtype = $question['type'];
$types = [
'unique_answer' => UNIQUE_ANSWER,
'multiple_answer' => MULTIPLE_ANSWER,
'fib' => FILL_IN_BLANKS,
'essay' => FREE_ANSWER,
];
$questionType = $types[$qtype];
$questionInstance = Question::getInstance($questionType);
if (empty($questionInstance)) {
continue;
}
$questionInstance->updateTitle(substr(Security::remove_XSS(strip_tags_blacklist($question['title'], ['br', 'p'])), 0, 20));
$questionText = Security::remove_XSS(strip_tags_blacklist($question['title'], ['br', 'p']));
// Replace the path from $1EdTech-CC-FILEBASE$ to a correct chamilo path
$questionText = preg_replace($token, $replacementPath, $questionText);
$questionInstance->updateDescription($questionText);
$questionInstance->updateLevel(1);
$questionInstance->updateCategory(0);
//Save normal question if NOT media
if ($questionInstance->type != MEDIA_QUESTION) {
$questionInstance->save($exercise);
// modify the exercise
$exercise->addToList($questionInstance->iid);
$exercise->update_question_positions();
}
if ($qtype == 'unique_answer') {
$objAnswer = new Answer($questionInstance->iid);
$questionWeighting = 0;
foreach ($question['answers'] as $slot => $answerValues) {
$correct = $answerValues['score'] ? (int) $answerValues['score'] : 0;
$answer = Security::remove_XSS(preg_replace($token, $replacementPath, $answerValues['title']));
$comment = Security::remove_XSS(preg_replace($token, $replacementPath, $answerValues['feedback']));
$weighting = $answerValues['score'];
$weighting = abs($weighting);
if ($weighting > 0) {
$questionWeighting += $weighting;
}
$goodAnswer = $correct ? true : false;
$objAnswer->createAnswer(
$answer,
$goodAnswer,
$comment,
$weighting,
$slot + 1,
null,
null,
''
);
}
// saves the answers into the database
$objAnswer->save();
// sets the total weighting of the question
$questionInstance->updateWeighting($questionWeighting);
$questionInstance->save($exercise);
} else {
$objAnswer = new Answer($questionInstance->iid);
$questionWeighting = 0;
if (is_array($question['answers'])) {
foreach ($question['answers'] as $slot => $answerValues) {
$answer = Security::remove_XSS(preg_replace($token, $replacementPath, $answerValues['title']));
$comment = Security::remove_XSS(preg_replace($token, $replacementPath, $answerValues['feedback']));
$weighting = $answerValues['score'];
if ($weighting > 0) {
$questionWeighting += $weighting;
}
$goodAnswer = $weighting > 0;
$objAnswer->createAnswer(
$answer,
$goodAnswer,
$comment,
$weighting,
$slot + 1,
null,
null,
''
);
}
} elseif ($qtype == 'essay') {
$questionWeighting = $question['ponderation'];
}
// saves the answers into the database
$objAnswer->save();
// sets the total weighting of the question
$questionInstance->updateWeighting($questionWeighting);
$questionInstance->save($exercise);
}
}
}
}
public function storeQuizzes($quizzes)
{
if (!empty($quizzes)) {
foreach ($quizzes as $quiz) {
$this->storeQuiz($quiz);
}
}
}
public function getQuizData($instance)
{
$values = [];
if (!empty($instance)) {
$questions = [];
if (!empty($instance['questions'])) {
foreach ($instance['questions'] as $question) {
$questions[$question['id']] = [
'title' => $question['title'],
'type' => $question['qtype'],
'ponderation' => $question['defaultgrade'],
'answers' => $question['answers'],
];
}
}
$values = [
'id' => $instance['id'],
'title' => $instance['title'],
'description' => $instance['description'],
'timelimit' => $instance['options']['timelimit'],
'max_attempts' => $instance['options']['max_attempts'],
'questions' => $questions,
];
}
return $values;
}
private function generateInstances()
{
$lastInstanceId = 0;
$lastQuestionId = 0;
$lastAnswerId = 0;
$instances = [];
$types = [TOOL_TYPE_QUIZ];
foreach ($types as $type) {
if (!empty(Cc1p3Convert::$instances['instances'][$type])) {
foreach (Cc1p3Convert::$instances['instances'][$type] as $instance) {
if ($type == TOOL_TYPE_QUIZ) {
$is_question_bank = 0;
} else {
$is_question_bank = 1;
}
// Get the path of the assessment.xml file
$assessmentFile = $this->getExternalXml($instance['resource_identifier']);
if (!empty($assessmentFile)) {
$assessment = $this->loadXmlResource(Cc1p3Convert::$pathToManifestFolder.DIRECTORY_SEPARATOR.$assessmentFile);
if (!empty($assessment)) {
$replaceValues = ['unlimited' => 0];
$questions = $this->getQuestions($assessment, $lastQuestionId, $lastAnswerId, dirname($assessmentFile), $is_question_bank);
$questionCount = count($questions);
if (!empty($questionCount)) {
$lastInstanceId++;
$instances[$instance['resource_identifier']]['questions'] = $questions;
$instances[$instance['resource_identifier']]['id'] = $lastInstanceId;
$instances[$instance['resource_identifier']]['title'] = $instance['title'];
$instances[$instance['resource_identifier']]['description'] = $this->getQuizDescription($assessment);
$instances[$instance['resource_identifier']]['is_question_bank'] = $is_question_bank;
$instances[$instance['resource_identifier']]['options']['timelimit'] = $this->getGlobalConfig($assessment, 'qmd_timelimit', 0);
$instances[$instance['resource_identifier']]['options']['max_attempts'] = $this->getGlobalConfig($assessment, 'cc_maxattempts', 0, $replaceValues);
}
}
}
}
}
}
return $instances;
}
private function getGlobalConfig($assessment, $option, $defaultValue, $replaceValues = '')
{
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
$metadata = $xpath->query('/xmlns:questestinterop/xmlns:assessment/xmlns:qtimetadata/xmlns:qtimetadatafield');
foreach ($metadata as $field) {
$fieldLabel = $xpath->query('xmlns:fieldlabel', $field);
$fieldLabel = !empty($fieldLabel->item(0)->nodeValue) ? $fieldLabel->item(0)->nodeValue : '';
if (strtolower($fieldLabel) == strtolower($option)) {
$fieldEntry = $xpath->query('xmlns:fieldentry', $field);
$response = !empty($fieldEntry->item(0)->nodeValue) ? $fieldEntry->item(0)->nodeValue : '';
}
}
$response = !empty($response) ? trim($response) : '';
if (!empty($replaceValues)) {
foreach ($replaceValues as $key => $value) {
$response = ($key == $response) ? $value : $response;
}
}
$response = empty($response) ? $defaultValue : $response;
return $response;
}
private function getQuizDescription(DOMDocument $assessment): string
{
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
$fieldEntry = $xpath->query('/xmlns:questestinterop/xmlns:assessment/xmlns:rubric/xmlns:material/xmlns:mattext');
$response = !empty($fieldEntry->item(0)->nodeValue) ? $fieldEntry->item(0)->nodeValue : '';
return $response;
}
private function getQuestions($assessment, &$lastQuestionId, &$last_answer_id, $rootPath, $is_question_bank)
{
$questions = [];
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
if (!$is_question_bank) {
$questionsItems = $xpath->query('/xmlns:questestinterop/xmlns:assessment/xmlns:section/xmlns:item');
} else {
$questionsItems = $xpath->query('/xmlns:questestinterop/xmlns:objectbank/xmlns:item');
}
foreach ($questionsItems as $question_item) {
$countQuestions = $xpath->evaluate('count(xmlns:presentation/xmlns:flow/xmlns:material/xmlns:mattext)', $question_item);
if ($countQuestions == 0) {
$questionTitle = $xpath->query('xmlns:presentation/xmlns:material/xmlns:mattext', $question_item);
} else {
$questionTitle = $xpath->query('xmlns:presentation/xmlns:flow/xmlns:material/xmlns:mattext', $question_item);
}
$questionTitle = !empty($questionTitle->item(0)->nodeValue) ? $questionTitle->item(0)->nodeValue : '';
$questionIdentifier = $xpath->query('@ident', $question_item);
$questionIdentifier = !empty($questionIdentifier->item(0)->nodeValue) ? $questionIdentifier->item(0)->nodeValue : '';
if (!empty($questionIdentifier)) {
$questionType = $this->getQuestionType($questionIdentifier, $assessment);
if (!empty($questionType['qtype'])) {
$lastQuestionId++;
$questions[$questionIdentifier]['id'] = $lastQuestionId;
$questionTitle = $this->updateSources($questionTitle, $rootPath);
$questionTitle = !empty($questionTitle) ? str_replace("%24", "\$", $this->includeTitles($questionTitle)) : '';
$questionname = $xpath->query('@title', $question_item);
$questionname = !empty($questionname->item(0)->nodeValue) ? $questionname->item(0)->nodeValue : '';
$questions[$questionIdentifier]['title'] = $questionTitle;
$questions[$questionIdentifier]['name'] = $questionname;
$questions[$questionIdentifier]['identifier'] = $questionIdentifier;
$questions[$questionIdentifier]['qtype'] = $questionType['qtype'];
$questions[$questionIdentifier]['cc_type'] = $questionType['cc'];
$questions[$questionIdentifier]['feedback'] = $this->getGeneralFeedback($assessment, $questionIdentifier);
$questions[$questionIdentifier]['defaultgrade'] = $this->getDefaultgrade($assessment, $questionIdentifier);
$questions[$questionIdentifier]['answers'] = $this->getAnswers($questionIdentifier, $assessment, $lastAnswerId);
}
}
}
$questions = !empty($questions) ? $questions : '';
return $questions;
}
private function getDefaultgrade($assessment, $questionIdentifier)
{
$result = 1;
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
$query = '//xmlns:item[@ident="'.$questionIdentifier.'"]';
$query .= '//xmlns:qtimetadatafield[xmlns:fieldlabel="cc_weighting"]/xmlns:fieldentry';
$defgrade = $xpath->query($query);
if (!empty($defgrade) && ($defgrade->length > 0)) {
$resp = (int) $defgrade->item(0)->nodeValue;
if ($resp >= 0 && $resp <= 99) {
$result = $resp;
}
}
return $result;
}
private function getGeneralFeedback($assessment, $questionIdentifier)
{
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
$respconditions = $xpath->query('//xmlns:item[@ident="'.$questionIdentifier.'"]/xmlns:resprocessing/xmlns:respcondition');
if (!empty($respconditions)) {
foreach ($respconditions as $respcondition) {
$continue = $respcondition->getAttributeNode('continue');
$continue = !empty($continue->nodeValue) ? strtolower($continue->nodeValue) : '';
if ($continue == 'yes') {
$displayFeedback = $xpath->query('xmlns:displayfeedback', $respcondition);
if (!empty($displayFeedback)) {
foreach ($displayFeedback as $feedback) {
$feedbackIdentifier = $feedback->getAttributeNode('linkrefid');
$feedbackIdentifier = !empty($feedbackIdentifier->nodeValue) ? $feedbackIdentifier->nodeValue : '';
if (!empty($feedbackIdentifier)) {
$feedbackIdentifiers[] = $feedbackIdentifier;
}
}
}
}
}
}
$feedback = '';
$feedbackIdentifiers = empty($feedbackIdentifiers) ? '' : $feedbackIdentifiers;
if (!empty($feedbackIdentifiers)) {
foreach ($feedbackIdentifiers as $feedbackIdentifier) {
$feedbacks = $xpath->query('//xmlns:item[@ident="'.$questionIdentifier.'"]/xmlns:itemfeedback[@ident="'.$feedbackIdentifier.'"]/xmlns:flow_mat/xmlns:material/xmlns:mattext');
$feedback .= !empty($feedbacks->item(0)->nodeValue) ? $feedbacks->item(0)->nodeValue.' ' : '';
}
}
return $feedback;
}
private function getFeedback($assessment, $identifier, $itemIdentifier, $questionType)
{
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
$resourceProcessing = $xpath->query('//xmlns:item[@ident="'.$itemIdentifier.'"]/xmlns:resprocessing/xmlns:respcondition');
if (!empty($resourceProcessing)) {
foreach ($resourceProcessing as $response) {
$varequal = $xpath->query('xmlns:conditionvar/xmlns:varequal', $response);
$varequal = !empty($varequal->item(0)->nodeValue) ? $varequal->item(0)->nodeValue : '';
if (strtolower($varequal) == strtolower($identifier) || ($questionType == CC_QUIZ_ESSAY)) {
$displayFeedback = $xpath->query('xmlns:displayfeedback', $response);
if (!empty($displayFeedback)) {
foreach ($displayFeedback as $feedback) {
$feedbackIdentifier = $feedback->getAttributeNode('linkrefid');
$feedbackIdentifier = !empty($feedbackIdentifier->nodeValue) ? $feedbackIdentifier->nodeValue : '';
if (!empty($feedbackIdentifier)) {
$feedbackIdentifiers[] = $feedbackIdentifier;
}
}
}
}
}
}
$feedback = '';
$feedbackIdentifiers = empty($feedbackIdentifiers) ? '' : $feedbackIdentifiers;
if (!empty($feedbackIdentifiers)) {
foreach ($feedbackIdentifiers as $feedbackIdentifier) {
$feedbacks = $xpath->query('//xmlns:item[@ident="'.$itemIdentifier.'"]/xmlns:itemfeedback[@ident="'.$feedbackIdentifier.'"]/xmlns:flow_mat/xmlns:material/xmlns:mattext');
$feedback .= !empty($feedbacks->item(0)->nodeValue) ? $feedbacks->item(0)->nodeValue.' ' : '';
}
}
return $feedback;
}
private function getAnswersFib($questionIdentifier, $identifier, $assessment, &$lastAnswerId)
{
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
$correctanswersfib = [];
$incorrectanswersfib = [];
$responseItems = $xpath->query('//xmlns:item[@ident="'.$questionIdentifier.'"]/xmlns:resprocessing/xmlns:respcondition');
$correctrespcond = $xpath->query('//xmlns:item[@ident="'.$questionIdentifier.'"]/xmlns:resprocessing/xmlns:respcondition/xmlns:setvar[text()="100"]/..');
$correctanswers = $xpath->query('xmlns:conditionvar/xmlns:varequal', $correctrespcond->item(0));
// Correct answers.
foreach ($correctanswers as $correctans) {
$answertitle = !empty($correctans->nodeValue) ? $correctans->nodeValue : '';
if (empty($answertitle)) {
continue;
}
$lastAnswerId++;
$correctanswersfib[$answertitle] = [
'id' => $lastAnswerId,
'title' => $answertitle,
'score' => 1,
'feedback' => '',
'case' => 0, ];
}
// Handle incorrect answers and feedback for all items.
foreach ($responseItems as $response_item) {
$setvar = $xpath->query('xmlns:setvar', $response_item);
if (!empty($setvar->length) && $setvar->item(0)->nodeValue == '100') {
// Skip the correct answer responsecondition.
continue;
}
$varequal = $xpath->query('xmlns:conditionvar/xmlns:varequal', $response_item);
if (empty($varequal->length)) {
// Skip respcondition elements that don't have varequal containing an answer
continue;
}
$answerTitle = !empty($varequal->item(0)->nodeValue) ? $varequal->item(0)->nodeValue : '';
$displayFeedback = $xpath->query('xmlns:displayfeedback', $response_item);
unset($feedbackIdentifiers);
if (!empty($displayFeedback)) {
foreach ($displayFeedback as $feedback) {
$feedbackIdentifier = $feedback->getAttributeNode('linkrefid');
$feedbackIdentifier = !empty($feedbackIdentifier->nodeValue) ? $feedbackIdentifier->nodeValue : '';
if (!empty($feedbackIdentifier)) {
$feedbackIdentifiers[] = $feedbackIdentifier;
}
}
}
$feedback = '';
$feedbackIdentifiers = empty($feedbackIdentifiers) ? '' : $feedbackIdentifiers;
if (!empty($feedbackIdentifiers)) {
foreach ($feedbackIdentifiers as $feedbackIdentifier) {
$feedbacks = $xpath->query('//xmlns:item[@ident="'.$questionIdentifier.'"]/xmlns:itemfeedback[@ident="'.$feedbackIdentifier.'"]/xmlns:flow_mat/xmlns:material/xmlns:mattext');
$feedback .= !empty($feedbacks->item(0)->nodeValue) ? $feedbacks->item(0)->nodeValue.' ' : '';
}
}
if (array_key_exists($answerTitle, $correctanswersfib)) {
// Already a correct answer, just need the feedback for the correct answer.
$correctanswerfib[$answerTitle]['feedback'] = $feedback;
} else {
// Need to add an incorrect answer.
$lastAnswerId++;
$incorrectanswersfib[] = [
'id' => $lastAnswerId,
'title' => $answerTitle,
'score' => 0,
'feedback' => $feedback,
'case' => 0, ];
}
}
$answersFib = array_merge($correctanswersfib, $incorrectanswersfib);
$answersFib = empty($answersFib) ? '' : $answersFib;
return $answersFib;
}
private function getAnswersPatternMatch($questionIdentifier, $identifier, $assessment, &$lastAnswerId)
{
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
$answersFib = [];
$responseItems = $xpath->query('//xmlns:item[@ident="'.$questionIdentifier.'"]/xmlns:resprocessing/xmlns:respcondition');
foreach ($responseItems as $response_item) {
$setvar = $xpath->query('xmlns:setvar', $response_item);
$setvar = is_object($setvar->item(0)) ? $setvar->item(0)->nodeValue : '';
if ($setvar != '') {
$lastAnswerId++;
$answerTitle = $xpath->query('xmlns:conditionvar/xmlns:varequal[@respident="'.$identifier.'"]', $response_item);
$answerTitle = !empty($answerTitle->item(0)->nodeValue) ? $answerTitle->item(0)->nodeValue : '';
if (empty($answerTitle)) {
$answerTitle = $xpath->query('xmlns:conditionvar/xmlns:varsubstring[@respident="'.$identifier.'"]', $response_item);
$answerTitle = !empty($answerTitle->item(0)->nodeValue) ? '*'.$answerTitle->item(0)->nodeValue.'*' : '';
}
if (empty($answerTitle)) {
$answerTitle = '*';
}
$case = $xpath->query('xmlns:conditionvar/xmlns:varequal/@case', $response_item);
$case = is_object($case->item(0)) ? $case->item(0)->nodeValue : 'no'
;
$case = strtolower($case) == 'yes' ? 1 :
0;
$displayFeedback = $xpath->query('xmlns:displayfeedback', $response_item);
unset($feedbackIdentifiers);
if (!empty($displayFeedback)) {
foreach ($displayFeedback as $feedback) {
$feedbackIdentifier = $feedback->getAttributeNode('linkrefid');
$feedbackIdentifier = !empty($feedbackIdentifier->nodeValue) ? $feedbackIdentifier->nodeValue : '';
if (!empty($feedbackIdentifier)) {
$feedbackIdentifiers[] = $feedbackIdentifier;
}
}
}
$feedback = '';
$feedbackIdentifiers = empty($feedbackIdentifiers) ? '' : $feedbackIdentifiers;
if (!empty($feedbackIdentifiers)) {
foreach ($feedbackIdentifiers as $feedbackIdentifier) {
$feedbacks = $xpath->query('//xmlns:item[@ident="'.$questionIdentifier.'"]/xmlns:itemfeedback[@ident="'.$feedbackIdentifier.'"]/xmlns:flow_mat/xmlns:material/xmlns:mattext');
$feedback .= !empty($feedbacks->item(0)->nodeValue) ? $feedbacks->item(0)->nodeValue.' ' : '';
}
}
$answersFib[] = ['id' => $lastAnswerId,
'title' => $answerTitle,
'score' => $setvar,
'feedback' => $feedback,
'case' => $case, ];
}
}
$answersFib = empty($answersFib) ? '' : $answersFib;
return $answersFib;
}
private function getAnswers($identifier, $assessment, &$lastAnswerId)
{
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
$answers = [];
$questionCcType = $this->getQuestionType($identifier, $assessment);
$questionCcType = $questionCcType['cc'];
$isMultiresponse = ($questionCcType == CC_QUIZ_MULTIPLE_RESPONSE);
if ($questionCcType == CC_QUIZ_MULTIPLE_CHOICE || $isMultiresponse || $questionCcType == CC_QUIZ_TRUE_FALSE) {
$queryAnswers = '//xmlns:item[@ident="'.$identifier.'"]/xmlns:presentation/xmlns:response_lid/xmlns:render_choice/xmlns:response_label';
$queryAnswersWithFlow = '//xmlns:item[@ident="'.$identifier.'"]/xmlns:presentation/xmlns:flow/xmlns:response_lid/xmlns:render_choice/xmlns:response_label';
$queryIndentifer = '@ident';
$queryTitle = 'xmlns:material/xmlns:mattext';
}
if ($questionCcType == CC_QUIZ_ESSAY) {
$queryAnswers = '//xmlns:item[@ident="'.$identifier.'"]/xmlns:presentation/xmlns:response_str';
$queryAnswersWithFlow = '//xmlns:item[@ident="'.$identifier.'"]/xmlns:presentation/xmlns:flow/xmlns:response_str';
$queryIndentifer = '@ident';
$queryTitle = 'xmlns:render_fib';
}
if ($questionCcType == CC_QUIZ_FIB || $questionCcType == CC_QUIZ_PATTERN_MACHT) {
$xpathQuery = '//xmlns:item[@ident="'.$identifier.'"]/xmlns:presentation/xmlns:response_str/@ident';
$xpathQueryWithFlow = '//xmlns:item[@ident="'.$identifier.'"]/xmlns:presentation/xmlns:flow/xmlns:response_str/@ident';
$countResponse = $xpath->evaluate('count('.$xpathQueryWithFlow.')');
if ($countResponse == 0) {
$answerIdentifier = $xpath->query($xpathQuery);
} else {
$answerIdentifier = $xpath->query($xpathQueryWithFlow);
}
$answerIdentifier = !empty($answerIdentifier->item(0)->nodeValue) ? $answerIdentifier->item(0)->nodeValue : '';
if ($questionCcType == CC_QUIZ_FIB) {
$answers = $this->getAnswersFib($identifier, $answerIdentifier, $assessment, $lastAnswerId);
} else {
$answers = $this->getAnswersPatternMatch($identifier, $answerIdentifier, $assessment, $lastAnswerId);
}
} else {
$countResponse = $xpath->evaluate('count('.$queryAnswersWithFlow.')');
if ($countResponse == 0) {
$responseItems = $xpath->query($queryAnswers);
} else {
$responseItems = $xpath->query($queryAnswersWithFlow);
}
if (!empty($responseItems)) {
if ($isMultiresponse) {
$correctAnswerScore = 0;
//get the correct answers count
$canswers_query = "//xmlns:item[@ident='{$identifier}']//xmlns:setvar[@varname='SCORE'][.=100]/../xmlns:conditionvar//xmlns:varequal[@case='Yes'][not(parent::xmlns:not)]";
$canswers = $xpath->query($canswers_query);
if ($canswers->length > 0) {
$correctAnswerScore = round(1.0 / (float) $canswers->length, 7); //weird
$correctAanswersIdent = [];
foreach ($canswers as $cnode) {
$correctAanswersIdent[$cnode->nodeValue] = true;
}
}
}
foreach ($responseItems as $response_item) {
$lastAnswerId++;
$answerIdentifier = $xpath->query($queryIndentifer, $response_item);
$answerIdentifier = !empty($answerIdentifier->item(0)->nodeValue) ? $answerIdentifier->item(0)->nodeValue : '';
$answerTitle = $xpath->query($queryTitle, $response_item);
$answerTitle = !empty($answerTitle->item(0)->nodeValue) ? $answerTitle->item(0)->nodeValue : '';
$answerFeedback = $this->getFeedback($assessment, $answerIdentifier, $identifier, $questionCcType);
$answer_score = $this->getScore($assessment, $answerIdentifier, $identifier);
if ($isMultiresponse && isset($correctAanswersIdent[$answerIdentifier])) {
$answer_score = $correctAnswerScore;
}
$answers[] = ['id' => $lastAnswerId,
'title' => $answerTitle,
'score' => $answer_score,
'identifier' => $answerIdentifier,
'feedback' => $answerFeedback, ];
}
}
}
$answers = empty($answers) ? '' : $answers;
return $answers;
}
private function getScore($assessment, $identifier, $questionIdentifier)
{
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
$resourceProcessing = $xpath->query('//xmlns:item[@ident="'.$questionIdentifier.'"]/xmlns:resprocessing/xmlns:respcondition');
if (!empty($resourceProcessing)) {
foreach ($resourceProcessing as $response) {
$questionCcType = $this->getQuestionType($questionIdentifier, $assessment);
$questionCcType = $questionCcType['cc'];
$varequal = $xpath->query('xmlns:conditionvar/xmlns:varequal', $response);
$varequal = !empty($varequal->item(0)->nodeValue) ? $varequal->item(0)->nodeValue : '';
if (strtolower($varequal) == strtolower($identifier)) {
$score = $xpath->query('xmlns:setvar', $response);
$score = !empty($score->item(0)->nodeValue) ? $score->item(0)->nodeValue : '';
}
}
}
$score = empty($score) ? "0.0000000" : '1.0000000';
return $score;
}
private function getQuestionType($identifier, $assessment)
{
$xpath = Cc1p3Convert::newxPath($assessment, Cc1p3Convert::getquizns());
$metadata = $xpath->query('//xmlns:item[@ident="'.$identifier.'"]/xmlns:itemmetadata/xmlns:qtimetadata/xmlns:qtimetadatafield');
foreach ($metadata as $field) {
$fieldLabel = $xpath->query('xmlns:fieldlabel', $field);
$fieldLabel = !empty($fieldLabel->item(0)->nodeValue) ? $fieldLabel->item(0)->nodeValue : '';
if ($fieldLabel == 'cc_profile') {
$fieldEntry = $xpath->query('xmlns:fieldentry', $field);
$type = !empty($fieldEntry->item(0)->nodeValue) ? $fieldEntry->item(0)->nodeValue : '';
}
}
$returnType = [];
$returnType['qtype'] = '';
$returnType['cc'] = $type;
switch ($type) {
case CC_QUIZ_MULTIPLE_CHOICE:
$returnType['qtype'] = 'unique_answer';
break;
case CC_QUIZ_MULTIPLE_RESPONSE:
$returnType['qtype'] = 'multiple_answer';
break;
case CC_QUIZ_FIB:
$returnType['qtype'] = 'fib';
break;
case CC_QUIZ_ESSAY:
$returnType['qtype'] = 'essay';
break;
}
return $returnType;
}
}

View File

@@ -0,0 +1,207 @@
<?php
/* For licensing terms, see /license.txt */
class Cc13Resource extends Cc13Entities
{
public function generateData($resource_type)
{
$data = [];
if (!empty(Cc1p3Convert::$instances['instances'][$resource_type])) {
foreach (Cc1p3Convert::$instances['instances'][$resource_type] as $instance) {
$data[] = $this->getResourceData($instance);
}
}
return $data;
}
public function storeLinks($links)
{
foreach ($links as $link) {
$_POST['title'] = $link[1];
$_POST['url'] = $link[4];
$_POST['description'] = '';
$_POST['category_id'] = 0;
$_POST['target'] = '_blank';
Link::addlinkcategory('link');
}
return true;
}
public function storeDocuments($documents, $path)
{
$courseInfo = api_get_course_info();
$sessionId = api_get_session_id();
$groupId = api_get_group_id();
$documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'].'/document';
foreach ($documents as $document) {
if ($document[2] == 'file') {
$filepath = $path.DIRECTORY_SEPARATOR.$document[4];
$files = [];
$files['file']['name'] = $document[1];
$files['file']['tmp_name'] = $filepath;
$files['file']['type'] = mime_content_type($filepath);
$files['file']['error'] = 0;
$files['file']['size'] = filesize($filepath);
$files['file']['from_file'] = true;
$files['file']['move_file'] = true;
$_POST['language'] = $courseInfo['language'];
$_POST['cc_import'] = true;
$destDir = dirname($document[4]);
// Create the subdirectory if necessary
create_unexisting_directory(
$courseInfo,
api_get_user_id(),
$sessionId,
$groupId,
null,
$documentPath,
'/commoncartridge/'.$destDir,
$destDir,
1
);
DocumentManager::upload_document(
$files,
'/commoncartridge/'.$destDir,
$document[1],
'',
null,
null,
true,
true
);
}
}
return true;
}
public function getResourceData($instance)
{
//var_dump($instance);
$xpath = Cc1p3Convert::newxPath(Cc1p3Convert::$manifest, Cc1p3Convert::$namespaces);
$link = '';
if ($instance['common_cartridge_type'] == Cc1p3Convert::CC_TYPE_WEBCONTENT || $instance['common_cartridge_type'] == Cc1p3Convert::CC_TYPE_ASSOCIATED_CONTENT) {
$resource = $xpath->query('/imscc:manifest/imscc:resources/imscc:resource[@identifier="'.$instance['resource_identifier'].'"]/@href');
if ($resource->length > 0) {
$resource = !empty($resource->item(0)->nodeValue) ? $resource->item(0)->nodeValue : '';
} else {
$resource = '';
}
if (empty($resource)) {
unset($resource);
// src has been set in CcBase::createInstances() based on the contents of <file href="...">
$resource = $instance['src'];
}
if (!empty($resource)) {
$link = $resource;
}
}
if ($instance['common_cartridge_type'] == Cc1p3Convert::CC_TYPE_WEBLINK) {
// src has been set in CcBase::createInstances() based on the contents of <file href="...">
$external_resource = $instance['src'];
if ($external_resource) {
$resource = $this->loadXmlResource(Cc1p3Convert::$pathToManifestFolder.DIRECTORY_SEPARATOR.$external_resource);
if (!empty($resource)) {
$xpath = Cc1p3Convert::newxPath($resource, Cc1p3Convert::$resourcens);
$resource = $xpath->query('/wl:webLink/wl:url/@href');
if ($resource->length > 0) {
$rawlink = $resource->item(0)->nodeValue;
if (!validateUrlSyntax($rawlink, 's+')) {
$changed = rawurldecode($rawlink);
if (validateUrlSyntax($changed, 's+')) {
$link = $changed;
} else {
$link = 'http://invalidurldetected/';
}
} else {
$link = htmlspecialchars(trim($rawlink), ENT_COMPAT, 'UTF-8', false);
}
}
}
}
}
$mod_type = 'file';
$mod_options = 'objectframe';
$mod_reference = $link;
$mod_alltext = '';
// detect if we are dealing with html file
if (!empty($link) && ($instance['common_cartridge_type'] == Cc1p3Convert::CC_TYPE_WEBCONTENT)) {
$ext = strtolower(pathinfo($link, PATHINFO_EXTENSION));
if (in_array($ext, ['html', 'htm', 'xhtml'])) {
$mod_type = 'html';
//extract the content of the file and treat it
$rootpath = realpath(Cc1p3Convert::$pathToManifestFolder);
$htmlpath = realpath($rootpath.DIRECTORY_SEPARATOR.$link);
$dirpath = dirname($htmlpath);
if (file_exists($htmlpath)) {
$fcontent = file_get_contents($htmlpath);
$mod_alltext = $this->prepareContent($fcontent);
$mod_reference = '';
$mod_options = '';
/**
* try to handle embedded resources
* images, linked static resources, applets, videos.
*/
$doc = new DOMDocument();
$cdir = getcwd();
chdir($dirpath);
try {
$doc->loadHTML($mod_alltext);
$xpath = new DOMXPath($doc);
$attributes = ['href', 'src', 'background', 'archive', 'code'];
$qtemplate = "//*[@##][not(contains(@##,'://'))]/@##";
$query = '';
foreach ($attributes as $attrname) {
if (!empty($query)) {
$query .= " | ";
}
$query .= str_replace('##', $attrname, $qtemplate);
}
$list = $xpath->query($query);
$searches = [];
$replaces = [];
foreach ($list as $resrc) {
$rpath = $resrc->nodeValue;
$rtp = realpath($rpath);
if (($rtp !== false) && is_file($rtp)) {
//file is there - we are in business
$strip = str_replace("\\", "/", str_ireplace($rootpath, '/', $rtp));
//$encoded_file = '$@FILEPHP@$'.str_replace('/', '$@SLASH@$', $strip);
$encoded_file = $strip;
$searches[] = $resrc->nodeValue;
$replaces[] = $encoded_file;
}
}
$mod_alltext = str_replace($searches, $replaces, $mod_alltext);
} catch (Exception $e) {
//silence the complaints
}
chdir($cdir);
$mod_alltext = self::safexml($mod_alltext);
}
}
}
$values = [
$instance['instance'],
self::safexml($instance['title']),
$mod_type,
$mod_alltext,
$mod_reference, // src or href
$mod_options,
];
return $values;
}
}

View File

@@ -0,0 +1,248 @@
<?php
/* For licensing terms, see /license.txt */
class CcEntities
{
/**
* Prepares convert for inclusion into XML.
*
* @param string $value
*
* @return string
*/
public static function safexml($value)
{
$result = htmlspecialchars(html_entity_decode($value, ENT_QUOTES, 'UTF-8'),
ENT_NOQUOTES,
'UTF-8',
false);
return $result;
}
public function loadXmlResource($path_to_file)
{
$resource = new DOMDocument();
Cc1p3Convert::logAction('Load the XML resource file: '.$path_to_file);
if (!$resource->load($path_to_file)) {
Cc1p3Convert::logAction('Cannot load the XML resource file: '.$path_to_file, false);
}
return $resource;
}
public function updateSources($html, $rootPath = '')
{
$document = $this->loadHtml($html);
$tags = ['img' => 'src', 'a' => 'href'];
foreach ($tags as $tag => $attribute) {
$elements = $document->getElementsByTagName($tag);
foreach ($elements as $element) {
$attribute_value = $element->getAttribute($attribute);
$protocol = parse_url($attribute_value, PHP_URL_SCHEME);
if (empty($protocol)) {
// The CC standard changed with the renaming of IMS Global to 1stEdTech, so we don't know what we're going to get
$attribute_value = str_replace("\$1EdTech[-_]CC[-_]FILEBASE\$", "", $attribute_value);
$attribute_value = str_replace("\$IMS[-_]CC[-_]FILEBASE\$", "", $attribute_value);
$attribute_value = $this->fullPath($rootPath."/".$attribute_value, "/");
//$attribute_value = "\$@FILEPHP@\$"."/".$attribute_value;
}
$element->setAttribute($attribute, $attribute_value);
}
}
$html = $this->htmlInsidebody($document);
return $html;
}
public function fullPath($path, $dir_sep = DIRECTORY_SEPARATOR)
{
$token = '/\$(?:IMS|1EdTech)[-_]CC[-_]FILEBASE\$/';
$path = preg_replace($token, '', $path);
if (is_string($path) && ($path != '')) {
$dot_dir = '.';
$up_dir = '..';
$length = strlen($path);
$rtemp = trim($path);
$start = strrpos($path, $dir_sep);
$can_continue = ($start !== false);
$result = $can_continue ? '' : $path;
$rcount = 0;
while ($can_continue) {
$dir_part = ($start !== false) ? substr($rtemp, $start + 1, $length - $start) : $rtemp;
$can_continue = ($dir_part !== false);
if ($can_continue) {
if ($dir_part != $dot_dir) {
if ($dir_part == $up_dir) {
$rcount++;
} else {
if ($rcount > 0) {
$rcount--;
} else {
$result = ($result == '') ? $dir_part : $dir_part.$dir_sep.$result;
}
}
}
$rtemp = substr($path, 0, $start);
$start = strrpos($rtemp, $dir_sep);
$can_continue = (($start !== false) || (strlen($rtemp) > 0));
}
}
}
return $result;
}
public function includeTitles($html)
{
$document = $this->loadHtml($html);
$images = $document->getElementsByTagName('img');
foreach ($images as $image) {
$src = $image->getAttribute('src');
$alt = $image->getAttribute('alt');
$title = $image->getAttribute('title');
$filename = pathinfo($src);
$filename = $filename['filename'];
$alt = empty($alt) ? $filename : $alt;
$title = empty($title) ? $filename : $title;
$image->setAttribute('alt', $alt);
$image->setAttribute('title', $title);
}
$html = $this->htmlInsidebody($document);
return $html;
}
public function getExternalXml($identifier)
{
$xpath = Cc1p3Convert::newxPath(Cc1p3Convert::$manifest, Cc1p3Convert::$namespaces);
$files = $xpath->query('/imscc:manifest/imscc:resources/imscc:resource[@identifier="'.
$identifier.'"]/imscc:file/@href');
if (empty($files)) {
$response = '';
} else {
$response = $files->item(0)->nodeValue;
}
return $response;
}
public function generateRandomString($length = 6)
{
$response = '';
$source = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
if ($length > 0) {
$response = '';
$source = str_split($source, 1);
for ($i = 1; $i <= $length; $i++) {
$num = mt_rand(1, count($source));
$response .= $source[$num - 1];
}
}
return $response;
}
public function truncateText($text, $max, $remove_html)
{
if ($max > 10) {
$text = substr($text, 0, ($max - 6)).' [...]';
} else {
$text = substr($text, 0, $max);
}
$text = $remove_html ? strip_tags($text) : $text;
return $text;
}
protected function prepareContent($content)
{
$result = $content;
if (empty($result)) {
return '';
}
$encoding = null;
$xml_error = new LibxmlErrorsMgr();
$dom = new DOMDocument();
$dom->validateOnParse = false;
$dom->strictErrorChecking = false;
if ($dom->loadHTML($content)) {
$encoding = $dom->xmlEncoding;
}
if (empty($encoding)) {
$encoding = mb_detect_encoding($content, 'auto', true);
}
if (!empty($encoding) && !mb_check_encoding($content, 'UTF-8')) {
$result = mb_convert_encoding($content, 'UTF-8', $encoding);
}
// See if we can strip off body tag and anything outside of it.
foreach (['body', 'html'] as $tagname) {
$regex = str_replace('##', $tagname, "/<##[^>]*>(.+)<\/##>/is");
if (preg_match($regex, $result, $matches)) {
$result = $matches[1];
break;
}
}
return $result;
}
/**
* @param string $html
*
* @return DOMDocument
*/
private function loadHtml($html)
{
// Need to make sure that the html passed has charset meta tag.
$metatag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
if (strpos($html, $metatag) === false) {
$html = '<html><head>'.$metatag.'</head><body>'.$html.'</body></html>';
}
$document = new DOMDocument();
@$document->loadHTML($html);
return $document;
}
/**
* @param DOMDocument $domdocument
*
* @return string
*/
private function htmlInsidebody($domdocument)
{
$html = '';
$bodyitems = $domdocument->getElementsByTagName('body');
if ($bodyitems->length > 0) {
$body = $bodyitems->item(0);
$html = str_ireplace(['<body>', '</body>'], '', $body->C14N());
}
return $html;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/* For licensing terms, see /license.txt */
// GENERAL PARAMETERS ************************************************************************************************* //
define('ROOT_DEEP', 2);
// PACKAGES FORMATS *************************************************************************************************** //
define('FORMAT_UNKNOWN', 'NA');
define('FORMAT_COMMON_CARTRIDGE', 'CC');
define('FORMAT_BLACK_BOARD', 'BB');
// FORMATS NAMESPACES ************************************************************************************************* //
define('NS_COMMON_CARTRIDGE', 'http://www.imsglobal.org/xsd/imsccv1p3/imscp_v1p1');
define('NS_BLACK_BOARD', 'http://www.blackboard.com/content-packaging');
// CC RESOURCES TYPE ************************************************************************************************** //
define('CC_TYPE_FORUM', 'imsdt_xmlv1p3');
define('CC_TYPE_QUIZ', 'imsqti_xmlv1p3/imscc_xmlv1p3/assessment');
define('CC_TYPE_QUESTION_BANK', 'imsqti_xmlv1p3/imscc_xmlv1p3/question-bank');
define('CC_TYPE_WEBLINK', 'imswl_xmlv1p3');
define('CC_TYPE_WEBCONTENT', 'webcontent');
define('CC_TYPE_ASSOCIATED_CONTENT', 'associatedcontent/imscc_xmlv1p3/learning-application-resource');
define('CC_TYPE_BASICLTI', 'imsbasiclti_xmlv1p3');
define('CC_TYPE_EMPTY', '');
// COURSE RESOURCES TYPE ********************************************************************************************** //
define('TOOL_TYPE_FORUM', 'forum');
define('TOOL_TYPE_QUIZ', 'quiz');
define('TOOL_TYPE_DOCUMENT', 'document');
define('TOOL_TYPE_WEBLINK', 'link');
// UNKNOWN TYPE ******************************************************************************************************* //
define('TYPE_UNKNOWN', '[UNKNOWN]');
// CC QUESTIONS TYPES ************************************************************************************************* //
define('CC_QUIZ_MULTIPLE_CHOICE', 'cc.multiple_choice.v0p1');
define('CC_QUIZ_TRUE_FALSE', 'cc.true_false.v0p1');
define('CC_QUIZ_FIB', 'cc.fib.v0p1');
define('CC_QUIZ_MULTIPLE_RESPONSE', 'cc.multiple_response.v0p1');
define('CC_QUIZ_PATTERN_MACHT', 'cc.pattern_match.v0p1');
define('CC_QUIZ_ESSAY', 'cc.essay.v0p1');
//COURSE QUESTIONS TYPES ********************************************************************************************** //
define('TOOL_QUIZ_MULTIPLE_CHOICE', 'multichoice');
define('TOOL_QUIZ_MULTIANSWER', 'multianswer');
define('TOOL_QUIZ_MULTIPLE_RESPONSE', 'multichoice');

View File

@@ -0,0 +1,447 @@
<?php
/* Source: https://github.com/moodle/moodle/blob/MOODLE_310_STABLE/lib/validateurlsyntax.php under GNU/GPL license */
/**
* BEGINNING OF validateUrlSyntax() function.
*/
function validateUrlSyntax($urladdr, $options = "")
{
// Force Options parameter to be lower case
// DISABLED PERMAMENTLY - OK to remove from code
// $options = strtolower($options);
// Check Options Parameter
if (!preg_match('/^([sHSEFRuPaIpfqr][+?-])*$/', $options)) {
trigger_error("Options attribute malformed", E_USER_ERROR);
}
// Set Options Array, set defaults if options are not specified
// Scheme
if (strpos($options, 's') === false) {
$aOptions['s'] = '?';
} else {
$aOptions['s'] = substr($options, strpos($options, 's') + 1, 1);
}
// http://
if (strpos($options, 'H') === false) {
$aOptions['H'] = '?';
} else {
$aOptions['H'] = substr($options, strpos($options, 'H') + 1, 1);
}
// https:// (SSL)
if (strpos($options, 'S') === false) {
$aOptions['S'] = '?';
} else {
$aOptions['S'] = substr($options, strpos($options, 'S') + 1, 1);
}
// mailto: (email)
if (strpos($options, 'E') === false) {
$aOptions['E'] = '-';
} else {
$aOptions['E'] = substr($options, strpos($options, 'E') + 1, 1);
}
// ftp://
if (strpos($options, 'F') === false) {
$aOptions['F'] = '-';
} else {
$aOptions['F'] = substr($options, strpos($options, 'F') + 1, 1);
}
// rtmp://
if (strpos($options, 'R') === false) {
$aOptions['R'] = '-';
} else {
$aOptions['R'] = substr($options, strpos($options, 'R') + 1, 1);
}
// User section
if (strpos($options, 'u') === false) {
$aOptions['u'] = '?';
} else {
$aOptions['u'] = substr($options, strpos($options, 'u') + 1, 1);
}
// Password in user section
if (strpos($options, 'P') === false) {
$aOptions['P'] = '?';
} else {
$aOptions['P'] = substr($options, strpos($options, 'P') + 1, 1);
}
// Address Section
if (strpos($options, 'a') === false) {
$aOptions['a'] = '+';
} else {
$aOptions['a'] = substr($options, strpos($options, 'a') + 1, 1);
}
// IP Address in address section
if (strpos($options, 'I') === false) {
$aOptions['I'] = '?';
} else {
$aOptions['I'] = substr($options, strpos($options, 'I') + 1, 1);
}
// Port number
if (strpos($options, 'p') === false) {
$aOptions['p'] = '?';
} else {
$aOptions['p'] = substr($options, strpos($options, 'p') + 1, 1);
}
// File Path
if (strpos($options, 'f') === false) {
$aOptions['f'] = '?';
} else {
$aOptions['f'] = substr($options, strpos($options, 'f') + 1, 1);
}
// Query Section
if (strpos($options, 'q') === false) {
$aOptions['q'] = '?';
} else {
$aOptions['q'] = substr($options, strpos($options, 'q') + 1, 1);
}
// Fragment (Anchor)
if (strpos($options, 'r') === false) {
$aOptions['r'] = '?';
} else {
$aOptions['r'] = substr($options, strpos($options, 'r') + 1, 1);
}
// Loop through options array, to search for and replace "-" to "{0}" and "+" to ""
foreach ($aOptions as $key => $value) {
if ($value == '-') {
$aOptions[$key] = '{0}';
}
if ($value == '+') {
$aOptions[$key] = '';
}
}
// DEBUGGING - Unescape following line to display to screen current option values
// echo '<pre>'; print_r($aOptions); echo '</pre>';
// Preset Allowed Characters
$alphanum = '[a-zA-Z0-9]'; // Alpha Numeric
$unreserved = '[a-zA-Z0-9_.!~*'.'\''.'()-]';
$escaped = '(%[0-9a-fA-F]{2})'; // Escape sequence - In Hex - %6d would be a 'm'
$reserved = '[;/?:@&=+$,]'; // Special characters in the URI
// Beginning Regular Expression
// Scheme - Allows for 'http://', 'https://', 'mailto:', 'ftp://' or 'rtmp://'
$scheme = '(';
if ($aOptions['H'] === '') {
$scheme .= 'http://';
} elseif ($aOptions['S'] === '') {
$scheme .= 'https://';
} elseif ($aOptions['E'] === '') {
$scheme .= 'mailto:';
} elseif ($aOptions['F'] === '') {
$scheme .= 'ftp://';
} elseif ($aOptions['R'] === '') {
$scheme .= 'rtmp://';
} else {
if ($aOptions['H'] === '?') {
$scheme .= '|(http://)';
}
if ($aOptions['S'] === '?') {
$scheme .= '|(https://)';
}
if ($aOptions['E'] === '?') {
$scheme .= '|(mailto:)';
}
if ($aOptions['F'] === '?') {
$scheme .= '|(ftp://)';
}
if ($aOptions['R'] === '?') {
$scheme .= '|(rtmp://)';
}
$scheme = str_replace('(|', '(', $scheme); // fix first pipe
}
$scheme .= ')'.$aOptions['s'];
// End setting scheme
// User Info - Allows for 'username@' or 'username:password@'. Note: contrary to rfc, I removed ':' from username section, allowing it only in password.
// /---------------- Username -----------------------\ /-------------------------------- Password ------------------------------\
$userinfo = '(('.$unreserved.'|'.$escaped.'|[;&=+$,]'.')+(:('.$unreserved.'|'.$escaped.'|[;:&=+$,]'.')+)'.$aOptions['P'].'@)'.$aOptions['u'];
// IP ADDRESS - Allows 0.0.0.0 to 255.255.255.255
$ipaddress = '((((2(([0-4][0-9])|(5[0-5])))|([01]?[0-9]?[0-9]))\.){3}((2(([0-4][0-9])|(5[0-5])))|([01]?[0-9]?[0-9])))';
// Tertiary Domain(s) - Optional - Multi - Although some sites may use other characters, the RFC says tertiary domains have the same naming restrictions as second level domains
$domain_tertiary = '('.$alphanum.'(([a-zA-Z0-9-]{0,62})'.$alphanum.')?\.)*';
$domain_toplevel = '([a-zA-Z](([a-zA-Z0-9-]*)[a-zA-Z0-9])?)';
if ($aOptions['I'] === '{0}') { // IP Address Not Allowed
$address = '('.$domain_tertiary. /* MDL-9295 $domain_secondary . */ $domain_toplevel.')';
} elseif ($aOptions['I'] === '') { // IP Address Required
$address = '('.$ipaddress.')';
} else { // IP Address Optional
$address = '(('.$ipaddress.')|('.$domain_tertiary. /* MDL-9295 $domain_secondary . */ $domain_toplevel.'))';
}
$address = $address.$aOptions['a'];
// Port Number - :80 or :8080 or :65534 Allows range of :0 to :65535
// (0-59999) |(60000-64999) |(65000-65499) |(65500-65529) |(65530-65535)
$port_number = '(:(([0-5]?[0-9]{1,4})|(6[0-4][0-9]{3})|(65[0-4][0-9]{2})|(655[0-2][0-9])|(6553[0-5])))'.$aOptions['p'];
// Path - Can be as simple as '/' or have multiple folders and filenames
$path = '(/((;)?('.$unreserved.'|'.$escaped.'|'.'[:@&=+$,]'.')+(/)?)*)'.$aOptions['f'];
// Query Section - Accepts ?var1=value1&var2=value2 or ?2393,1221 and much more
$querystring = '(\?('.$reserved.'|'.$unreserved.'|'.$escaped.')*)'.$aOptions['q'];
// Fragment Section - Accepts anchors such as #top
$fragment = '(\#('.$reserved.'|'.$unreserved.'|'.$escaped.')*)'.$aOptions['r'];
// Building Regular Expression
$regexp = '#^'.$scheme.$userinfo.$address.$port_number.$path.$querystring.$fragment.'$#i';
// DEBUGGING - Uncomment Line Below To Display The Regular Expression Built
// echo '<pre>' . htmlentities(wordwrap($regexp,70,"\n",1)) . '</pre>';
// Running the regular expression
if (preg_match($regexp, $urladdr)) {
return true; // The domain passed
} else {
return false; // The domain didn't pass the expression
}
} // END Function validateUrlSyntax()
/**
* About ValidateEmailSyntax():
* This function uses the ValidateUrlSyntax() function to easily check the
* syntax of an email address. It accepts the same options as ValidateURLSyntax
* but defaults them for email addresses.
*
* Released under same license as validateUrlSyntax()
*/
function validateEmailSyntax($emailaddr, $options = "")
{
// Check Options Parameter
if (!preg_match('/^([sHSEFuPaIpfqr][+?-])*$/', $options)) {
trigger_error("Options attribute malformed", E_USER_ERROR);
}
// Set Options Array, set defaults if options are not specified
// Scheme
if (strpos($options, 's') === false) {
$aOptions['s'] = '-';
} else {
$aOptions['s'] = substr($options, strpos($options, 's') + 1, 1);
}
// http://
if (strpos($options, 'H') === false) {
$aOptions['H'] = '-';
} else {
$aOptions['H'] = substr($options, strpos($options, 'H') + 1, 1);
}
// https:// (SSL)
if (strpos($options, 'S') === false) {
$aOptions['S'] = '-';
} else {
$aOptions['S'] = substr($options, strpos($options, 'S') + 1, 1);
}
// mailto: (email)
if (strpos($options, 'E') === false) {
$aOptions['E'] = '?';
} else {
$aOptions['E'] = substr($options, strpos($options, 'E') + 1, 1);
}
// ftp://
if (strpos($options, 'F') === false) {
$aOptions['F'] = '-';
} else {
$aOptions['F'] = substr($options, strpos($options, 'F') + 1, 1);
}
// User section
if (strpos($options, 'u') === false) {
$aOptions['u'] = '+';
} else {
$aOptions['u'] = substr($options, strpos($options, 'u') + 1, 1);
}
// Password in user section
if (strpos($options, 'P') === false) {
$aOptions['P'] = '-';
} else {
$aOptions['P'] = substr($options, strpos($options, 'P') + 1, 1);
}
// Address Section
if (strpos($options, 'a') === false) {
$aOptions['a'] = '+';
} else {
$aOptions['a'] = substr($options, strpos($options, 'a') + 1, 1);
}
// IP Address in address section
if (strpos($options, 'I') === false) {
$aOptions['I'] = '-';
} else {
$aOptions['I'] = substr($options, strpos($options, 'I') + 1, 1);
}
// Port number
if (strpos($options, 'p') === false) {
$aOptions['p'] = '-';
} else {
$aOptions['p'] = substr($options, strpos($options, 'p') + 1, 1);
}
// File Path
if (strpos($options, 'f') === false) {
$aOptions['f'] = '-';
} else {
$aOptions['f'] = substr($options, strpos($options, 'f') + 1, 1);
}
// Query Section
if (strpos($options, 'q') === false) {
$aOptions['q'] = '-';
} else {
$aOptions['q'] = substr($options, strpos($options, 'q') + 1, 1);
}
// Fragment (Anchor)
if (strpos($options, 'r') === false) {
$aOptions['r'] = '-';
} else {
$aOptions['r'] = substr($options, strpos($options, 'r') + 1, 1);
}
// Generate options
$newoptions = '';
foreach ($aOptions as $key => $value) {
$newoptions .= $key.$value;
}
// DEBUGGING - Uncomment line below to display generated options
// echo '<pre>' . $newoptions . '</pre>';
// Send to validateUrlSyntax() and return result
return validateUrlSyntax($emailaddr, $newoptions);
} // END Function validateEmailSyntax()
/**
* About ValidateFtpSyntax():
* This function uses the ValidateUrlSyntax() function to easily check the
* syntax of an FTP address. It accepts the same options as ValidateURLSyntax
* but defaults them for FTP addresses.
*
* Usage:
* <code>
* validateFtpSyntax( url_to_check[, options])
* </code>
* url_to_check - string - The url to check
*
* options - string - A optional string of options to set which parts of
* the url are required, optional, or not allowed. Each option
* must be followed by a "+" for required, "?" for optional, or
* "-" for not allowed. See ValidateUrlSyntax() docs for option list.
*
* The default options are changed to:
* s?H-S-E-F+u?P?a+I?p?f?q-r-
*
* Examples:
* <code>
* validateFtpSyntax('ftp://netscape.com')
* validateFtpSyntax('moz:iesucks@netscape.com')
* validateFtpSyntax('ftp://netscape.com:2121/browsers/ns7/', 'u-')
* </code>
*
* Author(s):
* Rod Apeldoorn - rod(at)canowhoopass(dot)com
*
*
* Homepage:
* http://www.canowhoopass.com/
*
*
* License:
* Copyright 2004 - Rod Apeldoorn
*
* Released under same license as validateUrlSyntax(). For details, contact me.
*/
function validateFtpSyntax($ftpaddr, $options = "")
{
// Check Options Parameter
if (!preg_match('/^([sHSEFuPaIpfqr][+?-])*$/', $options)) {
trigger_error("Options attribute malformed", E_USER_ERROR);
}
// Set Options Array, set defaults if options are not specified
// Scheme
if (strpos($options, 's') === false) {
$aOptions['s'] = '?';
} else {
$aOptions['s'] = substr($options, strpos($options, 's') + 1, 1);
}
// http://
if (strpos($options, 'H') === false) {
$aOptions['H'] = '-';
} else {
$aOptions['H'] = substr($options, strpos($options, 'H') + 1, 1);
}
// https:// (SSL)
if (strpos($options, 'S') === false) {
$aOptions['S'] = '-';
} else {
$aOptions['S'] = substr($options, strpos($options, 'S') + 1, 1);
}
// mailto: (email)
if (strpos($options, 'E') === false) {
$aOptions['E'] = '-';
} else {
$aOptions['E'] = substr($options, strpos($options, 'E') + 1, 1);
}
// ftp://
if (strpos($options, 'F') === false) {
$aOptions['F'] = '+';
} else {
$aOptions['F'] = substr($options, strpos($options, 'F') + 1, 1);
}
// User section
if (strpos($options, 'u') === false) {
$aOptions['u'] = '?';
} else {
$aOptions['u'] = substr($options, strpos($options, 'u') + 1, 1);
}
// Password in user section
if (strpos($options, 'P') === false) {
$aOptions['P'] = '?';
} else {
$aOptions['P'] = substr($options, strpos($options, 'P') + 1, 1);
}
// Address Section
if (strpos($options, 'a') === false) {
$aOptions['a'] = '+';
} else {
$aOptions['a'] = substr($options, strpos($options, 'a') + 1, 1);
}
// IP Address in address section
if (strpos($options, 'I') === false) {
$aOptions['I'] = '?';
} else {
$aOptions['I'] = substr($options, strpos($options, 'I') + 1, 1);
}
// Port number
if (strpos($options, 'p') === false) {
$aOptions['p'] = '?';
} else {
$aOptions['p'] = substr($options, strpos($options, 'p') + 1, 1);
}
// File Path
if (strpos($options, 'f') === false) {
$aOptions['f'] = '?';
} else {
$aOptions['f'] = substr($options, strpos($options, 'f') + 1, 1);
}
// Query Section
if (strpos($options, 'q') === false) {
$aOptions['q'] = '-';
} else {
$aOptions['q'] = substr($options, strpos($options, 'q') + 1, 1);
}
// Fragment (Anchor)
if (strpos($options, 'r') === false) {
$aOptions['r'] = '-';
} else {
$aOptions['r'] = substr($options, strpos($options, 'r') + 1, 1);
}
// Generate options
$newoptions = '';
foreach ($aOptions as $key => $value) {
$newoptions .= $key.$value;
}
// Send to validateUrlSyntax() and return result
return validateUrlSyntax($ftpaddr, $newoptions);
} // END Function validateFtpSyntax()