diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index 2fe34d65..e01446e6 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -206,6 +206,9 @@ final class Wall extends VKAPIRequestHandler if($post->isDeactivationMessage()) $post_temp_obj->final_post = 1; + if($post->getGeo()) + $post_temp_obj->geo = $post->getVkApiGeo(); + $items[] = $post_temp_obj; if ($from_id > 0) @@ -321,14 +324,10 @@ final class Wall extends VKAPIRequestHandler } else if ($attachment instanceof \openvk\Web\Models\Entities\Video) { $attachments[] = $attachment->getApiStructure($this->getUser()); } else if ($attachment instanceof \openvk\Web\Models\Entities\Note) { - if(VKAPI_DECL_VER === '4.100') { - $attachments[] = $attachment->toVkApiStruct(); - } else { - $attachments[] = [ - 'type' => 'note', - 'note' => $attachment->toVkApiStruct() - ]; - } + $attachments[] = [ + 'type' => 'note', + 'note' => $attachment->toVkApiStruct() + ]; } else if ($attachment instanceof \openvk\Web\Models\Entities\Audio) { $attachments[] = [ "type" => "audio", @@ -419,6 +418,9 @@ final class Wall extends VKAPIRequestHandler if($post->isDeactivationMessage()) $post_temp_obj->final_post = 1; + if($post->getGeo()) + $post_temp_obj->geo = $post->getVkApiGeo(); + $items[] = $post_temp_obj; if ($from_id > 0) @@ -493,7 +495,11 @@ final class Wall extends VKAPIRequestHandler ]; } - function post(string $owner_id, string $message = "", string $copyright = "", int $from_group = 0, int $signed = 0, string $attachments = "", int $post_id = 0): object + function post(string $owner_id, string $message = "", string $copyright = "", int $from_group = 0, int $signed = 0, string $attachments = "", int $post_id = 0, + float $lat = NULL, + float $long = NULL, + string $place_name = '' + ): object { $this->requireUser(); $this->willExecuteWriteAction(); @@ -599,6 +605,45 @@ final class Wall extends VKAPIRequestHandler } catch(\Throwable) {} } + /*$info = file_get_contents("https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=jsonv2", false, stream_context_create([ + 'http' => [ + 'method' => 'GET', + 'header' => implode("\r\n", [ + 'User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2', + "Referer: https://$_SERVER[SERVER_NAME]/" + ]) + ] + ])); + + if ($info) { + $info = json_decode($info, true, JSON_UNESCAPED_UNICODE); + if (key_exists("place_id", $info)) { + $geo["name"] = $info["name"] ?? $info["display_name"]; + } + }*/ + if($lat && $long) { + if(($lat > 90 || $lat < -90) || ($long > 180 || $long < -180)) { + $this->fail(-785, 'Invalid geo info'); + } + + $latitude = number_format((float) $lat, 8, ".", ''); + $longitude = number_format((float) $long, 8, ".", ''); + + $res = [ + 'lat' => $latitude, + 'lng' => $longitude + ]; + if($place_name && mb_strlen($place_name) > 0) { + $res['name'] = $place_name; + } else { + $res['name'] = 'Geopoint'; + } + + $post->setGeo(json_encode($res)); + $post->setGeo_Lat($latitude); + $post->setGeo_Lon($longitude); + } + if($should_be_suggested) $post->setSuggested(1); @@ -1129,6 +1174,52 @@ final class Wall extends VKAPIRequestHandler return 1; } + function getNearby(int $owner_id, int $post_id) + { + $this->requireUser(); + + $post = (new PostsRepo)->getPostById($owner_id, $post_id); + if(!$post || $post->isDeleted()) + $this->fail(100, "One of the parameters specified was missing or invalid: post_id is undefined"); + + if(!$post->canBeViewedBy($this->getUser())) + $this->fail(15, "Access denied"); + + $lat = $post->getLat(); + $lon = $post->getLon(); + + if(!$lat || !$lon) + $this->fail(-97, "Post doesn't contains geo"); + + $query = file_get_contents(__DIR__ . "/../../Web/Models/sql/get-nearest-posts.tsql"); + $_posts = \Chandler\Database\DatabaseConnection::i()->getContext()->query($query, $lat, $lon, $post->getId())->fetchAll(); + $posts = []; + + foreach($_posts as $post) { + $distance = $post["distance"]; + $post = (new PostsRepo)->get($post["id"]); + if (!$post || $post->isDeleted() || !$post->canBeViewedBy($this->getUser())) continue; + + $owner = $post->getOwner(); + $preview = mb_substr($post->getText(), 0, 50) . (strlen($post->getText()) > 50 ? "..." : ""); + $posts[] = [ + "message" => strlen($preview) > 0 ? $preview : "(нет текста)", + "url" => "/wall" . $post->getPrettyId(), + "created" => $post->getPublicationTime()->html(), + "owner" => [ + "domain" => $owner->getURL(), + "photo_50" => $owner->getAvatarURL(), + "name" => $owner->getCanonicalName(), + "verified" => $owner->isVerified(), + ], + "geo" => $post->getGeo(), + "distance" => $distance + ]; + } + + return $posts; + } + private function getApiPhoto($attachment) { return [ "type" => "photo", diff --git a/Web/Models/Entities/Post.php b/Web/Models/Entities/Post.php index 30c6eac3..f3f8e1f7 100644 --- a/Web/Models/Entities/Post.php +++ b/Web/Models/Entities/Post.php @@ -455,6 +455,31 @@ class Post extends Postable return $item; } + + function getGeo(): ?object + { + if (!$this->getRecord()->geo) return NULL; + + return (object) json_decode($this->getRecord()->geo, true, JSON_UNESCAPED_UNICODE); + } + + function getLat(): ?float + { + return (float) $this->getRecord()->geo_lat ?? NULL; + } + + function getLon(): ?float + { + return (float) $this->getRecord()->geo_lon ?? NULL; + } + + function getVkApiGeo(): object + { + return (object) [ + 'type' => 'point', + 'coordinates' => $this->getLat() . ',' . $this->getLon(), + ]; + } use Traits\TRichText; } diff --git a/Web/Models/sql/get-nearest-posts.tsql b/Web/Models/sql/get-nearest-posts.tsql new file mode 100644 index 00000000..5da7faa8 --- /dev/null +++ b/Web/Models/sql/get-nearest-posts.tsql @@ -0,0 +1,11 @@ +SELECT *, + SQRT( + POW(69.1 * (? - geo_lat), 2) + + POW(69.1 * (? - geo_lon) * COS(RADIANS(geo_lat)), 2) + ) AS distance +FROM posts +WHERE id <> ? +AND FROM_UNIXTIME(created) >= DATE_SUB(NOW(), INTERVAL 1 MONTH) +HAVING distance < 1 AND distance IS NOT NULL +ORDER BY distance +LIMIT 25; diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php index dc0b2936..3790e5a3 100644 --- a/Web/Presenters/WallPresenter.php +++ b/Web/Presenters/WallPresenter.php @@ -309,6 +309,19 @@ final class WallPresenter extends OpenVKPresenter $this->flashFail("err", tr("failed_to_publish_post"), "Poll format invalid"); } + $geo = NULL; + + if (!is_null($this->postParam("geo")) && $this->postParam("geo") != "") { + $geo = json_decode($this->postParam("geo"), true, JSON_UNESCAPED_UNICODE); + if($geo["lat"] && $geo["lng"] && $geo["name"]) { + $latitude = number_format((float) $geo["lat"], 8, ".", ''); + $longitude = number_format((float) $geo["lng"], 8, ".", ''); + if($latitude > 90 || $latitude < -90 || $longitude > 180 || $longitude < -180) { + $this->flashFail("err", tr("error"), "Invalid latitude or longitude"); + } + } + } + if(empty($this->postParam("text")) && sizeof($horizontal_attachments) < 1 && sizeof($vertical_attachments) < 1 && !$poll) $this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big")); @@ -332,6 +345,11 @@ final class WallPresenter extends OpenVKPresenter if($should_be_suggested) $post->setSuggested(1); + if ($geo) { + $post->setGeo(json_encode($geo)); + $post->setGeo_Lat($latitude); + $post->setGeo_Lon($longitude); + } $post->save(); } catch (\LengthException $ex) { $this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big")); diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index b7d84b96..811548cd 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -404,6 +404,11 @@ {script "js/al_feed.js"} {/ifset} + {script "js/node_modules/leaflet/dist/leaflet.js"} + {script "js/node_modules/leaflet-control-geocoder/dist/Control.Geocoder.js"} + {css "js/node_modules/leaflet/dist/leaflet.css"} + {css "js/node_modules/leaflet-control-geocoder/dist/Control.Geocoder.css"} + diff --git a/Web/Presenters/templates/Wall/Feed.xml b/Web/Presenters/templates/Wall/Feed.xml index 59d55de3..f5274cca 100644 --- a/Web/Presenters/templates/Wall/Feed.xml +++ b/Web/Presenters/templates/Wall/Feed.xml @@ -20,7 +20,7 @@
+ + + + | +
+
+
+
+ ${escapeHtml(post.message)}
+
+
+
+
+
+ ${tplMapIcon}
+ ${escapeHtml(post.geo.name)}
+
+ |
+
+