Compare commits

...

57 commits

Author SHA1 Message Date
themohooks
f4cb133e46 fixes 2025-02-21 23:55:20 +03:00
themohooks
540e643e9a Update Index.php 2025-02-21 23:30:47 +03:00
themohooks
e82faca521 патч номер один 2025-02-21 00:31:27 +03:00
themohooks
dea3e88c38 Create sql_0003.sql 2025-02-18 16:41:25 +03:00
themohooks
8681ba9060 Update ngallery-example.yaml 2025-02-18 16:28:01 +03:00
themohooks
cfab1d2e96 update admin pages 2025-02-18 00:28:37 +03:00
themohooks
118968739c update contests 2025-02-18 00:08:13 +03:00
themohooks
04373e197c update contest pages 2025-02-17 23:10:16 +03:00
themohooks
847ebb32be Update VotingWaiting.php 2025-02-17 22:33:04 +03:00
themohooks
0275ff30c6 update send pretend 2025-02-17 22:19:26 +03:00
themohooks
b41ec07317 contests 2025-02-17 22:10:46 +03:00
themohooks
e9655f6463 trash 2025-02-16 06:19:08 +03:00
themohooks
8b8cedf6c1 prretend send 2025-02-16 06:19:04 +03:00
themohooks
303d70d1a3 Update ExecContests.php 2025-02-16 06:18:56 +03:00
themohooks
c31617fd10 update pages 2025-02-16 06:18:52 +03:00
themohooks
4ee7f4b79b update rate contest photos 2025-02-16 06:18:43 +03:00
themohooks
0581edcc3d update js 2025-02-16 06:18:24 +03:00
themohooks
83b0e8ccc9 Update Register.php 2025-02-16 06:18:13 +03:00
themohooks
1dab03afe8 update pages 2025-02-16 04:37:24 +03:00
themohooks
be2963117a task manager & contests via server 2025-02-16 04:37:18 +03:00
themohooks
b5ff9d1998 update contests api 2025-02-16 04:37:06 +03:00
themohooks
3360b9d208 update admin 2025-02-16 04:36:52 +03:00
themohooks
52dbf10d3d update core files 2025-02-16 04:36:38 +03:00
themohooks
be85a9531a contests to photos 2025-02-15 04:54:34 +03:00
themohooks
13f06db887 admin contest pages 2025-02-15 04:54:22 +03:00
themohooks
22e0fe0cec add contest pages 2025-02-15 04:54:12 +03:00
themohooks
b0c8b51fe6 Update Register.php 2025-02-15 04:54:02 +03:00
themohooks
020ec19124 contests api 2025-02-15 04:53:57 +03:00
themohooks
2e5c8ffbb0 materials update 2025-02-15 04:53:38 +03:00
themohooks
8087b7c720 Update .gitignore 2025-02-15 04:53:15 +03:00
themohooks
2918dd347b fix register 2025-02-14 21:29:20 +03:00
themohooks
a665b92e11 Update README.md 2025-02-14 19:56:11 +03:00
themohooks
07a6a41468 another changes 2025-02-14 19:55:09 +03:00
themohooks
61c0e0cc9e some changes to vehicles 2025-02-14 19:54:55 +03:00
themohooks
dce7aa906a comments.php 2025-02-12 16:58:20 +03:00
themohooks
3078238206 fix place on photo 2025-02-12 16:35:08 +03:00
themohooks
69706f7df6 Update Comment.php 2025-02-12 13:00:02 +03:00
themohooks
ab9b61c3ea fix show delete & edit buttons in comments 2025-02-12 12:58:17 +03:00
themohooks
3d03ccce6f fix entity show info 2025-02-11 21:53:43 +03:00
themohooks
9774d329fd Update Main.php 2025-02-11 21:31:13 +03:00
themohooks
3b3efb4c84 fix show page 2025-02-11 21:17:35 +03:00
themohooks
33ce890c07 Create sql_0002.sql 2025-02-11 20:51:14 +03:00
themohooks
558a5cb4c0 fix pin 2025-02-11 20:38:42 +03:00
themohooks
82888d6e06 fixes 2025-02-11 20:25:11 +03:00
themohooks
5ecb218306 Update README.md 2025-02-11 19:34:16 +03:00
themohooks
16322d1664 geodb 2025-02-11 19:30:48 +03:00
themohooks
a4922fed39 Update README.md 2025-02-11 17:12:08 +03:00
themohooks
6c2e8b46a5 some changes 2025-02-11 17:04:31 +03:00
themohooks
a193a702d7 Merge branch 'main' of https://github.com/claradex/nativegallery 2025-02-11 14:20:34 +03:00
themohooks
a57d779caf set comment & new reason for decline 2025-02-11 14:20:32 +03:00
themohooks
8d0ccdeaa3
Update README.md 2025-02-09 03:13:46 +03:00
themohooks
1caedf698b pin comments and some fixes 2025-02-09 03:12:54 +03:00
themohooks
cc193f35ab Merge branch 'main' of https://github.com/claradex/nativegallery 2025-02-07 17:33:40 +03:00
themohooks
0226560702 что-то очень интересное! 2025-02-07 17:33:19 +03:00
themohooks
0eb4b6735e
Update README.md 2025-02-07 00:07:18 +03:00
themohooks
b9e6278d0a
Update README.md 2025-01-25 01:46:48 +03:00
themohooks
3455e55f71 Получается, релиз? 2024-10-16 17:36:15 +03:00
107 changed files with 5919 additions and 584 deletions

4
.gitignore vendored
View file

@ -6,10 +6,12 @@
/ngallery.yaml
ngallery.yaml
/cdn/
*.yaml
ngallery.yaml
/views/pages/t.php
views/pages/t.php
rules.txt
rules.txt
/uploads/*
t.php
logs
.txt

View file

@ -1,8 +1,9 @@
# NativeGallery
![](https://raw.githubusercontent.com/claradex/nativegallery/main/static/img/banner.png)
NativeGallery - это реверсивный open-source движок популярного сайта transphoto.org (СТТС) и ему подобных.
NativeGallery - это реверсивный open-source движок популярного сайта transphoto.org (СТТС), RailGallery, Fotobus и ему подобных.
### ❗ Движок находится в разработке. Некоторые функции, которые присутствуют на оригинальных галереях могут отличаться от функционала NativeGallery или отсуствовать совсем. Пожалуйста, оставляйте найденные баги и свои пожелания в Issues.
# Почему я должен использовать ваш движок?
* **Свобода**: СТТС не предоставляет всем свой исходный код для создания отдельных подобных ему сайтов. С NativeGallery вы сможете обойти это предпятствие!
@ -13,7 +14,7 @@ NativeGallery - это реверсивный open-source движок попу
Мы настоятельно рекомендуем устанавливать движок на VPS/VDS/выделенный сервер. Поддержка на Shared-хостингах не осуществляется!
**Операционная система**: Ubuntu 20.04 и выше\
**PHP:** 8.3 и выше
**PHP:** 8.3 и выше\
**База данных**: MySQL 8.0 и выше
# Статус функционала
@ -21,20 +22,20 @@ NativeGallery - это реверсивный open-source движок попу
- [x] Авторизация, Регистрация
- [x] Просмотр профилей
- [ ] Публикация фото:
- [ ] Привязка сущности (Транспортное Средство, Поезд, Самокат, Камень и прочее)
- [x] Привязка сущности (Транспортное Средство, Поезд, Самокат, Камень и прочее)
- [x] Загрузка фото
- [ ] GeoDB
- [x] GeoDB
- [x] Геометка
- [ ] Направление съёмки
- [ ] Состояние фотографии
- [ ] Временная публикация
- [ ] Условная публикация
- [ ] Техническая публикация
- [x] Временная публикация
- [x] Условная публикация
- [x] Техническая публикация
- [ ] Требует исправления
- [ ] Возможность создания своих статусов
- [x] Галереи
- [x] Вид сущности (Трамвай, Метрополитен, Троллейбус и т.д)
- [ ] GeoDB
- [x] GeoDB
- [ ] Фотоконкурс
- [ ] Поиск
- [ ] Поиск по критериям
@ -51,10 +52,9 @@ NativeGallery - это реверсивный open-source движок попу
- [x] Комментирование
- [x] Рейтинг комментариев
- [x] Избранные фотографии
- [ ] Полноценный EXIF
- [x] Полноценный EXIF
- [x] Модерация
- [ ] Редактирование
- [ ] Примечания (для сущностей)
- [ ] Обновления:
- [x] Новые фотографии
- [x] Новые фотографии из подписок

View file

@ -0,0 +1,31 @@
<?php
namespace App\Controllers\Api\Admin\Contests;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF};
use App\Models\{User, Vote, Photo};
class Create
{
public function __construct()
{
$openprdate = strtotime($_POST['openpretendsdate']);
$closeprdate = strtotime($_POST['closepretendsdate']);
$opendate = strtotime($_POST['opendate']);
$closedate = strtotime($_POST['closedate']);
if ($_POST['startContestNow'] === "1") {
$opendate = $closeprdate;
}
DB::query('INSERT INTO contests VALUES (\'0\', :themeid, :openprdate, :closeprdate, :opendate, :closedate, 0)', array(':themeid' => $_POST['themeid'], ':openprdate' => $openprdate, ':closeprdate'=>$closeprdate, ':opendate' => $opendate, ':closedate'=>$closedate));
echo json_encode(
array(
'errorcode' => 0,
'error' => 0
)
);
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Controllers\Api\Admin\Contests;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF};
use App\Models\{User, Vote, Photo};
class CreateTheme
{
public function __construct()
{
if ($_POST['active'] === "1") {
$status = 1;
} else {
$status = 0;
}
DB::query('INSERT INTO contests_themes VALUES (\'0\', :title, :status)', array(':title' => $_POST['body'], ':status' => $status));
echo json_encode(
array(
'errorcode' => 0,
'error' => 0
)
);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace App\Controllers\Api\Admin\GeoDB;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF};
use App\Models\{User, Vote, Photo};
class Create
{
public function __construct()
{
DB::query('INSERT INTO geodb VALUES (\'0\', :body)', array(':body' => $_POST['body']));
echo json_encode(
array(
'errorcode' => 0,
'error' => 0
)
);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace App\Controllers\Api\Admin\GeoDB;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF};
use App\Models\{User, Vote, Photo};
class Delete
{
public function __construct()
{
DB::query('DELETE FROM geodb WHERE id=:id', array(':id' => $_GET['id']));
echo json_encode(
array(
'errorcode' => 0,
'error' => 0
)
);
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Controllers\Api\Admin\GeoDB;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF, Date};
use App\Models\{User, Vote, Photo};
class Load
{
public function __construct()
{
$geodb = DB::query('SELECT * FROM geodb');
foreach ($geodb as $u) {
echo '<tr>
<th>' . $u['id'] . '</th>
<td>' . $u['title'] . '</td>
<td><div class="cmt-submit"><a class="btn btn-sm btn-primary" href="/admin?type=UserEdit&user_id=' . $u['id'] . '">Редактировать</a><a style="margin-left: 15px;" class="btn btn-sm btn-danger" href="/admin?type=UserEdit&user_id=' . $u['id'] . '">Удалить</a></div></td>
</tr>';
}
}
}

View file

@ -12,18 +12,27 @@ class SetVisibility
{
public function __construct()
{
$priority = 0;
$photo = new Photo($_GET['id']);
$data = json_decode($photo->i('content'), true);
if (!array_key_exists('declineReason', $data)) {
$data['declineReason'] = null;
}
$data['declineReason'] = $_GET['decline_reason'];
if ($_POST['comment'] != null) {
$data['declineComment'] = $_POST['comment'];
}
if ($_GET['mod'] != 1) {
$data['declineReason'] = $_GET['reason'];
} else {
$priority = $_GET['reason'];
}
$updatedJsonString = json_encode($data);
DB::query('UPDATE photos SET moderated=:mod, timeupload=:time, content=:c WHERE id=:id', array(':id'=>$_GET['id'], ':mod'=>$_GET['mod'], ':time'=>time(), ':c'=>$updatedJsonString));
DB::query('UPDATE photos SET moderated=:mod, timeupload=:time, priority=:pr, content=:c WHERE id=:id', array(':id'=>$_GET['id'], ':mod'=>$_GET['mod'], ':time'=>time(), ':pr'=>$priority, ':c'=>$updatedJsonString));
$uid = DB::query('SELECT user_id FROM photos WHERE id=:id', array(':id'=>$_GET['id']))[0]['user_id'];
if ($_GET['mod'] === 1) {
$followers = DB::query('SELECT * FROM followers WHERE user_id=:uid', array(':uid'=>$uid));

View file

@ -0,0 +1,37 @@
<?php
namespace App\Controllers\Api\Admin\Settings;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF, TaskScheduler};
use App\Models\{User, Vote, Photo};
class TaskManager
{
public function __construct()
{
$task = new TaskScheduler();
foreach (NGALLERY_TASKS as $t) {
$id = $_GET['id'];
if (isset($t['id']) && $t['id'] == $id) {
if ($_GET['type'] === 0) {
$task->removeTask($t['id'], "php ".$_SERVER['DOCUMENT_ROOT'].$t['handler']);
} else {
$task->addTask(
$t['id'],
"php ".$_SERVER['DOCUMENT_ROOT'].$t['handler']." >> ".$_SERVER['DOCUMENT_ROOT'].NGALLERY['root']['logslocation']." 2>&1",
"* * * * *"
);
}
echo json_encode(
array(
'errorcode' => 0,
'error' => 0
)
);
}
}
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Controllers\Api\Contests;
use App\Services\{DB, Json};
class GetInfo
{
private array $contest;
private string $pretendsStatus;
private string $publicStatus;
public function __construct()
{
$this->fetchContest();
$this->setStatuses();
$this->outputJson();
}
private function fetchContest(): void
{
$statuses = [0, 1, 2];
foreach ($statuses as $status) {
$contest = DB::query('SELECT * FROM contests WHERE status = :status', [':status' => $status]);
if (!empty($contest)) {
$this->contest = $contest[0];
break;
}
}
}
private function setStatuses(): void
{
$time = time();
$status = $this->contest['status'] ?? null;
if ($status === 0) {
$this->pretendsStatus = ($this->contest['openpretendsdate'] > $time) ? 'waiting' : 'waitingforserver';
$this->publicStatus = 'closed';
} elseif ($status === 1) {
$this->pretendsStatus = ($this->contest['closepretendsdate'] > $time) ? 'opened' : 'waitingforserver';
$this->publicStatus = 'closed';
} elseif ($status === 2) {
$this->pretendsStatus = 'closed';
$this->publicStatus = ($this->contest['closedate'] <= $time) ? 'waitingforserver' : 'opened';
}
}
private function outputJson(): void
{
echo json_encode(array(
'contest' => $this->contest,
'statuses' => [
'pretends' => $this->pretendsStatus,
'public' => $this->publicStatus,
]
));
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Controllers\Api\GeoDB;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF, Date};
use App\Models\{User, Vote, Photo};
class Search
{
public function __construct()
{
$query = $_GET['place'];
if ($query) {
$addresses = DB::query('SELECT title FROM geodb WHERE LOWER(title) LIKE LOWER(:query)', array(':query' => "%$query%"));
$titles = array_map(function($address) {
return $address['title'];
}, $addresses);
echo json_encode($titles, JSON_UNESCAPED_UNICODE);
} else {
echo json_encode(["error" => "No query provided"], JSON_UNESCAPED_UNICODE);
}
}
}

View file

@ -4,15 +4,15 @@ namespace App\Controllers\Api\Images\Comments;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, Files, Shell};
use App\Models\Notification;
use App\Services\Upload as Upload;
class Create
{
static $filesrc;
private static function create($content, $id)
{
DB::query('INSERT INTO photos_comments VALUES (\'0\', :userid, :postid, :postbody, :time, :content)', array('postid' => $_POST['id'], ':postbody' => $_POST['wtext'], ':userid' => Auth::userid(), ':time' => time(), ':content'=>''));
DB::query('INSERT INTO photos_comments VALUES (\'0\', :userid, :postid, :postbody, :time, :content)', array('postid' => $_POST['id'], ':postbody' => $_POST['wtext'], ':userid' => Auth::userid(), ':time' => time(), ':content'=>$content));
}
public function __construct()
{
@ -21,15 +21,54 @@ class Create
if ((int)$id === DB::query('SELECT id FROM photos WHERE id=:id', array(':id' => $id))[0]['id']) {
$content = Json::return(
array(
'type' => 'none',
'by' => 'user'
)
);
if ($_FILES['filebody']['error'] != 4) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['filebody']['tmp_name']);
finfo_close($finfo);
$filename = GenerateRandomStr::gen_uuid();
if (preg_match('/^image\//', $mime)) {
$info = getimagesize($_FILES['filebody']['tmp_name']);
if (strlen($postbody) < 4096 || strlen($postbody) > 1) {
if (trim($postbody) != '') {
if ($info['mime'] == 'image/jpeg')
$image = imagecreatefromjpeg($_FILES['filebody']['tmp_name']);
elseif ($info['mime'] == 'image/gif')
$image = imagecreatefromgif($_FILES['filebody']['tmp_name']);
elseif ($info['mime'] == 'image/png')
$image = imagecreatefrompng($_FILES['filebody']['tmp_name']);
$type = 'img';
$destination = '/cdn/temp/'.$filename.'.jpg';
imagejpeg($image, $_SERVER['DOCUMENT_ROOT'].$destination, 60);
} else if (preg_match('/^audio\//', $mime)) {
return "Аудио";
} else if (preg_match('/^video\//', $mime)) {
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$ffmpeg = \FFMpeg\FFMpeg::create(array(
'ffmpeg.binaries' => $_SERVER['DOCUMENT_ROOT'] . '/app/Controllers/Exec/ffmpeg.exe',
'ffprobe.binaries' => $_SERVER['DOCUMENT_ROOT'] . '/app/Controllers/Exec/ffprobe.exe',
'timeout' => 3600, // The timeout for the underlying process
'ffmpeg.threads' => 12, // The number of threads that FFMpeg should use
), $logger);
} else {
$ffmpeg = \FFMpeg\FFMpeg::create();
}
$video = $ffmpeg->open($_FILES['filebody']['tmp_name']);
$video->save(new \FFMpeg\Format\Video\X264(), $_SERVER['DOCUMENT_ROOT'] . "/cdn/temp/" . $filename . '.mp4');
$video->frame(\FFMpeg\Coordinate\TimeCode::fromSeconds(1))->save($_SERVER['DOCUMENT_ROOT'] . "/cdn/temp/VIDPRV_" . $filename . '.jpg');
$type = 'video';
$destination = '/cdn/temp/'.$filename.'.mp4';
$destination_vidprv = '/cdn/temp/VIDPRV'.$filename.'.jpg';
} else {
return "Неизвестный тип файла";
}
$upload = new Upload($_SERVER['DOCUMENT_ROOT'].$destination, 'cdn/'.$type.'/');
self::$filesrc = $upload->getSrc();
}
if ((strlen($postbody) < 4096 || strlen($postbody) > 1) || $_FILES['filebody']['error'] != 4) {
if (trim($postbody) != '' || $_FILES['filebody']['error'] != 4) {
$postbody = ltrim($postbody);
echo json_encode(
array(
@ -48,17 +87,24 @@ class Create
} else {
die(json_encode(
array(
'errorcode' => '1',
'errorcode' => '2',
'error' => 1
)
));
}
$content = Json::return(
array(
'type' => 'none',
'by' => 'user',
'filetype' => $type,
'src' => self::$filesrc
)
);
self::create($content, $id);
} else {
die(json_encode(
array(
'errorcode' => '1',
'errorcode' => '3',
'error' => 1
)
));

View file

@ -5,7 +5,7 @@ namespace App\Controllers\Api\Images\Comments;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF};
use App\Models\{User, Vote, Comment};
use App\Models\{User, Vote, Comment, Photo};
class Load
@ -13,10 +13,12 @@ class Load
public function __construct()
{
$comments = DB::query('SELECT * FROM photos_comments WHERE photo_id=:pid', array(':pid' => explode('/', $_SERVER['REQUEST_URI'])[4]));
$photo = new Photo(explode('/', $_SERVER['REQUEST_URI'])[4]);
$comments = DB::query('SELECT * FROM photos_comments WHERE photo_id=:pid ORDER BY CASE WHEN id = :pinnedid THEN 0 ELSE 1 END, id ASC', array(':pid' => explode('/', $_SERVER['REQUEST_URI'])[4], ':pinnedid' => $photo->i('pinnedcomment_id')));
$number = 1;
foreach ($comments as $c) {
$comm = new Comment($c);
$photo = new Photo($c['photo_id']);
if ($comm->content('deleted') != 'true') {
if ($number % 2 == 0) {
$class = 's11';

View file

@ -0,0 +1,41 @@
<?php
namespace App\Controllers\Api\Images\Comments;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json};
class Pin
{
public function __construct()
{
$postId = explode('/', $_SERVER['REQUEST_URI'])[4];
$cpostid = DB::query('SELECT photo_id FROM photos_comments WHERE id=:id', array(':id' => $postId))[0]['photo_id'];
if (DB::query('SELECT user_id FROM photos WHERE id=:id', array(':id' => $cpostid))[0]['user_id'] === Auth::userid()) {
$data = DB::query('SELECT * FROM photos WHERE id=:id', array(':id'=>$cpostid))[0];
if ($data['pinnedcomment_id'] === (int)$postId) {
DB::query('UPDATE photos SET pinnedcomment_id=0 WHERE id=:id', array(':id'=>$cpostid));
echo json_encode(
array(
'errorcode' => '0',
'error' => 0,
'action' => 'unpin',
)
);
} else {
DB::query('UPDATE photos SET pinnedcomment_id=:pid WHERE id=:id', array(':pid'=>$postId, ':id'=>$cpostid));
echo json_encode(
array(
'errorcode' => '0',
'error' => 0,
'action' => 'pin',
)
);
}
}
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace App\Controllers\Api\Images\Contests;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF};
use App\Models\{User, Vote, Photo};
class Rate
{
public function __construct()
{
$count = 3;
$uservotes = DB::query('SELECT COUNT(*) FROM contests_rates WHERE user_id=:uid AND contest_id=:cid', array(':uid' => Auth::userid(), ':cid' => $_GET['kid']))[0]['COUNT(*)'];
$countvotes = $count - $uservotes;
$contest = DB::query('SELECT * FROM contests WHERE id=:id', array(":id" => $_GET['kid']))[0];
$photo = new Photo($_GET['pid']);
if ($contest['status'] != 2) {
exit;
}
if ($photo->i('on_contest') != 1 && $photo->i('contest_id') != $_GET['kid']) {
exit;
}
if ((int)DB::query('SELECT photo_id FROM contests_rates WHERE photo_id=:pid AND user_id=:uid AND contest_id=:cid', array(':uid' => Auth::userid(), ':pid' => $_GET['pid'], ':cid' => $_GET['kid']))[0]['photo_id'] === (int)$_GET['pid']) {
DB::query('DELETE FROM contests_rates WHERE user_id=:uid AND photo_id=:pid AND contest_id=:cid', array(':pid' => $_GET['pid'], ':uid' => Auth::userid(), ':cid' => $_GET['kid']));
$status = 0;
$newval = $countvotes + 1;
} else {
$newval = $countvotes - 1;
if ($newval >= 0) {
DB::query('INSERT INTO contests_rates VALUES (\'0\', :pid, :uid, :cid)', array(':pid' => $_GET['pid'], ':uid' => Auth::userid(), ':cid' => $_GET['kid']));
$status = 1;
}
}
if ($newval < 0) {
$text = 'Вы можете выбрать максимум 3 фотографии.';
} else if ($newval === 0) {
$text = 'Вы выбрали 3 фотографии. Спасибо за голосование!';
} else {
$text = "Вы можете выбрать ещё {$newval} фото.";
}
echo '[{"' . $_GET['pid'] . '":'.$status.'},"' . $text . '"]';
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace App\Controllers\Api\Images\Contests;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF};
use App\Models\{User, Vote};
class SendPretend
{
public function __construct()
{
if (isset($_POST['cid'])) {
if (DB::query('SELECT contest_id FROM photos WHERE user_id=:uid', array(':uid' => Auth::userid()))[0]['contest_id'] != $_POST['cid']) {
DB::query('UPDATE photos SET on_contest=1, contest_id=:id WHERE id=:idd', array(':id' => $_POST['cid'], ':idd' => $_POST['photo_id']));
echo json_encode(
array(
'errorcode' => 0,
'error' => 0
)
);
}
} else {
echo json_encode(
array(
'errorcode' => 1,
'error' => 0
)
);
}
}
}

View file

@ -13,7 +13,7 @@ class LoadRecent
$response = [];
if ($_POST['serverhost'] != 'transphoto.org') {
$photos = DB::query('SELECT * FROM photos WHERE moderated=1 ORDER BY id DESC LIMIT 30');
$photos = DB::query('SELECT * FROM photos WHERE moderated=1 AND id<:id ORDER BY id DESC LIMIT 30', array(':id'=>$_GET['lastpid']));
foreach ($photos as $p) {
@ -23,7 +23,7 @@ class LoadRecent
$date = Date::zmdate($p['posted_at']);
}
$user = DB::query('SELECT * FROM users WHERE id=:id', array(':id' => $p['user_id']))[0];
$comments = DB::query('SELECT COUNT(*) FROM photos_comments WHERE photo_id=:pid', array(':pid'=>$p['id']))[0]['COUNT(*)'];
$response[] = [
'id' => $p['id'],
'place' => htmlspecialchars($p['place']),
@ -31,7 +31,8 @@ class LoadRecent
'user_name' => $user['username'],
'user_id' => $p['user_id'],
'photourl' => $p['photourl'],
'photourl_small' => 'https://' . $_SERVER['SERVER_NAME'] . '/api/photo/compress?url=' . $p['photourl']
'photourl_small' => 'https://' . $_SERVER['SERVER_NAME'] . '/api/photo/compress?url=' . $p['photourl'],
'ccnt' => $comments
];
}
} else {

View file

@ -2,69 +2,103 @@
namespace App\Controllers\Api\Images;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF};
use App\Models\{User, Vote};
use App\Models\{User, Vote, VoteContest};
class Rate
{
public function __construct()
{
if (isset($_GET['vote']) && isset($_GET['pid'])) {
if (Vote::photo(Auth::userid(), $_GET['pid']) === -1) {
DB::query('INSERT INTO photos_rates VALUES (\'0\', :id, :pid, :type)', array(':id'=>Auth::userid(), ':pid' => $_GET['pid'], ':type'=>$_GET['vote']));
if (Vote::photo(Auth::userid(), $_GET['pid']) != $_GET['vote']) {
DB::query('DELETE FROM photos_rates WHERE user_id=:id AND photo_id=:pid AND type=:type', array(':id'=>Auth::userid(), ':pid' => $_GET['pid'], ':type'=>Vote::photo(Auth::userid(), $_GET['pid'])));
$userId = Auth::userid();
$photoId = $_GET['pid'];
$voteType = (int) $_GET['vote'];
$contest = (isset($_GET['action']) && $_GET['action'] === 'vote-konk') ? 1 : 0;
$contestId = $_GET['cid'];
if ($contest === 1) {
if (VoteContest::photo($userId, $photoId, $contestId) === -1) {
DB::query(
'INSERT INTO photos_rates_contest (id, user_id, photo_id, type, contest_id) VALUES (NULL, :id, :pid, :type, :cid)',
[':id' => $userId, ':pid' => $photoId, ':type' => $voteType, ':cid'=>$contestId]
);
if (VoteContest::photo($userId, $photoId, $contestId) != $voteType) {
DB::query(
'DELETE FROM photos_rates_contest WHERE user_id=:id AND photo_id=:pid AND type=:type AND contest_id=:cid',
[':id' => $userId, ':pid' => $photoId, ':type' => VoteContest::photo($userId, $photoId, $contestId), ':cid'=>$contestId]
);
}
} elseif (VoteContest::photo($userId, $photoId, $contestId) === $voteType) {
DB::query(
'DELETE FROM photos_rates_contest WHERE user_id=:id AND photo_id=:pid AND contest_id=:cid',
[':id' => $userId, ':pid' => $photoId, ':cid'=>$contestId]
);
} else {
DB::query(
'UPDATE photos_rates_contest SET type=:type WHERE user_id=:id AND photo_id=:pid AND contest_id=:cid',
[':id' => $userId, ':pid' => $photoId, ':type' => $voteType, ':cid'=>$contestId]
);
}
} else if (Vote::photo(Auth::userid(), $_GET['pid']) === (int)$_GET['vote']) {
DB::query('DELETE FROM photos_rates WHERE user_id=:id AND photo_id=:pid', array(':id'=>Auth::userid(), ':pid' => $_GET['pid']));
} else {
DB::query('UPDATE photos_rates SET type=:type WHERE user_id=:id AND photo_id=:pid', array(':id'=>Auth::userid(), ':pid' => $_GET['pid'], ':type'=>$_GET['vote']));
if (Vote::photo($userId, $photoId) === -1) {
DB::query(
'INSERT INTO photos_rates (id, user_id, photo_id, type, contest) VALUES (NULL, :id, :pid, :type, 0)',
[':id' => $userId, ':pid' => $photoId, ':type' => $voteType]
);
if (Vote::photo($userId, $photoId) != $voteType) {
DB::query(
'DELETE FROM photos_rates WHERE user_id=:id AND photo_id=:pid AND type=:type AND contest=0',
[':id' => $userId, ':pid' => $photoId, ':type' => Vote::photo($userId, $photoId)]
);
}
} elseif (Vote::photo($userId, $photoId) === $voteType) {
DB::query(
'DELETE FROM photos_rates WHERE user_id=:id AND photo_id=:pid AND contest=0',
[':id' => $userId, ':pid' => $photoId]
);
} else {
DB::query(
'UPDATE photos_rates SET type=:type WHERE user_id=:id AND photo_id=:pid AND contest=0',
[':id' => $userId, ':pid' => $photoId, ':type' => $voteType]
);
}
}
$votes = DB::query('SELECT * FROM photos_rates WHERE photo_id=:id ORDER BY id DESC', array(':id' => $_GET['pid']));
$votes = DB::query('SELECT * FROM photos_rates WHERE photo_id=:id ORDER BY id DESC', [':id' => $photoId]);
$formattedVotesPos = [];
$formattedVotesNeg = [];
foreach ($votes as $vote) {
$user = new User($vote['user_id']);
if ($vote['type'] === 0) {
$type = 0;
$formattedVotesNeg[] = [$vote['user_id'], $user->i('username'), $type];
} else if ($vote['type'] === 1) {
$type = 1;
$formattedVotesPos[] = [$vote['user_id'], $user->i('username'), $type];
$formattedVotesNeg[] = [$vote['user_id'], $user->i('username'), 0];
} elseif ($vote['type'] === 1) {
$formattedVotesPos[] = [$vote['user_id'], $user->i('username'), 1];
}
}
if (Vote::photo(Auth::userid(), $_GET['pid']) === 0) {
$negbtn = true;
$posbtn = false;
} else if (Vote::photo(Auth::userid(), $_GET['pid']) === 1) {
$negbtn = false;
$posbtn = true;
$currentVote = Vote::photo($userId, $photoId);
$contCurrentVote = VoteContest::photo($userId, $photoId, $contestId);
if ($contest === 0) {
$count = Vote::count($photoId);
} else {
$negbtn = false;
$posbtn = false;
$count = VoteContest::count($photoId, $contestId);
}
$result = [
'buttons' => [$negbtn, $posbtn],
'buttons' => [
'negbtn' => $currentVote === 0,
'posbtn' => $currentVote === 1,
'negbtn_contest' => $contCurrentVote === 0,
'posbtn_contest' => $contCurrentVote === 1,
],
'errors' => '',
'rating' => Vote::count($_GET['pid'])
'rating' => $count,
'votes' => [
1 => $formattedVotesPos,
0 => $formattedVotesNeg
]
];
$votes = [];
$votes[1] = $formattedVotesPos;
$votes[0] = $formattedVotesNeg;
if (!empty($votes)) {
$result['votes'] = $votes;
}
header('Content-Type: application/json');
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

View file

@ -37,7 +37,7 @@ class Upload
} else {
$moderated = 1;
}
DB::query('INSERT INTO photos VALUES (\'0\', :userid, :postbody, :photourl, :time, :timeup, :exif, 0, :moderated, :place, 0, :gallery, :entityid, :content)', array(':postbody' => $postbody, ':userid' => Auth::userid(), ':time' => mktime(0, 0, 0, $_POST['month'], $_POST['day'], $_POST['year']), ':content' => $content, ':photourl' => self::$photourl, ':exif' => $exif, ':place' => $_POST['place'], ':timeup' => time(), ':moderated' => $moderated, ':gallery'=>$_POST['gallery'], ':entityid'=>self::$entitydata_id));
DB::query('INSERT INTO photos VALUES (\'0\', :userid, :postbody, :photourl, :time, :timeup, :exif, 0, :moderated, :place, 0, :gallery, :entityid, 0, 0, 0, :content)', array(':postbody' => $postbody, ':userid' => Auth::userid(), ':time' => mktime(0, 0, 0, $_POST['month'], $_POST['day'], $_POST['year']), ':content' => $content, ':photourl' => self::$photourl, ':exif' => $exif, ':place' => $_POST['place'], ':timeup' => time(), ':moderated' => $moderated, ':gallery'=>$_POST['gallery'], ':entityid'=>self::$entitydata_id));
if (($moderated === 1) && (self::$subsnotify != 'disabled')) {
$followers = DB::query('SELECT * FROM followers WHERE user_id=:uid', array(':uid' => Auth::userid()));
foreach ($followers as $f) {

View file

@ -7,7 +7,9 @@ use \App\Controllers\ExceptionRegister;
use \App\Core\Page;
use donatj\UserAgent\UserAgentParser;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use Beeyev\DisposableEmailFilter\DisposableEmailFilter;
class Register
{
@ -294,6 +296,7 @@ class Register
$password = $_POST['password'];
$email = $_POST['email'];
$forbusernames = explode(',', NGALLERY['root']['registration']['prohibited_usernames']);
$status = 0;
if (!self::checkforb($_POST['username'], $forbusernames)) {
if (!strcasecmp(DB::query('SELECT username FROM users WHERE (LOWER(username) LIKE :username)', array(':username' => '%' . $username . '%'))[0]['username'], $username) === false) {
@ -315,11 +318,107 @@ class Register
'regdate' => time()
)
);
DB::query('INSERT INTO users VALUES (\'0\', :username, :email, :password, :photourl, 5, :online, 0, 0, :content)', array(':username' => ltrim($username), ':password' => password_hash(ltrim($password), PASSWORD_BCRYPT), ':photourl' => '/static/img/avatar.png', ':email' => $email, ':content' => $content, ':online' => time()));
$cstrong = True;
if (NGALLERY['root']['registration']['emailverify'] === true) {
$status === 3;
}
$token = GenerateRandomStr::gen_uuid();
DB::query('INSERT INTO users VALUES (\'0\', :username, :email, :password, :photourl, 5, :online, 0, :status, :content)', array(':username' => ltrim($username), ':password' => password_hash(ltrim($password), PASSWORD_BCRYPT), ':photourl' => '/static/img/avatar.png', ':email' => $email, ':content' => $content, ':online' => time(), ':status'=>$status));
$user_id = DB::query('SELECT id FROM users WHERE username=:username', array(':username' => $username))[0]['id'];
if (NGALLERY['root']['registration']['emailverify'] === true) {
$disposableEmailFilter = new DisposableEmailFilter();
if ($disposableEmailFilter->isDisposableEmailAddress($_POST['email'])) {
echo json_encode(
array(
'errorcode' => '9',
'errortitle' => 'Почта запрещена для регистрации',
'error' => 1
)
);
die();
}
$key = GenerateRandomStr::gen_uuid();
$content = Json::return(
array(
'user_id' => $user_id
)
);
$mail = new PHPMailer(true);
DB::query('INSERT INTO servicekeys VALUES (\'0\', :token, :type, 1, :content)', array(':token'=>$key, ':type'=>'EMAILVERIFY', ':content'=>$content));
try {
$mail->isSMTP();
$mail->Host = NGALLERY['root']['email']['credentials']['host'];
$mail->SMTPAuth = true;
$mail->CharSet = "UTF-8";
$mail->Username = NGALLERY['root']['email']['credentials']['username'];
$mail->Password = NGALLERY['root']['email']['credentials']['password'];
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = NGALLERY['root']['email']['credentials']['port'];
$mail->setFrom(NGALLERY['root']['email']['from']['address'], NGALLERY['root']['title']);
$mail->addAddress($_POST['email']);
$mail->isHTML(true);
$mail->Subject = 'Подтверждение регистрации | '.NGALLERY['root']['title'];
$mail->Body = '
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
.container {
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
max-width: 600px;
margin: auto;
}
h1 {
color: #333;
}
.code {
font-size: 24px;
font-weight: bold;
color: #007bff;
margin: 20px 0;
}
.footer {
margin-top: 20px;
font-size: 12px;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<h1>Подтверждение регистрации</h1>
<p>Это письмо было отправлено на ваш почтовый ящик, так как оно было указано при регистрации на '.NGALLERY['root']['title'].' ('.$_SERVER['HTTP_HOST'].')<br>Если вы его не запрашивали, проигнорируйте его.</p><br><br>Ссылка для подтверждения адреса: <a href="https://'.$_SERVER['HTTP_HOST'].'/api/users/emailverify?token='.$key.'">https://'.$_SERVER['HTTP_HOST'].'/api/users/emailverify?token='.$key.'</a>
</div>
</body>
</html>';
$mail->send();
} catch (Exception $e) {
echo json_encode(
array(
'errorcode' => '8',
'errortitle' => 'Не удалось отправить письмо: '.$mail->ErrorInfo,
'error' => 1,
)
);
die();
}
}
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
@ -328,19 +427,11 @@ class Register
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
$parser = new UserAgentParser();
$ua = $parser->parse();
$ua = $parser();
$servicekey = GenerateRandomStr::gen_uuid();
$url = 'http://ip-api.com/json/' . $ip;
$response = file_get_contents($url);
$data = json_decode($response, true);
$loc = $data['country'] . ', ' . $data['city'];
DB::query('INSERT INTO login_tokens VALUES (\'0\', :token, :user_id)', array(
':token' => $token,
':user_id' => $user_id,
@ -350,7 +441,6 @@ class Register
setcookie("NGALLERYSESS", $token, time() + 120 * 180 * 240 * 720, '/', NULL, NULL, TRUE);
setcookie("NGALLERYSESS_", '1', time() + 120 * 180 * 240 * 360, '/', NULL, NULL, TRUE);
setcookie("NGALLERYID", $user_id, time() + 10 * 10 * 24 * 72, '/', NULL, NULL, TRUE);
echo json_encode(
array(
'errorcode' => '0',

View file

@ -0,0 +1,25 @@
<?php
namespace App\Controllers\Api\Users;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF};
use App\Models\{User, Vote};
use \App\Core\Page;
class EmailVerify
{
public function __construct()
{
if (isset($_GET['token'])) {
$data = DB::query('SELECT * FROM servicekeys WHERE token=:token AND type="EMAILVERIFY"', array(':token'=>$_GET['token']))[0];
$user_id = json_decode($data['content'], true)['user_id'];
if ($data['status'] != 0) {
DB::query('UPDATE users SET status=0 WHERE id=:id', [':id' => $user_id]);
DB::query('UPDATE servicekeys SET status=0 WHERE token=:id', [':id' => $_GET['token']]);
Page::set('System/EmailVerify');
}
}
}
}

View file

@ -16,15 +16,28 @@ use \App\Controllers\Api\Images\Stats as PhotoStats;
use \App\Controllers\Api\Images\Comments\Create as PhotoComment;
use \App\Controllers\Api\Images\Comments\Edit as PhotoCommentEdit;
use \App\Controllers\Api\Images\Comments\Delete as PhotoCommentDelete;
use \App\Controllers\Api\Images\Comments\Pin as PhotoCommentPin;
use \App\Controllers\Api\Images\Comments\Load as PhotoCommentLoad;
use \App\Controllers\Api\Images\Comments\Rate as PhotoCommentVote;
use \App\Controllers\Api\Images\Contests\SendPretend as PhotoContestsSendPretend;
use \App\Controllers\Api\Images\Contests\Rate as PhotoContestsRate;
use \App\Controllers\Api\Contests\GetInfo as ContestsGetInfo;
use \App\Controllers\Api\GeoDB\Search as GeoDBSearch;
use \App\Controllers\Api\Vehicles\Load as VehiclesLoad;
use \App\Controllers\Api\Profile\Update as ProfileUpdate;
use \App\Controllers\Api\Users\LoadUser as UserLoad;
use \App\Controllers\Api\Users\EmailVerify as EmailVerify;
use \App\Controllers\Api\Admin\Images\SetVisibility as AdminPhotoSetVisibility;
use \App\Controllers\Api\Admin\CreateNews as AdminCreateNews;
use \App\Controllers\Api\Admin\LoadNews as AdminLoadNews;
use \App\Controllers\Api\Admin\GetVehicleInputs as AdminGetVehicleInputs;
use \App\Controllers\Api\Admin\GeoDB\Create as AdminGeoDBCreate;
use \App\Controllers\Api\Admin\GeoDB\Load as AdminGeoDBLoad;
use \App\Controllers\Api\Admin\GeoDB\Delete as AdminGeoDBDelete;
use \App\Controllers\Api\Admin\Contests\CreateTheme as AdminContestsCreateTheme;
use \App\Controllers\Api\Admin\Contests\Create as AdminContestsCreate;
use \App\Controllers\Api\Admin\Settings\TaskManager as AdminTaskManager;
class ApiController
{
@ -38,9 +51,15 @@ class ApiController
public static function upload() {
return new Upload();
}
public static function emailverify() {
return new EmailVerify();
}
public static function photovote() {
return new PhotoVote();
}
public static function photovotecontest() {
return new PhotoContestsRate();
}
public static function photofavorite() {
return new PhotoFavorite();
}
@ -53,6 +72,9 @@ class ApiController
public static function photocommentdelete() {
return new PhotoCommentDelete();
}
public static function photocommentpin() {
return new PhotoCommentPin();
}
public static function photocommentvote() {
return new PhotoCommentVote();
}
@ -65,6 +87,9 @@ class ApiController
public static function photocompress() {
return new PhotoCompress();
}
public static function geodbsearch() {
return new GeoDBSearch();
}
public static function adminsetvis() {
return new AdminPhotoSetVisibility();
}
@ -77,6 +102,9 @@ class ApiController
public static function recentphotos() {
return new PhotoLoadRecent();
}
public static function sendpretendphoto() {
return new PhotoContestsSendPretend();
}
public static function loaduser() {
return new UserLoad();
}
@ -92,9 +120,30 @@ class ApiController
public static function admingetvehicleinputs() {
return new AdminGetVehicleInputs();
}
public static function admincontestscreatetheme() {
return new AdminContestsCreateTheme();
}
public static function admincontestscreate() {
return new AdminContestsCreate();
}
public static function admingeodbcreate() {
return new AdminGeoDBCreate();
}
public static function admingeodbload() {
return new AdminGeoDBLoad();
}
public static function admingeodbdelete() {
return new AdminGeoDBDelete();
}
public static function admintaskmanager() {
return new AdminTaskManager();
}
public static function vehiclesload() {
return new VehiclesLoad();
}
public static function contestsgetinfo() {
return new ContestsGetInfo();
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Controllers;
use \App\Services\{Router, Auth, DB, Json};
use \App\Controllers\ExceptionRegister;
use \App\Core\Page;
class ContestsController
{
public static function results()
{
Page::set('Contests/VotingResults');
}
public static function index()
{
Page::set('Contests/VotingIndex');
}
public static function waiting()
{
Page::set('Contests/VotingWaiting');
}
public static function sendpretend()
{
Page::set('Contests/VotingSendPretend');
}
}

View file

@ -0,0 +1,27 @@
$tempfile = $args[0]
$weburl = $args[1]
$id = $args[2]
#$tempfile = 'G:\video.mp4'
#$weburl = 'kandle.loc'
#$id = '40'
$hash = -join (((48..57)+(65..90)+(97..122)) * 80 |Get-Random -Count 32 |%{[char]$_})
$hashT = -join (((48..57)+(65..90)+(97..122)) * 80 |Get-Random -Count 222 |%{[char]$_})
$temp = [System.IO.Path]::GetTempFileName()
$shell = Get-WmiObject Win32_process -filter "ProcessId = $PID"
$shell.SetPriority(16384)
Copy-Item $tempfile E:\$hash
E:\Maksim\kandle\app\Controllers\Video\Exec\ffmpeg.exe -i E:\$hash -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -y C:\kandletemp\$hashT.mp4
$uri = 'http://'+$weburl+'/api/video/exec/upload?file=C:\kandletemp\'+$hashT+'.mp4&videoid='+$id
Invoke-WebRequest -Uri $uri
Remove-Item C:\kandletemp\$hashT.mp4

View file

@ -0,0 +1,14 @@
tmpfile="$RANDOM-$(date +%s%N)"
copyfile="$0"
weburl="$1"
videoid="$2"
#copyfile='/var/www/fastuser/data/www/kandle.cats.ovh/video.mp4'
#weburl='kandle.loc'
#videoid='1'
cp $1 "/tmp/vid_$tmpfile.bin"
nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -y "$4/cdn/temp/ffmOi$tmpfile.mp4"
curl "http://$2/api/video/exec/upload?file=$4/cdn/temp/ffmOi$tmpfile.mp4&videoid=$3"

View file

@ -0,0 +1,186 @@
<?php
namespace App\Controllers\Exec\Tasks;
require_once __DIR__ . '/../../../../vendor/autoload.php';
use Symfony\Component\Yaml\Yaml;
define("NGALLERY", Yaml::parse(file_get_contents(__DIR__ . '/../../../../ngallery.yaml'))['ngallery']);
use App\Services\{Router, Auth, DB, Json, Date};
use App\Controllers\ExceptionRegister;
use App\Core\Page;
class ExecContests
{
public static function run()
{
if (NGALLERY['root']['contests']['enabled'] != true) {
echo "Contests on this server disabled. Skip...";
exit;
}
$contests = DB::query('SELECT * FROM contests WHERE status < 3');
foreach ($contests as $contest) {
self::processContest($contest);
}
}
private static function processContest(array $contest)
{
echo "Checking contest ID {$contest['id']}\n";
switch ($contest['status']) {
case 0:
self::handleOpenPretends($contest);
break;
case 1:
self::handleClosePretends($contest);
break;
case 2:
self::handleClosingContest($contest);
break;
case 02:
self::handleClosePretendsByTime($contest);
break;
}
}
private static function handleOpenPretends(array $contest)
{
if (self::isAnotherContestInStatus(1)) {
echo "[{$contest['id']}] Waiting for another contest to complete dialing. Skip...\n";
return;
}
if ($contest['openpretendsdate'] <= time()) {
DB::query('UPDATE contests SET status = 1 WHERE id = :id', [':id' => $contest['id']]);
echo "[{$contest['id']}] Opened for pretends.\n";
} else {
echo "[{$contest['id']}] Not ready for open pretends. Skip...\n";
}
}
private static function handleClosePretends(array $contest)
{
if (self::isAnotherContestInStatus(2) || self::isAnotherContestInStatus(02)) {
echo "[{$contest['id']}] Waiting for another contest to end. Skip...\n";
return;
}
if ($contest['closepretendsdate'] <= time()) {
DB::query('UPDATE photos SET on_contest=2 WHERE on_contest=1 AND contest_id=:id', array(':id'=>$contest['id']));
if ($contest['opendate'] <= time()) {
DB::query('UPDATE contests SET status = 2 WHERE id = :id', [':id' => $contest['id']]);
echo "[{$contest['id']}] Opened.\n";
} else {
DB::query('UPDATE contests SET status = 02 WHERE id = :id', [':id' => $contest['id']]);
}
echo "[{$contest['id']}] Closed for pretends.\n";
} else {
echo "[{$contest['id']}] Not closed for pretends. Skip...\n";
}
}
private static function handleClosePretendsByTime(array $contest)
{
echo "[{$contest['id']}] Cheking for Open by time...\n";
if ($contest['opendate'] <= time()) {
DB::query('UPDATE contests SET status = 2 WHERE id = :id', [':id' => $contest['id']]);
echo "[{$contest['id']}] .\n";
} else {
echo "[{$contest['id']}] not opened by time. Skip...\n";
}
echo "[{$contest['id']}] Opened.\n";
}
private static function handleClosingContest(array $contest)
{
if ($contest['closedate'] > time()) {
echo "[{$contest['id']}] Waiting for end time. Skip...\n";
return;
}
echo "[{$contest['id']}] Ready for closing!\n";
self::processVotes($contest);
DB::query('UPDATE contests SET status = 3 WHERE id = :id', [':id' => $contest['id']]);
DB::query('UPDATE photos SET contest_id = 0, on_contest = 0 WHERE contest_id = :id', [':id' => $contest['id']]);
echo "[{$contest['id']}] Closed.\n";
if (NGALLERY['root']['contests']['autonew']['enabled'] === true) {
echo "Creating new contest...";
$theme = DB::query('SELECT * FROM contests_themes WHERE status=1 ORDER BY RAND() LIMIT 1')[0];
if (count($theme) <= 0) {
echo "Not found themes for autocreating Contest. Skip...\n";
return;
}
$time = time();
if (NGALLERY['root']['contests']['autonew']['times']['pretendsopen'] === 'now') {
$pretendsopen = $time;
$status = 1;
} else {
$status = 0;
$pretendsopen = Date::addTime(NGALLERY['root']['contests']['autonew']['times']['pretendsopen']);
}
$pretendsclose = Date::addTime(NGALLERY['root']['contests']['autonew']['times']['pretendsclose']);
if (NGALLERY['root']['contests']['autonew']['times']['open'] === 'now') {
$contestopen = $pretendsclose;
} else {
$contestopen = Date::addTime(NGALLERY['root']['contests']['autonew']['times']['open']);
}
$contestclose = Date::addTime(NGALLERY['root']['contests']['autonew']['times']['close']);
DB::query('INSERT INTO contests VALUES (\'0\', :themeid, :openprdate, :closeprdate, :opendate, :closedate, :status)', array(':themeid'=>$theme['id'], ':openprdate'=>$pretendsopen, ':closeprdate'=>$pretendsclose, ':opendate'=>$contestopen, ':closedate'=>$contestclose, ':status'=>$status));
echo "Contest created! Continue...";
}
}
private static function processVotes(array $contest)
{
$votes = DB::query(
'SELECT user_id, photo_id, COUNT(*) AS vote_count
FROM contests_rates WHERE contest_id = :id
GROUP BY user_id ORDER BY vote_count DESC LIMIT 10',
[':id' => $contest['id']]
);
$place = 1;
foreach ($votes as $vote) {
self::updatePhotoContent($vote, $contest, $place);
$place++;
}
}
private static function updatePhotoContent(array $vote, array $contest, int $place)
{
$photo = DB::query('SELECT * FROM photos WHERE id = :id', [':id' => $vote['photo_id']])[0];
$photoData = json_decode($photo['content'], true);
if (!isset($photoData['contests']) || !is_array($photoData['contests'])) {
$photoData['contests'] = [];
}
$theme = DB::query('SELECT title FROM contests_themes WHERE id = :id', [':id' => $contest['themeid']])[0]['title'];
$photoData['contests'][] = [
'id' => $contest['id'],
'contesttheme' => $theme,
'votenum' => $vote['vote_count'],
'place' => $place
];
DB::query('INSERT INTO contests_winners VALUES (\'0\', :photo_id, :place, :contest_id, :date)', array(':photo_id'=>$vote['photo_id'], ':place'=>$place, ':contest_id'=>$contest['id'], ':date'=>time()));
DB::query('UPDATE photos SET content = :content, on_contest=0, contest_id=0 WHERE id = :id', [
':id' => $vote['photo_id'],
':content' => json_encode($photoData, JSON_UNESCAPED_UNICODE)
]);
}
private static function isAnotherContestInStatus(int $status): bool
{
return !empty(DB::query('SELECT status FROM contests WHERE status = :status', [':status' => $status]));
}
}
if (php_sapi_name() === 'cli') {
ExecContests::run();
}

View file

@ -0,0 +1,4 @@
tasks:
- id: "ExecContests"
type: "cron"
handler: "/app/Controllers/Exec/Tasks/ExecContests.php"

Binary file not shown.

Binary file not shown.

View file

@ -16,6 +16,11 @@ class MainController
{
Page::set('Main');
}
public static function page()
{
Page::set('Page');
}
public static function about()
{
@ -47,11 +52,6 @@ class MainController
{
Page::set('Top30');
}
public static function vehicle()
{
Page::set('Vehicle');
}
public static function feed()
{
@ -72,6 +72,21 @@ class MainController
{
Page::set('FavAuthors');
}
public static function emailverify()
{
Page::set('Errors/EmailVerify');
}
public static function comments()
{
Page::set('Comments/Index');
}
public static function tour()
{
Page::set('Tour');
}
public static function robots() {
echo 'User-Agent: *

View file

@ -9,7 +9,7 @@ class SearchController
{
public static function i()
{
Page::set('Search');
Page::set('Search/Index');
}

View file

@ -0,0 +1,23 @@
<?php
namespace App\Controllers;
use \App\Services\{Router, Auth, DB, Json};
use \App\Controllers\ExceptionRegister;
use \App\Core\Page;
class VehicleController
{
public static function i()
{
Page::set('Vehicle/Index');
}
public static function iedit()
{
Page::set('Vehicle/IndexEdit');
}
public static function dbedit()
{
Page::set('Vehicle/DBEdit');
}
}

View file

@ -17,39 +17,46 @@ class Routes
Router::get('/register', 'RegisterController@i');
Router::get('/photo/$id', 'PhotoController@i');
Router::get('/author/$id', 'ProfileController@i');
Router::get('/page/$id', 'MainController@page');
Router::post('/api/login', 'ApiController@login');
Router::post('/api/register', 'ApiController@register');
Router::get('/api/photo/stats', 'ApiController@photostats');
Router::get('/about', 'MainController@about');
Router::get('/rules', 'MainController@rules');
Router::get('/vehicle/$id', 'MainController@vehicle');
Router::get('/rules/pub', 'MainController@publicationRules');
Router::get('/rules/photo', 'MainController@photoRules');
Router::get('/rules/video', 'MainController@videoRules');
Router::get('/feed', 'MainController@feed');
Router::get('/tour', 'MainController@tour');
Router::get('/update', 'MainController@update');
Router::get('/top30', 'MainController@top30');
Router::get('/photoext', 'PhotoController@photoext');
Router::get('/api/photo/compress', 'ApiController@photocompress');
Router::get('/api/photo/loadrecent', 'ApiController@recentphotos');
Router::get('/api/users/load/$id', 'ApiController@loaduser');
Router::get('/api/users/emailverify', 'ApiController@emailverify');
Router::get('/article/$id', 'MainController@gallery');
Router::get('/voting', 'ContestsController@index');
Router::get('/voting/results', 'ContestsController@results');
Router::get('/voting/waiting', 'ContestsController@waiting');
Router::get('/comments', 'MainController@comments');
if (Auth::userid() > 0) {
$user = new \App\Models\User(Auth::userid());
Router::get('/lk', 'ProfileController@lk');
Router::get('/lk/upload', 'ProfileController@upload');
Router::get('/lk/history', 'ProfileController@lkhistory');
Router::get('/lk/profile', 'ProfileController@lkprofile');
Router::get('/lk/pday', 'ProfileController@photoindexhistory');
Router::get('/fav_authors', 'MainController@favauthors');
Router::get('/search', 'SearchController@i');
Router::get('/fav', 'MainController@fav');
Router::get('/voting/sendpretend', 'ContestsController@sendpretend');
Router::get('/vehicle/edit', 'VehicleController@iedit');
Router::get('/vehicle/dbedit', 'VehicleController@dbedit');
Router::post('/api/upload', 'ApiController@upload');
Router::post('/api/profile/update', 'ApiController@updateprofile');
Router::post('/api/photo/comment', 'ApiController@photocomment');
@ -61,18 +68,29 @@ class Routes
Router::get('/api/photo/comment/rate', 'ApiController@photocommentvote');
Router::post('/api/photo/comment/$id/edit', 'ApiController@photocommentedit');
Router::post('/api/photo/comment/$id/delete', 'ApiController@photocommentdelete');
Router::post('/api/photo/comment/$id/pin', 'ApiController@photocommentpin');
Router::post('/api/photo/contests/sendpretend', 'ApiController@sendpretendphoto');
Router::get('/api/photo/contests/rate', 'ApiController@photovotecontest');
Router::get('/api/contests/getinfo', 'ApiController@contestsgetinfo');
Router::get('/api/vehicles/load', 'ApiController@vehiclesload');
Router::get('/api/geodb/search', 'ApiController@geodbsearch');
if ($user->i('admin') > 0) {
Router::any('/admin', 'AdminController@index');
Router::any('/api/admin/images/setvisibility', 'ApiController@adminsetvis');
Router::any('/api/admin/createnews', 'ApiController@admincreatenews');
Router::any('/api/admin/loadnews', 'ApiController@adminloadnews');
Router::any('/api/admin/getvehicleinputs/$id', 'ApiController@admingetvehicleinputs');
Router::any('/api/admin/geodb/create', 'ApiController@admingeodbcreate');
Router::any('/api/admin/geodb/load', 'ApiController@admingeodbload');
Router::any('/api/admin/contests/createtheme', 'ApiController@admincontestscreatetheme');
Router::any('/api/admin/contests/create', 'ApiController@admincontestscreate');
Router::any('/api/admin/settings/taskmanager', 'ApiController@admintaskmanager');
}
Router::get('/logout', 'MainController@logout');
Router::get('/404', 'ExceptionRegister@notfound');
} else {
Router::redirect('/login?return='.$_SERVER['HTTP_REFERER']);
}
Router::get('/vehicle/$id', 'VehicleController@i');
}
}

View file

@ -1,68 +1,91 @@
<?php
namespace App\Models;
use \App\Services\{DB, Date, Auth};
use \App\Models\{User, Photo, Vote};
class Comment {
class Comment
{
public $commentid;
public $c;
public $class;
function __construct($user_id) {
function __construct($user_id)
{
$this->c = $user_id;
}
public function class($class) {
public function class($class)
{
$this->class = $class;
}
public function content($table) {
public function content($table)
{
$content = json_decode($this->c['content'], true);
return $content[$table];
}
public function i() {
public function i()
{
$user = new User($this->c['user_id']);
$content = json_decode($this->c['content'], true);
echo '<div class="'.$this->class.' comment" wid="'.$this->c['id'].'">
$photo = new \App\Models\Photo($this->c['photo_id']);
$pinc = 'Закрепить';
echo '<div class="' . $this->class . ' comment" wid="' . $this->c['id'] . '">';
if ($photo->i('pinnedcomment_id') === $this->c['id']) {
echo '<i style="padding-bottom: 15px;">Комментарий закреплён</i>';
$pinc = 'Открепить';
}
echo '
<div style="float:right; text-align:right" class="sm">
<span class="message_date">'.Date::zmdate($this->c['posted_at']).'</span><br>
<span class="message_date">' . Date::zmdate($this->c['posted_at']) . '</span><br>
<a href="#" class="quoteLink dot">Цитировать</a>
·
<a href="#'.$this->c['id'].'" class="cmLink dot">Ссылка</a>
<a href="#' . $this->c['id'] . '" class="cmLink dot">Ссылка</a>
';
echo '
echo '
</div>
<a name="2681468"></a><a name="last"></a>
<div><img src="'.$user->i('photourl').'" width="32" style="border-radius: 3px; margin-right: 5px;"><b><a href="/author/'.$this->c['user_id'].'/" class="message_author">'.htmlspecialchars($user->i('username')).'</a></b> &middot;
<div><img src="' . $user->i('photourl') . '" width="32" style="border-radius: 3px; margin-right: 5px;"><b><a href="/author/' . $this->c['user_id'] . '/" class="message_author">' . htmlspecialchars($user->i('username')) . '</a></b> &middot;
<span class="flag">';
if (json_decode($user->i('content'), true)['aboutrid']['value'] != null) {
echo '<img src="/static/img/flags/'.json_decode($user->i('content'), true)['aboutrid']['value'].'.gif">';
}
if (json_decode($user->i('content'), true)['aboutlive']['value'] != null) {
echo ' '.htmlspecialchars(json_decode($user->i('content'), true)['aboutlive']['value']);
}
if ($content['edited'] === 'true') {
echo '<br>(отредактировано)';
}
if ($user->i('admin') === 1) {
$admintype = ' · Администратор сервера';
} else if ($user->i('admin') === 2) {
$admintype = ' · Фотомодератор';
}
if ((int)Vote::countcommrates($this->c['id'], -1) >= 1) {
$commclass = 'pro';
$symb = '+';
} else if ((int)Vote::countcommrates($this->c['id'], -1) < 0) {
$commclass = 'con';
$symb = '';
} else if ((int)Vote::countcommrates($this->c['id'], -1) === 0) {
$commclass = '';
}
echo '</span></div>
<div class="rank">Фото: '.Photo::fetchAll($this->c['user_id']).' '.$admintype.'</div>
<div class="message-text">'.preg_replace("~(?:[\p{M}]{1})([\p{M}])+?~uis","", htmlspecialchars($this->c['body'])).'</div>
if (json_decode($user->i('content'), true)['aboutrid']['value'] != null) {
echo '<img src="/static/img/flags/' . json_decode($user->i('content'), true)['aboutrid']['value'] . '.gif">';
}
if (json_decode($user->i('content'), true)['aboutlive']['value'] != null) {
echo ' ' . htmlspecialchars(json_decode($user->i('content'), true)['aboutlive']['value']);
}
if ($content['edited'] === 'true') {
echo '<br>(отредактировано)';
}
if ($user->i('admin') === 1) {
$admintype = ' · Администратор сервера';
} else if ($user->i('admin') === 2) {
$admintype = ' · Фотомодератор';
}
if ((int)Vote::countcommrates($this->c['id'], -1) >= 1) {
$commclass = 'pro';
$symb = '+';
} else if ((int)Vote::countcommrates($this->c['id'], -1) < 0) {
$commclass = 'con';
$symb = '';
} else if ((int)Vote::countcommrates($this->c['id'], -1) === 0) {
$commclass = '';
}
echo '</span></div>
<div class="rank">Фото: ' . Photo::fetchAll($this->c['user_id']) . ' ' . $admintype . '</div>
<div class="message-text">' . preg_replace("~(?:[\p{M}]{1})([\p{M}])+?~uis", "", htmlspecialchars($this->c['body'])) . '</div>
';
if ($content['filetype'] === 'img') {
echo '<div class="message-text"><img src="'.$content['src'].'" width="250"></div>';
}
if ($content['filetype'] === 'video') {
echo '<div class="message-text"><video controls src="'.$content['src'].'" width="250"></div>';
}
echo '
<div class="comment-votes-block">
';
echo '<style>
echo '<style>
.dropdown {
position: relative;
display: inline-block;
@ -82,34 +105,39 @@ class Comment {
display: block;
}
</style>';
if ($this->c['user_id'] === Auth::userid()) {
echo '
if ($this->c['user_id'] === Auth::userid() || $photo->i('user_id') === Auth::userid()) {
echo '
<div class="dropdown">
<a style="color: #000" class="compl" href="/lk/ticket.php?action=add&amp;wid=3252565">...</a>
<div class="dropdown-content">'; ?>
<a style="margin-bottom: 10px;" href="#" onclick="createModal(<?=$this->c['id']?>, 'EDIT_COMMENT', '<?=htmlspecialchars($this->c['body'])?>', 'modaledit<?=$this->c['id']?>'); return false;">Редактировать</a><br>
<a href="#" onclick="createModal(<?=$this->c['id']?>, 'DELETE_COMMENT', '', 'modaldel<?=$this->c['id']?>'); return false;">Удалить</a>
<?php
echo '
<a style="color: #000" class="compl" href="#">...</a>
<div class="dropdown-content">';
?>
<a href="#" onclick="pinComment(<?= $this->c['id'] ?>); return false;"><?=$pinc?></a><br>
<?php
if ($this->c['user_id'] === Auth::userid()) { ?>
<a style="margin-bottom: 10px;" href="#" onclick="createModal(<?= $this->c['id'] ?>, 'EDIT_COMMENT', '<?= htmlspecialchars($this->c['body']) ?>', 'modaledit<?= $this->c['id'] ?>'); return false;">Редактировать</a><br>
<a href="#" onclick="createModal(<?= $this->c['id'] ?>, 'DELETE_COMMENT', '', 'modaldel<?= $this->c['id'] ?>'); return false;">Удалить</a>
<?php }
echo '
</div>
</div>
';
}
echo '
<div class="wvote" wid="'.$this->c['id'].'">
}
echo '
<div class="wvote" wid="' . $this->c['id'] . '">
<a href="#" vote="1" class="w-btn s2"><span>+</span></a>
<div class="w-rating '.$commclass.' active">'.$symb.Vote::countcommrates($this->c['id'], -1).'</div>
<div class="w-rating ' . $commclass . ' active">' . $symb . Vote::countcommrates($this->c['id'], -1) . '</div>
<div class="w-rating-ext">
<div><span class="pro">+'.Vote::countcommrates($this->c['id'], 1).'</span> / <span class="con">'.Vote::countcommrates($this->c['id'], 0).'</span></div>
<div><span class="pro">+' . Vote::countcommrates($this->c['id'], 1) . '</span> / <span class="con">' . Vote::countcommrates($this->c['id'], 0) . '</span></div>
</div>
<a href="#" vote="0" class="w-btn s5"><span></span></a>
</div>
</div>
</div>';
}
}

View file

@ -5,7 +5,7 @@ use \App\Services\DB;
class Photo {
public $photoid;
function __construct(int $user_id) {
function __construct($user_id) {
$this->photoid = $user_id;
}
public function i($table) {
@ -37,6 +37,9 @@ class Photo {
case 5:
return 'Расчленёнка';
break;
case 6:
return 'Файл сломан';
break;
default:
return 'Не подходит для сайта';
break;

View file

@ -5,7 +5,7 @@ use \App\Services\DB;
class User {
public $userid;
function __construct(int $user_id) {
function __construct($user_id) {
$this->userid = $user_id;
}
public function i($table) {

View file

@ -7,7 +7,23 @@ class Vote
{
public static function photo($user_id, $pid)
{
$result = DB::query('SELECT type FROM photos_rates WHERE user_id=:uid AND photo_id=:pid', array(':uid' => $user_id, ':pid' => $pid));
$result = DB::query('SELECT type FROM photos_rates WHERE user_id=:uid AND photo_id=:pid AND contest=0', array(':uid' => $user_id, ':pid' => $pid));
if (!empty($result)) {
$type = $result[0]['type'];
if ($type < 0) {
$type = -1;
}
return $type;
} else {
return -1;
}
}
public static function photoContest($user_id, $pid)
{
$result = DB::query('SELECT type FROM photos_rates WHERE user_id=:uid AND photo_id=:pid AND contest=1', array(':uid' => $user_id, ':pid' => $pid));
if (!empty($result)) {
$type = $result[0]['type'];
if ($type < 0) {

View file

@ -0,0 +1,37 @@
<?php
namespace App\Models;
use App\Services\{DB, GenerateRandomStr};
class VoteContest
{
public static function photo($user_id, $pid, $cid)
{
$result = DB::query('SELECT type FROM photos_rates_contest WHERE user_id=:uid AND photo_id=:pid AND contest_id=:id', array(':uid' => $user_id, ':pid' => $pid, ':id'=>$cid));
if (!empty($result)) {
$type = $result[0]['type'];
if ($type < 0) {
$type = -1;
}
return $type;
} else {
return -1;
}
}
public static function count($pid, $cid) {
$result = DB::query('SELECT * FROM photos_rates_contest WHERE photo_id=:pid AND contest_id=:id', array(':pid' => $pid, ':id'=>$cid));
$votes = 0;
foreach ($result as $r) {
if ($r['type'] === 1) {
$votes++;
} else {
$votes--;
}
}
return $votes;
}
}
?>

View file

@ -1,5 +1,6 @@
<?php
namespace App\Services;
use InvalidArgumentException;
class Date
{
@ -65,5 +66,38 @@ class Date
);
return $formattedDate;
}
public static function addTime($timeString, int $baseTime = null)
{
if ($baseTime === null) {
$baseTime = time();
}
preg_match('/^(\d+)([smhdwMy])$/', $timeString, $matches);
if (!$matches) {
throw new InvalidArgumentException("Неверный формат времени: $timeString");
}
[$fullMatch, $amount, $unit] = $matches;
$amount = (int) $amount;
$multipliers = [
's' => 1, // секунды
'm' => 60, // минуты
'h' => 3600, // часы
'd' => 86400, // дни
'w' => 604800, // недели
'M' => 2629743, // месяцы (среднее значение)
'y' => 31556926 // годы (среднее значение)
];
if (!isset($multipliers[$unit])) {
throw new InvalidArgumentException("Неизвестная единица измерения: $unit");
}
return $baseTime + ($amount * $multipliers[$unit]);
}
}
?>

View file

@ -0,0 +1,52 @@
<?php
namespace App\Services;
class KeyTranslation
{
public static function key($key)
{
switch ($key) {
case 'title':
return 'Название сервера';
break;
case 'adminemail':
return 'Почта Администратора';
break;
case 'showtitle':
return 'Отображать название в заголовке';
break;
case 'logo':
return 'Расположение логотипа';
break;
case 'description':
return 'Описание сервера';
break;
case 'keywords':
return 'Ключевые слова (для SEO)';
break;
case 'maintenance':
return 'Режим технических работ';
break;
case 'debug':
return 'Дебаг';
break;
case 'access':
return 'Доступ к сайту';
break;
case 'type':
return 'Тип';
break;
case 'countries':
return 'Страны';
break;
case 'cloudflare-caching':
return 'Кэширование Cloudflare (и прочих CDN)';
break;
default:
return $key;
break;
}
}
}

View file

@ -0,0 +1,152 @@
<?php
namespace App\Services;
class TaskScheduler {
public function __construct() {}
private function isWindows() {
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
}
public function addTask($taskName, $command, $interval = "* * * * *") {
return $this->isWindows() ? $this->addWindowsTask($taskName, $command) : $this->addLinuxTask($command, $interval);
}
public function isTaskExists($taskName = null, $command = null) {
return $this->isWindows() ? $this->isWindowsTaskExists($taskName) : $this->isLinuxTaskExists($command);
}
public function removeTask($taskName = null, $command = null) {
return $this->isWindows() ? $this->removeWindowsTask($taskName) : $this->removeLinuxTask($command);
}
public function getTaskStatus($taskName, $command = null) {
if (!$this->isTaskExists($taskName, $command)) {
return "Не работает (задача отсутствует)";
}
return $this->isWindows() ? $this->getWindowsTaskStatus($taskName) : $this->getLinuxTaskStatus($command);
}
public function findHandlerById($array, $id) {
foreach ($array as $item) {
if (isset($item['id']) && $item['id'] === $id) {
return $item['handler'] ?? null;
}
}
return null;
}
private function addLinuxTask($command, $interval) {
$cronJob = "{$interval} {$command}";
if ($this->isLinuxTaskExists($command)) {
return "✅ Cron-задача уже установлена.";
}
exec("crontab -l 2>&1", $output, $return_var);
if ($return_var !== 0) {
$output = [];
}
$output[] = $cronJob;
file_put_contents("/tmp/my_cron", implode(PHP_EOL, $output) . PHP_EOL);
exec("crontab /tmp/my_cron");
unlink("/tmp/my_cron");
return "✅ Cron-задача добавлена!";
}
private function isLinuxTaskExists($command = null) {
exec("crontab -l 2>&1", $output);
foreach ($output as $line) {
if (strpos($line, $command) !== false) {
return true;
}
}
return false;
}
private function removeLinuxTask($command = null) {
exec("crontab -l 2>&1", $output, $return_var);
if ($return_var !== 0) {
return "❌ Нет задач для удаления.";
}
$filteredOutput = array_filter($output, function ($line) use ($command) {
return strpos($line, $command) === false;
});
file_put_contents("/tmp/my_cron", implode(PHP_EOL, $filteredOutput) . PHP_EOL);
exec("crontab /tmp/my_cron");
unlink("/tmp/my_cron");
return "✅ Cron-задача удалена!";
}
private function getLinuxTaskStatus($command) {
exec("ps aux | grep '" . escapeshellarg($command) . "' | grep -v grep", $output, $return_code);
if (empty($output)) {
return "Не работает (ошибка: процесс не найден)";
}
return "✅ Работает корректно";
}
private function addWindowsTask($taskName, $command) {
if ($this->isWindowsTaskExists($taskName)) {
return "✅ Задача уже существует в Windows.";
}
$cmd = "schtasks /Create /SC MINUTE /MO 1 /TN \"{$taskName}\" /TR \"{$command}\" /F";
exec($cmd, $output, $return_code);
return ($return_code === 0) ? "✅ Задача добавлена в Windows!" : "❌ Ошибка при добавлении задачи.";
}
private function isWindowsTaskExists($taskName = null) {
exec("schtasks /Query /TN \"". ($taskName) ."\" 2>&1", $output, $return_var);
return $return_var === 0;
}
private function removeWindowsTask($taskName = null) {
if (!$this->isWindowsTaskExists($taskName)) {
return "❌ Задача не найдена в Windows.";
}
exec("schtasks /Delete /TN \"". ($taskName) ."\" /F", $output, $return_code);
return ($return_code === 0) ? "✅ Задача удалена из Windows!" : "❌ Ошибка при удалении задачи.";
}
private function getWindowsTaskStatus($taskName) {
exec("schtasks /Query /TN \"{$taskName}\" /FO LIST /V", $output, $return_var);
if ($return_var !== 0) {
return "Не работает (задача отсутствует)";
}
$status = "Не работает (ошибка: неизвестно)";
$output = array_map(function($line) {
return iconv('Windows-1251', 'UTF-8', $line);
}, $output);
// Ищем статус задачи
foreach ($output as $line) {
if (strpos($line, "Статус:") !== false) {
if (stripos($line, "Выполняется") !== false) {
$status = "✅ Работает корректно";
} elseif (stripos($line, "Готово") !== false) {
$status = "Не работает (но активна)";
} elseif (stripos($line, "Не удалось запустить") !== false) {
$status = "Не работает (ошибка: не удалось запустить)";
}
break;
}
}
return $status;
}
}
?>

View file

@ -28,7 +28,7 @@ class Upload
$tmpname = $file['tmp_name'];
$type = explode('/', $file['type'])[0];
$name = $file['name'];
$fileext = pathinfo($file['name']);
$fileext = pathinfo($file['name'], PATHINFO_EXTENSION);
} else {
$tmpname = $file;
$type = filetype($file);
@ -88,8 +88,6 @@ class Upload
$destination = "{$uploadDir}/" . basename($tmpname);
$this->type = $type;
$this->src = "/uploads/{$folder}";
$this->size = self::human_filesize(filesize($tmpname));

View file

@ -20,6 +20,11 @@
"donatj/phpuseragentparser": "^1.8",
"php-ffmpeg/php-ffmpeg": "^1.2",
"chriskonnertz/bbcode": "^1.1",
"paquettg/php-html-parser": "^2.2"
"paquettg/php-html-parser": "^2.2",
"phpmailer/phpmailer": "^6.9",
"beeyev/disposable-email-filter-php": "^1.3"
},
"require-dev": {
"phpstan/phpstan": "*"
}
}

221
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "31de33165b326f2edef27c1fc9ae5b08",
"content-hash": "844a94a4ce9ce290e30dd5845c27ed3f",
"packages": [
{
"name": "aws/aws-crt-php",
@ -155,6 +155,77 @@
},
"time": "2024-07-03T18:12:51+00:00"
},
{
"name": "beeyev/disposable-email-filter-php",
"version": "v1.3.83",
"source": {
"type": "git",
"url": "https://github.com/beeyev/disposable-email-filter-php.git",
"reference": "9ee8422c390d0ce0fff25ff5616a4b9654f73bb0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/beeyev/disposable-email-filter-php/zipball/9ee8422c390d0ce0fff25ff5616a4b9654f73bb0",
"reference": "9ee8422c390d0ce0fff25ff5616a4b9654f73bb0",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"ext-json": "*",
"friendsofphp/php-cs-fixer": "^3.4",
"kubawerlos/php-cs-fixer-custom-fixers": "^3.7",
"phpstan/phpstan": "^1.11",
"phpstan/phpstan-phpunit": "^1.3",
"phpstan/phpstan-strict-rules": "^1.5",
"phpunit/phpunit": "^8.5 || ^9",
"symplify/phpstan-rules": "^12.3"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"DisposableEmail": "Beeyev\\DisposableEmailFilter\\Adapters\\Laravel\\Facades\\DisposableEmail"
},
"providers": [
"Beeyev\\DisposableEmailFilter\\Adapters\\Laravel\\DisposableEmailFilterServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Beeyev\\DisposableEmailFilter\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alexander Tebiev",
"email": "alexander.tebiev@gmail.com",
"homepage": "https://github.com/beeyev/"
}
],
"description": "Disposable (temporary/throwaway/fake) email detection library. Automatically updated every week.",
"homepage": "https://github.com/beeyev/disposable-email-filter-php",
"keywords": [
"disposable",
"email",
"fake",
"temporary",
"throwaway"
],
"support": {
"docs": "https://github.com/beeyev/disposable-email-filter-php/",
"issues": "https://github.com/beeyev/disposable-email-filter-php/issues",
"rss": "https://github.com/beeyev/disposable-email-filter-php/releases.atom",
"source": "https://github.com/beeyev/disposable-email-filter-php.git"
},
"time": "2025-02-05T14:49:14+00:00"
},
{
"name": "chriskonnertz/bbcode",
"version": "v1.1.2",
@ -913,6 +984,87 @@
},
"time": "2024-01-02T10:37:01+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.9.3",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e",
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-filter": "*",
"ext-hash": "*",
"php": ">=5.5.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"doctrine/annotations": "^1.2.6 || ^1.13.3",
"php-parallel-lint/php-console-highlighter": "^1.0.0",
"php-parallel-lint/php-parallel-lint": "^1.3.2",
"phpcompatibility/php-compatibility": "^9.3.5",
"roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.7.2",
"yoast/phpunit-polyfills": "^1.0.4"
},
"suggest": {
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
},
"type": "library",
"autoload": {
"psr-4": {
"PHPMailer\\PHPMailer\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-only"
],
"authors": [
{
"name": "Marcus Bointon",
"email": "phpmailer@synchromedia.co.uk"
},
{
"name": "Jim Jagielski",
"email": "jimjag@gmail.com"
},
{
"name": "Andy Prevost",
"email": "codeworxtech@users.sourceforge.net"
},
{
"name": "Brent R. Matzelle"
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3"
},
"funding": [
{
"url": "https://github.com/Synchro",
"type": "github"
}
],
"time": "2024-11-24T18:04:13+00:00"
},
{
"name": "psr/cache",
"version": "3.0.0",
@ -2096,13 +2248,72 @@
"time": "2024-04-29T11:44:00+00:00"
}
],
"packages-dev": [],
"packages-dev": [
{
"name": "phpstan/phpstan",
"version": "2.1.5",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "451b17f9665481ee502adc39be987cb71067ece2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/451b17f9665481ee502adc39be987cb71067ece2",
"reference": "451b17f9665481ee502adc39be987cb71067ece2",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0"
},
"conflict": {
"phpstan/phpstan-shim": "*"
},
"bin": [
"phpstan",
"phpstan.phar"
],
"type": "library",
"autoload": {
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHPStan - PHP Static Analysis Tool",
"keywords": [
"dev",
"static analysis"
],
"support": {
"docs": "https://phpstan.org/user-guide/getting-started",
"forum": "https://github.com/phpstan/phpstan/discussions",
"issues": "https://github.com/phpstan/phpstan/issues",
"security": "https://github.com/phpstan/phpstan/security/policy",
"source": "https://github.com/phpstan/phpstan-src"
},
"funding": [
{
"url": "https://github.com/ondrejmirtes",
"type": "github"
},
{
"url": "https://github.com/phpstan",
"type": "github"
}
],
"time": "2025-02-13T12:49:56+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

View file

@ -15,6 +15,7 @@ class App
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/ngallery.yaml')) {
define("NGALLERY", Yaml::parse(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/ngallery.yaml'))['ngallery']);
define("NGALLERY_TASKS", Yaml::parse(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/app/Controllers/Exec/Tasks/ngallery-tasks.yaml'))['tasks']);
if (NGALLERY['root']['debug'] === true) {
Debugger::enable();
}

View file

@ -13,6 +13,14 @@ ngallery:
type: 'allow'
countries: ''
cloudflare-caching: false
email:
credentials:
host: 'example@mail.com'
username: ''
password: ''
port: 465
from:
address:
db:
name: ''
host: ''
@ -40,6 +48,7 @@ ngallery:
proxy: true
percent: 50
registration:
emailverify: false
prohibited_usernames: ''
access:
public: true
@ -59,5 +68,12 @@ ngallery:
allowgif: true
comments:
premoderation: false
contests:
enabled: true
autonew:
enabled: true
times:
pretendsopen: 'now'
pretendsclose: '2d'
open: 'now'
close: '2d'

View file

@ -57,7 +57,7 @@ CREATE TABLE `login_tokens` (
`id` int NOT NULL,
`token` text NOT NULL,
`user_id` int NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
@ -90,7 +90,7 @@ CREATE TABLE `photos` (
`place` text NOT NULL,
`endmoderation` int NOT NULL DEFAULT '0',
`content` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
@ -104,7 +104,7 @@ CREATE TABLE `photos_comments` (
`photo_id` int NOT NULL,
`body` text NOT NULL,
`posted_at` int NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
@ -117,7 +117,7 @@ CREATE TABLE `photos_comments_rates` (
`user_id` int NOT NULL,
`comment_id` int NOT NULL,
`type` int NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
@ -130,7 +130,7 @@ CREATE TABLE `photos_rates` (
`user_id` int NOT NULL,
`photo_id` int NOT NULL,
`type` int NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
@ -162,7 +162,7 @@ CREATE TABLE `users` (
`admin` int NOT NULL DEFAULT '0',
`status` int NOT NULL DEFAULT '0',
`content` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- Индексы сохранённых таблиц

View file

@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS `entities` (
`sampledata` text NOT NULL,
`color` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `entities_data` (
`id` int NOT NULL AUTO_INCREMENT,
@ -17,21 +17,21 @@ CREATE TABLE IF NOT EXISTS `entities_data` (
`comment` text NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `galleries` (
`id` int NOT NULL AUTO_INCREMENT,
`title` text NOT NULL,
`opened` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `photos_favorite` (
`id` int NOT NULL AUTO_INCREMENT,
`photo_id` int NOT NULL,
`user_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `uploadindex_history` (
`id` int NOT NULL AUTO_INCREMENT,
@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS `uploadindex_history` (
`type` int NOT NULL,
`photo_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Изменение существующих таблиц
@ -61,7 +61,7 @@ MODIFY COLUMN `online` int NOT NULL DEFAULT '0',
MODIFY COLUMN `admin` int NOT NULL;
-- Обновление кодировки для таблиц, использующих utf8
ALTER TABLE `followers` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
ALTER TABLE `followers_notifications` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
ALTER TABLE `news` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
ALTER TABLE `photos_views` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
ALTER TABLE `followers` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `followers_notifications` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `news` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `photos_views` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

38
sqlcore/sql_0002.sql Normal file
View file

@ -0,0 +1,38 @@
-- Migration script to update database schema
-- Add new tables from 222.sql that don't exist in 111.sql
CREATE TABLE IF NOT EXISTS `contests` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`themeid` int(10) NOT NULL,
`opendate` int(100) NOT NULL,
`closedate` int(10) NOT NULL,
`status` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `geodb` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`title` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `servicekeys` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`token` text NOT NULL,
`type` text NOT NULL,
`status` int(10) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Modify existing tables to add new columns
-- Using proper ALTER TABLE syntax for MySQL
ALTER TABLE `photos` ADD COLUMN `pinnedcomment_id` int(10) NOT NULL DEFAULT 0 AFTER `entitydata_id`;
-- Set AUTO_INCREMENT values for the new tables
ALTER TABLE `contests` AUTO_INCREMENT = 1;
ALTER TABLE `geodb` AUTO_INCREMENT = 1;
ALTER TABLE `servicekeys` AUTO_INCREMENT = 1;
-- Commit the changes
COMMIT;

162
sqlcore/sql_0003.sql Normal file
View file

@ -0,0 +1,162 @@
-- Add new tables for contest functionality
CREATE TABLE IF NOT EXISTS `contests_pretends` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`photo_id` int(10) NOT NULL,
`contest_id` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `contests_rates` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`photo_id` int(10) NOT NULL,
`user_id` int(10) NOT NULL,
`contest_id` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `contests_themes` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`title` text NOT NULL,
`status` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `contests_winners` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`photo_id` int(10) NOT NULL,
`place` int(10) NOT NULL,
`contest_id` int(10) NOT NULL,
`date` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `photos_rates_contest` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`photo_id` int(10) NOT NULL,
`user_id` int(10) NOT NULL,
`contest_id` int(10) NOT NULL,
`type` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Add new pages table
CREATE TABLE IF NOT EXISTS `pages` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`title` text NOT NULL,
`body` text NOT NULL,
`created_by` int(10) NOT NULL,
`created_at` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Modify existing tables
-- Update contests table
SET @s = (SELECT IF(
EXISTS(
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'contests'
AND COLUMN_NAME = 'openpretendsdate'
),
'SELECT 1',
'ALTER TABLE `contests` ADD COLUMN `openpretendsdate` int(10) NOT NULL AFTER `themeid`'
));
PREPARE stmt FROM @s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @s = (SELECT IF(
EXISTS(
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'contests'
AND COLUMN_NAME = 'closepretendsdate'
),
'SELECT 1',
'ALTER TABLE `contests` ADD COLUMN `closepretendsdate` int(10) NOT NULL AFTER `openpretendsdate`'
));
PREPARE stmt FROM @s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Update entities table
ALTER TABLE `entities` MODIFY `createdate` bigint(20) NOT NULL;
-- Update news table
ALTER TABLE `news` MODIFY `body` mediumtext NOT NULL;
-- Update photos table
SET @s = (SELECT IF(
EXISTS(
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'photos'
AND COLUMN_NAME = 'on_contest'
),
'SELECT 1',
'ALTER TABLE `photos` ADD COLUMN `on_contest` int(10) NOT NULL DEFAULT 0 AFTER `pinnedcomment_id`'
));
PREPARE stmt FROM @s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @s = (SELECT IF(
EXISTS(
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'photos'
AND COLUMN_NAME = 'contest_id'
),
'SELECT 1',
'ALTER TABLE `photos` ADD COLUMN `contest_id` int(10) NOT NULL DEFAULT 0 AFTER `on_contest`'
));
PREPARE stmt FROM @s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Update charset and collation for tables
ALTER TABLE `entities`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `entities_data`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `followers`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `followers_notifications`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `galleries`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `news`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `photos_favorite`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `photos_views`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `uploadindex_history`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `login_tokens`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `photos`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `photos_comments`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `photos_comments_rates`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `photos_rates`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `users`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Binary file not shown.

File diff suppressed because one or more lines are too long

115
static/css/mobile.css Normal file
View file

@ -0,0 +1,115 @@
body, td, p, li, input, select, optgroup, option, button { font-size:14px; }
#title { font-family:var(--narrow-font); font-size:32px; padding:5px 7px; position:relative; z-index:2001; }
#title img { margin:0 8px 0 -2px; }
#title2 { padding-right:5px; }
.main, .adframe { padding:0 5px 10px; }
.footer { padding:10px 5px; }
.p20, .p20i { padding-left:15px; padding-right:15px; }
h1 { font-size:24px; }
h2 { font-size:20px; }
h3 { font-size:17px; }
h4 { font-size:17px; margin-bottom:5px; }
.f, .fv3, .pb-photo, .temp, .hpshade { width:125px; }
.fv2, .fv1, .fv, .fv > .f { width:115px; }
.pb-photo { padding:5px; }
.mm-mobile-btn { display:flex; justify-content:center; align-items:center; position:absolute; top:0; right:0; z-index:2002; box-sizing:border-box; width:56px; height:54px; background-color:rgba(0,0,0,0.2); color:#fff; font-size:28px; }
.mm-mobile-btn.active { background-color:rgba(255,255,255,0.2); }
#mm-user-btn { right:57px; }
#mm-bars-btn { right:0; }
.mm { position:absolute; top:54px; width:100%; z-index:2002; border-bottom:solid 1px var(--theme-bg-color); box-shadow:0 8px 8px #0007; }
.mm-bar ul, .mm-bar { list-style-type:none; padding:0; margin:0; }
.mm-bar ul { display:none; background:linear-gradient(rgba(0,0,0,0.2),rgba(0,0,0,0.2)) var(--theme-bg-color); }
.mm ul { margin:0 10px 7px; }
.mm-bar-item li { display:flex; flex-direction:column; align-items:center; justify-content:center; position:relative; box-sizing:border-box; border-top:solid 1px var(--theme-bg-color); font-family:var(--narrow-font); font-size:28px; }
.mm-bar-item li div { box-sizing:border-box; width:100%; background-color:#eee; }
.mm-bar-item li div::before { content:'>'; color:#fff; display:block; position:absolute; top:7px; left:10px; z-index:2003; transform:scaleX(0.8); transition:transform .1s ease-out; }
.mm-level-3 > div::before { top:1px; }
a.mm-item, label.mm-item { color:#fff; }
.mm-item { box-sizing:border-box; width:100%; min-height:50px; padding:5px 0; text-align:center; border:none; }
.mm-level-2 .mm-item { min-height: 42px; font-size: 22px; }
.mm-level-3 .mm-item { min-height: 34px; font-size: 16px; }
.mm-bar-item label:hover { border:none; }
.mm-bar-item input[type="checkbox"] { display:none; }
.mm-bar-item input[type="checkbox"]:checked + div > ul { display:flex; flex-direction:column; }
.mm-bar-item input[type="checkbox"]:checked + div::before { transform:scaleY(0.8) rotate(90deg); }
span.mm-icon, span.mm-right-icon { display:block; position:absolute; z-index:2003; }
.mm-icon { top:7px; left:10px; }
.mm-right-icon { top:5px; right:10px; }
.mm-notify { position: absolute; right: 7px; bottom: 7px; }
.mm-level-1 .mm-notify { top: 10px; font-size: 16px; line-height: 16px; height: 30px; min-width: 30px; }
.mm-level-2 .mm-notify { top: 9px; font-size: 12px; line-height: 12px; height: 24px; min-width: 24px; }
#idx-main { margin-top:5px; }
#idx-column-center { padding:0; }
#idx-column-menu { display:none; position:absolute; top:54px; right:0; padding:5px 10px; background-color:#f7f7f7; width:300px; border-left:solid 1px #ddd; border-bottom:solid 1px #ddd; z-index:2001; }
.idx-donate { margin-right:-10px; }
.idx-donate > a { padding-right:15px; }
.ix-photos-oneline { width:100vw; min-height:70px; margin:0 -5px 7px; overflow-x:auto; }
.ix-photos-oneline:before, .ix-photos-oneline:after { content:''; width:5px; flex-shrink:0; background-color:var(--theme-main-color); }
.ix-photos-oneline > a { flex-grow:0; flex-shrink:0; flex-basis:auto; width:100px; }
.ix-photos-multiline { min-height:210px; margin-bottom:10px; }
.ix-photos-multiline > a { flex-basis:100px; }
.prw-animate, .prw-grid-item { height:70px; }
.prw-animate:hover { animation:none; }
#morerand { top:4px; }
#loadmore { height:auto; padding-bottom:4px; margin-bottom:7px; }
.rtable { box-sizing:border-box; width:100vw; margin:0 -5px; overflow-x:auto; }
.rtable > :first-child { margin:0 5px; }
[type="text"],
[type="password"],
[type="color"],
[type="date"],
[type="datetime"],
[type="datetime-local"],
[type="email"],
[type="number"],
[type="search"],
[type="tel"],
[type="time"],
[type="url"],
[type="month"],
[type="week"],
select,
textarea {
box-shadow:none;
-moz-box-shadow:none;
-webkit-box-shadow:none;
}
.mf-center-block-wide,
.mf-center-block-x-wide {
width:calc(100% + 10px);
margin-left: -5px;
margin-right:-5px;
}
.cmt-showall { display:block; text-align:center; margin-bottom:-5px; }
.mark-btn { margin-bottom:7px; }
.mid-btn { margin:7px 0; }
.mark-btn a, .mid-btn a { width:100%; }

View file

@ -371,10 +371,10 @@ table.nospaces > tbody > tr > td.lcol { padding:2px 6px 0; }
.flag-left { padding-left:26px !important; }
.input-flag { margin:-2px -26px 0 5px; position:relative; z-index:11; }
.contestBtn { display:block; cursor:pointer; width:56px; height:28px; margin:10px; background:url('/img/vote_contest.gif') no-repeat; opacity:0.7; }
.contestBtn { display:block; cursor:pointer; width:56px; height:28px; margin:10px; background:url('/static/img/vote_contest.gif') no-repeat; opacity:0.7; }
.contestBtn:hover { opacity:1; }
.contestBtn.voted { opacity:1; background:url('/img/vote_contest_pressed.gif') no-repeat; }
.contestBtn.loading { opacity:1; background:url('/img/vote_contest_loading.gif') no-repeat; }
.contestBtn.voted { opacity:1; background:url('/static/img/vote_contest_pressed.gif') no-repeat; }
.contestBtn.loading { opacity:1; background:url('/static/img/vote_contest_loading.gif') no-repeat; }
.died { border:solid 1px black; padding:0 2px; }

89
static/css/tabs.css Normal file
View file

@ -0,0 +1,89 @@
.v-header {
/**
* Redefine media queries, because header mobile styles are enabled
* when page width is less than two-columns layout width (~960px)
*/
--border-radius: 8px;
--offset-x: var(--island-offset-x, 20px);
--offset-y: 24px;
border-radius: var(--border-radius);
padding: var(--offset-y) var(--offset-x);
font-size: 16px;
line-height: 1.5em;
}
@media (max-width: 999px) {
.v-header {
--offset-x: var(--island-offset-x, 16px);
--offset-y: 16px;
}
}
.v-header__tabs {
margin-bottom: calc(var(--offset-y)* -1);
margin-left: calc(var(--offset-x)* -1);
margin-right: calc(var(--offset-x)* -1);
}
.v-tabs {
--height: 63px;
--nav-size: 36px;
--tab-offset: 12px;
font-size: var(--font-size);
height: var(--height);
position: relative;
min-width: 0;
}
.v-tabs__scroll {
white-space: nowrap;
overflow-x: hidden;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}
.v-header__tabs .v-tabs__content {
padding-left: var(--offset-x);
padding-right: var(--offset-x);
}
.v-tabs__content {
display: -ms-inline-flexbox;
display: inline-flex;
vertical-align: top;
}
.v-tab:first-child {
padding-left: 0;
}
.v-tab--active {
pointer-events: none;
color: #ffffff;
}
.v-tab {
padding: 0 var(--tab-offset);
-ms-flex-negative: 0;
flex-shrink: 0;
cursor: pointer;
color: #000000;
font-weight: 400;
}
.v-tab--active .v-tab__label {
position: relative;
}
.v-tab__label {
display: -ms-flexbox;
display: flex;
-ms-flex-align: baseline;
align-items: baseline;
height: var(--height);
line-height: calc(var(--height) + 1px);
}
.v-tab--active .v-tab__label::after {
content: '';
width: 100%;
height: 3px;
background-color: #306bff;
position: absolute;
bottom: 0;
left: 0;
border-radius: 500px;
}
.active__block, .active__blockm {
display: block !important;
transition: 0.3s all;
}

BIN
static/img/cond.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/img/loader_wb.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 215 KiB

BIN
static/img/star_people.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

BIN
static/img/tour1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
static/img/tour2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
static/img/tour3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 KiB

BIN
static/img/vote_contest.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/img/vs1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

BIN
static/img/vs2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

BIN
static/img/vs3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

View file

@ -31,7 +31,7 @@ function createModal(id, type, value, modalid) {
</div>
</div>
</div>`;
}
}
document.body.innerHTML += modal;
}
@ -59,6 +59,50 @@ document.addEventListener("click", function(event) {
const pinComment = (postId) => {
$(document).ready(function() {
$.ajax({
type: "POST",
url: '/api/photo/comment/'+postId+'/pin',
success: function(response) {
var jsonData = JSON.parse(response);
console.log(response);
if (jsonData.errorcode == "1") {
Notify.noty('danger', JSON.stringify(response));
} else {
if (jsonData.action == "pin") {
Notify.noty('success', 'Успешно закреплено!');
} else {
Notify.noty('success', 'Успешно откреплено!');
}
const url = window.location.pathname;
const segments = url.split('/');
const id = segments[2];
console.log(segments);
$.ajax({
type: "POST",
url: "/api/photo/getcomments/"+id,
processData: false,
async: true,
success: function(r) {
$('#posts').html(r)
},
error: function(r) {
console.log(r)
}
});
}
}
});
});
}
const editComment = (postId, body, modalid) => {
$(document).ready(function() {
@ -78,7 +122,7 @@ const editComment = (postId, body, modalid) => {
Notify.noty('success', 'Успешно отредактировано!');
const url = window.location.pathname;
const segments = url.split('/');
const id = segments[segments.length - 1];
const id = segments[2];
$.ajax({

39
static/js/changeTab.js Normal file
View file

@ -0,0 +1,39 @@
function changeTab(id) {
const $activeTabs = $('.v-tab-b.v-tab--active');
const $activeBlocks = $('.active__block');
const $newTab = $('#' + id);
if ($activeTabs.length) {
$activeTabs.removeClass('v-tab--active');
}
$newTab.addClass('v-tab--active');
if ($activeBlocks.length) {
$activeBlocks.stop(true, true).animate({
opacity: 0,
}, 200, function () {
$(this).css('display', 'none').removeClass('active__block');
const $newBlock = $('#' + id + '__block');
$newBlock.css({
display: 'block',
opacity: 0
}).animate({
opacity: 1
}, 150, function () {
$(this).addClass('active__block');
});
});
} else {
// Если нет активных блоков, сразу показываем новый блок
$('#' + id + '__block').css({
display: 'block',
opacity: 0
}).animate({
opacity: 1
}, 150, function () {
$(this).addClass('active__block');
});
}
}

View file

@ -165,7 +165,19 @@ $(document).ready(function()
var placeElement = document.getElementById('place');
if (placeElement) {
$('#place').autocompleteHL({
minLength: 3,
source: function(request, response)
{
var cid = $('#search_cid').val();
if (cid != 0)
$.getJSON('/api/geodb/search', { place: request.term }, response).fail(function(jx) { alert(jx.responseText); });
else response(null);
}
});
}
@ -223,7 +235,6 @@ $(document).ready(function()
$('#day, #month, #year').on('change', function() { $('#dateAbsent').hide(); });
$('#search_type').on('change', function() { changeColor(this); }).change();
// Комментарий
@ -318,7 +329,7 @@ document.onclick = function(e)
{
e = e || window.event;
E = e.target || e.srcElement;
if (E.id != 'phint' && E.parentNode.id != 'phint' && E != _getID('mform').place) $('#phint').slideUp();
if (E.className != 'searchVehiclesBtn' && E.id != 'vlist_table' && E.className != 'num' && $('#vlist').css('display') == 'block') $('#vlist').hide().html('');
@ -349,6 +360,7 @@ function artClick()
function loadGalleries(cid)
{
if (cid == gal_cid) return;
@ -414,7 +426,6 @@ function setDate(d, m, y)
function showHint(id) { $('#'+id+'_hint').fadeIn() }
function hideHint(id) { $('#'+id+'_hint').fadeOut() }
function changeColor(sel) { sel.className = sel.options[sel.selectedIndex].className }

View file

@ -121,7 +121,7 @@ function searchVehicles()
function AddPhotoToBlock(block, arr, prepend)
{
block[prepend ? 'prepend' : 'append']('<div class="prw-grid-item"><div class="prw-wrapper">' + arr.links + '<div>' + arr.pdate + '</div></div><a href="/photo/' + arr.pid + '/" target="_blank" class="prw-animate" style="background-image:url(\'' + arr.prw + '\')">' + (arr.ccnt != 0 ? '<div class="hdshade"><div class="com-icon">' + arr.ccnt + '</div></div>' : '') + '</a></div>');
block[prepend ? 'prepend' : 'append']('<div class="prw-grid-item"><div class="prw-wrapper">' + arr.place + '<div>' + arr.date + '</div></div><a href="/photo/' + arr.id + '/" target="_blank" class="prw-animate" style="background-image:url(\'' + arr.photourl_small + '\')">' + (arr.ccnt != 0 ? '<div class="hdshade"><div class="com-icon">' + arr.ccnt + '</div></div>' : '') + '</a></div>');
}
@ -166,24 +166,59 @@ function LoadRecentPhotos()
loadmore.prop('disabled', true).addClass('loader-button').val(' ');
$.getJSON('/api.php', { action: 'get-recent-photos', width: width, lastpid: lastpid, hidden: hidden.length }, function(data)
$.getJSON('/api/photo/loadrecent', { lastpid: lastpid }, function(data)
{
if (data)
{
if (lastpid == 0) recent.attr('firstpid', data[0].pid);
if (lastpid == 0) recent.attr('firstpid', data[0].id);
hidden.show();
for (var i = 0; i < data.length; i++) AddPhotoToBlock(recent, data[i]);
recent.attr('lastpid', data[i-1].pid);
recent.attr('lastpid', data[i-1].id);
}
else recent.append('Load error');
loadmore.prop('disabled', false).removeClass('loader-button').val(_text['IX_LOADMORE']);
loadmore.prop('disabled', false).removeClass('loader-button').val('Загрузить ещё');
})
.fail(function(jx) { if (jx.responseText != '') console.log(jx.responseText); });
}
function startCountdown(unixTimestamp) {
function padZero(num) {
return num < 10 ? '0' + num : num;
}
function getWord(num, words) {
if (num % 10 === 1 && num % 100 !== 11) return words[0];
if (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20)) return words[1];
return words[2];
}
function updateTimer() {
const now = Math.floor(Date.now() / 1000);
const diff = unixTimestamp - now;
if (diff <= 0) {
clearInterval(interval);
document.getElementById('countdown').textContent = "00 дней 00 часов 00 минут 00 секунд";
return;
}
const days = Math.floor(diff / 86400);
const hours = Math.floor((diff % 86400) / 3600);
const minutes = Math.floor((diff % 3600) / 60);
const seconds = diff % 60;
document.getElementById('countdown').textContent =
`${padZero(days)} ${getWord(days, ['день', 'дня', 'дней'])} ` +
`${padZero(hours)} ${getWord(hours, ['час', 'часа', 'часов'])} ` +
`${padZero(minutes)} ${getWord(minutes, ['минута', 'минуты', 'минут'])} ` +
`${padZero(seconds)} ${getWord(seconds, ['секунда', 'секунды', 'секунд'])}`;
}
updateTimer(); // сразу обновляем отображение
const interval = setInterval(updateTimer, 1000);
}
function LoadPubPhotos()
{

View file

@ -87,9 +87,8 @@ $(document).ready(function()
}
$('#votes').html(html)[html == '' ? 'hide' : 'show']();
$('.vote_btn[vote="1"]')[data.buttons[1] ? 'addClass' : 'removeClass']('voted');
$('.vote_btn[vote="0"]')[data.buttons[0] ? 'addClass' : 'removeClass']('voted');
$('.vote[pid="' + pid + '"][vote="1"]')[data.buttons.posbtn ? 'addClass' : 'removeClass']('voted');
$('.vote[pid="' + pid + '"][vote="0"]')[data.buttons.negbtn ? 'addClass' : 'removeClass']('voted')
var rating = parseInt(data.rating);
if (rating > 0) $('#rating').html('+' + rating); else
@ -119,7 +118,7 @@ $(document).ready(function()
if (vote != 0 && vote != 1 || $(this).is('.locked')) return false;
var pid = $(this).closest('.vote').attr('pid');
var cid = $(this).closest('.vote').attr('cid');
var savedClass1 = $('.vote[pid="' + pid + '"] .konk_btn[vote="1"]').attr('class');
var savedClass0 = $('.vote[pid="' + pid + '"] .konk_btn[vote="0"]').attr('class');
@ -127,19 +126,19 @@ $(document).ready(function()
$(this).toggleClass('voted');
if ($(this).is('.voted')) $('.vote[pid="' + pid + '"] .konk_btn[vote="' + Number(!Number(vote)) + '"]').removeClass('voted');
var self_p = 0;
if (!self_p) // Чужие фото
{
$(this).closest('.p20p').removeAttr('class').css('padding', '6px 6px 5px');
$.getJSON('/api.php', { action: 'vote-konk', pid: pid, vote: vote }, function (data)
$.getJSON('/api/photo/vote', { action: 'vote-konk', pid: pid, vote: vote, cid: cid }, function (data)
{
if (data && !data.errors)
{
$('.star[pid="' + pid + '"]').html(data.star ? '<img src="/img/star_' + data.star + '.png" alt="" />' : '');
$('.vote[pid="' + pid + '"] .konk_btn[vote="1"]')[data.buttons.posbtn_contest ? 'addClass' : 'removeClass']('voted');
$('.vote[pid="' + pid + '"] .konk_btn[vote="0"]')[data.buttons.negbtn_contest ? 'addClass' : 'removeClass']('voted');
$('.vote[pid="' + pid + '"] .konk_btn[vote="1"]')[data.buttons[1] ? 'addClass' : 'removeClass']('voted');
$('.vote[pid="' + pid + '"] .konk_btn[vote="0"]')[data.buttons[0] ? 'addClass' : 'removeClass']('voted');
var rat = $('.s_rating[pid="' + pid + '"]');
if (rat.length)
@ -164,14 +163,14 @@ $(document).ready(function()
}
else // Свои фото
{
$.getJSON('/api.php', { action: 'vote-author', pid: pid, vote: vote }, function (data)
$.getJSON('/api/photo/vote', { action: 'vote-author', pid: pid, vote: vote }, function (data)
{
if (data && !data.errors)
{
$('#star[pid="' + pid + '"]').html(data.star ? '<img src="/img/star_' + data.star + '.png" alt="" />' : '');
$('.konk_btn[vote="1"]')[data.buttons[1] ? 'addClass' : 'removeClass']('voted');
$('.konk_btn[vote="0"]')[data.buttons[0] ? 'addClass' : 'removeClass']('voted');
$('.vote[pid="' + pid + '"] .konk_btn[vote="1"]')[data.buttons.posbtn_contest ? 'addClass' : 'removeClass']('voted');
$('.vote[pid="' + pid + '"] .konk_btn[vote="0"]')[data.buttons.negbtn_contest ? 'addClass' : 'removeClass']('voted');
}
else
{

View file

@ -4,7 +4,7 @@ use \App\Core\Page;
use \App\Services\DB;
$nonreviewedimgs = DB::query('SELECT COUNT(*) FROM photos WHERE moderated=0')[0]['COUNT(*)'];
if ($nonreviewedimgs > 0) {
$nonr = '<span class="mm-notify notify-count">'.$nonreviewedimgs.'</span>';
$nonr = '<span class="badge text-bg-danger">'.$nonreviewedimgs.'</span>';
}
?>
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
@ -15,11 +15,13 @@ body {
}
</style>
<script src="/static/js/changeTab.js" defer></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/boxicons@latest/css/boxicons.min.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.5/dist/umd/popper.min.js" integrity="sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.min.js" integrity="sha384-ODmDIVzN+pFdexxHEHFBQH3/9/vQ9uori45z4JjnFsRydbmQbmL5t1tQ0culUzyK" crossorigin="anonymous"></script>
<link rel="stylesheet" href="/static/css/header.admin.css">
<link rel="stylesheet" href="/static/css/tabs.css">
<div class="layout__left-column layout__sticky">
<header style="background-color: #0d1012;" class="header">
<div class="header__container">
@ -37,7 +39,7 @@ body {
<nav class="nav__container">
<div>
<a href="#" class="nav__link nav__logo">
<a href="/admin" class="nav__link nav__logo">
<h5><b><?=NGALLERY['root']['title']?></b></h5>
</a>
@ -56,7 +58,7 @@ body {
</a>
<a href="/admin?type=Photo" class="nav__link">
<i class="bx bx-camera nav__icon"></i>
<span class="nav__name">Фотографии</span>
<span class="nav__name">Фотографии<?=$nonr?></span>
</a>
<a href="/admin?type=Galleries" class="nav__link">
<i class="bx bx-images nav__icon"></i>
@ -66,6 +68,10 @@ body {
<i class="bx bx-news nav__icon"></i>
<span class="nav__name">Новости сайта</span>
</a>
<a href="/admin?type=Contests" class="nav__link">
<i class="bx bx-party nav__icon"></i>
<span class="nav__name">Фотоконкурсы <span class="badge text-bg-warning">BETA</span></span>
</a>
<a href="/admin?type=Entities" class="nav__link">
<i class="bx bx-package nav__icon"></i>
<span class="nav__name">Сущности</span>
@ -74,7 +80,18 @@ body {
<i class="bx bx-data nav__icon"></i>
<span class="nav__name">База моделей</span>
</a>
<a href="/admin?type=GeoDB" class="nav__link">
<i class="bx bx-world nav__icon"></i>
<span class="nav__name">GeoDB<span class="badge text-bg-warning">BETA</span></span>
</a>
<!--a href="/admin?type=Pages" class="nav__link">
<i class="bx bx-file-blank nav__icon"></i>
<span class="nav__name">Страницы</span>
</!--a-->
<a href="/admin?type=Settings" class="nav__link">
<i class="bx bx-cog nav__icon"></i>
<span class="nav__name">Настройки<span class="badge text-bg-warning">BETA</span></span>
</a>
</div>

View file

@ -1,6 +1,6 @@
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
<meta name="viewport" content="width=1000,user-scalable=yes">
<title>NativeGallery</title>
<title><?=NGALLERY['root']['title']?></title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=PT+Sans+Narrow:wght@400;700&amp;display=swap" rel="stylesheet">
@ -12,11 +12,14 @@
<link rel="stylesheet" href="/static/css/notie.css<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>">
<link rel="stylesheet" href="/static/css/comments.css<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>">
<link rel="stylesheet" href="/static/css/map.css<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>">
<link rel="stylesheet" href="/static/css/jquery-ui-1.8.20.custom.css<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>">
<script src="/static/js/jquery.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/jquery-ui.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/jquery.form.min.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/core.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/core_lk.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/index.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/jquery-ui.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/selector.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/selector2.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/imageupload.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/progress.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/notie.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
@ -24,9 +27,9 @@
<script src="/static/js/comments.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/newcore.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/act.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/selector2.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/selector.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/core_lk.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/tablesort.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<div class="progress-container fixed-top">

View file

@ -28,6 +28,12 @@ if ($noncheckedimgs > 0) {
?>
<tr>
<?php
if (NGALLERY['root']['registration']['emailverify'] === true && $user->i('status') === 3) { ?>
<div class="label-orange" style="padding:10px; margin:0 -20px; color:#fff">
<center><h4 style="color:#fff; margin-bottom:3px">Пожалуйста, подтвердите свою почту</h4>
<div>Так мы сможем убедиться, что Вы настоящий человек. После подтверждения, Вам будет доступен полностью функционал сайта.<br><br><b>Письмо с ссылкой для подтверждения почты была отправлена на Ваш ящик, указанный при регистрации.</b></div></center></div>
<?php } ?>
<td class="mm-bar">
<?php
if (explode('/', $_SERVER['REQUEST_URI'])[1] === 'photo') { ?>
@ -45,7 +51,7 @@ if ($noncheckedimgs > 0) {
</ul>
</div>
</li>
<li><a href="/comments.php" class="mm-item"><span class="mm-label">Комментарии</span></a></li>
<li><a href="/comments" class="mm-item"><span class="mm-label">Комментарии</span></a></li>
<li><a href="#" onclick="return false" class="mm-item"><span class="mm-label">Обновления</span><?=$nonrw?></a>
<div>
<ul class="mm-level-2">
@ -100,11 +106,13 @@ if ($noncheckedimgs > 0) {
?>
<li><a href="/admin" class="mm-item"><span class="mm-icon"><i class="fas fa-sm fa-fw fa-info-circle"></i></span><span class="mm-label">Admin</span><?=$nonr?></a></li>
<?php } ?>
<?php }
if (NGALLERY['root']['registration']['emailverify'] != true || $user->i('status') != 3) { ?>
<li><a href="/lk/upload" class="mm-item"><span class="mm-icon"><i class="fas fa-sm fa-fw fa-plus-square"></i></span><span class="mm-label"><b>Предложить медиа</b></span></a></li>
<?php } ?>
<li><a href="/lk/history" class="mm-item"><span class="mm-icon"><i class="fas fa-sm fa-fw fa-images"></i></span><span class="mm-label">Журнал</span></a></li>
<li><a href="/lk/konkurs.php" class="mm-item"><span class="mm-icon"><i class="fas fa-sm fa-fw fa-compass"></i></span><span class="mm-label">Конкурс</span></a></li>
<li><a href="/lk/vehicles.php" class="mm-item"><span class="mm-icon"><i class="fas fa-sm fa-fw fa-folder-plus"></i></span><span class="mm-label"><b>Правка БД</b></span></a></li>
<li><a href="/vehicle/edit" class="mm-item"><span class="mm-icon"><i class="fas fa-sm fa-fw fa-folder-plus"></i></span><span class="mm-label"><b>Правка БД</b></span></a></li>
<li><a href="/lk/ticket.php" class="mm-item"><span class="mm-icon"><i class="fas fa-sm fa-fw fa-question-circle"></i></span><span class="mm-label">Мои заявки</span></a></li>
<li><a href="/lk/profile" class="mm-item"><span class="mm-icon"><i class="fas fa-sm fa-fw fa-cog"></i></span><span class="mm-label">Настройки профиля</span></a></li>
<li><a href="/search?id=<?=Auth::userid()?>" class="mm-item"><span class="mm-icon"><i class="far fa-sm fa-fw fa-images"></i></span><span class="mm-label">Мои фотографии</span></a></li>

View file

@ -0,0 +1,260 @@
<?php
use \App\Services\{Auth, DB, Date, TaskScheduler};
use \App\Models\User;
$task = new TaskScheduler();
$contestCreate = true;
if (!$task->isTaskExists("ExecContests", "php ".$_SERVER['DOCUMENT_ROOT'].$task->findHandlerById(NGALLERY_TASKS, 'ExecContests'))) {
$contestCreate = false;
}
?>
<tr>
<td class="main">
<h1><b>Фотоконкурсы</b></h1>
<div class="v-header__tabs">
<div class="v-tabs">
<div class="v-tabs__scroll">
<div class="v-tabs__content"><a href="#" onclick="changeTab('contests')" id="contests" class="v-tab v-tab-b v-tab--active"><span class="v-tab__label">
Конкурсы
</span></a><a href="#" onclick="changeTab('categories')" id="categories" class="v-tab v-tab-b"><span class="v-tab__label">
Категории
</span></a>
</div>
</div>
</div>
</div>
<div class="kandle__block active__block" id="contests__block">
<div class="p20w" style="display:block">
<a data-bs-toggle="modal" data-bs-target="#createContest" href="#" class="btn btn-primary mt-3 <?php if ($contestCreate === false) {
echo 'disabled';
} ?>">Провести новый</a>
<table class="table">
<?php
if ($contestCreate === false) {
echo "<div class='alert alert-warning mt-3' role='alert'>У вас не добавлена задача на проведение конкурсов. Без неё, сервер не сможет завершать конкурс и проводить новый автоматически.<a href='/admin?type=Settings' type='button' style='margin-left: 5px;' class='btn btn-sm btn-outline-dark'>Включить</a></div>";
}
?>
<tbody>
<tr>
<th width="100">ID</th>
<th width="25%">Тема</th>
<th>Дата начала отбора</th>
<th>Дата конца отбора</th>
<th>Дата начала</th>
<th>Дата конца</th>
<th>Статус</th>
<th></th>
</tr>
<?php
$themes = DB::query('SELECT * FROM contests ORDER BY id DESC');
foreach ($themes as $t) {
$themetitle = DB::query('SELECT title FROM contests_themes WHERE id=:id', array(':id' => $t['themeid']))[0]['title'];
if ($t['status'] === 0) {
$status = 'Ещё не проведён';
} else if ($t['status'] === 1) {
$status = 'Отбор кандидатов';
} else if ($t['status'] === 02) {
$status = 'Ещё не открыт для отбора победителей';
} else if ($t['status'] === 2) {
$status = 'Отбор победителей';
} else if ($t['status'] === 3) {
$status = 'Завершён';
} else {
$status = 'Сбой';
}
echo '<tr class="' . $color . '">
<td>' . $t['id'] . '</td>
<td>' . $themetitle . '</td>
<td>' . Date::zmdate($t['openpretendsdate']) . '</td>
<td>' . Date::zmdate($t['closepretendsdate']) . '</td>
<td>' . Date::zmdate($t['opendate']) . '</td>
<td>' . Date::zmdate($t['closedate']) . '</td>
<td>' . $status . '</td>
</tr>';
}
?>
</tbody>
</table>
</div>
</div>
<div class="kandle__block" style="display: none;" id="categories__block">
<div class="p20w" style="display:block">
<a data-bs-toggle="modal" data-bs-target="#createContestTheme" href="#" class="btn btn-primary mt-3">Создать</a>
<table class="table">
<tbody>
<tr>
<th width="100">ID</th>
<th width="50%">Название</th>
<th>В автоматическом отборе</th>
<th></th>
</tr>
<?php
$themes = DB::query('SELECT * FROM contests_themes');
foreach ($themes as $t) {
if ($t['status'] === 1) {
$auto = 'Да';
} else {
$auto = 'Нет';
}
echo '<tr class="' . $color . '">
<td>' . $t['id'] . '</td>
<td>' . $t['title'] . '</td>
<td>' . $auto . '</td>
</tr>';
}
?>
</tbody>
</table>
</div>
</div>
</td>
</tr>
<div class="modal fade" id="createContest" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel"><b>Создание конкурса</b></h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="contest">
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">Тематика</label>
<select name="themeid" class="form-select" aria-label="Default select example">
<?php
$themes = DB::query('SELECT * FROM contests_themes');
foreach ($themes as $t) {
echo '<option value="'.$t['id'].'">'.$t['title'].'</option>';
}
?>
</select>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">Дата начала отбора претенднетов</label>
<input name="openpretendsdate" type="datetime-local" class="form-select">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">Дата конца отбора претенднетов</label>
<input name="closepretendsdate" type="datetime-local" class="form-select">
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input name="startContestNow" class="form-check-input" type="checkbox" value="1" id="startContestNow">
<label class="form-check-label" for="startContestNow">
Провести конкурс сразу после конца отбора претендентов
</label>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="contestStart" class="form-label">Дата начала проведения конкурса</label>
<input id="contestStartInput" name="opendate" type="datetime-local" class="form-select">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">Дата конца проведения конкурса</label>
<input name="closedate" type="datetime-local" class="form-select">
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<a type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</a>
<a href="#" onclick="createContest(); return false;" data-bs-dismiss="modal" class="btn btn-primary">Добавить</a>
</div>
</div>
</div>
</div>
<div class="modal fade" id="createContestTheme" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel"><b>Добавление категории конкурса</b></h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="contestTheme">
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">Название</label>
<input class="form-control" id="exampleFormControlTextarea1" name="body">
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="1" name="active" id="flexCheckChecked" checked>
<label class="form-check-label" for="flexCheckChecked">
Активна для автоматического подбора
</label>
</div>
</form>
</div>
<div class="modal-footer">
<a type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</a>
<a href="#" onclick="createContestTheme(); return false;" data-bs-dismiss="modal" class="btn btn-primary">Добавить</a>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
$('#startContestNow').on('change', function() {
$('#contestStartInput').prop('disabled', $(this).is(':checked'));
});
});
function createContestTheme() {
var formData = new FormData(document.getElementById("contestTheme"));
$.ajax({
type: "POST",
url: '/api/admin/contests/createtheme',
data: formData,
success: function(response) {
var jsonData = JSON.parse(response);
Notify.noty('success', 'OK!');
},
cache: false,
contentType: false,
processData: false
});
}
function createContest() {
var formData = new FormData(document.getElementById("contest"));
$.ajax({
type: "POST",
url: '/api/admin/contests/create',
data: formData,
success: function(response) {
var jsonData = JSON.parse(response);
Notify.noty('success', 'OK!');
},
cache: false,
contentType: false,
processData: false
});
}
</script>

View file

@ -0,0 +1,87 @@
<?php
use \App\Services\{Auth, DB};
use \App\Models\User;
?>
<h1><b>GeoDB</b></h1>
<a data-bs-toggle="modal" data-bs-target="#createGeoDB" href="#" class="btn btn-primary">Создать</a>
<table class="table" style="margin-top: 15px;">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Название</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<?php
$geodb = DB::query('SELECT * FROM geodb');
foreach ($geodb as $u) {
echo '<tr id="geodb'.$u['id'].'">
<th>' . $u['id'] . '</th>
<td>' . $u['title'] . '</td>
<td><div class="cmt-submit"><a style="margin-left: 15px;" class="btn btn-sm btn-danger" onclick="deleteGeoDB(`'.$u['id'].'`); return false;">Удалить</a></div></td>
</tr>';
}
?>
</tbody>
</table>
<div class="modal fade" id="createGeoDB" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel"><b>Добавление элемента GeoDB</b></h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">Содержание</label>
<textarea class="form-control" id="exampleFormControlTextarea1" name="body" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<a type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</a>
<a href="#" onclick="createGeoDB(document.querySelector(`textarea[name='body']`).value); return false;" data-bs-dismiss="modal" class="btn btn-primary">Добавить</a>
</div>
</div>
</div>
</div>
<script>
function createGeoDB(body) {
$.ajax({
type: "POST",
url: '/api/admin/geodb/create',
data: {
body: body
},
success: function(response) {
Notify.noty('success', 'OK!');
}
});
}
function deleteGeoDB(id) {
$.ajax({
type: "POST",
url: '/api/admin/geodb/delete?id='+id,
data: $(this).serialize(),
success: function(response) {
$('#geodb'+id).remove();
Notify.noty('success', 'OK!');
//$("#result").html("<div class='alert alert-successnew container mt-5' role='alert'>Успешный вход!</div>");
}
});
}
</script>

View file

@ -0,0 +1,151 @@
<script>
function submitUpload() {
$('#buttonUpload').html('<button style="border-radius: 10px 0px 0px 10px !important;" type="submit" id="createpost" class="btn btn-primary mb-3 mt-3" disabled><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Загружаем...</button>');
$('#buttonUploadModal').html('<button type="submit" id="createpost" class="btn btn-primary" disabled><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Загружаем...</button>');
$('#buttonPreView').html('<button style="border-radius: 0px 10px 10px 0px !important;" type="submit" id="createpost" class="btn btn-primarynew mb-3 mt-3" disabled>Предпросмотр</button>');
$.ajax({
type: "POST",
url: '/exapi/dialogs/create',
data: JSON.stringify( { "bodypost":$("#bodypost").val(), "title": $("#title").val(), "did": '00000' } ),
dataType: "json",
success: function(response) {
var jsonData = JSON.parse(JSON.stringify(response));
console.log(jsonData);
if (jsonData.errorcode == "1") {
$('#buttonUpload').html('<button href="#" data-bs-toggle="modal" data-bs-target="#createDialogModal" style="border-radius: 10px 0px 0px 10px !important;" type="submit" id="createpost" class="btn btn-primary mb-3 mt-3">Опубликовать</button>');
$('#buttonPreView').html('<button onclick="submitPreview()" style="border-radius: 0px 10px 10px 0px !important;" type="submit" id="createpost" class="btn btn-primarynew mb-3 mt-3">Предпросмотр</button>');
$('#buttonUploadModal').html('<button onclick="submitUpload()"type="submit" class="btn btn-primary">Да, я уверен</button>');
Notify.noty('danger', 'Мало контента!');
} else {
$('#buttonUpload').html('<button href="#" data-bs-toggle="modal" data-bs-target="#createDialogModal" style="border-radius: 10px 0px 0px 10px !important;" type="submit" id="createpost" class="btn btn-primary mb-3 mt-3">Опубликовать</button>');
$('#buttonPreView').html('<button onclick="submitPreview()" style="border-radius: 0px 10px 10px 0px !important;" type="submit" id="createpost" class="btn btn-primarynew mb-3 mt-3">Предпросмотр</button>');
$('#buttonUploadModal').html('<button onclick="submitUpload()"type="submit" class="btn btn-primary">Да, я уверен</button>');
window.location.replace("/dialogs/<?=$id_dialog?>/topic/<?=$id_topic?>/post/"+jsonData.id);
}
}
});
}
function submitPreview() {
$('#buttonUpload').html('<button style="border-radius: 10px 0px 0px 10px !important;" type="submit" id="createpost" class="btn btn-primary mb-3 mt-3" disabled>Опубликовать</button>');
$('#buttonPreView').html('<button style="border-radius: 0px 10px 10px 0px !important;" type="submit" id="createpost" class="btn btn-primarynew mb-3 mt-3" disabled><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Загружаем...</button>');
$.ajax({
type: "POST",
url: '/exapi/dialogs/createPreview',
data: JSON.stringify( { "bodypost":$("#bodypost").val(), "title": $("#title").val() } ),
dataType: "json",
success: function(response) {
var jsonData = JSON.parse(JSON.stringify(response));
console.log(response);
console.log(jsonData);
if (jsonData.errorcode == "1") {
$('#buttonUpload').html('<button href="#" data-bs-toggle="modal" data-bs-target="#createDialogModal" style="border-radius: 10px 0px 0px 10px !important;" type="submit" id="createpost" class="btn btn-primary mb-3 mt-3">Опубликовать</button>');
$('#buttonPreView').html('<button onclick="submitPreview()" style="border-radius: 0px 10px 10px 0px !important;" type="submit" id="createpost" class="btn btn-primarynew mb-3 mt-3">Предпросмотр</button>');
$("#result").html("<div class='alert alert-dangernew container' role='alert'>Может, что-нибудь напишите в свой пост?</div>");
} else {
$('#buttonUpload').html('<button href="#" data-bs-toggle="modal" data-bs-target="#createDialogModal" style="border-radius: 10px 0px 0px 10px !important;" type="submit" id="createpost" class="btn btn-primary mb-3 mt-3">Опубликовать</button>');
$('#buttonPreView').html('<button onclick="submitPreview()" style="border-radius: 0px 10px 10px 0px !important;" type="submit" id="createpost" class="btn btn-primarynew mb-3 mt-3">Предпросмотр</button>');
window.open('/dialogs/preview?id='+jsonData.preid, '_blank');
}
}
});
}
</script>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">Заголовок</label>
<input name="title" id="title" type="text" class="form-control" id="exampleFormControlInput1">
</div>
<div class="col-md-auto d-flex align-items-center">
<button onclick="document.getElementById('bodypost').value += '[b] [/b]';" type="button" class="btn btn-primary btn-sm mb-2" style="margin-right: 6px;"><i class="bx bx-bold"></i></button>
<button onclick="document.getElementById('bodypost').value += '[i] [/i]';" type="button" class="btn btn-primary btn-sm mb-2" style="margin-right: 6px;"><i class="bx bx-italic"></i></button>
<button onclick="document.getElementById('bodypost').value += '[u] [/u]';" type="button" class="btn btn-primary btn-sm mb-2" style="margin-right: 6px;"><i class="bx bx-underline"></i></button>
<button onclick="document.getElementById('bodypost').value += '[s] [/s]';" type="button" class="btn btn-primary btn-sm mb-2" style="margin-right: 6px;"><i class="bx bx-strikethrough"></i></button>
<button onclick="document.getElementById('bodypost').value += '[link=] [/link]';" type="button" class="btn btn-primary btn-sm mb-2" style="margin-right: 36px;"><i class="bx bx-link"></i></button>
<button href="#" data-bs-toggle="modal" data-bs-target="#pickImageModal" type="button" class="btn btn-primary btn-sm mb-2" style="margin-right: 6px;"><i class="bx bx-image-alt"></i></button>
<button href="#" data-bs-toggle="modal" data-bs-target="#pickVideoModal" type="button" class="btn btn-primary btn-sm mb-2" style="margin-right: 6px;"><i class="bx bx-video"></i></button>
<button href="#" data-bs-toggle="modal" data-bs-target="#pickMusicModal" type="button" class="btn btn-primary btn-sm mb-2" style="margin-right: 6px;"><i class="bx bx-music"></i></button>
</div>
<textarea id="bodypost" class="form-control" name="bodypost" placeholder="Вы можете написать свою историю" cols="30" rows="10"></textarea>
<div class="btn-group" role="group" aria-label="Basic example">
<div id="buttonUpload">
<button href="#" data-bs-toggle="modal" data-bs-target="#createDialogModal" style="border-radius: 10px 0px 0px 10px !important;" type="submit" id="createpost" class="btn btn-primary mb-3 mt-3">Опубликовать</button>
</div>
<div id="buttonPreView">
<button onclick="submitPreview()" style="border-radius: 0px 10px 10px 0px !important;" type="submit" id="createpost" class="btn btn-outline-primary mb-3 mt-3">Предпросмотр</button>
</div>
</div>
</form>
<div class="modal fade" id="pickImageModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Прикрепление фотографии</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form enctype="multipart/form-data" id="pickImageForm">
<div class="modal-body">
<div class="mb-3">
<input id="filebody" type="file" name="filebodyImage" class="form-control" type="file">
</div>
</div>
<div class="modal-footer">
<div><button type="button" class="btn-r btn-secondary" data-bs-dismiss="modal">Отмена</button></div>
<div id="r"><button id="t" type="submit" class="btn btn-primary" data-bs-dismiss="modal">Прикрепить</button></div>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="pickMusicModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Прикрепление музыки</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<input id="filebody" type="file" name="filebody" class="form-control" type="file">
</div>
</div>
<div class="modal-footer">
<div><button type="button" class="btn-r btn-secondary" data-bs-dismiss="modal">Отмена</button></div>
<div id="r"><button id="t" type="submit" class="btn btn-primary">Прикрепить</button></div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="pickVideoModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Прикрепление видео</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form enctype="multipart/form-data" id="pickVideoForm">
<div class="modal-body">
<div class="mb-3">
<input id="filebody" type="file" name="filebodyVideo" class="form-control" type="file">
</div>
</div>
<div class="modal-footer">
<div><button type="button" class="btn-r btn-secondary" data-bs-dismiss="modal">Отмена</button></div>
<div id="r"><button id="t" type="submit" class="btn btn-primary" data-bs-dismiss="modal">Прикрепить</button></div>
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,19 @@
<?php
use \App\Services\{Auth, DB, Date};
use \App\Models\User;
?>
<h1><b>Страницы</b></h1>
<a href="?type=PageCreate" class="btn btn-primary mb-3">Создать</a>
<div id="pages">
<?php
$pages = DB::query('SELECT * FROM pages ORDER BY id');
foreach ($pages as $p) {
echo '<div class="card mb-3"><div class="card-body">' . Date::zmdate($n['time']) . '<br>' . $n['body'] . '</div></div>';
}
?>
</div>

View file

@ -35,10 +35,26 @@ use \App\Models\User;
}
</style>
<td class="main">
<h1><b>Журнал</b></h1>
<h1><b>Фотографии</b></h1>
<div class="v-header__tabs">
<div class="v-tabs">
<div class="v-tabs__scroll">
<div class="v-tabs__content"><!--a href="#" onclick="changeTab('full')" id="full" class="v-tab v-tab-b"><span class="v-tab__label">
Полный список
</span></!--a--><a href="#" onclick="changeTab('moderate')" id="moderate" class="v-tab v-tab-b v-tab--active"><span class="v-tab__label">
Ожидают модерации
</span></a>
</div>
</div>
</div>
</div>
<script src="/js/diff.js"></script>
<script src="/js/pwrite-compare.js"></script>
<br clear="all"><br>
<div id="moderate__block"class="active__block" >
<div class="p20w" style="display:block">
<table class="table">
<tbody>
@ -48,6 +64,7 @@ use \App\Models\User;
<th>Действия</th>
</tr>
<?php
$photos = DB::query('SELECT * FROM photos WHERE moderated=0 ORDER BY id DESC');
foreach ($photos as $p) {
@ -74,7 +91,7 @@ use \App\Models\User;
<td class="c">
';
if ($p['moderated'] === 0) {
echo '<a href="/api/admin/images/setvisibility?id='.$p['id'].'&mod=1" class="btn btn-primary">Принять</a>
echo '<a data-bs-toggle="modal" data-bs-target="#acceptPhotoModal'.$p['id'].'" href="#" class="btn btn-primary">Принять</a>
<a data-bs-toggle="modal" data-bs-target="#declinePhotoModal'.$p['id'].'" href="#" class="btn btn-danger">Отклонить</a>';
}
echo '
@ -85,6 +102,56 @@ use \App\Models\User;
echo '
</tr>
<div class="modal fade" id="acceptPhotoModal'.$p['id'].'" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel"><b>Принятие фотографии</b></h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="form-check">
<input name="accept'.$p['id'].'" value="0" class="form-check-input" type="radio" name="flexRadioDefault" id="acceptReason1" checked>
<label class="form-check-label" for="acceptReason1">
Нормальная публикация
</label>
</div>
<div class="form-check">
<input name="accept'.$p['id'].'" value="1" class="form-check-input" type="radio" name="flexRadioDefault" id="acceptReason3">
<label class="form-check-label" for="acceptReason3">
Условная публикация
</label>
</div>
<div class="form-check">
<input name="accept'.$p['id'].'" value="2" class="form-check-input" type="radio" name="flexRadioDefault" id="acceptReason2">
<label class="form-check-label" for="acceptReason2">
Временная публикация
</label>
</div>
<div class="form-check">
<input name="accept'.$p['id'].'" value="3" class="form-check-input" type="radio" name="flexRadioDefault" id="acceptReason4">
<label class="form-check-label" for="acceptReason4">
Техническая публикация
</label>
</div>
<h6 class="mt-3">Другие действия</h6>
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">Дополнительный комментарий</label>
<textarea class="form-control" id="exampleFormControlTextarea1" name="comment" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<a type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</a>'; ?>
<a href="#" onclick="photoAction(<?=$p['id']?>, document.querySelector(`input[name='accept<?=$p['id']?>']:checked`).value, 1); return false;" data-bs-dismiss="modal" class="btn btn-primary">Сохранить</a>
<?php echo '
</div>
</div>
</div>
</div>
<div class="modal fade" id="declinePhotoModal'.$p['id'].'" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@ -123,7 +190,17 @@ use \App\Models\User;
Расчленёнка
</label>
</div>
<div class="form-check">
<input name="decline'.$p['id'].'" value="6" class="form-check-input" type="radio" name="flexRadioDefault" id="declineReason6">
<label class="form-check-label" for="declineReason6">
Файл сломан
</label>
</div>
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">Дополнительный комментарий</label>
<textarea class="form-control" id="exampleFormControlTextarea1" name="comment" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<a type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</a>'; ?>
@ -141,7 +218,10 @@ use \App\Models\User;
</tbody>
</table>
</div><br>
</div></div>
<div style="display: none;" id="full__block">
fgdfg
</div>
</td>
</tr>
@ -149,7 +229,7 @@ use \App\Models\User;
function photoAction(photo_id, decline_reason, mod) {
$.ajax({
type: "GET",
url: '/api/admin/images/setvisibility?id='+photo_id+'&mod='+mod+'&decline_reason='+decline_reason,
url: '/api/admin/images/setvisibility?id='+photo_id+'&mod='+mod+'&reason='+decline_reason,
data: $(this).serialize(),
success: function(response) {
$('#pht'+photo_id).remove();

View file

@ -0,0 +1,171 @@
<?php
use \App\Services\{KeyTranslation, TaskScheduler};
use \App\Models\User;
use Symfony\Component\Yaml\Yaml;
$task = new TaskScheduler();
$yamlFile = $_SERVER['DOCUMENT_ROOT'] . '/ngallery.yaml';
function renderInputs($data, $prefix = '')
{
foreach ($data as $key => $value) {
$name = $prefix ? "{$prefix}[{$key}]" : $key;
$key = KeyTranslation::key($key);
if (is_string($value) || is_bool($value)) {
if ($value === true || $value === false) {
$value = $value ? 'true' : 'false';
}
if ($value === 'true' || $value === 'false') {
echo "<div class='mb-2'>
<label>{$key}</label>
<select class='form-control' name='{$name}'>
<option value='true' " . ($value === 'true' ? 'selected' : '') . ">true</option>
<option value='false' " . ($value === 'false' ? 'selected' : '') . ">false</option>
</select>
</div>";
} else {
echo "<div class='mb-2'>
<label>{$key}</label>
<input class='form-control' type='text' name='{$name}' value='" . htmlspecialchars($value, ENT_QUOTES) . "'>
</div>";
}
} elseif (is_numeric($value)) {
echo "<div class='mb-2'>
<label>{$key}</label>
<input class='form-control' type='number' name='{$name}' value='{$value}'>
</div>";
} elseif (is_array($value)) {
echo "<fieldset class='mb-3 p-2 border'><legend>{$key}</legend>";
renderInputs($value, $name);
echo "</fieldset>";
}
}
}
?>
<!DOCTYPE html>
<html lang="ru">
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
<link rel="stylesheet" href="/static/css/notie.css<?php if (NGALLERY['root']['cloudflare-caching'] === true) {
echo '?' . time();
} ?>">
<script src="/static/js/notie.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) {
echo '?' . time();
} ?>"></script>
<script>
notie.setOptions({
transitionCurve: 'cubic-bezier(0.2, 0, 0.2, 1)'
});
var Notify = {
noty: function(status, text) {
if (status == 'danger') status = 'error';
return notie.alert({
type: status,
text: text
})
},
}
</script>
<body>
<div class="container">
<?= \App\Controllers\AdminController::loadMenu(); ?>
<?= \App\Controllers\AdminController::loadContent(); ?>
<h1><b>Настройки</b></h1>
<div class="v-header__tabs">
<div class="v-tabs">
<div class="v-tabs__scroll">
<div class="v-tabs__content"><!--a href="#" onclick="changeTab('config')" id="config" class="v-tab v-tab-b v-tab--active"><span class="v-tab__label">
Конфиг сервера
</span></!--a--><a href="#" onclick="changeTab('tasks')" id="tasks" class="v-tab v-tab-b v-tab--active"><span class="v-tab__label">
Задачи
</span></a>
</div>
</div>
</div>
</div>
<div id="config__block" style="display: none;" >
<div class="alert alert-warning" role="alert">
Изменяйте только на свой страх и риск.
</div>
<div class="p20w" style="display:block">
<?php
// Вывод формы
echo '<form method="post">';
foreach (NGALLERY as $ng) {
renderInputs($ng);
}
echo '<button type="submit">Сохранить</button>';
echo '</form>';
?>
</div><br>
</div>
<div class="active__block"id="tasks__block">
<table class="table">
<tbody>
<tr>
<th width="100">ID</th>
<th width="50%">Статус</th>
<th>Действия</th>
</tr>
<?php
foreach (NGALLERY_TASKS as $nt) {
$nt = $nt;
echo '<tr><td>
'.$nt['id'].'
</td><td>
'.$task->getTaskStatus($nt['id'], "php {$nt['handler']}").'
</td><td class="c">
<a onclick="taskManager(`'.$nt['id'].'`, 1)" class="btn btn-sm btn-primary">Запустить</a> <a onclick="taskManager(`'.$nt['id'].'`, 0)" class="btn btn-sm btn-danger">Остановить</a>
</td> <tr>';
}
?>
</tbody>
</table>
</div>
</div>
</body>
<script>
function taskManager(id, type) {
$.ajax({
type: "GET",
url: '/api/admin/settings/taskmanager?id='+id+'&type='+type,
success: function(response) {
Notify.noty('success', 'OK!');
}
});
}
</script>
</html>

View file

@ -0,0 +1,167 @@
<?php
use \App\Services\{Auth, DB, Date};
use \App\Models\{User, Photo, Vote};
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/LoadHead.php'); ?>
</head>
<body>
<div id="backgr"></div>
<table class="tmain">
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Navbar.php'); ?>
<tr>
<td class="main">
<h1>Лента комментариев</h1>
<script src="/js/jquery-ui.js?1633005526"></script>
<script src="/js/selector.js?1730197663"></script>
<script>
$(document).ready(function() {
$('#cname').citySelector('cid', {
defaultLabel: 'Все города'
});
$('#type').val(0).change(function() {
$(this).attr('class', $('option:selected', this).attr('class'));
}).change();
$('#applyFilter').on('click', function() {
var cid = $('#cid').val();
var type = $('#type').val();
var redir = '/comments.php';
if (cid > 0 || type > 0) {
redir = redir + '?';
if (cid > 0) {
redir = redir + 'cid=' + cid;
if (type > 0) redir = redir + '&';
}
if (type > 0) redir = redir + 't=' + type;
}
window.location.href = redir;
});
});
</script>
<div id="upd_anchor"></div>
<?php
$comments = DB::query('SELECT * FROM photos_comments ORDER BY id DESC LIMIT 30');
foreach ($comments as $c) {
$user = new User($c['user_id']);
$content = json_decode($c['content'], true);
$photo = new Photo($c['photo_id']);
if ($user->i('admin') === 1) {
$admintype = ' · Администратор сервера';
} else if ($user->i('admin') === 2) {
$admintype = ' · Фотомодератор';
}
if ((int)Vote::countcommrates($c['id'], -1) >= 1) {
$commclass = 'pro';
$symb = '+';
} else if ((int)Vote::countcommrates($c['id'], -1) < 0) {
$commclass = 'con';
$symb = '';
} else if ((int)Vote::countcommrates($c['id'], -1) === 0) {
$commclass = '';
}
echo '<div class="p-comment p20p">
<div class="pc-photo"><a href="/photo/'.$c['photo_id'].'/?top=1" target="_blank" class="prw"><img src="/api/photo/compress?url='.$photo->i('photourl').'" class="f"></a></div>
<div class="pc-content">
<a class="pc-topost" href="/photo/'.$c['photo_id'].'/?top=1#' . $c['id'] . '" target="_blank">Ссылка</a>
<div class="pc-text">
<div><span class="cmt-aname">
<b><a href="/author/'.$c['user_id'].'/">'.$user->i('username').'</a></b></span> &middot; <span class="sm nw">'.Date::zmdate($c['posted_at']).'</span>
<div class="rank">Фото: '.Photo::fetchAll($c['user_id']).''.$admintype.'</div>
<div class="message-text feed">'.$c['body'].'</div>
</div>
</div>
<div class="pc-compl">
<div class="comment-votes-block">
<div class="wvote" wid="' . $c['id'] . '">
<a href="#" vote="1" class="w-btn s2"><span>+</span></a>
<div class="w-rating '.$commclass.' active">' . $symb . Vote::countcommrates($c['id'], -1) . '</div>
<div class="w-rating-ext">
<div><span class="pro">+' . Vote::countcommrates($c['id'], 1) . '</span> / <span class="con">' . Vote::countcommrates($c['id'], 0) . '</span></div>
</div>
<a href="#" vote="0" class="w-btn s5"><span></span></a>
</div>
</div>
</div>
</div>
</div>';
}
?>
<div id="scroll_anchor"></div>
<script src="/js/endless.js?031021"></script>
<script>
var ltime = 1739362440;
var last_k = 20;
var st = 20;
var cnt = -1;
var ajaxQuery = '/comments.php?ajax=1&cnt=-1';
</script>
<script>
var ftime = 1739367547;
function markAllRead(force) {
if (confirm('Вы действительно хотите отметить эти комментарии как прочитанные?')) {
var ts = force ? 0 : ltime + 1;
window.location.href = '?markread=' + ts;
}
return false;
}
var k = 20;
var new_cnt = 0;
function updateTitle() {
document.title = (new_cnt ? '(' + new_cnt + ') ' : '') + $('h1').text();
}
var updateInterval = setInterval(function() {
$.get('/comments.php?ajax=1&upd=1', {
ltime: ftime
}, function(r) {
if (!r) return;
if (r == 'logout') {
clearInterval(updateInterval);
document.location.href = '/login.php';
return;
}
if ($(window).scrollTop() > 100) {
var diff = $(document).height();
var wrapper = $('<div id="wrapper" style="display:none">' + r + '</div>').insertAfter('#upd_anchor');
$(window).scrollTop($(window).scrollTop() + wrapper.height());
wrapper.show();
wrapper.children().eq(0).unwrap();
} else $('#upd_anchor').after(r);
}, 'html');
}, 30000);
</script>
<div id="loader-anchor"></div>
</td>
</tr>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Footer.php'); ?>
</table>
</body>
</html>

View file

@ -0,0 +1,186 @@
<?php
use \App\Services\{Auth, DB, Date};
use \App\Models\{User};
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/LoadHead.php'); ?>
</head>
<body>
<div id="backgr"></div>
<table class="tmain">
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Navbar.php'); ?>
<script>
var kid = <?=DB::query('SELECT id FROM contests WHERE status=2')[0]['id']?>;
var tipTimeout = null;
function hideTip()
{
$('#tip').fadeOut('fast', function()
{
$(this).attr('lock', 0);
$('#img').html('');
});
}
$(document).ready(function()
{
$('.contestBtn').click(function()
{
var pid = $(this).attr('pid');
var savedClass = $(this).attr('class');
$(this).addClass('loading');
$.getJSON('/api/photo/contests/rate', { action: 'vote-contest', kid: kid, pid: pid }, function (data)
{
if (data[0])
{
for (var pid in data[0])
$('.contestBtn[pid="' + pid + '"]').attr('class', 'contestBtn' + (data[0][pid] == 0 ? '' : ' voted'));
}
else $('.contestBtn[pid="' + pid + '"]').attr('class', savedClass);
if (data[1]) alert(data[1]);
})
.fail(function(jx) { alert(jx.responseText); });
return false;
});
$(document).on('mouseenter', '.f', function()
{
var hidden_img = $(this).closest('.p20p').prev('img');
$('#img').html('<a href="/photo/' + hidden_img.attr('pid') + '/" target="_blank"><img src="' + (hidden_img.length ? hidden_img.attr('src') : this.src.replace('_s', '')) + '"></a>');
$('#tip').css('top', $(window).scrollTop() + 20).show();
})
.on('mouseenter', '.f, #tip', function()
{
clearTimeout(tipTimeout);
var lock = Math.min(parseInt($('#tip').attr('lock')) + 1, 2);
$('#tip').attr('lock', lock);
})
.on('mouseleave', '.f, #tip', function()
{
var lock = Math.max(parseInt($('#tip').attr('lock')) - 1, 0);
$('#tip').attr('lock', lock);
tipTimeout = setTimeout(function() { if ($('#tip').attr('lock') == 0) hideTip(); }, 100);
})
.on('mousemove', '.f, #tip', function(e)
{
if (e.pageX > $(document).width() * 0.5) hideTip();
});
});
</script>
<tr>
<td class="main">
<center>
<h1>Фотоконкурс</h1>
<p class="narrow" style="font-size:19px"><b>Голосование</b> &nbsp;&middot;&nbsp; <a href="/voting/results">Победители</a> &nbsp;&middot;&nbsp; <a href="/voting/rating">Рейтинг</a> &nbsp;&middot;&nbsp; <a href="/voting/waiting">Претенденты</a></p>
<div style="margin-top:20px">Чтобы проголосовать, отметьте одну, две или три фотографии, которые Вам понравились</div><br><br>
<?php
if (DB::query('SELECT status FROM contests WHERE status=2')[0]['status'] != 2) {
$contest = DB::query('SELECT * FROM contests WHERE status=1')[0];
echo '<div class="p20">
<h4>Сейчас конкурс не проводится. Пожалуйста, заходите позже.</h4>
</div>
<script>startCountdown(' . $contest['openpretendsdate'] . ');</script>
<h2>Следующий Фотоконкурс будет через:</h2>
<h1 id="countdown"></h1>';
} else { ?>
<div id="tip" lock="0"><span id="img"></span></div>
<?php
$contest = DB::query('SELECT * FROM contests WHERE status=2')[0];
$photos_contest = DB::query('SELECT * FROM photos WHERE on_contest=2 AND contest_id=:id', array(':id'=>$contest['id']));
foreach ($photos_contest as $pc) {
$user = new User($pc['user_id']);
$class = '';
if ((int)DB::query('SELECT photo_id FROM contests_rates WHERE photo_id=:pid AND user_id=:uid AND contest_id=:cid', array(':uid' => Auth::userid(), ':pid' => $pc['id'], ':cid' => $contest['id']))[0]['photo_id'] === (int)$pc['id']) {
$class = ' voted';
}
echo '<img pid="'.$pc['id'].'" src="'.$pc['photourl'].'" style="display:none">
<div class="p20p">
<table>
<tr>
<td><a href="#" pid="'.$pc['id'].'" class="contestBtn'.$class.'"></a></td>
<td class="pb_photo" id="p2068176"><a href="/photo/'.$pc['id'].'/" target="_blank" class="prw"><img class="f" src="/api/photo/compress?url='.$pc['photourl'].'" data-src="/api/photo/compress?url='.$pc['photourl'].'" alt="630 КБ">
<div class="hpshade">
<div class="eye-icon">'.DB::query('SELECT COUNT(*) FROM photos_views WHERE photo_id=:id', array(':id'=>$pc['id']))[0]['COUNT(*)'].'</div>
</div>
</a></td>
<td class="pb_descr">
<p>'.htmlspecialchars($pc['postbody']).'</p>
<p><b class="pw-place">'.htmlspecialchars($pc['place']).'</b></p>
<p class="sm"><b>'.Date::zmdate($pc['posted_at']).'</b><br>Автор: <a href="/author/'.$pc['user_id'].'/">'.$user->i('username').'</a></p>
</td>
</tr>
</table>
</div>';
}
?>
<br>Число проголосовавших: <b><?=DB::query('SELECT COUNT(DISTINCT user_id) AS unique_user_count FROM contests_rates WHERE contest_id=:id', array(':id'=>$contest['id']))[0]['unique_user_count']?></b><br>Число голосов: <b><?=DB::query('SELECT COUNT(*) FROM contests_rates WHERE contest_id=:id', array(':id'=>$contest['id']))[0]['COUNT(*)']?></b><br><br>
</center>
<?php }
?>
<br>
</center>
</td>
</tr>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Footer.php'); ?>
</tr>
</table>
<script>
// Установите дату и время, до которого нужно отсчитывать
const countdownDate = new Date("Mar 1, 2025 00:00:00").getTime();
// Обновляем отсчет каждую секунду
const x = setInterval(function() {
// Получаем текущее время
const now = new Date().getTime();
// Вычисляем разницу между целевой датой и текущим временем
const distance = countdownDate - now;
// Вычисляем дни, часы, минуты и секунды
const days = Math.floor(distance / (1000 * 60 * 60 * 24));
const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
// Отображаем результат в элементе с id "countdown"
document.getElementById("countdown").innerHTML =
days + ":" + hours + ":" + minutes + ":" + seconds;
// Если отсчет завершен, выводим сообщение
if (distance < 0) {
clearInterval(x);
document.getElementById("countdown").innerHTML = "Время истекло!";
}
}, 1000);
</script>
</body>
</html>

View file

View file

@ -0,0 +1,84 @@
<?php
use \App\Services\{Auth, DB, Date};
use \App\Models\{User, Photo};
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/LoadHead.php'); ?>
</head>
<body>
<div id="backgr"></div>
<table class="tmain">
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Navbar.php'); ?>
<?php
?>
<tr>
<td class="main">
<center>
<h1>Победители фотоконкурса</h1>
<p class="narrow" style="font-size:19px"><a href="/voting">Голосование</a> &nbsp;&middot;&nbsp; <b>Победители</b> &nbsp;&middot;&nbsp; <a href="/voting/rating">Рейтинг</a> &nbsp;&middot;&nbsp; <a href="/voting/waiting">Претенденты</a></p>
<?php
$photos = DB::query("SELECT * FROM contests_winners
WHERE place BETWEEN 1 AND 3
ORDER BY contest_id, place;
");
$grouped = [];
foreach ($photos as $row) {
$grouped[$row['contest_id']][] = $row;
}
$final_result = [];
foreach ($grouped as $contest_id => $rows) {
$chunks = array_chunk($rows, 3);
foreach ($chunks as $chunk) {
$final_result[] = $chunk;
}
}
foreach ($final_result as $fc) {
$themeid = DB::query('SELECT themeid FROM contests WHERE id=:id', array(':id'=>$fc[0]['contest_id']))[0]['themeid'];
$theme = DB::query('SELECT title FROM contests_themes WHERE id=:id', array(':id'=>$themeid))[0]['title'];
echo '<p><span class="narrow" style="font-size:21px"><b><a href="?show=table&amp;date=2025-02-04" title="Подробный отчёт о конкурсе">'.date('d.m.Y', $fc[0]['date']).'</a></b></span><br><span class="sm">'.$theme.'</span></p>
<table>
<tr>';
foreach ($fc as $f) {
$photo = new Photo($f['photo_id']);
if ($f['place'] === 1) {
$img = 'vs3';
} else if ($f['place'] === 2) {
$img = 'vs2';
} else if ($f['place'] === 3) {
$img = 'vs1';
}
echo '<a href="/photo/'.$f['photo_id'].'/" class="p20" style="display:table-cell; text-align:center; vertical-align:bottom; padding:20px 20px 10px; font-size:17px"><img src="'.$photo->i('photourl').'" class="f" style="margin-bottom:7px"><br><img src="/static/img/'.$img.'.png" style="position:relative; top:-2px"> &nbsp;</a>';
}
echo '
</tr>
</table><br>';
}
?>
<p class="narrow" style="font-size:19px"><a href="/voting">Голосование</a> &nbsp;&middot;&nbsp; <b>Победители</b> &nbsp;&middot;&nbsp; <a href="/voting/rating">Рейтинг</a> &nbsp;&middot;&nbsp; <a href="/voting/waiting">Претенденты</a></p>
</center>
</td>
</tr>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Footer.php'); ?>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,190 @@
<?php
use \App\Services\{Auth, DB, Date};
use \App\Models\{Vehicle, User};
function convertUnixToRussianDateTime($unixTime)
{
// Создаем объект DateTime из Unix-времени
$dateTime = new DateTime("@$unixTime");
// Устанавливаем временную зону (можно изменить на нужную)
$dateTime->setTimezone(new DateTimeZone('Europe/Moscow'));
// Форматируем дату и время с использованием IntlDateFormatter
$formatter = new IntlDateFormatter(
'ru_RU',
IntlDateFormatter::LONG,
IntlDateFormatter::NONE,
'Europe/Moscow',
IntlDateFormatter::GREGORIAN,
'd MMMM yyyy года в H:mm'
);
return $formatter->format($dateTime);
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/LoadHead.php'); ?>
</head>
<body>
<div id="backgr"></div>
<table class="tmain">
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Navbar.php'); ?>
<tr>
<td class="main">
<h1>Принять участие в Фотоконкурсе</h1>
<script src="/js/jquery-ui.js?1633005526"></script>
<script src="/js/selector.js?1730197663"></script>
<form id="sendForm" method="post" id="mform">
<h4>В каком Фотоконкурсе вы хотите принять участие?</h4>
<div class="p20w">
<table>
<tbody>
<tr>
<th></th>
<th>Тематика</th>
<th>Старт набора претендентов</th>
<th>Закрытие набора претендентов</th>
<th>Начало проведения</th>
<th>Итоги и победители</th>
</tr>
<?php
$entities = DB::query('SELECT * FROM contests WHERE closepretendsdate>=:id', array(':id' => time()));
foreach ($entities as $e) {
$theme = DB::query('SELECT * FROM contests_themes WHERE id=:id', array(':id' => $e['themeid']))[0];
echo '<tr>
<td class="ds"><input type="radio" name="cid" id="n' . $e['id'] . '" value="' . $e['id'] . '" onclick="fillFields(' . $e['id'] . ')"></td>
<td class="n">' . $theme['title'] . '</td>
<td class="ds">' . convertUnixToRussianDateTime($e['openpretendsdate']) . '</td>
<td class="ds">' . convertUnixToRussianDateTime($e['closepretendsdate']) . '</td>
<td class="ds">' . convertUnixToRussianDateTime($e['opendate']) . '</td>
<td class="ds">' . convertUnixToRussianDateTime($e['closedate']) . '</td>
</tr>';
}
?>
</tbody>
</table>
</div>
<br clear="all"><br>
<div class="p20" style="padding-left:5px; margin-bottom:15px">
<table class="nospaces" width="100%">
<tbody>
<?php
$vehicle = DB::query('SELECT * FROM entities WHERE id=:id', array(':id' => $_GET['type']))[0];
$data = json_decode($vehicle['sampledata'], true);
$count = 1;
foreach ($data as $d) {
if ($d['important'] === "1") {
$imp = 'required';
}
echo '
<tr>
<td class="lcol">' . $d['name'] . '</td>
<td style="padding-bottom:15px"><input type="text" name="modelinput_' . $count . '" id="num" style="width:80px" maxlength="21" value=""></td>
</tr>';
$count++;
}
?>
<tr>
<td style="width: 10%"></td>
</tr>
<tr>
<tr>
<td class="lcol">Фотография, которую вы хотите отправить на Фотоконкурс</td>
<td style="padding-bottom:15px">
<select id="photoId" name="photo_id">
<option value="'.$p['id'].'" disabled selected>Выберите фотографию</option>
<?php
$photos = DB::query('SELECT * FROM photos WHERE user_id=:uid AND on_contest=0', array(':uid' => Auth::userid()));
foreach ($photos as $p) {
$content = json_decode($p['content'], true);
if (($content['video'] === null || $content['type'] === 'image') && $p['moderated'] === 1) {
echo '<option photourl="/api/photo/compress?url=' . $p['photourl'] . '" value="' . $p['id'] . '">[ID: ' . $p['id'] . '] ' . $p['place'] . '</option>';
}
}
?>
</select>
</td>
</tr>
<td>
<div id="result"></div>
</td>
<td>
<br>
<input type="submit" value="&nbsp; &nbsp; &nbsp; Отправить &nbsp; &nbsp; &nbsp;">
</td>
</tr>
</tbody>
</table>
</div>
</form>
</td>
</tr>
<script>
$('#sendForm').submit(function(e) {
e.preventDefault();
$.ajax({
type: "POST",
url: '/api/photo/contests/sendpretend',
data: $(this).serialize(),
success: function(response) {
var jsonData = JSON.parse(response);
if (jsonData.errorcode === 0) {
alert('Фотография успешно отправлена на претенденты на Фотоконкурс');
} else {
alert('Пожалуйста, выберите Фотоконкурс на который вы хотите отправить фотографию!');
}
}
});
});
document.getElementById('photoId').addEventListener('change', function() {
const selectedOption = this.options[this.selectedIndex];
const photoUrl = selectedOption.getAttribute('photourl');
if (photoUrl) {
const imgElement = document.createElement('img');
imgElement.src = photoUrl;
imgElement.alt = 'Изображение';
imgElement.style.maxWidth = '500px';
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '';
resultDiv.appendChild(imgElement);
}
});
</script>
<tr>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Footer.php'); ?>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,140 @@
<?php
use \App\Services\{Auth, DB, Date};
use \App\Models\{User, VoteContest, Vote};
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/LoadHead.php'); ?>
</head>
<body>
<div id="backgr"></div>
<table class="tmain">
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Navbar.php'); ?>
<tr>
<td class="main">
<center>
<h1>Претенденты на участие в конкурсе</h1>
<script>
var self_p = false;
var pid = 0;
</script>
<p class="narrow" style="font-size:19px"><a href="/voting">Голосование</a> &nbsp;·&nbsp; <a href="/voting/results">Победители</a> &nbsp;·&nbsp; <a href="/voting/rating">Рейтинг</a> &nbsp;·&nbsp; <b>Претенденты</b></p>
<p style="margin:20px">На этой странице собраны фотографии, предложенные для участия в конкурсе их авторами либо пользователями сайта, для того, чтобы Вы проголосовали за их участие или против. Снимки отсортированы по времени публикации.<br><br>Пожалуйста, не стесняйтесь нажимать синюю кнопку.</p>
<?php
if (DB::query('SELECT status FROM contests WHERE status=1')[0]['status'] === 1) {
$contest = DB::query('SELECT * FROM contests WHERE status=1')[0];
$photos_contest = DB::query('SELECT p.*, COUNT(prc.photo_id) AS rates_count
FROM photos p
LEFT JOIN photos_rates_contest prc ON p.id = prc.photo_id
WHERE p.on_contest = 1 AND p.contest_id = :id
GROUP BY p.id
ORDER BY rates_count DESC;
', array(':id'=>$contest['id']));
foreach ($photos_contest as &$photo) {
$photo['votes'] = VoteContest::count($pc['id'], $contest['id']);
}
usort($photos_contest, function ($a, $b) {
return $b['votes'] <=> $a['votes'];
});
foreach ($photos_contest as $pc) {
$user = new User($pc['user_id']);
if (VoteContest::photo(Auth::userid(), $pc['id'], $contest['id']) === 1) {
$classp = 'voted';
} else if (VoteContest::photo(Auth::userid(), $pc['id'], $contest['id']) === 0) {
$classm = 'voted';
}
echo '<div class="p20p">
<table>
<tbody>
<tr>
<td class="pb_pre vote" style="padding-left:15px; padding-right:10px; display:table-cell" cid="'.$contest['id'].'" pid="'.$pc['id'].'">
<a href="#" vote="1" class="konk_btn '.$classp.'"><span>Красиво, на&nbsp;конкурс!</span></a>
<table style="margin:5px 0 7px; width:100px">
<tbody>
<tr>
<td style="width:20px"><img class="loader" pid="2072294" src="/img/loader.gif"></td>
<td align="center" style="padding:2px"><b class="s_rating" pid="'.$pc['id'].'">'.VoteContest::count($pc['id'], $contest['id']).'</b></td>
<td style="width:20px; display:table-cell" class="star" pid="2072294"><img src="/img/star_people.png" alt=""></td>
</tr>
</tbody>
</table>
<a href="#" vote="0" class="konk_btn '.$classm.'"><span>Неконкурсное фото</span></a>
</td>
<td class="pb_photo" id="p2072294"><a href="/photo/'.$pc['id'].'" target="_blank" class="prw"><img class="f" src="/api/photo/compress?url='.$pc['photourl'].'" alt="597 КБ" style="display: inline;">
<div class="hpshade">
<div class="eye-icon">'.DB::query('SELECT COUNT(*) FROM photos_views WHERE photo_id=:id', array(':id'=>$pc['id']))[0]['COUNT(*)'].'</div>
</div>
</a></td>
<td class="pb_descr">
<p>'.htmlspecialchars($pc['postbody']).'</p>
<p><b class="pw-place">'.htmlspecialchars($pc['place']).'</b></p>
<p class="sm"><b>'.Date::zmdate($pc['posted_at']).'</b><br>Автор: <a href="/author/'.$pc['user_id'].'/">'.$user->i('username').'</a></p>
</td>
</tr>
</tbody>
</table>
</div>';
}
} else {
echo '<h2><b>Следующего конкурса нет. Пожалуйста, заходите сюда позже.</b></h2>';
}
?>
<br>
<p class="narrow" style="font-size:19px"><a href="/voting">Голосование</a> &nbsp;·&nbsp; <a href="/voting/results">Победители</a> &nbsp;·&nbsp; <a href="/voting/rating">Рейтинг</a> &nbsp;·&nbsp; <b>Претенденты</b></p>
</center>
</td>
</tr>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Footer.php'); ?>
</tr>
</table>
<script>
// Установите дату и время, до которого нужно отсчитывать
const countdownDate = new Date("Mar 1, 2025 00:00:00").getTime();
// Обновляем отсчет каждую секунду
const x = setInterval(function() {
// Получаем текущее время
const now = new Date().getTime();
// Вычисляем разницу между целевой датой и текущим временем
const distance = countdownDate - now;
// Вычисляем дни, часы, минуты и секунды
const days = Math.floor(distance / (1000 * 60 * 60 * 24));
const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
// Отображаем результат в элементе с id "countdown"
document.getElementById("countdown").innerHTML =
days + ":" + hours + ":" + minutes + ":" + seconds;
// Если отсчет завершен, выводим сообщение
if (distance < 0) {
clearInterval(x);
document.getElementById("countdown").innerHTML = "Время истекло!";
}
}, 1000);
</script>
</body>
</html>

View file

@ -1,46 +0,0 @@
<?php
header("HTTP/1.1 403 Forbidden");
?>
<html>
<head>
<style>body {
font-family: sans-serif;
position: relative;
height: 100vh;
overflow: hidden;
}
#dbErrorBody {
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%);
width: 400px;
text-align: center;
}
#dbErrorBody h1 {
margin-top: 5px;
margin-bottom: 2px;
}
#dbErrorBody span {
color: grey;
}
#dbErrorBody img {
max-width: 256px;
}
</style>
<title>Resource Busy</title>
</head>
<body>
<div id="dbErrorBody">
<img src="/static/img/busy.png" alt="Error">
<h1>Вы не участвуете в программе тестирования Birux Streams</h1>
<span>К сожалению, мы уже набрали достаточное количество участников. Следующая волна заявок будет скоро следите в Telegram-канале
<a href="https://t.me/biruxch">Birux</a>
</span>
</div>
</body>
</html>

View file

@ -1,44 +0,0 @@
<?php
header("HTTP/1.1 403 Forbidden");
?>
<html>
<head>
<style>body {
font-family: sans-serif;
position: relative;
height: 100vh;
overflow: hidden;
}
#dbErrorBody {
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%);
width: 400px;
text-align: center;
}
#dbErrorBody h1 {
margin-top: 5px;
margin-bottom: 2px;
}
#dbErrorBody span {
color: grey;
}
#dbErrorBody img {
max-width: 256px;
}
</style>
<title>Resource Busy</title>
</head>
<body>
<div id="dbErrorBody">
<img src="/static/img/busy.png" alt="Error">
<h1>Доступ запрещён!</h1>
<span>Такие дела. Эфир не принадлежит вам.</span>
</div>
</body>
</html>

0
views/pages/Live.php Normal file
View file

View file

@ -78,7 +78,11 @@ LIMIT 10;');
<img width="250" src="/api/photo/compress?url=' . $p['photourl'] . '">
<div class="hpshade">
<div class="eye-icon">+' . $pd['view_count'] . '</div>
</div>
</div>';
if ((int)$p['priority'] === 1) {
echo '<div class="temp" style="background-image:url(/static/img/cond.png)"></div>';
}
echo '
</a>';
}
}
@ -118,6 +122,85 @@ LIMIT 10;');
}
?>
</div>
<style>
#contestNotify {
background-size: 550px 211.2px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 550 211.2" width="550" height="211.2" style="opacity: 0.3; filter: grayscale(0);"><text x="0em" y="1em" font-size="88" transform="rotate(17 55 52.8)">🎁</text><text x="1.25em" y="2em" font-size="88" transform="rotate(17 165 140.8)">🎈</text><text x="2.5em" y="1em" font-size="88" transform="rotate(17 275 52.8)">🎀</text><text x="3.75em" y="2em" font-size="88" transform="rotate(17 385 140.8)">🎊</text><text x="5em" y="1em" font-size="88" transform="rotate(17 495 52.8)">🎉</text></svg>');
}
</style>
<?php
if (DB::query('SELECT status FROM contests WHERE status=2')[0]['status'] === 2) {
$contest = DB::query('SELECT * FROM contests WHERE status=2')[0];
$theme = DB::query('SELECT * FROM contests_themes WHERE id=:id', array(':id' => $contest['themeid']))[0];
echo ' <div id="contestNotify" style="float:left; border:solid 1px #171022; padding:6px 10px 7px; margin-bottom:13px; background-color:#E5D6FF"><h4>Фотоконкурс!</h4>
<span id="timett">Закончится через:</span> <b id="countdown"></b><br>
Тематика: <b>' . $theme['title'] . '</b><br>
<b style="color: #412378;">Голосуйте за лучшие фотографии, которые должны стать победителями сегодняшнего конкурса!</b><br><br>
<div id="contestBtns"><a href="/voting" style="background-color: #37009D; color: #fff;" type="button">Голосовать!</a></div>
<script>startCountdown(' . $contest['closedate'] . ');</script>';
} else if (DB::query('SELECT status FROM contests WHERE status=1')[0]['status'] === 1) {
$contest = DB::query('SELECT * FROM contests WHERE status=1')[0];
$theme = DB::query('SELECT * FROM contests_themes WHERE id=:id', array(':id' => $contest['themeid']))[0];
echo ' <div id="contestNotify" style="float:left; border:solid 1px #171022; padding:6px 10px 7px; margin-bottom:13px; background-color:#E5D6FF"><h4>Фотоконкурс!</h4>
<span id="timett">Начнётся через:</span> <b id="countdown"></b><br>
Тематика: <b>' . $theme['title'] . '</b><br>
<b id="textContest" style="color: #412378;">Лучшие фотографии по мнению сообщества ' . NGALLERY['root']['title'] . ' будут отмечены</b><br><br>
<div id="contestBtns"><a href="/voting/sendpretend" style="background-color: #37009D; color: #fff;" type="button">Участвовать!</a> <a href="/voting/waiting" style="background-color: #37009D; color: #fff;" type="button">Голосовать за претендентов</a></div>
<script>
$(document).ready(function () {
let unixThreshold = '.$contest['closepretendsdate'].'; // Задайте нужное значение UNIX
let checkInterval = 1000; // Интервал проверки в миллисекундах (1 секунда)
let isRequestSent = false;
function checkUnixTime() {
let currentUnixTime = Math.floor(Date.now() / 1000);
if (currentUnixTime > unixThreshold) {
$("#countdown").text("Ожидаем ответ от сервера...");
$.ajax({
url: "/api/contests/getinfo", // Укажите свой URL
method: "GET",
success: function (response) {
let data = typeof response === "string" ? JSON.parse(response) : response;
if (data.statuses.pretends === "closed" && data.statuses.public === "opened") {
clearInterval(pingInterval); // Останавливаем старый пинг
$("#textContest").text("Голосуйте за лучшие фотографии, которые должны стать победителями сегодняшнего конкурса!");
$("#timett").text("Закончится через:");
$("#contestBtns").html(`<a href="/voting" style="background-color: #37009D; color: #fff;" type="button">Голосовать!</a>`)
unixThreshold = data.contest.closedate;
startCountdown(data.contest.closedate);
pingInterval = setInterval(checkUnixTime, checkInterval);
}
},
error: function (xhr, status, error) {
console.error("Ошибка запроса:", error);
}
});
} else {
console.log(currentUnixTime);
}
}
// Запускаем периодический пинг
let pingInterval = setInterval(checkUnixTime, checkInterval);
});
</script>';
}
?>
</div>
</div>
@ -125,42 +208,32 @@ LIMIT 10;');
<h4 style="clear:both"><a href="/update">Недавно добавленные фотографии</a></h4>
<div id="recent-photos" class="ix-photos ix-photos-multiline" lastpid="1970527" firstpid="1970550">
<?php
$photos = DB::query('SELECT * FROM photos WHERE moderated=1 ORDER BY id DESC LIMIT 30');
foreach ($photos as $p) {
if ($p['posted_at'] === 943909200 || Date::zmdate($p['posted_at']) === '30 ноября 1999 в 00:00') {
$date = 'дата не указана';
} else {
$date = Date::zmdate($p['posted_at']);
}
$bck = 'background-image:url("/api/photo/compress?url=' . $p['photourl'] . '")';
echo ' <div class="prw-grid-item">
<div class="prw-wrapper"><span style="word-spacing:-1px"><b>' . htmlspecialchars($p['place']) . '</b></span>
<div>' . $date . '</div>
</div>
'; ?>
<a href="/photo/<?= $p['id'] ?>" target="_blank" class="prw-animate" style='background-image:url("/api/photo/compress?url=<?= $p['photourl'] ?>")'></a>
</div>
<?php }
?>
</div>
<h4>Сейчас на сайте (<?= DB::query('SELECT COUNT(*) FROM users WHERE online>=:time-300 ORDER BY online DESC', array(':time' => time()))[0]['COUNT(*)'] ?>)</h4>
<div>
<?php
$online = DB::query('SELECT * FROM users WHERE online>=:time-300 ORDER BY online DESC', array(':time' => time()));
foreach ($online as $o) {
echo '<a href="/author/' . $o['id'] . '/">' . htmlspecialchars($o['username']) . '</a>, ';
}
?>
$photos = DB::query('SELECT * FROM photos WHERE moderated=1 ORDER BY id DESC LIMIT 30');
</div>
$first_id = $photos[0]['id'];
$last_id = end($photos)['id'];
?>
<div id="recent-photos" class="ix-photos ix-photos-multiline" lastpid="<?= $first_id + 1 ?>" firstpid="<?= $last_id ?>">
</div>
</div>
<div style="text-align:center; margin:10px 0"><input type="button" name="button" id="loadmore" class="" value="Загрузить ещё"></div>
<h4>Сейчас на сайте (<?= DB::query('SELECT COUNT(*) FROM users WHERE online>=:time-300 ORDER BY online DESC', array(':time' => time()))[0]['COUNT(*)'] ?>)</h4>
<div>
<?php
$online = DB::query('SELECT * FROM users WHERE online>=:time-300 ORDER BY online DESC', array(':time' => time()));
foreach ($online as $o) {
echo '<a href="/author/' . $o['id'] . '/">' . htmlspecialchars($o['username']) . '</a>, ';
}
?>
</div>
</td>
<td style="padding-left:20px; width:254px; vertical-align:top">
@ -169,8 +242,8 @@ LIMIT 10;');
<?php
$news = DB::query('SELECT * FROM news ORDER BY id DESC LIMIT 10');
foreach ($news as $n) {
echo '<div class="ix-news-item"><b>'.Date::zmdate($n['time']).'</b>
<div class="break-links" style="padding-top:3px">'.$n['body'].'</div>
echo '<div class="ix-news-item"><b>' . Date::zmdate($n['time']) . '</b>
<div class="break-links" style="padding-top:3px">' . $n['body'] . '</div>
</div>';
}
?>

0
views/pages/Page.php Normal file
View file

View file

@ -125,6 +125,25 @@ if ($photo->i('id') !== null) {
<img onerror="errimg(); this.onerror = null;" id="ph" src="<?= $photo->i('photourl') ?>" alt="" title="Фотография">
<?php
}
if ($photo->i('on_contest') === 2) { ?>
<a class="underphoto" href="/voting"><img style="margin-top:-4px" src="/static/img/star_people.png"> &nbsp;Фотография участвует в голосовании</a>
<?php }
foreach ($photo->content('contests') as $c) {
if ($c['place'] === 1) {
$img = '3';
}
if ($c['place'] === 2) {
$img = '2';
}
if ($c['place'] === 3) {
$img = '1';
}
echo '<a class="underphoto" style="font-weight:bold" href="/pk.php?pid=2068816&amp;type=d"><img style="margin-top:-4px" src="/static/img/vs'.$img.'.png"> &nbsp;'.$c['place'].'-е место на фотоконкурсе</a>';
}
if ($photo->i('priority') === 1) { ?>
<div class="underphoto s17" style="cursor:help" title="Фотография не удовлетворяет действующим на момент публикации критериям качества снимков."><i style="position:relative; top:1px" class="fas fa-info-circle"></i>&ensp;<b class="dot">Условная публикация</b></div>
<?php } else if ($photo->i('priority') === 2) { ?>
@ -172,8 +191,8 @@ if ($photo->i('id') !== null) {
<table class="pwrite">
<tr>
<?php
if ($photo->i('postbody') != null) { ?>
<td class="nw" valign="top" align="right"><b><?= htmlspecialchars($photo->i('postbody')) ?></b></td>
if ($photo->i('place') != null) { ?>
<td class="nw" valign="top" align="right"><b><?= htmlspecialchars($photo->i('place')) ?></b></td>
<?php } ?>
</tr>
@ -197,7 +216,7 @@ if ($photo->i('id') !== null) {
<?php } ?>
<div>
<?php
if ($photo->content('comment') != null) { ?>
if ($photo->content('comment') != null) { ?>
<div style="padding-top:8px"><?= htmlspecialchars($photo->content('comment')) ?></div>
<?php } ?>
</div><br>
@ -222,6 +241,8 @@ if ($photo->i('id') !== null) {
<a href="/photoext?id=<?= $id ?>">Подробная информация</a>
</div>
</div>
<?php
if (Auth::userid() > 0) { ?>
<div class="p0" id="pp-item-tools">
<h4 class="pp-item-header">Инструменты</h4>
<div class="pp-item-body" style="margin:7px 5px">
@ -239,6 +260,7 @@ if ($photo->i('id') !== null) {
</div>
</div>
</div>
<?php } ?>
<?php if ($photo->i('moderated') === 1 && $photo->content('rating') != 'disabled') { ?>
<div class="p20a" id="pp-item-vote">
<h4 class="pp-item-header">Оценка</h4>
@ -247,7 +269,7 @@ if ($photo->i('id') !== null) {
<div class="rtext">Рейтинг: <b id="rating"><?= Vote::count($id) ?></b></div>
<div class="star" pid="1361063"></div>
<?php
if (Auth::userid() > 0) { ?>
if (Auth::userid() > 0 && (NGALLERY['root']['registration']['emailverify'] != true || $user->i('status') != 3)) { ?>
<div class="vote" pid="<?= $id ?>">
<a href="#" vote="1" class="vote_btn <?php if (Vote::photo(Auth::userid(), $id) === 1) {
echo 'voted';
@ -255,14 +277,24 @@ if ($photo->i('id') !== null) {
<a href="#" vote="0" class="vote_btn <?php if (Vote::photo(Auth::userid(), $id) === 0) {
echo 'voted';
} ?>"><span>Мне не&nbsp;нравится</span></a>
<!--a class="konk_btn" vote="1" href="#"><span>Красиво, на&nbsp;конкурс!</span></!--a>
<a-- href="#" vote="0" class="konk_btn"><span>Неконкурсное фото</span></a-->
<?php
if (($photo->content('video') === null && $photo->i('user_id') != Auth::userid()) || $photo->i('on_contest') != 2) { ?>
<a class="konk_btn <?php if (Vote::photoContest(Auth::userid(), $id) === 1) {
echo 'voted';
} ?>" vote="1" href="#"><span>Красиво, на&nbsp;конкурс!</span></a>
<a href="#" vote="0" class="konk_btn <?php if (Vote::photoContest(Auth::userid(), $id) === 0) {
echo 'voted';
} ?>"><span>Неконкурсное фото</span></a>
<?php } else if ($photo->i('user_id') === Auth::userid() && $photo->i('on_contest') != 2) { ?>
<a href="#" vote="1" class="konk_btn"><span>Выставить на&nbsp;конкурс</span></a><a href="#" vote="0" class="konk_btn"><span>Не участвовать в&nbsp;конкурсе</span></a></div>
<?php } ?>
</div>
<?php } ?>
<div id="votes" class="votes">
<table class="vblock pro">
<?php
$votespos = DB::query('SELECT * FROM photos_rates WHERE photo_id=:pid AND type=1 ORDER BY id DESC', array(':pid' => $id));
$votespos = DB::query('SELECT * FROM photos_rates WHERE photo_id=:pid AND type=1 AND contest=0 ORDER BY id DESC', array(':pid' => $id));
foreach ($votespos as $ps) {
$uservote = new User($ps['user_id']);
echo ' <tr>
@ -275,7 +307,7 @@ if ($photo->i('id') !== null) {
</table>
<table class="vblock coN">
<?php
$votespos = DB::query('SELECT * FROM photos_rates WHERE photo_id=:pid AND type=0 ORDER BY id DESC', array(':pid' => $id));
$votespos = DB::query('SELECT * FROM photos_rates WHERE photo_id=:pid AND type=0 AND contest=0 ORDER BY id DESC', array(':pid' => $id));
foreach ($votespos as $ps) {
$uservote = new User($ps['user_id']);
echo ' <tr>
@ -330,8 +362,9 @@ if ($photo->i('id') !== null) {
</colgroup>
<tbody>
<?php
$entity = DB::query('SELECT * FROM entities_data WHERE id=:id', array(':id'=>$photo->i('entitydata_id')))[0];
$vehiclevariables = json_decode($vehicle->i('sampledata'), true);
$vehicledatavariables = json_decode($vehicle->i('content'), true);
$vehicledatavariables = json_decode($entity['content'], true);
$num = 1;
foreach ($vehiclevariables as $vb) {
echo ' <tr class="s11 h21">
@ -583,7 +616,7 @@ if ($photo->i('id') !== null) {
<?php
if ($photo->i('moderated') === 1) {
$comments = DB::query('SELECT * FROM photos_comments WHERE photo_id=:pid', array(':pid' => $id));
$comments = DB::query('SELECT * FROM photos_comments WHERE photo_id=:pid ORDER BY CASE WHEN id = :pinnedid THEN 0 ELSE 1 END, id ASC', array(':pid' => $id, ':pinnedid' => $photo->i('pinnedcomment_id')));
$commcount = 0;
foreach ($comments as $c) {
if (json_decode($c['content'], true)['deleted'] != 'true') {
@ -616,21 +649,100 @@ if ($photo->i('id') !== null) {
</div>
<div class="cmt-write s1">
<h4 class="pp-item-header">Ваш комментарий</h4>
<div style="padding:0 11px 11px">
<form action="/comment.php" method="post" id="f1">
<?php
if (Auth::userid() > 0) {
if (NGALLERY['root']['registration']['emailverify'] != true || $user->i('status') != 3) { ?>
<form action="/comment.php" method="post" id="f1" enctype="multipart/form-data">
<input type="hidden" name="sid" value="hgdl6old9r9qodmvkn1r4t7d6h">
<input type="hidden" name="last_comment_rand" value="893329610">
<input type="hidden" name="id" id="id" value="<?= $id ?>">
<input type="hidden" name="subj" id="subj" value="p">
<textarea name="wtext" id="wtext"></textarea><br>
<div id="fileList" class="mt-3"></div>
<p id="statusSend" style="display: none;">Ошибка</p>
<div class="cmt-submit"><input type="submit" value="Добавить комментарий" id="sbmt">&ensp;&emsp;Ctrl + Enter
<div class="cmt-submit"><input type="submit" value="Добавить комментарий" id="sbmt"><button style="display: inline;" type="button" id="attachFile"><i class='bx bx-paperclip bx-rotate-90' ></i></button>
</div>
</form>
<?php } else {
echo 'Комментарии могут оставлять только пользователи с подтверждённой почтой.';
}
} else {
echo 'Комментарии могут оставлять только зарегистрированные пользователи.';
}
?>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById("f1");
if (!form) {
console.error("Форма #f1 не найдена!");
return;
}
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.name = "filebody"; // Устанавливаем имя filebody
fileInput.style.display = "none";
form.appendChild(fileInput); // Добавляем input внутрь формы
const button = document.getElementById("attachFile");
const fileList = document.getElementById("fileList");
button.addEventListener("click", function () {
fileInput.click();
});
fileInput.addEventListener("change", function () {
const file = fileInput.files[0];
if (!file) return;
const maxSize = 100 * 1024 * 1024; // 100 MB
const forbiddenExtensions = [".html", ".php", ".htm", ".exe", ".com", ".cmd", ".bash", ".sh"];
const fileName = file.name.toLowerCase();
const fileSize = file.size;
if (fileSize > maxSize) {
alert("Файл превышает 100 МБ.");
return;
}
if (forbiddenExtensions.some(ext => fileName.endsWith(ext))) {
alert("Расширение не поддерживается.");
return;
}
const fileItem = document.createElement("div");
fileItem.setAttribute("style", "border:solid 1px #bbb; width:max-content; font-size: 12px; padding:3px 10px 3px; margin-bottom:13px; background-color:#e2e2e2");
fileItem.textContent = file.name;
const removeBtn = document.createElement("a");
removeBtn.classList.add("compl");
removeBtn.setAttribute("style", "display: inline-block; margin-left: 5px; color:#292929; cursor: pointer;");
removeBtn.textContent = "";
removeBtn.addEventListener("click", function () {
fileItem.remove();
fileInput.value = "";
});
fileItem.appendChild(removeBtn);
fileList.appendChild(fileItem);
});
});
</script>
<?php } else { ?>
<div class="p0" id="pp-item-comments">
@ -663,67 +775,64 @@ if ($photo->i('id') !== null) {
</tbody>
</table>
<script>
$(document).ready(function() {
$('#f1').submit(function(e) {
e.preventDefault();
$.ajax({
type: "POST",
url: '/api/photo/comment',
data: $(this).serialize(),
success: function(response) {
var jsonData = JSON.parse(response);
if (jsonData.errorcode == "1") {
$('#statusSend').css({
display: 'block',
color: 'red'
});
$('#statusSend').text('Комментарий некорректен');
//Notify.noty('danger', 'Комментарий неккоректен');
//$("#result").html("<div class='alert alert-dangernew container mt-5' role='alert'>Неправильная почта или пароль!</div>");
} else if (jsonData.errorcode == "2") {
$('#statusSend').css({
display: 'block',
color: 'yellow'
});
$('#statusSend').text('Пожалуйста, подождите...');
//Notify.noty('warning', 'Пожалуйста, подождите...');
setTimeout(function() {
window.location.replace(jsonData.twofaurl);
}, 1000);
} else if (jsonData.errorcode == "0") {
$('#wtext').val('');
$('#statusSend').css({
display: 'block',
color: 'green'
});
$('#statusSend').text('Комментарий отправлен!');
//Notify.noty('success', 'Комментарий отправлен!');
//$("#result").html("<div class='alert alert-successnew container mt-5' role='alert'>Успешный вход!</div>");
$.ajax({
$(document).ready(function() {
$('#f1').submit(function(e) {
e.preventDefault();
var formData = new FormData(this); // Собираем данные из формы, включая filebody
type: "POST",
url: "/api/photo/getcomments/<?= $id ?>",
processData: false,
async: true,
success: function(r) {
$('#posts').html(r)
$.ajax({
type: "POST",
url: "/api/photo/comment",
data: formData,
processData: false, // Не обрабатывать данные (важно для файлов)
contentType: false, // Не устанавливать заголовок Content-Type (браузер сделает сам)
success: function(response) {
var jsonData = JSON.parse(response);
if (jsonData.errorcode == "1") {
$('#statusSend').css({
display: 'block',
color: 'red'
}).text('Комментарий некорректен');
} else if (jsonData.errorcode == "2") {
$('#statusSend').css({
display: 'block',
color: 'yellow'
}).text('Пожалуйста, подождите...');
setTimeout(function() {
window.location.replace(jsonData.twofaurl);
}, 1000);
} else if (jsonData.errorcode == "0") {
$('#wtext').val('');
$('#statusSend').css({
display: 'block',
color: 'green'
}).text('Комментарий отправлен!');
},
error: function(r) {
console.log(r)
}
});
} else {
Notify.noty('danger', 'Неизвестная ошибка');
$.ajax({
type: "POST",
url: "/api/photo/getcomments/<?= $id ?>",
processData: false,
async: true,
success: function(r) {
$('#posts').html(r);
},
error: function(r) {
console.log(r);
}
}
});
});
});
} else {
alert('Неизвестная ошибка');
}
},
error: function(err) {
console.error("Ошибка при отправке формы", err);
}
});
});
});
function errimg() {
const content = `<center>

View file

@ -15,6 +15,8 @@ $photo = new \App\Models\Photo($_GET['id']);
<div id="backgr"></div>
<table class="tmain">
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Navbar.php'); ?>
<?php
if ($photo->i('moderated') === 1) { ?>
<tr>
<td class="main">
<center>
@ -61,7 +63,19 @@ $photo = new \App\Models\Photo($_GET['id']);
</center>
</td>
</tr>
<?php } else { ?>
<tr>
<td class="main">
<center>
<h1>Изображение не найдено</h1>
<div class="p20w" style="margin-bottom:20px; padding:10px 30px">
<img src="/static/img/pnp.jpg" alt="Пусто" width="400" height="205" border="0">
<p>Изображения с таким номером нет на сайте.<br />Может быть, его здесь никогда и не было.<br />Если Вы уверены, что что-то здесь всё-таки было, значит, администратор по каким-то причинам это удалил.</p>
</div>
</center>
</td>
</tr>
<?php } ?>
<tr>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Footer.php'); ?>
</tr>

View file

@ -225,7 +225,7 @@ $birthdate = json_decode($userprofile->i('content'), true)['aboutbirthday']['val
<div class="p20" style="margin-top: 8px; background-color: white !important;">
<h4>О себе</h4>
<?=
$about
htmlspecialchars($about)
?>
</div>
<?php } else if ($usercttc === True) {

View file

@ -69,6 +69,9 @@ use \App\Models\{User, Photo};
if ($p['moderated'] === 2) {
echo '<p class="sm"><b>Причина отклонения: '.$photo->declineReason((int)$photo->content('declineReason')).'</b></p>';
}
if ($photo->content('declineComment') != null) {
echo '<p class="sm"><b>Комментарий: </b> '.$photo->content('declineComment').'</p>';
}
echo '
</td>

View file

@ -43,6 +43,9 @@ $user = new User(Auth::userid());
var pub_pid = 0;
</script>
<td class="main">
<?php if (NGALLERY['root']['registration']['emailverify'] != false || $user->i('status') === 3) {
die('Чтобы публиковать Фотографии и Видео, нужно подтвердить почту.');
} ?>
<h1>Предложить медиа на публикацию</h1>
<p>Ваш текущий индекс загрузки: <b><?= $user->i('uploadindex') ?></b></p>

View file

@ -90,7 +90,7 @@ if (Auth::userid() > 0) {
</div>
<div style="color:#e00" id="err_email"></div>
<div class="styled-input">
<input name="password" id="password" type="text" required="">
<input name="password" id="password" type="password" required="">
<label for="password">Ваш пароль</label>
</div>
<div style="color:#e00" id="err_password"></div>
@ -98,6 +98,7 @@ if (Auth::userid() > 0) {
<input type="button" id="regbtn" class="mf-button-wide" style="margin-top:15px" value="Зарегистрироваться">
<p>Регистрируясь на сервере <?= NGALLERY['root']['title'] ?>, вы <a href="/rules">принимаете его правила.</a></p>
<p><b><a href="/tour">Вы можете пройти экскурсию по сайту.</a></b></p>
</form><br><br>
<br>
@ -150,7 +151,7 @@ if (Auth::userid() > 0) {
</center>
<?php } else { ?>
<center>
<h1>К сожалению, регистрация на сервере <?= NGALLERY['root']['title'] ?> запрещена.</h1>
<h1>К сожалению, регистрация на сервере <?= NGALLERY['root']['title'] ?> закрыта.</h1>
</center?
<?php } ?>
</td>

View file

@ -1,58 +0,0 @@
<?php
use App\Services\{Router, Auth, DB, Date};
?>
<html lang="ru">
<head>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/LoadHead.php'); ?>
</head>
<body>
<div id="backgr"></div>
<table class="tmain">
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Navbar.php'); ?>
<tr>
<td class="main">
<h1>Результаты поиска</h1>
<div>Найдено изображений: <b><?=DB::query('SELECT COUNT(*) FROM photos WHERE user_id=:uid AND moderated=1 ORDER BY id DESC', array(':uid'=>$_GET['id']))[0]['COUNT(*)']?></b> &nbsp;·&nbsp; <a href="#sf">Новый поиск</a></div><br>
<?php
$photos = DB::query('SELECT * FROM photos WHERE user_id=:uid AND moderated=1 ORDER BY id DESC', array(':uid'=>$_GET['id']));
foreach ($photos as $p) {
echo '<div class="p20p">
<table>
<tbody>
<tr>
<td class="pb_photo" id="p1936120"><a href="/photo/'.$p['id'].'" target="_blank" class="prw"><img class="f" src="'.$p['photourl'].'">
<div class="hpshade">
';
if (DB::query('SELECT COUNT(*) FROM photos_comments WHERE photo_id=:id', array(':id'=>$p['id']))[0]['COUNT(*)'] >= 1) {
echo '<div class="com-icon">'.DB::query('SELECT COUNT(*) FROM photos_comments WHERE photo_id=:id', array(':id'=>$p['id']))[0]['COUNT(*)'].'</div>';
}
echo '
<div class="eye-icon">'.DB::query('SELECT COUNT(*) FROM photos_views WHERE photo_id=:id', array(':id'=>$p['id']))[0]['COUNT(*)'].'</div></div>
</a></td>
<td class="pb_descr">
<p><b class="pw-place">'.htmlspecialchars($p['place']).'</b></p>
<span class="pw-descr">'.htmlspecialchars($p['postbody']).'</span>
<p class="sm"><b>'.Date::zmdate($p['timeupload']).'</b><br>Автор: <a href="/author/'.$p['user_id'].'/">'.htmlspecialchars($p['username']).'</a></p>
</td>
</tr>
</tbody>
</table>
</div>';
}
?>
</tbody>
</table>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more