$warnings = array(); $messages = array(); if (!isset($_REQUEST["qtiid"])) { badrequest("no QTI ID was specified"); } $item = getitem($_REQUEST["qtiid"]); if (!$item) { badrequest("no item with the given QTI ID exists in the database"); } // if not cloning... if (!isset($_REQUEST["clone"])) { // only the owner can edit it if (!loggedin()) { badrequest("you're not logged in so can't edit this item"); } if ($item["user"] != username()) { badrequest("you're not the owner of this item and so can't edit it"); } // if the item's already in session memory redirect straight to Eqiat if (isset($_SESSION["items"]) && array_key_exists($_REQUEST["qtiid"], $_SESSION["items"])) { redirect(SITEROOT_WEB . "eqiat/#item_" . $_REQUEST["qtiid"]); } } // make a QTIAssessmentItem object from the data we have and put it in session memory $metadata = array("description" => $item["description"], "keywords" => $item["keywords"]); $ai = xmltoqtiobject($item["xml"], $errors, $warnings, $messages, $metadata, isset($_REQUEST["clone"])); if ($ai === false) { servererror("Errors:\n" . implode("\n", $errors) . "\n\nWarnings:\n" . implode("\n", $warnings) . "\n\nMessages:\n" . implode("\n", $messages)); } $ai->sessionStore(); redirect(SITEROOT_WEB . "eqiat/#item_" . $ai->getQTIID());
public function getContentPackage() { // build the manifest $manifest = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?> <manifest xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" xmlns:imsmd="' . NS_IMSMD . '" xmlns:imsqti="' . NS_IMSQTI . '" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd http://www.imsglobal.org/xsd/imsmd_v1p2 imsmd_v1p2p4.xsd http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/imsqti_v2p1.xsd" /> '); $manifest->addAttribute("identifier", $this->getMID()); // organizations element $manifest->addChild("organizations"); // resources element $rs = $manifest->addChild("resources"); $r = $rs->addChild("resource"); $r->addAttribute("identifier", $this->getQTIID()); $r->addAttribute("type", "imsqti_item_xmlv2p1"); $r->addAttribute("href", "{$this->getTitleFS()}.xml"); $md = $r->addChild("metadata"); // resource qti metadata $qmd = $md->addChild("qtiMetadata", null, NS_IMSQTI); $qmd->addChild("timeDependent", "false", NS_IMSQTI); foreach ($this->interactionTypes() as $it) { $qmd->addChild("interactionType", $it, NS_IMSQTI); } $qmd->addChild("feedbackType", is_null($this->data("feedback")) ? "none" : "nonadaptive", NS_IMSQTI); $qmd->addChild("solutionAvailable", "true", NS_IMSQTI); // resource LOM metadata $lom = $md->addChild("lom", null, NS_IMSMD); $g = $lom->addChild("general", null, NS_IMSMD); $g->addChild("title", null, NS_IMSMD)->addChild("langstring", $this->data("data"), NS_IMSMD); if (!is_null($this->data("description"))) { $g->addChild("description", null, NS_IMSMD)->addChild("langstring", $this->data("description"), NS_IMSMD); } foreach ($this->getKeywords() as $keyword) { $g->addChild("keyword", null, NS_IMSMD)->addChild("langstring", $keyword, NS_IMSMD); } // file element $r->addChild("file")->addAttribute("href", "{$this->getTitleFS()}.xml"); // make temporary zip archive $zip = new ZipArchive(); $filename = "/tmp/" . uniqid("zip"); if ($zip->open($filename, ZIPARCHIVE::CREATE) !== true) { servererror("couldn't make zip file"); } $zip->addFromString("imsmanifest.xml", simplexml_indented_string($manifest)); $zip->addFromString("{$this->getTitleFS()}.xml", $this->getQTIIndentedString()); $zip->close(); // slurp contents $zipcontents = file_get_contents($filename); // delete the temporary zip archive unlink($filename); // return the zip contents as a binary string return $zipcontents; }
function qtiengine_bodydiv_html(SimpleXMLElement $page, $divid = "qtienginebodydiv") { // php5's support for default namespace is useless so we have to define it // manually $namespaces = $page->html->getNamespaces(); $defaultnamespace = $namespaces[""]; $page->registerXPathNamespace("n", $defaultnamespace); $bodydivs = $page->xpath("//n:div[@id='body']"); if (count($bodydivs) != 1) { servererror("didn't get expected HTML output from QTIEngine"); } $bodydiv = $bodydivs[0]; $bodydiv["id"] = $divid; foreach ($page->xpath("//n:input[@type='checkbox']") as $checkbox) { if (!preg_match('%\\]$%', (string) $checkbox["name"])) { $checkbox["name"] = (string) $checkbox["name"] . "[]"; } } return preg_replace(array('%<(/?)h2\\b%', '%<hr\\b.*?>%'), array('<\\1h3', ''), xhtml_to_html(simplexml_indented_string($bodydiv))); }
/* * Eqiat * Easy QTI Item Authoring Tool */ /*------------------------------------------------------------------------------ (c) 2010 JISC-funded EASiHE project, University of Southampton Licensed under the Creative Commons 'Attribution non-commercial share alike' licence -- see the LICENCE file for more details ------------------------------------------------------------------------------*/ if (isset($_REQUEST["itemtype"])) { // item type chosen // look for a item type class with this name $classname = "QTI" . ucfirst($_REQUEST["itemtype"]); if (!@class_exists($classname) || !is_subclass_of($classname, "QTIAssessmentItem")) { servererror("Item type doesn't exist or not implemented"); } $ai = new $classname(); $ai->sessionStore(); $action = new EditAssessmentItemAction(); redirect($action->actionURL($ai, false)); } // choose from a list of item types $items = item_types(); $GLOBALS["title"] = "New assessment item"; include "htmlheader.php"; ?> <h2><?php echo $GLOBALS["title"]; ?> </h2>
// build request string $multipart = new HttpRequestBodyMultipart(); $multipart->addpart("actionUrl", $actionurl); $multipart->addpart("uploadedContent", $item["xml"], "application/xml", "qb_" . $item["identifier"] . ".xml"); // set up curl handle to upload it $curl = curl_init(); curl_setopt_array($curl, array(CURLOPT_URL => "http://" . QTIENGINE_HOST . ":" . QTIENGINE_PORT . QTIENGINE_PATH . "rest/upload", CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => array("Content-Type: multipart/form-data; boundary=" . $multipart->boundary(), "Expect: "), CURLOPT_POSTFIELDS => $multipart->requeststring(), CURLOPT_FOLLOWLOCATION => true, CURLOPT_USERAGENT => PROGRAMNAME . "/" . VERSION)); // upload it -- curl will follow the location headers and return the // final response $response = curl_exec($curl); // get jsessionid from last URL we were directed to $responseinfo = curl_getinfo($curl); $_SESSION["qtiengine_session"] = preg_replace('%.*;jsessionid=([0-9A-F]+)\\?.*%', '\\1', $responseinfo["url"]); } // parse response $xml = new SimpleXMLElement($response) or servererror("couldn't parse XML response"); // inject javascript to header ob_start(); ?> <script type="text/javascript"> $j(document).ready(function() { $j("#getcommentslink").click(function(e) { e.preventDefault(); $j.ajax({ type: "GET", cache: false, dataType: "json", error: function(XMLHttpRequest, textStatus, errorThrown) { alert(XMLHttpRequest.responseText); }, success: function(data, textStatus) {
function item_actions() { // look for item action classes $dh = opendir(SITEROOT_LOCAL . "classes/itemactions") or servererror("Couldn't open item actions dir"); $types = array(); while (($file = readdir($dh)) !== false) { if (!preg_match('%^.+Action\\.class\\.php$%', $file)) { continue; } $classname = substr($file, 0, -10); // skip classes which aren't ItemActions if (!is_subclass_of($classname, "ItemAction")) { continue; } // skip abstract classes $rc = new ReflectionClass($classname); if ($rc->isAbstract()) { continue; } $types[] = new $classname(); } closedir($dh); usort($types, "compare_action_alpha"); return $types; }
public function getLogic() { $ai = QTIAssessmentItem::fromQTIID($_REQUEST["qtiid"]); // upload the QTI to QTIEngine // Doing this manually rather than using curl because until PHP 5.2.7 // (SVN r269951 to be specific) there is a bug // (http://bugs.php.net/bug.php?id=46696) which breaks the feature // needed to submit the uploaded file's mimetype. PHP 5.2.4 is still // common at the time of writing (it's in Ubuntu 8.04 LTS) so we can't // use curl here. // Could build the multipart/form-data ourselves but that still leaves // the issue that we don't want to follow the last redirect. // boundary -- see // http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html while (true) { $boundary = "----------------------------" . uniqid(); if (strpos($ai->getQTIIndentedString(), $boundary) === false) { break; } } // request $request = "--{$boundary}\r\n"; $request .= "Content-Disposition: form-data; name=\"uploadedContent\"; filename=\"{$ai->getTitleFS()}.xml\"\r\n"; $request .= "Content-Type: application/xml\r\n"; $request .= "\r\n"; $request .= $ai->getQTIIndentedString(); $request .= "\r\n--{$boundary}--\r\n\r\n"; // headers $reqheader = array("Host" => QTIENGINE_HOST, "Accept" => "*/*", "Content-Length" => strlen($request), "Content-Type" => "multipart/form-data; boundary={$boundary}"); $url = "/application/upload"; $reqaction = "POST {$url} HTTP/1.1"; // make requests until we're redirected to the preview page $error = null; while (true) { // open socket $sock = fsockopen(QTIENGINE_HOST, 80, $errno, $errstr, 30); if (!$sock) { servererror("Couldn't connect to QTIEngine (" . QTIENGINE_HOST . ")"); } // send data $reqheaderstrings = array(); foreach ($reqheader as $key => $value) { $reqheaderstrings[] = "{$key}: {$value}"; } fputs($sock, $reqaction . "\r\n" . implode("\r\n", $reqheaderstrings) . "\r\n\r\n" . $request); fflush($sock); // receive headers $header = array(); $httpcode = null; while (!feof($sock) && ($line = fgets($sock)) != "\r\n") { if (is_null($httpcode) && preg_match('%^HTTP/[^\\s]*\\s+\\d+%', $line)) { $httpcode = preg_replace('%^HTTP/[^\\s]*\\s+(\\d+).*$%', '\\1', $line); } else { $parts = explode(":", $line, 2); $header[trim($parts[0])] = trim($parts[1]); } } // close the socket fclose($sock); // check HTTP response code is a redirection if ($httpcode != 301 && $httpcode != 302 || !array_key_exists("Location", $header)) { $error = "Didn't get a redirection to the QTIEngine preview page. Last page was {$url}"; break; } // check its URL is valid $urlparts = parse_url($header["Location"]); if (!isset($urlparts)) { $error = "Hit a malformed Location header pointing to '" . $header["Location"] . "'"; break; } // stop if we've got to the preview page if (preg_match('%^/item/play/0;%', $urlparts["path"])) { break; } // redirect $url = $urlparts["path"]; $reqaction = "GET {$url} HTTP/1.1"; // delete POST related headers if (isset($reqheader["Content-Length"])) { unset($reqheader["Content-Length"]); unset($reqheader["Content-Type"]); } // clear the request data $request = ""; // loop... } if (!is_null($error)) { servererror($error); } redirect($header["Location"]); }