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 'loadIntervalsFromObject' 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->saveInObject($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(); } /** * Load TT content from dom documentElement */ protected function loadObjectFile($dom, $onlyDescription, $start = null, $limit = null) { $attributesToReturn = array(); $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; } $nameNodes = $dom->getElementsByTagName('name'); if ($nameNodes->length > 0) { $attributesToReturn['name'] = $nameNodes->item(0)->nodeValue; } if (!$onlyDescription) { $attributesToReturn['intervals'] = array(); $xpath = new DOMXPath($dom); $intervals = $xpath->query('//intervals'); if (!isset($start) || !isset($limit)) { foreach ($intervals as $interval) { $attributesToReturn['intervals'][] = $this->loadIntervalElement($interval); } } else { for ($iInt = 0; $iInt < $limit; ++$iInt) { if ($start + $iInt >= $intervals->length) { break; } $attributesToReturn['intervals'][] = $this->loadIntervalElement($intervals->item($start + $iInt)); } } } return $attributesToReturn; } /** * Get uploaded object * @param $name * @param $format * @param bool $onlyDescription * @return mixed */ public function getUploadedObject($name, $format, $onlyDescription = false) { 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']; } return array( 'name' => $name, 'objName' => $name, 'objFormat' => $format, 'success' => TRUE, ) + $this->loadObjectFile($this->objectDom, $onlyDescription); } /***************************************************************** * 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']; } return array( 'objName' => $name, 'folderId' => $folderId, 'success' => TRUE, ) + $this->loadObjectFile($dom, $onlyDescription); } /** * 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->loadIntervalsFromObject($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 = array( 'start' => $start[$iId], '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 loadIntervalsFromObject($id, $typeTT = '', $start = null, $limit = null) { if ($typeTT == 'sharedtimeTable' || $typeTT == 'sharedcatalog') { //Shared object $sharedObjMgr = new SharedObjectsMgr(); $sharedType = ($typeTT == 'sharedcatalog') ? 'catalog' : 'timeTable'; $path = $sharedObjMgr->getDataFilePath($sharedType, $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]; } $result = $this->loadObjectFile($this->objectDom, FALSE, $start, $limit); return array( 'totalCount' => $result['totalCount'], 'intervals' => $result['intervals'], '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, $options = array()) { 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, $options); $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)]; } /** * Load interval from DOMElement * @param DOMElement * @return interval */ protected function loadIntervalElement($intervalNode) { $startTimes = $intervalNode->getElementsByTagName('start'); $stopTimes = $intervalNode->getElementsByTagName('stop'); if (($startTimes->length != 1) || ($stopTimes->length != 1)) { return FALSE; } return array( 'start' => $startTimes->item(0)->nodeValue, 'stop' => $stopTimes->item(0)->nodeValue, ); } /** * Create interval element * @param $interval * @return DOMElement */ protected function createIntervalElement($interval, $options = array()) { $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->loadIntervalsFromObject($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 = array( 'start' => $start[$iId], '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; } }