mirror of
https://github.com/openvk/openvk
synced 2024-12-25 10:01:05 +03:00
Upd
This commit is contained in:
parent
b318e184e7
commit
7e5a114064
11 changed files with 598 additions and 28 deletions
42
Web/Models/Entities/BugtrackerPrivateProduct.php
Normal file
42
Web/Models/Entities/BugtrackerPrivateProduct.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Entities;
|
||||||
|
use openvk\Web\Util\DateTime;
|
||||||
|
use Chandler\Database\DatabaseConnection as DB;
|
||||||
|
use openvk\Web\Models\{RowModel};
|
||||||
|
use openvk\Web\Models\Entities\{User, BugtrackerProduct};
|
||||||
|
use openvk\Web\Models\Repositories\{Users, BugtrackerProducts};
|
||||||
|
|
||||||
|
class BugtrackerPrivateProduct extends BugtrackerProduct
|
||||||
|
{
|
||||||
|
protected $tableName = "bt_products_access";
|
||||||
|
|
||||||
|
function toProduct(): ?BugtrackerProduct
|
||||||
|
{
|
||||||
|
return (new BugtrackerProducts)->get($this->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getName(): string
|
||||||
|
{
|
||||||
|
return $this->toProduct()->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isClosed(): ?bool
|
||||||
|
{
|
||||||
|
return $this->toProduct()->isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCreator(): ?User
|
||||||
|
{
|
||||||
|
return $this->toProduct()->getCreator();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrivate(): ?bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModerator(): ?User
|
||||||
|
{
|
||||||
|
return (new Users)->get($this->getRecord("moderator"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
namespace openvk\Web\Models\Entities;
|
namespace openvk\Web\Models\Entities;
|
||||||
use openvk\Web\Util\DateTime;
|
use openvk\Web\Util\DateTime;
|
||||||
|
use Chandler\Database\DatabaseConnection as DB;
|
||||||
use openvk\Web\Models\{RowModel};
|
use openvk\Web\Models\{RowModel};
|
||||||
use openvk\Web\Models\Entities\{User};
|
use openvk\Web\Models\Entities\{User};
|
||||||
use openvk\Web\Models\Repositories\{Users};
|
use openvk\Web\Models\Repositories\{Users};
|
||||||
|
@ -43,4 +44,25 @@ class BugtrackerProduct extends RowModel
|
||||||
{
|
{
|
||||||
return new DateTime($this->getRecord()->created);
|
return new DateTime($this->getRecord()->created);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPrivate(): ?bool
|
||||||
|
{
|
||||||
|
return (bool) $this->getRecord()->private;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasAccess(User $user): bool
|
||||||
|
{
|
||||||
|
if ($user->isBtModerator() || !$this->isPrivate())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
$check = DB::i()->getContext()->table("bt_products_access")->where([
|
||||||
|
"tester" => $user->getId(),
|
||||||
|
"product" => $this->getId()
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (sizeof($check) > 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
58
Web/Models/Repositories/BugtrackerPrivateProducts.php
Normal file
58
Web/Models/Repositories/BugtrackerPrivateProducts.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Repositories;
|
||||||
|
use openvk\Web\Models\Entities\{BugtrackerProduct, BugtrackerPrivateProduct, User};
|
||||||
|
use openvk\Web\Models\Repositories\BugtrackerProducts;
|
||||||
|
use Nette\Database\Table\ActiveRow;
|
||||||
|
use Chandler\Database\DatabaseConnection;
|
||||||
|
|
||||||
|
class BugtrackerPrivateProducts
|
||||||
|
{
|
||||||
|
private $context;
|
||||||
|
private $private_products;
|
||||||
|
|
||||||
|
function __construct()
|
||||||
|
{
|
||||||
|
$this->context = DatabaseConnection::i()->getContext();
|
||||||
|
$this->private_products = $this->context->table("bt_products_access");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toPrivateProduct(?ActiveRow $ar)
|
||||||
|
{
|
||||||
|
return is_null($ar) ? NULL : new BugtrackerPrivateProduct($ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(int $id): ?BugtrackerPrivateProduct
|
||||||
|
{
|
||||||
|
return $this->toPrivateProduct($this->private_products->get($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAll(int $page = 1): \Traversable
|
||||||
|
{
|
||||||
|
$products = $this->private_products
|
||||||
|
->order("created DESC")
|
||||||
|
->page($page, 5);
|
||||||
|
|
||||||
|
foreach($products as $product)
|
||||||
|
yield (new BugtrackerProducts)->get($product->product);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getForUser(User $user, int $page = 1): \Traversable
|
||||||
|
{
|
||||||
|
|
||||||
|
$products = $this->private_products
|
||||||
|
->where(["tester" => $user->getId()])
|
||||||
|
->order("id ASC")
|
||||||
|
->page($page, 5);
|
||||||
|
|
||||||
|
foreach($products as $product)
|
||||||
|
yield (new BugtrackerProducts)->get($product->product);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCount(User $user): ?int
|
||||||
|
{
|
||||||
|
if ($user->isBtModerator())
|
||||||
|
return sizeof($this->getAll());
|
||||||
|
|
||||||
|
return sizeof($this->private_products->where(["tester" => $user->getId()]));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
namespace openvk\Web\Models\Repositories;
|
namespace openvk\Web\Models\Repositories;
|
||||||
use openvk\Web\Models\Entities\{BugtrackerProduct};
|
use openvk\Web\Models\Entities\{BugtrackerProduct, BugtrackerPrivateProduct, User};
|
||||||
|
use openvk\Web\Models\Repositories\{BugtrackerPrivateProducts};
|
||||||
|
|
||||||
use Nette\Database\Table\ActiveRow;
|
use Nette\Database\Table\ActiveRow;
|
||||||
use Chandler\Database\DatabaseConnection;
|
use Chandler\Database\DatabaseConnection;
|
||||||
|
|
||||||
|
@ -8,6 +10,7 @@ class BugtrackerProducts
|
||||||
{
|
{
|
||||||
private $context;
|
private $context;
|
||||||
private $products;
|
private $products;
|
||||||
|
private $private_products;
|
||||||
|
|
||||||
function __construct()
|
function __construct()
|
||||||
{
|
{
|
||||||
|
@ -27,18 +30,106 @@ class BugtrackerProducts
|
||||||
|
|
||||||
function getAll(int $page = 1): \Traversable
|
function getAll(int $page = 1): \Traversable
|
||||||
{
|
{
|
||||||
foreach($this->products->order("id ASC")->page($page, 5) as $product)
|
$products = $this->products
|
||||||
|
->where(["private" => 0])
|
||||||
|
->order("created DESC")
|
||||||
|
->page($page, 5);
|
||||||
|
|
||||||
|
foreach($products as $product)
|
||||||
yield new BugtrackerProduct($product);
|
yield new BugtrackerProduct($product);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOpen(int $page = 1): \Traversable
|
function getOpen(int $page = 1, bool $private = FALSE): \Traversable
|
||||||
{
|
{
|
||||||
foreach($this->products->where(["closed" => 0])->order("id ASC")->page($page, 5) as $product)
|
$products = $this->products
|
||||||
|
->where([
|
||||||
|
"closed" => 0,
|
||||||
|
"private" => $private
|
||||||
|
])
|
||||||
|
->order("id ASC")
|
||||||
|
->page($page, 5);
|
||||||
|
|
||||||
|
foreach($products as $product)
|
||||||
yield new BugtrackerProduct($product);
|
yield new BugtrackerProduct($product);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCount(): ?int
|
function getClosed(int $page = 1, bool $private = FALSE): \Traversable
|
||||||
{
|
{
|
||||||
return sizeof($this->products->where(["closed" => 0]));
|
$products = $this->products
|
||||||
|
->where([
|
||||||
|
"closed" => 1,
|
||||||
|
"private" => $private
|
||||||
|
])
|
||||||
|
->order("id ASC")
|
||||||
|
->page($page, 5);
|
||||||
|
|
||||||
|
foreach($products as $product)
|
||||||
|
yield new BugtrackerProduct($product);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrivate(int $page = 1): \Traversable
|
||||||
|
{
|
||||||
|
$products = $this->products
|
||||||
|
->where([
|
||||||
|
"private" => 1
|
||||||
|
])
|
||||||
|
->order("id ASC")
|
||||||
|
->page($page, 5);
|
||||||
|
|
||||||
|
foreach($products as $product)
|
||||||
|
yield new BugtrackerProduct($product);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrivateForUser(User $user, int $page = 1): \Traversable
|
||||||
|
{
|
||||||
|
if (!$user->isBtModerator()) {
|
||||||
|
return (new BugtrackerPrivateProducts)->getForUser($user, $page);
|
||||||
|
} else {
|
||||||
|
return (new BugtrackerPrivateProducts)->getAll($page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFiltered(User $tester, string $type = "all", int $page = 1): \Traversable
|
||||||
|
{
|
||||||
|
switch ($type) {
|
||||||
|
case 'open':
|
||||||
|
return $this->getOpen($page);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'closed':
|
||||||
|
return $this->getClosed($page);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'private':
|
||||||
|
if ($tester->isBtModerator())
|
||||||
|
return $this->getPrivate($page);
|
||||||
|
|
||||||
|
return (new BugtrackerPrivateProducts)->getForUser($tester, $page);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return $this->getAll($page);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCount(string $filter = "all", User $user = NULL): ?int
|
||||||
|
{
|
||||||
|
switch ($filter) {
|
||||||
|
case 'open':
|
||||||
|
return sizeof($this->products->where(["closed" => 0, "private" => 0]));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'closed':
|
||||||
|
return sizeof($this->products->where(["closed" => 1, "private" => 0]));
|
||||||
|
|
||||||
|
case 'private':
|
||||||
|
return (new BugtrackerPrivateProducts)->getCount($user);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return sizeof($this->products->where(["private" => 0]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
namespace openvk\Web\Models\Repositories;
|
namespace openvk\Web\Models\Repositories;
|
||||||
use openvk\Web\Models\Entities\{BugReport};
|
use openvk\Web\Models\Entities\{BugReport, User};
|
||||||
|
use openvk\Web\Models\Repositories\{BugtrackerProducts};
|
||||||
use Nette\Database\Table\ActiveRow;
|
use Nette\Database\Table\ActiveRow;
|
||||||
use Chandler\Database\DatabaseConnection;
|
use Chandler\Database\DatabaseConnection;
|
||||||
|
|
||||||
|
@ -26,18 +27,24 @@ class BugtrackerReports
|
||||||
return $this->toReport($this->reports->get($id));
|
return $this->toReport($this->reports->get($id));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllReports(int $page = 1): \Traversable
|
function getAllReports(User $user, int $page = 1): \Traversable
|
||||||
{
|
{
|
||||||
foreach($this->reports->where(["deleted" => NULL])->order("created DESC")->page($page, 5) as $report)
|
$reports = $this->reports->where(["deleted" => NULL])->order("created DESC")->page($page, 5);
|
||||||
|
|
||||||
|
foreach($reports as $report)
|
||||||
yield new BugReport($report);
|
yield new BugReport($report);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getReports(int $product_id = 0, int $priority = 0, int $page = 1): \Traversable
|
function getReports(int $product_id = 0, int $priority = 0, int $page = 1, User $user = NULL): \Traversable
|
||||||
{
|
{
|
||||||
$filter = ["deleted" => NULL];
|
$filter = ["deleted" => NULL];
|
||||||
$product_id && $filter["product_id"] = $product_id;
|
$product_id && $filter["product_id"] = $product_id;
|
||||||
$priority && $filter["priority"] = $priority;
|
$priority && $filter["priority"] = $priority;
|
||||||
|
|
||||||
|
$product = (new BugtrackerProducts)->get($product_id);
|
||||||
|
if (!$product->hasAccess($user))
|
||||||
|
return false;
|
||||||
|
|
||||||
foreach($this->reports->where($filter)->order("created DESC")->page($page, 5) as $report)
|
foreach($this->reports->where($filter)->order("created DESC")->page($page, 5) as $report)
|
||||||
yield new BugReport($report);
|
yield new BugReport($report);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,15 +43,16 @@ final class BugtrackerPresenter extends OpenVKPresenter
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'products':
|
case 'products':
|
||||||
$this->template->count = $this->products->getCount();
|
$this->template->filter = $this->queryParam("filter") ?? "all";
|
||||||
$this->template->iterator = $this->products->getAll($this->template->page);
|
$this->template->count = $this->products->getCount($this->template->filter, $this->user->identity);
|
||||||
|
$this->template->iterator = $this->products->getFiltered($this->user->identity, $this->template->filter, $this->template->page);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
$this->template->count = $this->reports->getReportsCount((int) $this->queryParam("product"), (int) $this->queryParam("priority"));
|
$this->template->count = $this->reports->getReportsCount((int) $this->queryParam("product"), (int) $this->queryParam("priority"), $this->user->identity);
|
||||||
$this->template->iterator = $this->queryParam("product")
|
$this->template->iterator = $this->queryParam("product")
|
||||||
? $this->reports->getReports((int) $this->queryParam("product"), (int) $this->queryParam("priority"), $this->template->page)
|
? $this->reports->getReports((int) $this->queryParam("product"), (int) $this->queryParam("priority"), $this->template->page, $this->user->identity)
|
||||||
: $this->reports->getAllReports($this->template->page);
|
: $this->reports->getAllReports($this->user->identity, $this->template->page);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +65,9 @@ final class BugtrackerPresenter extends OpenVKPresenter
|
||||||
|
|
||||||
$this->template->user = $this->user;
|
$this->template->user = $this->user;
|
||||||
|
|
||||||
|
if (!$this->reports->get($id)->getProduct()->hasAccess($this->template->user->identity))
|
||||||
|
$this->flashFail("err", tr("forbidden"));
|
||||||
|
|
||||||
if ($this->reports->get($id)) {
|
if ($this->reports->get($id)) {
|
||||||
$this->template->bug = $this->reports->get($id);
|
$this->template->bug = $this->reports->get($id);
|
||||||
$this->template->reporter = $this->template->bug->getReporter();
|
$this->template->reporter = $this->template->bug->getReporter();
|
||||||
|
@ -203,19 +207,21 @@ final class BugtrackerPresenter extends OpenVKPresenter
|
||||||
$this->assertUserLoggedIn();
|
$this->assertUserLoggedIn();
|
||||||
$this->willExecuteWriteAction();
|
$this->willExecuteWriteAction();
|
||||||
|
|
||||||
$moder = $this->user->identity->isBtModerator();
|
if (!$this->user->identity->isBtModerator())
|
||||||
|
|
||||||
if (!$moder)
|
|
||||||
$this->flashFail("err", tr("forbidden"));
|
$this->flashFail("err", tr("forbidden"));
|
||||||
|
|
||||||
$title = $this->postParam("title");
|
$title = $this->postParam("title");
|
||||||
$description = $this->postParam("description");
|
$description = $this->postParam("description");
|
||||||
|
$is_closed = (bool) $this->postParam("is_closed");
|
||||||
|
$is_private = (bool) $this->postParam("is_private");
|
||||||
|
|
||||||
DB::i()->getContext()->table("bt_products")->insert([
|
DB::i()->getContext()->table("bt_products")->insert([
|
||||||
"creator_id" => $this->user->identity->getId(),
|
"creator_id" => $this->user->identity->getId(),
|
||||||
"title" => $title,
|
"title" => $title,
|
||||||
"description" => $description,
|
"description" => $description,
|
||||||
"created" => time()
|
"created" => time(),
|
||||||
|
"closed" => $is_closed,
|
||||||
|
"private" => $is_private
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->redirect("/bugtracker?act=products");
|
$this->redirect("/bugtracker?act=products");
|
||||||
|
@ -235,4 +241,102 @@ final class BugtrackerPresenter extends OpenVKPresenter
|
||||||
|
|
||||||
$this->flashFail("succ", tr("bug_tracker_success"), tr("bug_tracker_reproduced_text"));
|
$this->flashFail("succ", tr("bug_tracker_success"), tr("bug_tracker_reproduced_text"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderManageAccess(int $product_id): void
|
||||||
|
{
|
||||||
|
$this->assertUserLoggedIn();
|
||||||
|
$this->willExecuteWriteAction();
|
||||||
|
|
||||||
|
if (!$this->user->identity->isBtModerator())
|
||||||
|
$this->flashFail("err", tr("forbidden"));
|
||||||
|
|
||||||
|
$user = (new Users)->get((int) $this->postParam("uid"));
|
||||||
|
$product = $this->products->get($product_id);
|
||||||
|
$action = $this->postParam("action");
|
||||||
|
|
||||||
|
if ($action === "give") {
|
||||||
|
if (!$product->isPrivate() || $product->hasAccess($user))
|
||||||
|
$this->flashFail("err", "Ошибка", $user->getCanonicalName() . " уже имеет доступ к продукту " . $product->getCanonicalName());
|
||||||
|
|
||||||
|
DB::i()->getContext()->table("bt_products_access")->insert([
|
||||||
|
"created" => time(),
|
||||||
|
"tester" => $user->getId(),
|
||||||
|
"product" => $product_id,
|
||||||
|
"moderator" => $this->user->identity->getId()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->flashFail("succ", "Успех", $user->getCanonicalName() . " теперь имеет доступ к продукту " . $product->getCanonicalName());
|
||||||
|
} else {
|
||||||
|
if ($user->isBtModerator())
|
||||||
|
$this->flashFail("err", "Ошибка", "Невозможно забрать доступ к продукту у модератора.");
|
||||||
|
|
||||||
|
if (!$product->hasAccess($user))
|
||||||
|
$this->flashFail("err", "Ошибка", $user->getCanonicalName() . " и так не имеет доступа к продукту " . $product->getCanonicalName());
|
||||||
|
|
||||||
|
DB::i()->getContext()->table("bt_products_access")->where([
|
||||||
|
"tester" => $user->getId(),
|
||||||
|
"product" => $product_id,
|
||||||
|
])->delete();
|
||||||
|
|
||||||
|
$this->flashFail("succ", "Успех", $user->getCanonicalName() . " теперь не имеет доступа к продукту " . $product->getCanonicalName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderManagePrivacy(int $product_id): void
|
||||||
|
{
|
||||||
|
$this->assertUserLoggedIn();
|
||||||
|
$this->willExecuteWriteAction();
|
||||||
|
|
||||||
|
if (!$this->user->identity->isBtModerator())
|
||||||
|
$this->flashFail("err", tr("forbidden"));
|
||||||
|
|
||||||
|
$user = (new Users)->get((int) $this->postParam("uid"));
|
||||||
|
$product = $this->products->get($product_id);
|
||||||
|
$action = $this->postParam("action");
|
||||||
|
|
||||||
|
if ($action == "open") {
|
||||||
|
if (!$product->isPrivate())
|
||||||
|
$this->flashFail("err", "Ошибка", "Продукт " . $product->getCanonicalName() . " и так открытый.");
|
||||||
|
|
||||||
|
DB::i()->getContext()->table("bt_products")->where("id", $product_id)->update(["private" => 0]);
|
||||||
|
|
||||||
|
$this->flashFail("succ", "Успех", "Продукт " . $product->getCanonicalName() . " теперь открытый.");
|
||||||
|
} else {
|
||||||
|
if ($product->isPrivate())
|
||||||
|
$this->flashFail("err", "Ошибка", "Продукт " . $product->getCanonicalName() . " и так приватный.");
|
||||||
|
|
||||||
|
DB::i()->getContext()->table("bt_products")->where("id", $product_id)->update(["private" => 1]);
|
||||||
|
|
||||||
|
$this->flashFail("succ", "Успех", "Продукт " . $product->getCanonicalName() . " теперь приватный.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderManageStatus(int $product_id): void
|
||||||
|
{
|
||||||
|
$this->assertUserLoggedIn();
|
||||||
|
$this->willExecuteWriteAction();
|
||||||
|
|
||||||
|
if (!$this->user->identity->isBtModerator())
|
||||||
|
$this->flashFail("err", tr("forbidden"));
|
||||||
|
|
||||||
|
$user = (new Users)->get((int) $this->postParam("uid"));
|
||||||
|
$product = $this->products->get($product_id);
|
||||||
|
$action = $this->postParam("action");
|
||||||
|
|
||||||
|
if ($action == "open") {
|
||||||
|
if (!$product->isClosed())
|
||||||
|
$this->flashFail("err", "Ошибка", "Продукт " . $product->getCanonicalName() . " и так открытый.");
|
||||||
|
|
||||||
|
DB::i()->getContext()->table("bt_products")->where("id", $product_id)->update(["closed" => 0]);
|
||||||
|
|
||||||
|
$this->flashFail("succ", "Успех", "Продукт " . $product->getCanonicalName() . " теперь открытый.");
|
||||||
|
} else {
|
||||||
|
if ($product->isClosed())
|
||||||
|
$this->flashFail("err", "Ошибка", "Продукт " . $product->getCanonicalName() . " и так закрытый.");
|
||||||
|
|
||||||
|
DB::i()->getContext()->table("bt_products")->where("id", $product_id)->update(["closed" => 1]);
|
||||||
|
|
||||||
|
$this->flashFail("succ", "Успех", "Продукт " . $product->getCanonicalName() . " теперь закрытый.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@
|
||||||
{_create}
|
{_create}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div n:if='in_array($mode, ["products", "new_product"])' n:attr='id => $mode === "new_product" ? "activetabs" : false' class="tab" style="float: right;">
|
<div n:if='in_array($mode, ["products", "new_product"]) AND $isModerator' n:attr='id => $mode === "new_product" ? "activetabs" : false' class="tab" style="float: right;">
|
||||||
<a n:attr='id => $mode === "new_product" ? "act_tab_a" : false' href="/bugtracker?act=new_product">
|
<a n:attr='id => $mode === "new_product" ? "act_tab_a" : false' href="/bugtracker?act=new_product">
|
||||||
{_create}
|
{_create}
|
||||||
</a>
|
</a>
|
||||||
|
@ -43,20 +43,23 @@
|
||||||
<table border="0" style="font-size: 11px; width: 100%;" class="post">
|
<table border="0" style="font-size: 11px; width: 100%;" class="post">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr n:foreach="$iterator as $bug">
|
<tr n:foreach="$iterator as $bug">
|
||||||
<td width="54" >
|
{var $isHidden = !$bug->getProduct()->hasAccess($user->identity)}
|
||||||
|
<td width="54">
|
||||||
<center>
|
<center>
|
||||||
<img src="/assets/packages/static/openvk/img/note_icon.png">
|
<img src="/assets/packages/static/openvk/img/note_icon.png">
|
||||||
</center>
|
</center>
|
||||||
</td>
|
</td>
|
||||||
<td width="92%" valign="top">
|
<td width="92%" valign="top">
|
||||||
<div class="post-author" href="#">
|
<div class="post-author" href="#">
|
||||||
<a href="/bug{$bug->getId()}">
|
<a n:attr="href => !$isHidden ? '/bug' . $bug->getId() : false">
|
||||||
<b>{$bug->getCanonicalName()}</b>
|
<b>
|
||||||
<span>#{$bug->getId()}</span>
|
{!$isHidden ? $bug->getCanonicalName() : "Скрытый отчёт"}
|
||||||
|
</b>
|
||||||
|
<span n:if="!$isHidden">#{$bug->getId()}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="post-content" style="padding: 4px 0; font-size: 11px;">
|
<div class="post-content" style="padding: 4px 0; font-size: 11px;">
|
||||||
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
|
<table n:if="!$isHidden" id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label"><span class="nobold">{_bug_tracker_product}:</span></td>
|
<td class="label"><span class="nobold">{_bug_tracker_product}:</span></td>
|
||||||
|
@ -86,6 +89,58 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<table n:if="$isHidden" id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0" style="filter: blur(3px)">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="label">
|
||||||
|
<span class="nobold">{_bug_tracker_product}:</span>
|
||||||
|
</td>
|
||||||
|
<td class="data">
|
||||||
|
<a>AbcdEFGH</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label">
|
||||||
|
<span class="nobold">{_bug_tracker_sent_by}: </span>
|
||||||
|
</td>
|
||||||
|
<td class="data">
|
||||||
|
<a>John Doe</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label">
|
||||||
|
<span class="nobold">{_bug_tracker_reproduced}:</span>
|
||||||
|
</td>
|
||||||
|
<td class="data">
|
||||||
|
<a>...</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label">
|
||||||
|
<span class="nobold">{_status}:</span>
|
||||||
|
</td>
|
||||||
|
<td class="data">
|
||||||
|
<a href="#">...</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label">
|
||||||
|
<span class="nobold">{_bug_tracker_priority}:</span>
|
||||||
|
</td>
|
||||||
|
<td class="data">
|
||||||
|
<a>...</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label">
|
||||||
|
<span class="nobold">{_created}:</span>
|
||||||
|
</td>
|
||||||
|
<td class="data">
|
||||||
|
<a>...</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -151,10 +206,36 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{elseif $mode === "products"}
|
{elseif $mode === "products"}
|
||||||
|
<div>
|
||||||
|
<div n:attr='id => $filter === "all" ? "activetabs" : false' class="tab">
|
||||||
|
<a n:attr='id => $filter === "all" ? "act_tab_a" : false' href="/bugtracker?act=products">
|
||||||
|
Все
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div n:attr='id => $filter === "open" ? "activetabs" : false' class="tab">
|
||||||
|
<a n:attr='id => $filter === "open" ? "act_tab_a" : false' href="/bugtracker?act=products&filter=open">
|
||||||
|
Открытые
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div n:attr='id => $filter === "closed" ? "activetabs" : false' class="tab">
|
||||||
|
<a n:attr='id => $filter === "closed" ? "act_tab_a" : false' href="/bugtracker?act=products&filter=closed">
|
||||||
|
Закрытые
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div n:attr='id => $filter === "private" ? "activetabs" : false' class="tab">
|
||||||
|
<a n:attr='id => $filter === "private" ? "act_tab_a" : false' href="/bugtracker?act=products&filter=private">
|
||||||
|
Приватные
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
{if $count < 1}
|
||||||
|
{include "../components/nothing.xml"}
|
||||||
|
{/if}
|
||||||
<table border="0" style="font-size: 11px; width: 100%;" class="post">
|
<table border="0" style="font-size: 11px; width: 100%;" class="post">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr n:foreach="$iterator as $product">
|
<tr n:foreach="$iterator as $product">
|
||||||
<td width="54" >
|
<td width="54">
|
||||||
<center>
|
<center>
|
||||||
<img src="/assets/packages/static/openvk/img/note_icon.png">
|
<img src="/assets/packages/static/openvk/img/note_icon.png">
|
||||||
</center>
|
</center>
|
||||||
|
@ -167,6 +248,10 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="post-content" style="padding: 4px 0; font-size: 11px;">
|
<div class="post-content" style="padding: 4px 0; font-size: 11px;">
|
||||||
|
<div n:if="$product->getDescription()" style="padding: 4px; font-size: 11px;">
|
||||||
|
{$product->getDescription()}
|
||||||
|
<hr color="#DAE1E8" size="1">
|
||||||
|
</div>
|
||||||
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
|
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -186,6 +271,28 @@
|
||||||
<td class="label"><span class="nobold">{_bug_tracker_product_creation_date}:</span></td>
|
<td class="label"><span class="nobold">{_bug_tracker_product_creation_date}:</span></td>
|
||||||
<td class="data"><a href="#">{$product->getCreationTime()}</a></td>
|
<td class="data"><a href="#">{$product->getCreationTime()}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr n:if="$isModerator">
|
||||||
|
<td class="label"><span class="nobold">действия:</span></td>
|
||||||
|
<td class="data" style="width: 100% !important;">
|
||||||
|
<span>
|
||||||
|
<a
|
||||||
|
onClick="showBtProductStatusDialog([{$product->getId()}, {$product->getCanonicalName()}], {$csrfToken})"
|
||||||
|
>
|
||||||
|
закрытый
|
||||||
|
</a>
|
||||||
|
・
|
||||||
|
<a
|
||||||
|
onClick="showBtPrivateProductDialog([{$product->getId()}, {$product->getCanonicalName()}], {$csrfToken})"
|
||||||
|
>
|
||||||
|
приватный
|
||||||
|
</a>
|
||||||
|
・
|
||||||
|
<a onClick="showBtProductAccessDialog([{$product->getId()}, {$product->getCanonicalName()}], {$csrfToken})">
|
||||||
|
доступ
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -208,7 +315,13 @@
|
||||||
<input type="text" name="title" placeholder="{_bug_tracker_product_name}">
|
<input type="text" name="title" placeholder="{_bug_tracker_product_name}">
|
||||||
<br><br>
|
<br><br>
|
||||||
<textarea placeholder="{_bug_tracker_product_description}" name="description" style="width: 100%; resize: vertical;"></textarea>
|
<textarea placeholder="{_bug_tracker_product_description}" name="description" style="width: 100%; resize: vertical;"></textarea>
|
||||||
|
<div style="display: inline;">
|
||||||
|
<input id="is_closed" type="checkbox" name="is_closed">
|
||||||
|
<label for="is_closed">Закрытый</label>
|
||||||
|
|
||||||
|
<input id="is_private" type="checkbox" name="is_private">
|
||||||
|
<label for="is_private">Приватный</label>
|
||||||
|
</div>
|
||||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||||
<br><br>
|
<br><br>
|
||||||
<input type="submit" value="{_create}" class="button" style="float: right;" />
|
<input type="submit" value="{_create}" class="button" style="float: right;" />
|
||||||
|
|
|
@ -33,6 +33,7 @@ services:
|
||||||
- openvk\Web\Models\Repositories\Tickets
|
- openvk\Web\Models\Repositories\Tickets
|
||||||
- openvk\Web\Models\Repositories\BugtrackerReports
|
- openvk\Web\Models\Repositories\BugtrackerReports
|
||||||
- openvk\Web\Models\Repositories\BugtrackerProducts
|
- openvk\Web\Models\Repositories\BugtrackerProducts
|
||||||
|
- openvk\Web\Models\Repositories\BugtrackerPrivateProducts
|
||||||
- openvk\Web\Models\Repositories\BugtrackerComments
|
- openvk\Web\Models\Repositories\BugtrackerComments
|
||||||
- openvk\Web\Models\Repositories\Messages
|
- openvk\Web\Models\Repositories\Messages
|
||||||
- openvk\Web\Models\Repositories\Restores
|
- openvk\Web\Models\Repositories\Restores
|
||||||
|
|
|
@ -49,6 +49,12 @@ routes:
|
||||||
handler: "Bugtracker->createProduct"
|
handler: "Bugtracker->createProduct"
|
||||||
- url: "/bug{num}/reproduce"
|
- url: "/bug{num}/reproduce"
|
||||||
handler: "Bugtracker->reproduced"
|
handler: "Bugtracker->reproduced"
|
||||||
|
- url: "/bt_product{num}/manageAccess"
|
||||||
|
handler: "Bugtracker->manageAccess"
|
||||||
|
- url: "/bt_product{num}/managePrivacy"
|
||||||
|
handler: "Bugtracker->managePrivacy"
|
||||||
|
- url: "/bt_product{num}/manageStatus"
|
||||||
|
handler: "Bugtracker->manageStatus"
|
||||||
- url: "/language"
|
- url: "/language"
|
||||||
handler: "About->language"
|
handler: "About->language"
|
||||||
- url: "/language/{text}.js"
|
- url: "/language/{text}.js"
|
||||||
|
|
|
@ -551,3 +551,109 @@ function showBtPriorityChangeDialog(report, currentBalance, hash) {
|
||||||
Function.noop
|
Function.noop
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showBtGiveProductAccessDialog(product, hash) {
|
||||||
|
MessageBox("Выдать доступ", `<form action="/bt_product${product[0]}/giveAccess" method="post" id="give_product_access_dialog">
|
||||||
|
<div>
|
||||||
|
Выдать пользователю <b>ID</b> 
|
||||||
|
<input style="width: 45px; height: 9px;" type="number" name="uid" value="1" min="1">
|
||||||
|
доступ к продукту <b>${product[1]}</b> (#${product[0]}).
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="hash" value="${hash}" />
|
||||||
|
</form>`, ["Продолжить", tr("cancel")], [
|
||||||
|
() => {
|
||||||
|
$("#give_product_access_dialog").submit();
|
||||||
|
},
|
||||||
|
Function.noop
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBtRevokeProductAccessDialog(product, hash) {
|
||||||
|
MessageBox("Забрать доступ", `<form action="/bt_product${product[0]}/revokeAccess" method="post" id="revoke_product_access_dialog">
|
||||||
|
<div>
|
||||||
|
Забрать у пользователя <b>ID</b> 
|
||||||
|
<input style="width: 45px; height: 9px;" type="number" name="uid" value="1" min="1">
|
||||||
|
доступ к продукту <b>${product[1]}</b> (#${product[0]}).
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="hash" value="${hash}" />
|
||||||
|
</form>`, ["Продолжить", tr("cancel")], [
|
||||||
|
() => {
|
||||||
|
$("#revoke_product_access_dialog").submit();
|
||||||
|
},
|
||||||
|
Function.noop
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBtProductAccessDialog(product, hash) {
|
||||||
|
MessageBox(`Доступ к ${product[1]} (#${product[0]})`, `<form action="/bt_product${product[0]}/manageAccess" method="post" id="give_product_access_dialog">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><input type="radio" name="action" value="give"></td>
|
||||||
|
<td><label for="priority_1">Выдать</label></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type="radio" name="action" value="revoke"></td>
|
||||||
|
<td><label for="priority_2">Забрать</label></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<div>
|
||||||
|
<b>ID</b> пользователя 
|
||||||
|
<input style="width: 45px; height: 9px;" type="number" name="uid" value="1" min="1">
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="hash" value="${hash}" />
|
||||||
|
</form>`, ["Продолжить", tr("cancel")], [
|
||||||
|
() => {
|
||||||
|
$("#give_product_access_dialog").submit();
|
||||||
|
},
|
||||||
|
Function.noop
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBtPrivateProductDialog(product, hash) {
|
||||||
|
MessageBox(`Настройки продукта ${product[1]} (#${product[0]})`, `<form action="/bt_product${product[0]}/managePrivacy" method="post" id="give_product_access_dialog">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><input type="radio" name="action" value="open"></td>
|
||||||
|
<td><label for="priority_1">Открытый</label></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type="radio" name="action" value="private"></td>
|
||||||
|
<td><label for="priority_2">Приватный</label></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<input type="hidden" name="hash" value="${hash}" />
|
||||||
|
</form>`, ["Продолжить", tr("cancel")], [
|
||||||
|
() => {
|
||||||
|
$("#give_product_access_dialog").submit();
|
||||||
|
},
|
||||||
|
Function.noop
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBtProductStatusDialog(product, hash) {
|
||||||
|
MessageBox(`Статус продукта ${product[1]} (#${product[0]})`, `<form action="/bt_product${product[0]}/manageStatus" method="post" id="give_product_access_dialog">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><input type="radio" name="action" value="open"></td>
|
||||||
|
<td><label for="priority_1">Открытый</label></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type="radio" name="action" value="closed"></td>
|
||||||
|
<td><label for="priority_2">Закрытый</label></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<input type="hidden" name="hash" value="${hash}" />
|
||||||
|
</form>`, ["Продолжить", tr("cancel")], [
|
||||||
|
() => {
|
||||||
|
$("#give_product_access_dialog").submit();
|
||||||
|
},
|
||||||
|
Function.noop
|
||||||
|
]);
|
||||||
|
}
|
|
@ -29,7 +29,9 @@ CREATE TABLE `bt_products` (
|
||||||
`title` varchar(255) NOT NULL,
|
`title` varchar(255) NOT NULL,
|
||||||
`description` varchar(10000) NOT NULL,
|
`description` varchar(10000) NOT NULL,
|
||||||
`created` bigint(20) NOT NULL,
|
`created` bigint(20) NOT NULL,
|
||||||
`closed` tinyint(1) DEFAULT 0
|
`closed` tinyint(1) DEFAULT 0,
|
||||||
|
`private` tinyint(4) DEFAULT 0,
|
||||||
|
`deleted` tinyint(1) DEFAULT 0
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
ALTER TABLE `bt_products`
|
ALTER TABLE `bt_products`
|
||||||
|
@ -59,3 +61,21 @@ ALTER TABLE `bt_comments`
|
||||||
ALTER TABLE `bt_comments`
|
ALTER TABLE `bt_comments`
|
||||||
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
|
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
|
/* bt_products_access */
|
||||||
|
|
||||||
|
CREATE TABLE `bt_products_access` (
|
||||||
|
`id` bigint(20) NOT NULL,
|
||||||
|
`created` bigint(20) NOT NULL,
|
||||||
|
`tester` bigint(20) NOT NULL,
|
||||||
|
`product` bigint(20) NOT NULL,
|
||||||
|
`moderator` bigint(20) NOT NULL,
|
||||||
|
`access` tinyint(1) DEFAULT 1
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
ALTER TABLE `bt_products_access`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
ALTER TABLE `bt_products_access`
|
||||||
|
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
|
||||||
|
COMMIT;
|
Loading…
Reference in a new issue