/** * This function analyzes the manifest. In order to perform faster, manifest entities are analyzed one-by-one (on the fly) * during parsing and not stored into memory * Algorithm: * 1. Detect organization elements. Each one will consist a separate units structure inside efront * 2. Dive inside an organization and detect item elements. Each <item> corresponds to a "unit" in efront * * * * * @param $manifest * @return unknown_type */ public static function import2($lesson, $manifest) { //@todo: parse $lesson //foreach ($namespaces as $prefix => $ns) { //$xml->registerXPathNamespace($prefix, $ns); //} //pr($xml -> xpath("$dfn:organizations/$dfn:organization/$dfn:item/adlcp:timeLimitAction")); $xml = simplexml_load_file(G_SCORMPATH . 'imsmanifest.xml', 'SimpleXMLIterator'); $namespaces = $xml->getNamespaces(true); if (isset($namespaces[""])) { //See notes for xpath() in php.net site $dfn = "default"; $xml->registerXPathNamespace($dfn, $namespaces[""]); // register a prefix for that default namespace: //$xml -> organizations -> registerXPathNamespace($dfn, $namespaces[""]); // register a prefix for that default namespace: } /** * Manifest (1/1): may contain the following elements: * - metadata (1/1) * - organizations (1/1) * - resources (1/1) * - manifest * - imsss:sequencingCollection (0/1) * And the following attributes * - identifier (xs:ID, m): unique identifier * - version (xs:string, o): manifest version * - xml:base (xs:anyURI, o): provides a relative path offset for the content file(s) contained in the manifest */ $manifest['identifier'] = (string) $xml->attributes()->identifier; $manifest['version'] = (string) $xml->attributes()->version; //@todo: handle 'xml:base' //$manifest['xml:base'] = (string)$xml -> attributes() -> xml:base; /** * Metadata: may contain the following elements: * - schema (1/1) * - schemaversion (1/1) * - {metadata} (0/1) */ $metadata['schema'] = (string) $xml->metadata->schema; $metadata['schemaversion'] = (string) $xml->metadata->schemaversion; //@todo: handle metadata /* * Organizations: may contain the following elements: * - organization (1/M) * And the following attributes: * - default (xs:IDREF, m): The id of the default organization */ $organizations['default'] = (string) $xml->organizations->attributes(); //@todo: check that default is actually an existing organization /* * Organization: may contain the following elements: * - title (1/1) * - item (1/M) * - metadata (0/1) * - imsss:sequencing * And the following attributes: * - identifier (xs:ID, m): identifier (unique within the manifest) * - structure (xs:string, o): Describes the shape of the organization (default: hierarchical) * - adlseq:objectivesGlobalToSystem (xs:boolean, o): self-explanatory ;) */ foreach ($xml->organizations->organization as $org) { $org->registerXPathNamespace($dfn, $namespaces[""]); // register a prefix for that default namespace: $id = (string) $org->attributes()->identifier; $org->attributes()->structure ? $organization[$id]['structure'] = $org->attributes()->structure : ($organization[$id]['structure'] = 'hierarchical'); $organization[$id]['title'] = $org->attributes()->title; //@todo: the importing may be done below existing elements, take this into account when considering $previousContentId (its initial value may not be 0) $contentTree = new EfrontContentTree($lesson); $previousContent = $contentTree->getLastNode() or $previousContent = array('id' => 0); //Create the "holding" unit, an empty unit that will hold this organization's elements $previousContent = $parentContent = EfrontUnit::createUnit(array('name' => $organization[$id]['title'], 'parent_content_ID' => 0, 'previous_content_ID' => $previousContent['id'], 'lessons_ID' => $lesson)); //Get contents of the organization foreach ($org as $key => $value) { /* * Item: may contain the following elements: * - title (1/1) * - item 0/M * - metadata 0/1 * - adlcp: timeLimitAction 0/1 * - adlcp: dataFromLMS 0/1 * - adlcp: completionThreshold 0/1 * - imsss:sequencing * - adlnav:presentation * And the following attributes: * - identifier (xs:ID, m): a unique identifier * - identifierref (xs:string, o): a reference to a resource * - isvisible (xs:boolean, o): whether this item is displayed when the structure of the package is displayed or rendered (Default true) * - parameters (xs:string, o): static parameters to be passed to the resource at launch time (max 1000 chars) */ if ($key == 'item') { $itemId = (string) $value->attributes()->identifier; //pr($value -> attributes() -> identifier); $item = array('identifier' => $itemId, 'identifierref' => (string) $value->attributes()->identifierref, 'isvisible' => (string) $value->attributes()->isvisible, 'parameters' => (string) $value->attributes()->parameters, 'title' => (string) $value->title, 'timeLimitAction' => (string) reset($org->xpath("{$dfn}:item[@identifier='{$itemId}']/adlcp:timeLimitAction")), 'dataFromLMS' => (string) reset($org->xpath("{$dfn}:item[@identifier='{$itemId}']/adlcp:dataFromLMS")), 'completionThreshold' => (string) reset($org->xpath("{$dfn}:item[@identifier='{$itemId}']/adlcp:completionThreshold"))); //@todo:<imsss:sequencing>, <adlnav:presentation> //@todo: nested items //@todo: metadata $previousContent = EfrontUnit::createUnit(array('name' => $item['title'], 'parent_content_ID' => $parentContent['id'], 'previous_content_ID' => $previousContent['id'], 'lessons_ID' => $lesson)); $items[$itemId] = $item['identifierref']; } } //@todo: handle adlseq:objectivesGlobalToSystem } /* * Resources: may contain the following elements: * - resource (0/M) * And the following attributes: * - xml:base (xs:anyURI, o): provides a relative path offset for the content file(s) */ $resources = $xml->resources; $resources->registerXPathNamespace($dfn, $namespaces[""]); // register a prefix for that default namespace: /* * Resource: may contain the following elements: * - metadata (0/1) * - file (0/M) * - dependency (0/M) * And the following attributes: * - identifier (xs:ID, m): a unique identifier * - type (xs:string, m): the type of the resource * - href (xs:string, o): the �entry point� or �launching point� of this resource * - xml:base (xs:anyURI, o): a relative path offset for the files contained in the manifest * - adlcp:scormType (xs:string, m): the type of SCORM resource ("sco" or "asset") */ foreach ($resources->resource as $key => $value) { $resourceId = (string) $value->attributes()->identifier; $resource = array('identifier' => $resourceId, 'type' => (string) $value->attributes()->type, 'href' => (string) $value->attributes()->href, 'base' => (string) $value->attributes($namespaces['xml'])->base, 'scormType' => (string) $value->attributes($namespaces['adlcp'])->scormType); /** * File: may contain the following elements: * - metadata (0/1) * And the following attributes: * - href (xs:string, m): identifies the location of the file */ foreach ($value->file as $f) { $file = array('href' => (string) $f->attributes()->href); } /** * Dependency: may contain the following elements: * <none> * And the following attributes: * - identifierref (xs:string, m): an identifier attribute of a resource */ foreach ($value->dependency as $d) { $dependency = array('identifierref' => (string) $d->attributes()->identifierref); } } //@todo: sequencingCollection //pr($organization); // $result = $xml -> xpath("//$dfn:manifest/$dfn:organizations/$dfn:organization"); /* $iterator = new SimpleXMLIterator($xml -> asXML()); foreach (new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST) as $key => $value) { } */ /* //$iterator = new SimpleXMLIterator($data); //$iterator = simplexml_load_string($data, 'SimpleXMLIterator'); */ }
/** * Insert unit to tree * * This function is used to insert a new unit to the content tree * <br/>Example: * <code> * $unit = array("id" => 99, //Create the array of the new unit * "name" => "Test Insert Unit", * "lessons_ID" => 1, * "timestamp" => time(), * "ctg_type" => "theory", * "active" => 1, * "parent_content_ID" => 5, * "previous_content_ID" => 5); * * $content = new EfrontContentTree(4); //Initialize content tree for lesson with id 4 * $content -> insertNode($unit); //Insert the new unit * </code> * * @param array $unit The unit array * @param int $parentUnit The parent of the specified node, if it is not set inside the node (not used for the moment) * @param int $previousUnit The previous of the specified node, if it is not set inside the node (not used for the moment) * @return EfrontUnit The new unit * @since 3.5.0 * @access public * @todo implement $parentUnit/$previousUnit functionality, when not present inside $unit */ public function insertNode($unit, $parentUnit = false, $previousUnit = false) { if (!isset($unit['id'])) { if (!isset($unit['previous_content_ID'])) { $unit['parent_content_ID'] ? $children = $this->getNodeChildren($unit['parent_content_ID']) : ($children = $this->tree); //Get the new unit's parent children. If the parent unit is 0, then we will append it to the end of the tree foreach ($iterator = new EfrontAttributeFilterIterator(new RecursiveIteratorIterator($children, RecursiveIteratorIterator::SELF_FIRST), 'id') as $lastUnitId) { } //Iterate through the parent's children until you reach the last one $lastUnitId ? $unit['previous_content_ID'] = $lastUnitId : ($unit['previous_content_ID'] = 0); //The new unit's parent last unit } $unit = EfrontUnit::createUnit($unit); } $iterator = new EfrontNodeFilterIterator(new RecursiveIteratorIterator($this->tree, RecursiveIteratorIterator::SELF_FIRST)); //Get an iterator for the current tree. This iterator returns only whole unit arrays and not unit members separately (such as id, timestamp etc) $iterator->rewind(); //Initialize iterator while ($iterator->valid() && $iterator->key() != $unit['previous_content_ID']) { //Forward iterator index until you reach the unit's previous unit $iterator->next(); } $iterator->next(); //Advance iterator once more to get the next unit if ($iterator->valid()) { //If there is a next unit $lastUnit = $unit; //Set the current last unit to be the inserted unit foreach (new EfrontNodeFilterIterator(new RecursiveIteratorIterator(new RecursiveArrayIterator($unit), RecursiveIteratorIterator::SELF_FIRST)) as $lastUnit) { } //If the inserted unit is actually a tree branch with children, advance to the last one $iterator->current()->offsetSet('previous_content_ID', $lastUnit['id']); //The current unit will have the $lastUnit as previous $iterator->current()->persist(); //Store value to the database } $this->reset(); //Rebuild content tree, so that the unit may appear to the right place return $this->seekNode($unit['id']); }
/** * Create test * * This function is used to create a new test. * In order to create the test, it firsts creates * a unit to hold it. * <br/>Example: * <code> * $contentFields = new array('name' => 'new unit'); * $testFields = new array('duration' => 100); * $test = EfrontTest :: createTest($contentFields, $testFields); * </code> * * @param mixed $content The content unit fields or an existing unitObject. If it's false, then the test will not be associated to a unit * @param array $test The test fields * @return EfrontTest the new test object * @since 3.5.0 * @access public * @static */ public static function createTest($content, $test) { if ($content === false) { $test['content_ID'] = 0; } elseif (!$content instanceof EfrontUnit) { $unit = EfrontUnit::createUnit($content); $test['content_ID'] = $unit['id']; $test['lessons_ID'] = $unit['lessons_ID']; } else { $unit = $content; $test['content_ID'] = $unit['id']; $test['lessons_ID'] = $unit['lessons_ID']; } unset($test['id']); if ($newId = eF_insertTableData("tests", $test)) { if ($test['lessons_ID'] != 0) { EfrontEvent::triggerEvent(array("type" => EfrontEvent::TEST_CREATION, "users_LOGIN" => $GLOBALS['currentUser']->user['login'], "lessons_ID" => $test['lessons_ID'])); } else { // Special treatment for skillgap tests EfrontEvent::triggerEvent(array("type" => EfrontEvent::TEST_CREATION, "users_LOGIN" => $GLOBALS['currentUser']->user['login'], "lessons_ID" => 0, "lessons_name" => _SKILLGAPTESTS)); } return new EfrontTest($newId); } else { return false; } }