/** * Test searching for a child term. */ public function testSearchChildTerm() { $terms = array(); $root = new PerTerm('type', 'uuid0'); for ($i = 5; $i > 0; $i--) { $term = new PerTerm('type', "uuid{$i}", "Child {$i}"); $root->addChild($term); $terms["uuid{$i}"] = $term; for ($j = 5; $j > 0; $j--) { $childTerm = new PerTerm('type', "uuid{$i}.{$j}", "Child {$i}.{$j}", "{$i}.{$j}"); $term->addChild($childTerm); $terms["uuid{$i}.{$j}"] = $childTerm; } } $this->assertEquals(null, $root->findChildByCode('4.2'), "Searching by code one level for a child that's located deeper returns null."); $this->assertEquals($terms['uuid4.1'], $terms['uuid4']->findChildByCode('4.1'), "Searching by code one level for a child that exists works."); $this->assertEquals($terms['uuid4.2'], $root->findChildByCodeRecursive('4.2'), "Recursively searching by code works."); }
/** * Load the curriculum definition from the BDPER API. * * By passing the official curriculum definition file (XML), this method * will parse it and return a curriculum definition it can understand and * treat. It mainly needs a "dictionary" of term types. * * @param string $url * The curriculum definition endpoint URL. * * @return array * An object with 2 properties: * - curriculum: A parsed and prepared curriculum tree. It uses * Educa\DSB\Client\Curriculum\Term\PerTerm elements to define * the curriculum tree. * - dictionary: A dictionary of term identifiers, with name and type * information for each one of them. * * @see \Educa\DSB\Client\Curriculum\PerCurriculum::setCurriculumDictionary() */ public static function fetchCurriculumData($url) { if (preg_match('/\\/$/', $url)) { $url = rtrim($url, '/'); } $dictionary = array(); // First, we want to prepare our 3 cycle base terms. In the Per logic, // the cycles are at the root of the tree. That is why we // prepare them here, and we will add relevant trees underneath when // needed. There are, technically, no cycle elements provided by the // PER API. But, to remain consistent, we create them anyway, and make // their type plural, as with all the other elements. $cycle1 = new PerTerm('cycles', 1, array('fr' => "Cycle 1")); $cycle2 = new PerTerm('cycles', 2, array('fr' => "Cycle 2")); $cycle3 = new PerTerm('cycles', 3, array('fr' => "Cycle 3")); // Prepare our root element, and add our cycles to it. $root = new PerTerm('root', 'root'); $root->addChild($cycle1)->addChild($cycle2)->addChild($cycle3); foreach ($root->getChildren() as $child) { $description = $child->describe(); $id = $description->id; unset($description->id); $dictionary["cycles:{$id}"] = $description; } $objectives = json_decode(@file_get_contents(defined('RUNNING_PHPUNIT') && RUNNING_PHPUNIT ? "{$url}/objectifs_all" : "{$url}/objectifs"), true); $themes = array(); $domains = array(); $disciplines = array(); // Prepare a little function for reducing school year lists. $reduceSchoolYears = function ($schoolYears) { return array_values(array_filter(array_map(function ($item) { switch ($item) { case 1: return '1-2'; case 3: return '3-4'; case 5: return '5-6'; case 7: return '7-8'; case 9: case 10: case 11: return (string) $item; } return null; }, array_unique($schoolYears)))); }; if (!empty($objectives)) { foreach ($objectives as $objectiveData) { $objectiveId = $objectiveData['id']; $cycleNum = (string) $objectiveData['cycle']; // To which cycle does it apply? switch ($cycleNum) { case '1': $cycle = $cycle1; break; case '2': $cycle = $cycle2; break; case '3': $cycle = $cycle3; break; default: throw new CurriculumInvalidDataStructureException(sprintf("Couldn't find valid cycle information for the objectif with ID %d", $objectiveId)); break; } // What is the domain ID? $domainId = $objectiveData['domaine']['id']; // Contrary to objectives and progressions, domains are shared. // Check if we already created this domain. If we did, re-use // the old one. if (isset($domains[$cycleNum][$domainId])) { $domain = $domains[$cycleNum][$domainId]; } else { $domain = new PerTerm('domaines', $domainId, array('fr' => $objectiveData['domaine']['nom'])); $cycle->addChild($domain); // Store it. $domains[$cycleNum][$domainId] = $domain; // Store the description in the dictionary. $description = $domain->describe(); unset($description->id); $dictionary["domaines:{$domainId}"] = $description; } // Prepare all theme names. foreach ($objectiveData['thematiques'] as $themeData) { if (!isset($themes[$themeData['id']])) { $themes[$themeData['id']] = $themeData['nom']; } } // Treat all disciplines. foreach ($objectiveData['disciplines'] as $disciplineData) { $disciplineId = $disciplineData['id']; // Contrary to objectives and progressions, disciplines are // shared. Check if we already created this discipline. If // we did, re-use the old one. if (isset($disciplines[$cycleNum][$domainId][$disciplineId])) { $discipline = $disciplines[$cycleNum][$domainId][$disciplineId]; } else { $discipline = new PerTerm('disciplines', $disciplineId, array('fr' => $disciplineData['nom'])); $domain->addChild($discipline); // Store it. $disciplines[$cycleNum][$domainId][$disciplineId] = $discipline; // Store the description in the dictionary. $description = $discipline->describe(); unset($description->id); $dictionary["disciplines:{$disciplineId}"] = $description; } // We can now set the objectives. Prepare the names. They // are based on the Thématiques, as they make more sense. $names = array(); foreach ($objectiveData['thematiques'] as $themeData) { $names[] = sprintf('%s (%s)', $themes[$themeData['id']], $objectiveData['code']); } // Objectives are not unique. They can be "shared" by // disciplines, meaning we actually have to duplicate them. $objective = new PerTerm('objectifs', $objectiveId, array('fr' => implode("\n", $names))); $discipline->addChild($objective); // Prepare a list for the objective's school years. $objectiveSchoolYears = array(); // Load the raw objective data, we need it for the school // years information, which is not provided in the global // call. $rawData = json_decode(@file_get_contents("{$url}/objectifs/" . $objectiveId), true); if (empty($rawData)) { continue; } foreach ($rawData['progressions'] as $progressionGroup) { $objectiveSchoolYears = array_merge($objectiveSchoolYears, $progressionGroup['annees']); // Fetch the "progressions". foreach ($progressionGroup['items'] as $item) { if (!empty($item['contenus'])) { foreach ($item['contenus'] as $content) { $progression = new PerTerm('progressions', $content['id'], array('fr' => $content['texte'])); $progression->setSchoolYears($reduceSchoolYears($progressionGroup['annees'])); $objective->addChild($progression); $description = $progression->describe(); $description->schoolYears = $progression->getSchoolYears(); unset($description->id); $dictionary["progressions:{$content['id']}"] = $description; } } } } $objective->setSchoolYears($reduceSchoolYears($objectiveSchoolYears)); $objective->setCode($objectiveData['code']); $description = $objective->describe(); $description->code = $objectiveData['code']; $description->schoolYears = $objective->getSchoolYears(); unset($description->id); $dictionary["objectifs:{$objectiveId}"] = $description; } } } // Return the parsed data. return (object) array('curriculum' => $root, 'dictionary' => $dictionary); }