contentRootId = 'timeTable-treeRootNode'; $this->contentRootTag = 'timetabList'; $this->attributes = ['name' => '', 'intervals' => '']; $this->optionalAttributes = []; $this->objTagName = 'timetab'; $this->id_prefix = 'tt_'; if (!$sharedObject && !file_exists($this->xmlName)) { $this->createDom(); $this->xp = new domxpath($this->contentDom); } } protected function createDom() { $types = ['timetab' => 'timeTable', 'catalog' => 'catalog']; $rootElement = $this->contentDom->createElement('ws'); foreach ($types as $key => $value) { $contentId = $value . '-treeRootNode'; $contentTag = $key . 'List'; $typeElement = $this->contentDom->createElement($contentTag); $typeElement->setAttribute('xml:id', $contentId); $rootElement->appendChild($typeElement); } $this->contentDom->appendChild($rootElement); $this->contentDom->save($this->xmlName); } /** * Get object * @param $id * @param $nodeType * @return array */ public function getObject($id, $nodeType = null) { if (substr($nodeType, 0, 6) == 'shared') { //Shared object $sharedObjMgr = new SharedObjectsMgr(); $path = $sharedObjMgr->getDataFilePath(str_replace('shared', '', $nodeType), $id); } else { $path = USERTTDIR . $id . '.xml'; } if (!file_exists($path)) { return ['error' => NO_OBJECT_FILE]; } $this->objectDom->load($path); if (!($objToGet = $this->objectDom->getElementById($id))) { return ['error' => NO_SUCH_ID]; } $attributesToReturn['id'] = $objToGet->getAttribute('xml:id'); $attributes = $objToGet->childNodes; $nbInt = 0; foreach ($attributes as $attribute) { if ($attribute->nodeType == XML_ELEMENT_NODE) { /*if ($attribute->tagName == 'intervals') { $start = $attribute -> getElementsByTagName('start')->item(0) -> nodeValue; $stop = $attribute -> getElementsByTagName('stop')->item(0) -> nodeValue; $attributesToReturn['intervals'][] = array('start' => $start, 'stop' => $stop); } else $attributesToReturn[$attribute->tagName] = $attribute->nodeValue;*/ //BRE - load all except intervals - Intervals will be loaded later with 'loadIntervalsFromTT' function if ($attribute->tagName != 'intervals') { $attributesToReturn[$attribute->tagName] = $attribute->nodeValue; } else { $nbInt++; } } } $attributesToReturn['nbIntervals'] = $nbInt; return $attributesToReturn; } /** * Modify an object * @param $p * @return array */ public function modifyObject($p) { $folder = $this->getObjectFolder($p->id); //Copy TT in a tempory file $ttFilePath = USERTTDIR . $p->id . '.xml'; $tmpFileExist = false; if (file_exists($ttFilePath)) { $tmpFileExist = copy($ttFilePath, $ttFilePath . ".tmp"); } //Delete TT $this->deleteObject($p); //Save modifications try { $result = $this->createObject($p, $folder); if ($result['error']) { throw new Exception($result['error']); } if ($tmpFileExist) { unlink($ttFilePath . ".tmp"); } return ['id' => $p->id, 'info' => $result['info']]; } catch (Exception $exception) { //Restore TT file if ($tmpFileExist) { copy($ttFilePath . ".tmp", $ttFilePath); unlink($ttFilePath . ".tmp"); } return ['error' => $exception->getMessage()]; } } /** * Create an object * @param $p * @param $folder * @return array */ public function createObject($p, $folder) { if ($p->leaf) { $result = $this->createParameter($p, $folder); if ($result['error']) { return $result; } $cacheMgr = new TimeTableCacheMgr(); if (isset($p->cacheToken) && ($p->cacheToken != '')) { $resultSaveInt = $cacheMgr->saveInTT($result['id'], "update", $p->cacheToken); if (!$resultSaveInt['success']) { if ($resultSaveInt['message']) { return ['error' => $resultSaveInt['message']]; } else { return ['error' => 'Unknown error during intervals save']; } } } return $result; } else { return ['error' => 'createFolder should be called from RENAME']; // return $this->createFolder($p); // TODO check if this is possible? } } /** * Create parameter (in case of catalogs) * @param $p * @param $folder * @return array */ protected function createParameter($p, $folder) { if ($this->objectExistsByName($p->name)) { $p->id = $this->getObjectIdByName($p->name); $this->deleteObject($p); } $this->id = $this->setId(); $this->created = date('Y-m-d\TH:i:s'); if (!$this->id) { return ['error' => ID_CREATION_ERROR]; } $this->resFileName = USERTTDIR . $this->id . '.xml'; // TODO catalog root element = 'timetable' $rootElement = $this->objectDom->createElement('timetable'); $rootElement->setAttribute('xml:id', $this->id); foreach ($p as $key => $value) { if ($key != 'id' && $key != 'leaf' && $key != 'nodeType' && $key != 'objName' && $key != 'objFormat' && $key != 'folderId' && $key != 'cacheToken') { if ($key == 'created') { $rootElement->appendChild($this->objectDom->createElement($key, $this->created)); } // it is catalog else { if ($key == 'parameters') { $paramsElement = $this->setParamDescription($value); if ($paramsElement) { $rootElement->appendChild($paramsElement); } } else { if ($key != 'intervals') { $rootElement->appendChild($this->objectDom->createElement($key, htmlspecialchars($value))); } } } } } $this->objectDom->appendChild($rootElement); $this->objectDom->save($this->resFileName); $obj = new stdClass(); $obj->name = $p->name; $obj->intervals = $p->nbIntervals; $this->addToContent($obj, $folder); // FIXME field created is undefined return ['id' => $this->id, 'created' => $this->created, 'info' => $obj->intervals . ' intervals']; } /** * Set parameter description * TODO * @param $param string The parameter * @return DOMNode */ protected function setParamDescription($param) { return new DOMNode(); } /** * Get uploaded object * @param $name * @param $format * @param bool $onlyDescription * @return mixed */ public function getUploadedObject($name, $format, $onlyDescription = false) { if (strpos($name, '.txt') !== false || strpos($name, '.asc') !== false || strpos($name, '.') == false) { $attributesToReturn = $this->textToAmda(USERTEMPDIR . $name, $onlyDescription); $attributesToReturn['objName'] = $name; $attributesToReturn['objFormat'] = $format; return $attributesToReturn; } if ($format == 'VOT') { $attributesToReturn = $this->vot2amda(USERTEMPDIR . $name, $onlyDescription); $attributesToReturn['objName'] = $name; $attributesToReturn['objFormat'] = $format; return $attributesToReturn; } if (strpos($name, '.xml') !== false) { $temp = explode('.xml', $name); $name = $temp[0]; } if (!file_exists(USERTEMPDIR . $name . '.xml')) { return ['error' => 'no such name']; } $this->objectDom->load(USERTEMPDIR . $name . '.xml'); if (!($objToGet = $this->objectDom->getElementsByTagName('timetable')->item(0)) && !($objToGet = $this->objectDom->getElementsByTagName('TimeTable')->item(0))) { return ['error' => 'no time table']; } $attributes = $objToGet->childNodes; $attributesToReturn['name'] = $name; $attributesToReturn['objName'] = $name; $attributesToReturn['objFormat'] = $format; /** @var DOMElement $attribute */ foreach ($attributes as $attribute) { if ($attribute->nodeType == XML_ELEMENT_NODE) { if ($attribute->tagName == 'intervals') { $start = $attribute->getElementsByTagName('start')->item(0)->nodeValue; $stop = $attribute->getElementsByTagName('stop')->item(0)->nodeValue; if (!$onlyDescription) { $attributesToReturn['intervals'][] = ['start' => $start, 'stop' => $stop]; } } else { if ($attribute->tagName == 'Interval') { $start = $attribute->getElementsByTagName('Start')->item(0)->nodeValue; $stop = $attribute->getElementsByTagName('Stop')->item(0)->nodeValue; if (!$onlyDescription) { $attributesToReturn['intervals'][] = ['start' => $start, 'stop' => $stop]; } } else { switch (strtolower($attribute->tagName)) { case 'created': $attributesToReturn['created'] = $attribute->nodeValue; break; case 'chain': case 'source': $attributesToReturn['description'] = $attribute->nodeValue; break; default: break; } } } } } return $attributesToReturn; } /* * Uploaded text file => convert to array */ /** * Convert text to AMDA attributes * @param $tmp_file * @param bool $onlyDescription * @return mixed */ protected function textToAmda($tmp_file, $onlyDescription = false) { $suffix = explode('.', basename($tmp_file)); $lines = file($tmp_file, FILE_SKIP_EMPTY_LINES); $description = "Uploaded Time Table" . PHP_EOL; $recordsNumber = count($lines); $descNumber = 0; foreach ($lines as $line) { $line = trim($line); if ($line[0] == '#') { // Comment $description = $description . PHP_EOL . substr($line, 1, -1); } else { $line = preg_replace('/[-:\/T\s]+/', ' ', $line); $isoFormat = 'Y-m-dTH:i:s'; $doyFormat = 'Y z H i s'; $doyRegex = '(\d{4}) (\d{3}) (\d{2}) (\d{2}) (\d{2})( \d{2})?'; if (preg_match('/^' . $doyRegex . ' ' . $doyRegex . '$/', $line)) { $start = DateTime::createFromFormat($doyFormat, substr($line, 0, 17)); $stop = DateTime::createFromFormat($doyFormat, substr($line, 18)); $startDate = $start->sub(new DateInterval('P1D'))->format($isoFormat); $stopDate = $stop->sub(new DateInterval('P1D'))->format($isoFormat); } else { $dateLength = round((strlen($line)-1) / 2); $start = explode(' ', substr($line, 0, $dateLength) . ' 00'); $startTime = strtotime("$start[0]/$start[1]/$start[2] $start[3]:$start[4]:$start[5]"); $stop = explode(' ', substr($line, $dateLength + 1) . ' 00'); $stopTime = strtotime("$stop[0]/$stop[1]/$stop[2] $stop[3]:$stop[4]:$stop[5]"); if (is_numeric($startTime) && is_numeric($stopTime)) { $startDate = date($isoFormat, $startTime); $stopDate = date($isoFormat, $stopTime); } else { $description = $description . PHP_EOL . $line; $descNumber++; continue; } } if (!$onlyDescription) { $attributesToReturn['intervals'][] = ['start' => $startDate, 'stop' => $stopDate]; } } } if ($recordsNumber == $descNumber) { $description = 'Looks like we can not read your time format...' . PHP_EOL . $description; } $attributesToReturn['description'] = $description; $attributesToReturn['name'] = basename($tmp_file, '.' . $suffix[1]); $attributesToReturn['created'] = date('Y-m-d\TH:i:s'); return $attributesToReturn; } /** * Convert VOTable time table to AMDA attributes * @param $tmp_file * @param bool $onlyDescription * @return mixed */ protected function vot2amda($tmp_file, $onlyDescription = false) { // Load Time table $this->objectDom->load($tmp_file); $objToGet = $this->objectDom->getElementsByTagName('TABLEDATA')->item(0); $attributesToReturn['name'] = $tmp_file; $attributes = $objToGet->childNodes; /** @var DOMElement $attribute */ foreach ($attributes as $attribute) { if ($attribute->tagName == 'TR') { $start = $attribute->getElementsByTagName('TD')->item(0)->nodeValue; $stop = $attribute->getElementsByTagName('TD')->item(1)->nodeValue; if (!$onlyDescription) { $attributesToReturn['intervals'][] = ['start' => $start, 'stop' => $stop]; } } } $suffix = explode('.', basename($tmp_file)); $attributesToReturn['name'] = basename($tmp_file, '.' . $suffix[1]); $attributesToReturn['created'] = date('Y-m-d') . "T" . date('H:i:s'); $description = $this->objectDom->getElementsByTagName('DESCRIPTION')->item(0)->nodeValue; $attributesToReturn['description'] = htmlspecialchars($description); return ($attributesToReturn); } /***************************************************************** * PUBLIC FUNCTIONS *****************************************************************/ /* * Get Object into Edit */ public function getTmpObject($folderId, $name, $onlyDescription = false) { $filePath = USERWORKINGDIR . $folderId . '/' . $name . '.xml'; if (!file_exists($filePath)) { return ['error' => 'Cannot find result file']; } $dom = new DomDocument('1.0'); $dom->formatOutput = true; if (!$dom->load($filePath)) { return ['error' => 'Cannot load result file']; } $descNodes = $dom->getElementsByTagName('description'); if ($descNodes->length > 0) { $attributesToReturn['description'] = $descNodes->item(0)->nodeValue; } $creatNodes = $dom->getElementsByTagName('created'); if ($creatNodes->length > 0) { $attributesToReturn['created'] = $creatNodes->item(0)->nodeValue; } $histNodes = $dom->getElementsByTagName('history'); if ($histNodes->length > 0) { $attributesToReturn['history'] = $histNodes->item(0)->nodeValue; } $attributesToReturn['objName'] = $name; $attributesToReturn['folderId'] = $folderId; if (!$onlyDescription) { $intNodes = $dom->getElementsByTagName('intervals'); /** @var DOMElement $intNode */ foreach ($intNodes as $intNode) { $startNodes = $intNode->getElementsByTagName('start'); if ($startNodes->length <= 0) { return ['error' => 'Error detected in result file']; } $stopNodes = $intNode->getElementsByTagName('stop'); if ($stopNodes->length <= 0) { return ['error' => 'Error detected in result file']; } $attributesToReturn['intervals'][] = [ 'start' => $startNodes->item(0)->nodeValue, 'stop' => $stopNodes->item(0)->nodeValue ]; } } return $attributesToReturn; } /** * Merge time tables * @param $obj * @return array */ public function merge($obj) { /** * Array of intervals, used like : * [{start:'2010-01-01T23:00:00',stop:'2011-01-01T20:00:00'},{start:'2009-01-01T23:00:00',stop:'2010-01-01T20:00:00'}] * $attributesToReturn['intervals'][] = array('start' => $start, 'stop' => $stop); */ $intervals = 0; for ($iId = 0; $iId < count($obj->ids); $iId++) { $table[$iId] = $this->loadIntervalsFromTT($obj->ids[$iId]); for ($jId = 0; $jId < count($table[$iId]['intervals']); $jId++) { $interval[$iId][$jId][0] = $table[$iId]['intervals'][$jId]['start']; $interval[$iId][$jId][1] = $table[$iId]['intervals'][$jId]['stop']; } $intervals += count($interval[$iId]); } if ($intervals > 10000) { set_time_limit(1800); } $final = []; for ($iId = 0; $iId < count($obj->ids); $iId++) { $final = array_merge($final, $interval[$iId]); } sort($final); // Algorithm of union $line = 0; $iId = 0; $val = $final[$iId][0]; while ($iId < count($final) - 1) { if ($final[$iId + 1][1] <= $final[$iId][1]) { array_splice($final, $iId + 1, 1); } else { if (($final[$iId + 1][0] <= $final[$iId][1]) && ($final[$iId + 1][1] >= $final[$iId][1])) { $iId++; } else { $start[$line] = $val; $stop[$line] = $final[$iId][1]; $iId++; $line++; $val = $final[$iId][0]; } } } $start[$line] = $val; $stop[$line] = $final[$iId][1]; $objTT = new stdClass(); $objTT->name = $obj->name; $objTT->nodeType = 'timeTable'; $objTT->leaf = true; $objTT->created = null; $objTT->history = $obj->history; for ($iId = 0; $iId < count($start); $iId++) { $inter = new stdClass(); $inter->start = $start[$iId]; $inter->stop = $stop[$iId]; $objTT->intervals[] = $inter; } $objTT->nbIntervals = count($start); $this->objectDom = new DomDocument('1.0'); $this->objectDom->formatOutput = true; $res = $this->createParameter($objTT, $folder); // FIXME $folder is undefined if ($res['error']) { return $res; } $this->saveIntervals($res['id'], $objTT->intervals, 'merge'); return $res; } /** * Load intervals from time table * @param $id * @param $typeTT * @param null $start * @param null $limit * @return array */ public function loadIntervalsFromTT($id, $typeTT = '', $start = null, $limit = null) { if ($typeTT == 'sharedtimeTable') { //Shared object $sharedObjMgr = new SharedObjectsMgr(); $path = $sharedObjMgr->getDataFilePath('timeTable', $id); } else { $path = USERTTDIR . $id . '.xml'; } //load intervals from TT id if (!file_exists($path)) { return ['success' => false, 'message' => "Cannot find TT file " . $id]; } $this->objectDom->load($path); if (!($objToGet = $this->objectDom->getElementById($id))) { return ['success' => false, 'message' => NO_SUCH_ID . " " . $id]; } $xpath = new DOMXPath($this->objectDom); $intervals = $xpath->query('//intervals'); $result = []; if (!isset($start) || !isset($limit)) { /** @var DOMElement $interval */ foreach ($intervals as $interval) { $startTime = $interval->getElementsByTagName('start')->item(0)->nodeValue; $stopTime = $interval->getElementsByTagName('stop')->item(0)->nodeValue; array_push($result, ['start' => $startTime, 'stop' => $stopTime]); } } else { for ($iInt = 0; $iInt < $limit; ++$iInt) { if ($start + $iInt >= $intervals->length) { break; } $startTime = $intervals->item($start + $iInt)->getElementsByTagName('start')->item(0)->nodeValue; $stopTime = $intervals->item($start + $iInt)->getElementsByTagName('stop')->item(0)->nodeValue; array_push($result, ['start' => $startTime, 'stop' => $stopTime]); } } return [ 'totalCount' => $intervals->length, 'intervals' => $result, 'start' => isset($start) ? $start : 0, 'limit' => isset($limit) ? $limit : 0, 'success' => true ]; } /** * Save intervals * @param $id * @param $intervals * @param $action * @return array */ public function saveIntervals($id, $intervals, $action) { if (substr($id, 0, 6) == 'shared') { return ['success' => false, 'message' => "Cannot save shared TimeTable"]; } else { $path = USERTTDIR . $id . '.xml'; } if (!file_exists($path)) { return ['success' => false, 'message' => "Cannot find TT file " . $id]; } $this->objectDom->load($path); if (!($objToGet = $this->objectDom->getElementById($id))) { return ['success' => false, 'message' => NO_SUCH_ID . " " . $id]; } //remove old intervals $crtNode = $objToGet->firstChild; while ($crtNode) { if (($crtNode->nodeType != XML_ELEMENT_NODE) || ($crtNode->tagName != 'intervals')) { $crtNode = $crtNode->nextSibling; continue; } $toRemove = $crtNode; $crtNode = $crtNode->nextSibling; $objToGet->removeChild($toRemove); unset($toRemove); } //add new intervals foreach ($intervals as $interval) { $newInterval = $this->createIntervalElement($interval); $this->objectDom->documentElement->appendChild($newInterval); } //save modifications $this->id = $id; $this->resFileName = USERTTDIR . $this->id . '.xml'; $this->objectDom->save($this->resFileName); unset($this->objectDom); return ['success' => true, 'action' => $action, 'nbIntervals' => count($intervals)]; } /** * Create interval element * @param $interval * @return DOMElement */ protected function createIntervalElement($interval) { $newInterval = $this->objectDom->createElement('intervals'); $newInterval->appendChild($this->objectDom->createElement('start', $interval->start)); $newInterval->appendChild($this->objectDom->createElement('stop', $interval->stop)); return $newInterval; } /** * Intersect time tables * @param $obj * @return array|string */ public function intersect($obj) { $intervals = 0; for ($iId = 0; $iId < count($obj->ids); $iId++) { $table[$iId] = $this->loadIntervalsFromTT($obj->ids[$iId]); for ($jId = 0; $jId < count($table[$iId]['intervals']); $jId++) { $interval[$iId][$jId][0] = $table[$iId]['intervals'][$jId]['start']; $interval[$iId][$jId][1] = $table[$iId]['intervals'][$jId]['stop']; } $intervals += count($interval[$iId]); } if ($intervals > 10000) { set_time_limit(1800); } // Sort intervals in time tables sort($interval[0]); sort($interval[1]); $iId = 0; $jId = 0; $line = 0; while (($iId < count($interval[0])) && ($jId < count($interval[1]))) { $inter = $this->callIntersection($interval[0][$iId], $interval[1][$jId]); if ($inter[0][0] != 0 && $inter[0][1] != 0) { $start[$line] = $inter[0][0]; $stop[$line] = $inter[0][1]; $line++; } if ($interval[0][$iId][1] < $interval[1][$jId][1]) { $iId++; } else { $jId++; } } // Intersection is empty if ($line == 0) { $result = "empty"; } else { $objTT->name = $obj->name; // FIXME $objTT is undefined $objTT->nodeType = 'timeTable'; $objTT->leaf = true; $objTT->created = null; $objTT->history = $obj->history; for ($iId = 0; $iId < count($start); $iId++) { $inter = new stdClass(); $inter->start = $start[$iId]; $inter->stop = $stop[$iId]; $objTT->intervals[] = $inter; } $objTT->nbIntervals = count($start); if (count($objTT->intervals) == 0) { $result = "empty"; } else { $this->objectDom = new DomDocument('1.0'); $this->objectDom->formatOutput = true; $result = $this->createObject($objTT, $folder); // FIXME $folder is undefined if (!isset($result['error'])) { $this->saveIntervals($result['id'], $objTT->intervals, 'intersect'); } } } return $result; } /** * Call intersection * @param $fst * @param $snd * @return array */ protected function callIntersection($fst, $snd) { $inf = ($fst[0] > $snd[0]) ? $fst[0] : $snd[0]; $sup = ($fst[1] < $snd[1]) ? $fst[1] : $snd[1]; $inter[] = ($inf >= $sup) ? [0, 0] : [$inf, $sup]; return $inter; } /** * Valid name object * TODO getObject only!!!! => change DD_Search output * @param $p * @return array */ public function validNameObject($p) { // overwritten $res = parent::validNameObject($p); if (!$res['valid']) { return $res; } //no space if (strpos($p->name, ' ') === false) { return ['valid' => true]; } return ['valid' => false, 'error' => 'Space character is not allowed']; } /** * Copy time table * @param $src_path * @param $dst_path * @param $newId * @param $newName * @param null $newDescription * @return bool */ public function copyTT($src_path, $dst_path, $newId, $newName, $newDescription = null) { if (!file_exists($src_path)) { return false; } if (!is_dir($dst_path)) { return false; } $dom = new DomDocument('1.0'); $dom->formatOutput = true; if (!$dom->load($src_path)) { return false; } $timeTableNodes = $dom->getElementsByTagName('timetable'); if ($timeTableNodes->length <= 0) { return false; } $timeTableNode = $timeTableNodes->item(0); $timeTableNode->setAttribute('xml:id', $newId); $nameNodes = $timeTableNode->getElementsByTagName('name'); if ($nameNodes->length <= 0) { //create name node (normally never append) $nameNode = $dom->createElement('name'); $timeTableNode->appendChild($nameNode); } else { $nameNode = $nameNodes->item(0); } $nameNode->nodeValue = $newName; if (isset($newDescription) && !empty($newDescription)) { $descriptionNodes = $timeTableNode->getElementsByTagName('description'); if ($descriptionNodes->length <= 0) { //create description node (normally never append) $descriptionNode = $dom->createElement('description'); $timeTableNode->appendChild($descriptionNode); } else { $descriptionNode = $descriptionNodes->item(0); } $descriptionNode->nodeValue = $newDescription; } $dstFilePath = $dst_path . "/" . $newId . ".xml"; if ($dom->save($dstFilePath) === false) { return false; } chgrp($dstFilePath, APACHE_USER); chmod($dstFilePath, 0775); return true; } /** * Rename in resource * @param $name * @param $id * @return bool */ protected function renameInResource($name, $id) { if (!file_exists(USERTTDIR . $id . '.xml')) { return false; } $this->objectDom->load(USERTTDIR . $id . '.xml'); if (!($objToRename = $this->objectDom->getElementById($id))) { return false; } $objToRename->getElementsByTagName('name')->item(0)->nodeValue = $name; $this->objectDom->save(USERTTDIR . $id . '.xml'); return true; } /** * Delete parameter * @param $id */ protected function deleteParameter($id) { if (file_exists(USERTTDIR . $id . '.xml')) { unlink(USERTTDIR . $id . '.xml'); } } /** * Rename only * @param $p * @return bool */ protected function renameOnly($p) { //if (!($p->intervals)) return true; return false; } }