* @copyright Julien Veyssier 2019 * @copyright Paul Schwörer 2019 * */ namespace OCA\Maps\Service; use OC\Archive\ZIP; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IL10N; use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; class FavoritesService { private $l10n; private $dbconnection; private $secureRandom; private $currentFavorite; private $currentFavoritesList; private ?string $currentXmlTag; private $insideWpt; private $nbImported; private $importUserId; private $kmlInsidePlacemark; private $kmlCurrentCategory; private bool $linesFound = false; public function __construct( private LoggerInterface $logger, IL10N $l10n, ISecureRandom $secureRandom, IDBConnection $dbconnection, ) { $this->l10n = $l10n; $this->secureRandom = $secureRandom; $this->dbconnection = $dbconnection; } private function db_quote_escape_string($str) { return $this->dbconnection->quote($str); } /** * @param string $userId * @param int $pruneBefore * @param string|null $filterCategory * @return array with favorites */ public function getFavoritesFromDB($userId, $pruneBefore = 0, $filterCategory = null, $isDeletable = true, $isUpdateable = true, $isShareable = true) { $favorites = []; $qb = $this->dbconnection->getQueryBuilder(); $qb->select('id', 'name', 'date_created', 'date_modified', 'lat', 'lng', 'category', 'comment', 'extensions') ->from('maps_favorites', 'f') ->where( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); if (intval($pruneBefore) > 0) { $qb->andWhere( $qb->expr()->gt('date_modified', $qb->createNamedParameter($pruneBefore, IQueryBuilder::PARAM_INT)) ); } if ($filterCategory !== null) { $qb->andWhere( $qb->expr()->eq('category', $qb->createNamedParameter($filterCategory, IQueryBuilder::PARAM_STR)) ); } $req = $qb->executeQuery(); while ($row = $req->fetch()) { $id = intval($row['id']); $name = $row['name']; $date_modified = intval($row['date_modified']); $date_created = intval($row['date_created']); $lat = floatval($row['lat']); $lng = floatval($row['lng']); $category = $row['category']; $comment = $row['comment']; $extensions = $row['extensions']; $favorites[] = [ 'id' => $id, 'name' => $name, 'date_modified' => $date_modified, 'date_created' => $date_created, 'lat' => $lat, 'lng' => $lng, 'category' => $category, 'comment' => $comment, 'extensions' => $extensions, 'isDeletable' => $isDeletable, //Saving maps information in the file 'isUpdateable' => $isUpdateable, 'isShareable' => $isShareable, ]; } $req->closeCursor(); return $favorites; } public function getFavoriteFromDB($id, $userId = null, $category = null, $isDeletable = true, $isUpdateable = true, $isShareable = true) { $favorite = null; $qb = $this->dbconnection->getQueryBuilder(); $qb->select('id', 'name', 'date_modified', 'date_created', 'lat', 'lng', 'category', 'comment', 'extensions') ->from('maps_favorites', 'f') ->where( $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) ); if ($userId !== null) { $qb->andWhere( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); } if ($category !== null) { $qb->andWhere( $qb->expr()->eq('category', $qb->createNamedParameter($category, IQueryBuilder::PARAM_STR)) ); } $req = $qb->executeQuery(); while ($row = $req->fetch()) { $id = intval($row['id']); $name = $row['name']; $date_modified = intval($row['date_modified']); $date_created = intval($row['date_created']); $lat = floatval($row['lat']); $lng = floatval($row['lng']); $category = $row['category']; $comment = $row['comment']; $extensions = $row['extensions']; $favorite = [ 'id' => $id, 'name' => $name, 'date_modified' => $date_modified, 'date_created' => $date_created, 'lat' => $lat, 'lng' => $lng, 'category' => $category, 'comment' => $comment, 'extensions' => $extensions, 'isDeletable' => $isDeletable, //Saving maps information in the file 'isUpdateable' => $isUpdateable, 'isShareable' => $isShareable, ]; break; } $req->closeCursor(); return $favorite; } public function addFavoriteToDB($userId, $name, $lat, $lng, $category, $comment, $extensions) { $nowTimeStamp = (new \DateTime())->getTimestamp(); $qb = $this->dbconnection->getQueryBuilder(); $qb->insert('maps_favorites') ->values([ 'user_id' => $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR), 'name' => $qb->createNamedParameter($name, IQueryBuilder::PARAM_STR), 'date_created' => $qb->createNamedParameter($nowTimeStamp, IQueryBuilder::PARAM_INT), 'date_modified' => $qb->createNamedParameter($nowTimeStamp, IQueryBuilder::PARAM_INT), 'lat' => $qb->createNamedParameter($lat, IQueryBuilder::PARAM_STR), 'lng' => $qb->createNamedParameter($lng, IQueryBuilder::PARAM_STR), 'category' => $qb->createNamedParameter($category, IQueryBuilder::PARAM_STR), 'comment' => $qb->createNamedParameter($comment, IQueryBuilder::PARAM_STR), 'extensions' => $qb->createNamedParameter($extensions, IQueryBuilder::PARAM_STR) ]); $qb->executeStatement(); $favoriteId = $qb->getLastInsertId(); return $favoriteId; } public function addMultipleFavoritesToDB($userId, $favoriteList) { $nowTimeStamp = (new \DateTime())->getTimestamp(); $qb = $this->dbconnection->getQueryBuilder(); $qb->insert('maps_favorites'); try { $this->dbconnection->beginTransaction(); foreach ($favoriteList as $fav) { if ( !isset($fav['lat']) or !is_numeric($fav['lat']) or !isset($fav['lng']) or !is_numeric($fav['lng']) ) { continue; } else { $lat = floatval($fav['lat']); $lng = floatval($fav['lng']); } $values = [ 'user_id' => $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR), 'date_modified' => $qb->createNamedParameter($nowTimeStamp, IQueryBuilder::PARAM_INT), 'lat' => $qb->createNamedParameter($lat, IQueryBuilder::PARAM_INT), 'lng' => $qb->createNamedParameter($lng, IQueryBuilder::PARAM_INT), ]; if (isset($fav['name']) and $fav['name'] !== '') { $values['name'] = $qb->createNamedParameter($fav['name'], IQueryBuilder::PARAM_STR); } if (isset($fav['date_created']) && is_numeric($fav['date_created'])) { $values['date_created'] = $qb->createNamedParameter($fav['date_created'], IQueryBuilder::PARAM_STR); } if (isset($fav['category']) && $fav['category'] !== '') { $values['category'] = $qb->createNamedParameter($fav['category'], IQueryBuilder::PARAM_STR); } if (isset($fav['comment']) && $fav['comment'] !== '') { $values['comment'] = $qb->createNamedParameter($fav['comment'], IQueryBuilder::PARAM_STR); } if (isset($fav['extensions']) && $fav['extensions'] !== '') { $values['extensions'] = $qb->createNamedParameter($fav['extensions'], IQueryBuilder::PARAM_STR); } $qb->values($values); $qb->executeStatement(); } $this->dbconnection->commit(); } catch (\Throwable) { $this->dbconnection->rollback(); } } public function renameCategoryInDB($userId, $cat, $newName) { $qb = $this->dbconnection->getQueryBuilder(); $qb->update('maps_favorites'); $qb->set('category', $qb->createNamedParameter($newName, IQueryBuilder::PARAM_STR)); $qb->where( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); $qb->andWhere( $qb->expr()->eq('category', $qb->createNamedParameter($cat, IQueryBuilder::PARAM_STR)) ); $qb->executeStatement(); } public function editFavoriteInDB($id, $name, $lat, $lng, $category, $comment, $extensions) { $nowTimeStamp = (new \DateTime())->getTimestamp(); $qb = $this->dbconnection->getQueryBuilder(); $qb->update('maps_favorites'); $qb->set('date_modified', $qb->createNamedParameter($nowTimeStamp, IQueryBuilder::PARAM_INT)); if ($name !== null) { $qb->set('name', $qb->createNamedParameter($name, IQueryBuilder::PARAM_STR)); } if ($lat !== null) { $qb->set('lat', $qb->createNamedParameter($lat, IQueryBuilder::PARAM_STR)); } if ($lng !== null) { $qb->set('lng', $qb->createNamedParameter($lng, IQueryBuilder::PARAM_STR)); } if ($category !== null) { $qb->set('category', $qb->createNamedParameter($category, IQueryBuilder::PARAM_STR)); } if ($comment !== null) { $qb->set('comment', $qb->createNamedParameter($comment, IQueryBuilder::PARAM_STR)); } if ($extensions !== null) { $qb->set('extensions', $qb->createNamedParameter($extensions, IQueryBuilder::PARAM_STR)); } $qb->where( $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) ); $qb->executeStatement(); } public function deleteFavoriteFromDB($id) { $qb = $this->dbconnection->getQueryBuilder(); $qb->delete('maps_favorites') ->where( $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) ); $qb->executeStatement(); } public function deleteFavoritesFromDB($ids, $userId) { $qb = $this->dbconnection->getQueryBuilder(); $qb->delete('maps_favorites') ->where( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); if (count($ids) > 0) { $or = $qb->expr()->orx(); foreach ($ids as $id) { $or->add($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))); } $qb->andWhere($or); } else { return; } $qb->executeStatement(); } public function countFavorites($userId, $categoryList, $begin, $end) { if ($categoryList === null or (is_array($categoryList) and count($categoryList) === 0) ) { return 0; } $qb = $this->dbconnection->getQueryBuilder(); $qb->select($qb->createFunction('COUNT(*) AS co')) ->from('maps_favorites', 'f') ->where( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); if ($begin !== null) { $qb->andWhere( $qb->expr()->gt('date_created', $qb->createNamedParameter($begin, IQueryBuilder::PARAM_INT)) ); } if ($end !== null) { $qb->andWhere( $qb->expr()->lt('date_created', $qb->createNamedParameter($end, IQueryBuilder::PARAM_INT)) ); } // apply category restrictions if it's a non-empty array if (!is_string($categoryList) and is_array($categoryList) and count($categoryList) > 0 ) { $or = $qb->expr()->orx(); foreach ($categoryList as $cat) { $or->add($qb->expr()->eq('category', $qb->createNamedParameter($cat, IQueryBuilder::PARAM_STR))); } $qb->andWhere($or); } $nbFavorites = 0; $req = $qb->executeQuery(); while ($row = $req->fetch()) { $nbFavorites = intval($row['co']); break; } $req->closeCursor(); return $nbFavorites; } /** * @param $file * @return array * @throws \Exception */ public function getFavoritesFromJSON($file) { $favorites = []; // Decode file content from JSON $data = json_decode($file->getContent(), true, 512); $id = 0; // Loop over all favorite entries foreach ($data['features'] as $value) { $currentFavorite = [ 'id' => $id, 'isDeletable' => $file->isUpdateable(), //Saving maps information in the file 'isUpdateable' => $file->isUpdateable(), 'isShareable' => false, 'extensions' => [], ]; // Read geometry $currentFavorite['lng'] = floatval($value['geometry']['coordinates'][0]); $currentFavorite['lat'] = floatval($value['geometry']['coordinates'][1]); foreach ($value['properties'] as $key => $v) { if ($key === 'Title') { $currentFavorite['name'] = $value['properties']['Title']; } elseif ($key === 'Published') { if (!is_numeric($value['properties']['Published'])) { $time = new \DateTime($value['properties']['Published']); $time = $time->getTimestamp(); } else { $time = $value['properties']['Published']; } $currentFavorite['date_created'] = $time; } elseif ($key === 'Updated') { if (!is_numeric($value['properties']['Updated'])) { $time = new \DateTime($value['properties']['Updated']); $time = $time->getTimestamp(); } else { $time = $value['properties']['Updated']; } $currentFavorite['date_modified'] = $time; } elseif ($key === 'Category') { $currentFavorite['category'] = $v; } elseif ($key === 'Comment') { $currentFavorite['comment'] = $v; } else { $currentFavorite[$key] = $v; $currentFavorite['extensions'][$key] = $v; } } if (!array_key_exists('category', $currentFavorite)) { $currentFavorite['category'] = $this->l10n->t('Personal'); } if (!array_key_exists('comment', $currentFavorite)) { $currentFavorite['comment'] = ''; } if ( array_key_exists('Location', $value['properties']) && array_key_exists('Address', $value['properties']['Location']) ) { $currentFavorite['comment'] = $currentFavorite['comment'] . "\n" . $value['properties']['Location']['Address']; } // Store this favorite $favorites[] = $currentFavorite; $id++; } return $favorites; } public function getFavoriteFromJSON($file, $id) { $favorites = $this->getFavoritesFromJSON($file); if (array_key_exists($id, $favorites)) { return $favorites[$id]; } else { return null; } } private function addFavoriteToJSONData($data, $name, $lat, $lng, $category, $comment, $extensions, $nowTimeStamp) { $favorite = [ 'type' => 'Feature', 'geometry' => [ 'type' => 'Point', 'coordinates' => [ $lng, $lat ] ], 'properties' => [ 'Title' => $name, 'Category' => $category, 'Published' => $nowTimeStamp, 'Updated' => $nowTimeStamp, 'Comment' => $comment, ] ]; if (is_array($extensions)) { foreach ($extensions as $key => $value) { $favorite['properties'][$key] = $value; } } $id = array_push($data['features'], $favorite) - 1; return [ 'id' => $id, 'data' => $data, ]; } public function addFavoriteToJSON($file, $name, $lat, $lng, $category, $comment, $extensions) { $nowTimeStamp = (new \DateTime())->getTimestamp(); $data = json_decode($file->getContent(), true, 512); $tmp = $this->addFavoriteToJSONData($data, $name, $lat, $lng, $category, $comment, $extensions, $nowTimeStamp); $file->putContent(json_encode($tmp['data'], JSON_PRETTY_PRINT)); return $tmp['id']; } public function addFavoritesToJSON($file, $favorites) { $nowTimeStamp = (new \DateTime())->getTimestamp(); $data = json_decode($file->getContent(), true, 512); $ids = []; foreach ($favorites as $favorite) { $tmp = $this->addFavoriteToJSONData($data, $favorite['name'], $favorite['lat'], $favorite['lng'], $favorite['category'], $favorite['comment'], $favorite['extensions'], $nowTimeStamp); $ids[] = $tmp['id']; $data = $tmp['data']; } $file->putContent(json_encode($data, JSON_PRETTY_PRINT)); return $ids; } public function renameCategoryInJSON($file, $cat, $newName) { $nowTimeStamp = (new \DateTime())->getTimestamp(); $data = json_decode($file->getContent(), true, 512); $this->logger->debug($cat); foreach ($data['features'] as $key => $value) { if (!array_key_exists('Category', $value['properties'])) { $value['properties']['Category'] = $this->l10n->t('Personal'); $data['features'][$key]['properties']['Category'] = $this->l10n->t('Personal'); } if (array_key_exists('Category', $value['properties']) && $value['properties']['Category'] == $cat) { $data['features'][$key]['properties']['Category'] = $newName; $data['features'][$key]['properties']['Updated'] = $nowTimeStamp; } } $file->putContent(json_encode($data, JSON_PRETTY_PRINT)); } public function editFavoriteInJSON($file, $id, $name, $lat, $lng, $category, $comment, $extensions) { $nowTimeStamp = (new \DateTime())->getTimestamp(); $data = json_decode($file->getContent(), true, 512); $createdTimeStamp = $data['features'][$id]['properties']['Published']; $favorite = [ 'type' => 'Feature', 'geometry' => [ 'type' => 'Point', 'coordinates' => [ $lng ?? $data['features'][$id]['geometry']['coordinates'][0], $lat ?? $data['features'][$id]['geometry']['coordinates'][1] ] ], 'properties' => [ 'Title' => $name ?? $data['features'][$id]['properties']['Title'], 'Category' => $category ?? $data['features'][$id]['properties']['Category'], 'Published' => $createdTimeStamp, 'Updated' => $nowTimeStamp, 'Comment' => $comment ?? $data['features'][$id]['properties']['Comment'], ] ]; if (is_array($extensions)) { foreach ($extensions as $key => $value) { $favorite['properties'][$key] = $value; } } $data['features'][$id] = $favorite; $file->putContent(json_encode($data, JSON_PRETTY_PRINT)); } public function deleteFavoriteFromJSON($file, $id): int { $data = json_decode($file->getContent(), true, 512); $countBefore = count($data['features']); array_splice($data['features'], $id, 1); $file->putContent(json_encode($data, JSON_PRETTY_PRINT)); return $countBefore - count($data['features']); } public function deleteFavoritesFromJSON($file, $ids) { $data = json_decode($file->getContent(), true, 512); foreach ($ids as $id) { array_splice($data['features'], $id, 1); } $file->putContent(json_encode($data, JSON_PRETTY_PRINT)); } public function exportFavorites($userId, $fileHandler, $categoryList, $begin, $end, $appVersion) { $qb = $this->dbconnection->getQueryBuilder(); $nbFavorites = $this->countFavorites($userId, $categoryList, $begin, $end); $gpxHeader = ' favourites '; fwrite($fileHandler, $gpxHeader . "\n"); $chunkSize = 10000; $favIndex = 0; while ($favIndex < $nbFavorites) { $gpxText = ''; $qb->select('id', 'name', 'date_created', 'date_modified', 'lat', 'lng', 'category', 'comment', 'extensions') ->from('maps_favorites', 'f') ->where( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); if ($begin !== null) { $qb->andWhere( $qb->expr()->gte('date_created', $qb->createNamedParameter($begin, IQueryBuilder::PARAM_INT)) ); } if ($end !== null) { $qb->andWhere( $qb->expr()->lte('date_created', $qb->createNamedParameter($end, IQueryBuilder::PARAM_INT)) ); } // apply category restrictions if it's a non-empty array if (!is_string($categoryList) and is_array($categoryList) and count($categoryList) > 0 ) { $or = $qb->expr()->orx(); foreach ($categoryList as $cat) { $or->add($qb->expr()->eq('category', $qb->createNamedParameter($cat, IQueryBuilder::PARAM_STR))); } $qb->andWhere($or); } $qb->orderBy('date_created', 'ASC') ->setMaxResults($chunkSize) ->setFirstResult($favIndex); $req = $qb->executeQuery(); while ($row = $req->fetch()) { $name = str_replace('&', '&', $row['name']); $epoch = $row['date_created']; $date = ''; if (is_numeric($epoch)) { $epoch = intval($epoch); $dt = new \DateTime("@$epoch"); $date = $dt->format('Y-m-d\TH:i:s\Z'); } $lat = $row['lat']; $lng = $row['lng']; $category = str_replace('&', '&', $row['category']); $comment = str_replace('&', '&', $row['comment']); $extensions = str_replace('&', '&', $row['extensions']); $gpxExtension = ''; $gpxText .= ' ' . "\n"; $gpxText .= ' ' . $name . '' . "\n"; $gpxText .= ' ' . "\n"; if ($category !== null && strlen($category) > 0) { $gpxText .= ' ' . $category . '' . "\n"; } else { $gpxText .= ' ' . $this->l10n->t('Personal') . '' . "\n"; } if ($comment !== null && strlen($comment) > 0) { $gpxText .= ' ' . $comment . '' . "\n"; } if ($extensions !== null && strlen($extensions) > 0) { $gpxExtension .= ' ' . $extensions . '' . "\n"; } if ($gpxExtension !== '') { $gpxText .= ' ' . "\n" . $gpxExtension; $gpxText .= ' ' . "\n"; } $gpxText .= ' ' . "\n"; } $req->closeCursor(); // write the chunk ! fwrite($fileHandler, $gpxText); $favIndex = $favIndex + $chunkSize; } $gpxEnd = '' . "\n"; fwrite($fileHandler, $gpxEnd); } public function importFavorites($userId, $file) { $lowerFileName = strtolower($file->getName()); if ($this->endswith($lowerFileName, '.gpx')) { return $this->importFavoritesFromGpx($userId, $file); } elseif ($this->endswith($lowerFileName, '.kml')) { $fp = $file->fopen('r'); $name = $file->getName(); return $this->importFavoritesFromKml($userId, $fp, $name); } elseif ($this->endswith($lowerFileName, '.kmz')) { return $this->importFavoritesFromKmz($userId, $file); } elseif ($this->endswith($lowerFileName, '.json') or $this->endswith($lowerFileName, '.geojson')) { return $this->importFavoritesFromGeoJSON($userId, $file); } } public function importFavoritesFromKmz($userId, $file) { $path = $file->getStorage()->getLocalFile($file->getInternalPath()); $name = $file->getName(); $zf = new ZIP($path); if (count($zf->getFiles()) > 0) { $zippedFilePath = $zf->getFiles()[0]; $fstream = $zf->getStream($zippedFilePath, 'r'); $result = $this->importFavoritesFromKml($userId, $fstream, $name); } else { $result = [ 'nbImported' => 0, 'linesFound' => false ]; } return $result; } public function importFavoritesFromKml($userId, $fp, $name) { $this->nbImported = 0; $this->linesFound = false; $this->currentFavoritesList = []; $this->importUserId = $userId; $this->kmlInsidePlacemark = false; $this->kmlCurrentCategory = ''; $xml_parser = xml_parser_create(); xml_set_object($xml_parser, $this); xml_set_element_handler($xml_parser, 'kmlStartElement', 'kmlEndElement'); xml_set_character_data_handler($xml_parser, 'kmlDataElement'); // using xml_parse to be able to parse file chunks in case it's too big while ($data = fread($fp, 4096000)) { if (!xml_parse($xml_parser, $data, feof($fp))) { $this->logger->error( 'Exception in ' . $name . ' parsing at line ' . xml_get_current_line_number($xml_parser) . ' : ' . xml_error_string(xml_get_error_code($xml_parser)), ['app' => 'maps'] ); return 0; } } fclose($fp); xml_parser_free($xml_parser); return [ 'nbImported' => $this->nbImported, 'linesFound' => $this->linesFound ]; } private function kmlStartElement($parser, $name, $attrs) { $this->currentXmlTag = $name; if ($name === 'PLACEMARK') { $this->currentFavorite = []; $this->kmlInsidePlacemark = true; } if ($name === 'LINESTRING') { $this->linesFound = true; } } private function kmlEndElement($parser, $name) { if ($name === 'KML') { // create last bunch if (count($this->currentFavoritesList) > 0) { $this->addMultipleFavoritesToDB($this->importUserId, $this->currentFavoritesList); } unset($this->currentFavoritesList); } elseif ($name === 'PLACEMARK') { $this->kmlInsidePlacemark = false; // store favorite $this->nbImported++; $this->currentFavorite['category'] = $this->kmlCurrentCategory; if (!isset($this->currentFavorite['category']) or $this->currentFavorite['category'] === '') { $this->currentFavorite['category'] = $this->l10n->t('Personal'); } // convert date if (isset($this->currentFavorite['date_created'])) { $time = new \DateTime($this->currentFavorite['date_created']); $timestamp = $time->getTimestamp(); $this->currentFavorite['date_created'] = $timestamp; } if (isset($this->currentFavorite['coordinates'])) { $spl = explode(',', $this->currentFavorite['coordinates']); if (count($spl) > 1) { $this->currentFavorite['lat'] = floatval($spl[1]); $this->currentFavorite['lng'] = floatval($spl[0]); } } array_push($this->currentFavoritesList, $this->currentFavorite); // if we have enough favorites, we create them and clean the array if (count($this->currentFavoritesList) >= 500) { $this->addMultipleFavoritesToDB($this->importUserId, $this->currentFavoritesList); unset($this->currentFavoritesList); $this->currentFavoritesList = []; } } } private function kmlDataElement($parser, $data) { $d = trim($data); if (!empty($d)) { if (!$this->kmlInsidePlacemark) { if ($this->currentXmlTag === 'NAME') { $this->kmlCurrentCategory = $this->kmlCurrentCategory . $d; } } else { if ($this->currentXmlTag === 'NAME') { $this->currentFavorite['name'] = (isset($this->currentFavorite['name'])) ? $this->currentFavorite['name'] . $d : $d; } elseif ($this->currentXmlTag === 'WHEN') { $this->currentFavorite['date_created'] = (isset($this->currentFavorite['date_created'])) ? $this->currentFavorite['date_created'] . $d : $d; } elseif ($this->currentXmlTag === 'COORDINATES') { $this->currentFavorite['coordinates'] = (isset($this->currentFavorite['coordinates'])) ? $this->currentFavorite['coordinates'] . $d : $d; } elseif ($this->currentXmlTag === 'DESCRIPTION') { $this->currentFavorite['comment'] = (isset($this->currentFavorite['comment'])) ? $this->currentFavorite['comment'] . $d : $d; } } } } public function importFavoritesFromGpx($userId, $file) { $this->nbImported = 0; $this->linesFound = false; $this->currentFavoritesList = []; $this->importUserId = $userId; $this->insideWpt = false; $xml_parser = xml_parser_create(); xml_set_object($xml_parser, $this); xml_set_element_handler($xml_parser, 'gpxStartElement', 'gpxEndElement'); xml_set_character_data_handler($xml_parser, 'gpxDataElement'); $fp = $file->fopen('r'); // using xml_parse to be able to parse file chunks in case it's too big while ($data = fread($fp, 4096000)) { if (!xml_parse($xml_parser, $data, feof($fp))) { $this->logger->error( 'Exception in ' . $file->getName() . ' parsing at line ' . xml_get_current_line_number($xml_parser) . ' : ' . xml_error_string(xml_get_error_code($xml_parser)), ['app' => 'maps'] ); return 0; } } fclose($fp); xml_parser_free($xml_parser); return [ 'nbImported' => $this->nbImported, 'linesFound' => $this->linesFound ]; } private function gpxStartElement($parser, $name, $attrs) { $this->currentXmlTag = $name; if ($name === 'WPT') { $this->insideWpt = true; $this->currentFavorite = []; if (isset($attrs['LAT'])) { $this->currentFavorite['lat'] = floatval($attrs['LAT']); } if (isset($attrs['LON'])) { $this->currentFavorite['lng'] = floatval($attrs['LON']); } } if ($name === 'TRK' or $name === 'RTE') { $this->linesFound = true; } } private function gpxEndElement($parser, $name) { if ($name === 'GPX') { // create last bunch if (count($this->currentFavoritesList) > 0) { $this->addMultipleFavoritesToDB($this->importUserId, $this->currentFavoritesList); } unset($this->currentFavoritesList); } elseif ($name === 'WPT') { $this->insideWpt = false; // store favorite $this->nbImported++; // convert date if (isset($this->currentFavorite['date_created'])) { $time = new \DateTime($this->currentFavorite['date_created']); $timestamp = $time->getTimestamp(); $this->currentFavorite['date_created'] = $timestamp; } if (!isset($this->currentFavorite['category']) or $this->currentFavorite['category'] === '') { $this->currentFavorite['category'] = $this->l10n->t('Personal'); } array_push($this->currentFavoritesList, $this->currentFavorite); // if we have enough favorites, we create them and clean the array if (count($this->currentFavoritesList) >= 500) { $this->addMultipleFavoritesToDB($this->importUserId, $this->currentFavoritesList); unset($this->currentFavoritesList); $this->currentFavoritesList = []; } } } private function gpxDataElement($parser, $data) { $d = trim($data); if (!empty($d)) { if ($this->insideWpt and $this->currentXmlTag === 'NAME') { $this->currentFavorite['name'] = (isset($this->currentFavorite['name'])) ? $this->currentFavorite['name'] . $d : $d; } elseif ($this->insideWpt and $this->currentXmlTag === 'TIME') { $this->currentFavorite['date_created'] = (isset($this->currentFavorite['date_created'])) ? $this->currentFavorite['date_created'] . $d : $d; } elseif ($this->insideWpt and $this->currentXmlTag === 'TYPE') { $this->currentFavorite['category'] = (isset($this->currentFavorite['category'])) ? $this->currentFavorite['category'] . $d : $d; } elseif ($this->insideWpt and $this->currentXmlTag === 'DESC') { $this->currentFavorite['comment'] = (isset($this->currentFavorite['comment'])) ? $this->currentFavorite['comment'] . $d : $d; } elseif ($this->insideWpt and $this->currentXmlTag === 'MAPS-EXTENSIONS') { $this->currentFavorite['extensions'] = (isset($this->currentFavorite['extensions'])) ? $this->currentFavorite['extensions'] . $d : $d; } } } public function importFavoritesFromGeoJSON($userId, $file) { $this->nbImported = 0; $this->linesFound = false; $this->currentFavoritesList = []; $this->importUserId = $userId; // Decode file content from JSON $data = json_decode($file->getContent(), true, 512); if ($data == null or !isset($data['features'])) { $this->logger->error( 'Exception parsing ' . $file->getName() . ': no places found to import', ['app' => 'maps'] ); } // Loop over all favorite entries foreach ($data['features'] as $key => $value) { $this->currentFavorite = []; // Ensure that we have a valid GeoJSON Point geometry if ($value['geometry']['type'] !== 'Point') { $this->linesFound = true; continue; } // Read geometry $this->currentFavorite['lng'] = floatval($value['geometry']['coordinates'][0]); $this->currentFavorite['lat'] = floatval($value['geometry']['coordinates'][1]); $this->currentFavorite['name'] = $value['properties']['Title']; $this->currentFavorite['category'] = $this->l10n->t('Personal'); $time = new \DateTime($value['properties']['Published']); $this->currentFavorite['date_created'] = $time->getTimestamp(); $time = new \DateTime($value['properties']['Updated']); $this->currentFavorite['date_modified'] = $time->getTimestamp(); if (isset($value['properties']['Location']['Address'])) { $this->currentFavorite['comment'] = $value['properties']['Location']['Address']; } // Store this favorite array_push($this->currentFavoritesList, $this->currentFavorite); $this->nbImported++; // if we have enough favorites, we create them and clean the array if (count($this->currentFavoritesList) >= 500) { $this->addMultipleFavoritesToDB($this->importUserId, $this->currentFavoritesList); unset($this->currentFavoritesList); $this->currentFavoritesList = []; } } // Store last set of favorites if (count($this->currentFavoritesList) > 0) { $this->addMultipleFavoritesToDB($this->importUserId, $this->currentFavoritesList); } unset($this->currentFavoritesList); return [ 'nbImported' => $this->nbImported, 'linesFound' => $this->linesFound ]; } private function endswith($string, $test) { $strlen = strlen($string); $testlen = strlen($test); if ($testlen > $strlen) { return false; } return substr_compare($string, $test, $strlen - $testlen, $testlen) === 0; } }