Skip to content

Commit 55064a0

Browse files
committed
Admins can view/edit authors
1 parent b8c7435 commit 55064a0

File tree

12 files changed

+379
-8
lines changed

12 files changed

+379
-8
lines changed

public/index.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
require __DIR__ . '/../vendor/autoload.php';
1515

16-
session_start();
16+
session_start(['cookie.samesite' => 'Strict']);
1717

1818
// Instantiate the app
1919
$settings = require __DIR__ . '/../src/settings.php';

public/static/admin-authors-edit.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
function entryPreview()
3+
{
4+
$.post('/manage/ajax/preview', {
5+
"markdown": $("#author-bio").val()
6+
}, function (res) {
7+
if (res['status'] !== 'SUCCESS') {
8+
console.log(res);
9+
return;
10+
}
11+
$("#contents-preview").html(res['preview']);
12+
});
13+
}
14+
15+
function addContributor()
16+
{
17+
$("#admin-contributors-list")
18+
.append(
19+
'<li><input class="admin-contrib" name="contributors[]" size="1" type="text" /></li>'
20+
);
21+
$('.admin-contrib').on('keydown', adminContribOnChange)
22+
}
23+
24+
function adminContribOnChange()
25+
{
26+
let el = $(this);
27+
let size = el.val().length + 1;
28+
el.attr('size', size < 10 ? size : 10);
29+
}
30+
31+
$(document).ready(function() {
32+
$("#author-bio").on('change', entryPreview);
33+
$("#add-contributor").on('click', addContributor);
34+
entryPreview();
35+
$('.admin-contrib').on('keydown', adminContribOnChange)
36+
});

public/static/faqoff.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,12 @@ i.color-error {
300300
.dark-body i.color-error {
301301
color: #c66;
302302
}
303+
304+
#admin-contributors-list {
305+
padding: 0.5rem 2.5rem 0.5rem 2.5rem;
306+
list-style: none;
307+
}
308+
#admin-contributors-list li {
309+
display: inline-block;
310+
margin-right: 0.5rem;
311+
}

public/static/manage-author.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ function addDeleteEvent() {
6868

6969
$(document).ready(function() {
7070
$("#author-bio").on('change', entryPreview);
71-
entryPreview();
7271

7372
$("#add-contributor-button").on('click', function (e) {
7473
let author = $("#contributors-form").data('id');

src/FaqOff/Endpoints/Admin/Accounts.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ protected function listAccounts(): ResponseInterface
8181
]
8282
);
8383
}
84+
8485
/**
8586
* @param RequestInterface $request
8687
* @param int $accountId
@@ -90,7 +91,7 @@ protected function listAccounts(): ResponseInterface
9091
* @throws RuntimeError
9192
* @throws SyntaxError
9293
*/
93-
protected function viewAccount(RequestInterface $request, int $accountId): ResponseInterface
94+
protected function viewAccount(int $accountId): ResponseInterface
9495
{
9596
return $this->view(
9697
'admin/accounts-view.twig',
@@ -122,7 +123,7 @@ public function __invoke(
122123
case 'edit':
123124
return $this->editAccount($request, (int) $routerParams['id']);
124125
case 'view':
125-
return $this->viewAccount($request, (int) $routerParams['id']);
126+
return $this->viewAccount((int) $routerParams['id']);
126127
case '':
127128
return $this->listAccounts();
128129
default:

src/FaqOff/Endpoints/Admin/Authors.php

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@
33
namespace Soatok\FaqOff\Endpoints\Admin;
44

55
use Interop\Container\Exception\ContainerException;
6+
use ParagonIE\Ionizer\InvalidDataException;
67
use Psr\Http\Message\RequestInterface;
78
use Psr\Http\Message\ResponseInterface;
9+
use Slim\Container;
810
use Soatok\AnthroKit\Endpoint;
11+
use Soatok\FaqOff\Filter\AdminEditAccountFilter;
12+
use Soatok\FaqOff\Filter\AdminEditAuthorFilter;
13+
use Soatok\FaqOff\MessageOnceTrait;
14+
use Soatok\FaqOff\Splices\Accounts as AccountSplice;
15+
use Soatok\FaqOff\Splices\Authors as AuthorSplice;
16+
use Soatok\FaqOff\Splices\EntryCollection;
917
use Twig\Error\LoaderError;
1018
use Twig\Error\RuntimeError;
1119
use Twig\Error\SyntaxError;
@@ -16,12 +24,78 @@
1624
*/
1725
class Authors extends Endpoint
1826
{
27+
use MessageOnceTrait;
28+
29+
/** @var AccountSplice $accounts */
30+
protected $accounts;
31+
32+
/** @var AuthorSplice $authors */
33+
protected $authors;
34+
35+
/** @var EntryCollection $collections */
36+
protected $collections;
37+
38+
public function __construct(Container $container)
39+
{
40+
parent::__construct($container);
41+
$this->accounts = $this->splice('Accounts');
42+
$this->authors = $this->splice('Authors');
43+
$this->collections = $this->splice('EntryCollection');
44+
}
45+
46+
/**
47+
* @param RequestInterface $request
48+
* @param int $authorId
49+
* @return ResponseInterface
50+
* @throws ContainerException
51+
* @throws InvalidDataException
52+
* @throws LoaderError
53+
* @throws RuntimeError
54+
* @throws SyntaxError
55+
*/
56+
protected function editAuthor(RequestInterface $request, int $authorId): ResponseInterface
57+
{
58+
$filter = new AdminEditAuthorFilter();
59+
$post = $this->post($request, self::TYPE_FORM, $filter);
60+
if ($post) {
61+
if ($this->authors->updateAuthorByAdmin($authorId, $post)) {
62+
$this->messageOnce('Author updated successfully.', 'success');
63+
return $this->redirect('/admin/author/edit/' . $authorId);
64+
}
65+
}
66+
return $this->view(
67+
'admin/authors-edit.twig',
68+
[
69+
'author' => $this->authors->getById($authorId),
70+
'contributors' => $this->authors->getContributorIds($authorId)
71+
]
72+
);
73+
}
74+
75+
/**
76+
* @return ResponseInterface
77+
* @throws ContainerException
78+
* @throws LoaderError
79+
* @throws RuntimeError
80+
* @throws SyntaxError
81+
*/
82+
protected function listAuthors(): ResponseInterface
83+
{
84+
return $this->view(
85+
'admin/authors-list.twig',
86+
[
87+
'authors' => $this->authors->listAll(true)
88+
]
89+
);
90+
}
91+
1992
/**
2093
* @param RequestInterface $request
2194
* @param ResponseInterface|null $response
2295
* @param array $routerParams
2396
* @return ResponseInterface
2497
* @throws ContainerException
98+
* @throws InvalidDataException
2599
* @throws LoaderError
26100
* @throws RuntimeError
27101
* @throws SyntaxError
@@ -31,6 +105,14 @@ public function __invoke(
31105
?ResponseInterface $response = null,
32106
array $routerParams = []
33107
): ResponseInterface {
34-
return $this->json($routerParams);
108+
$action = $routerParams['action'] ?? null;
109+
switch ($action) {
110+
case 'edit':
111+
return $this->editAuthor($request, (int) $routerParams['id']);
112+
case '':
113+
return $this->listAuthors();
114+
default:
115+
return $this->redirect('/admin/authors');
116+
}
35117
}
36118
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
declare(strict_types=1);
3+
namespace Soatok\FaqOff\Filter;
4+
5+
use ParagonIE\Ionizer\Filter\BoolFilter;
6+
use ParagonIE\Ionizer\Filter\IntArrayFilter;
7+
use ParagonIE\Ionizer\Filter\StringFilter;
8+
use ParagonIE\Ionizer\InputFilterContainer;
9+
10+
/**
11+
* Class AdminEditAuthorFilter
12+
* @package Soatok\FaqOff\Filter
13+
*/
14+
class AdminEditAuthorFilter extends InputFilterContainer
15+
{
16+
public function __construct()
17+
{
18+
$this
19+
->addFilter('screenname', new StringFilter())
20+
->addFilter('biography', new StringFilter())
21+
->addFilter('contributors', new IntArrayFilter())
22+
;
23+
}
24+
}

src/FaqOff/Splices/Authors.php

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,30 @@ public function accountIsContributor(int $authorId, int $accountId): bool
6161
);
6262
}
6363

64+
/**
65+
* @param int $authorId
66+
* @return int
67+
*/
68+
public function countCollections(int $authorId): int
69+
{
70+
return (int) $this->db->cell(
71+
"SELECT count(*) FROM faqoff_collection WHERE authorid = ?",
72+
$authorId
73+
);
74+
}
75+
76+
/**
77+
* @param int $authorId
78+
* @return int
79+
*/
80+
public function countContributors(int $authorId): int
81+
{
82+
return (int) $this->db->cell(
83+
"SELECT count(*) FROM faqoff_author_contributor WHERE authorid = ?",
84+
$authorId
85+
);
86+
}
87+
6488
/**
6589
* @param string $screenName
6690
* @param int $accountId
@@ -148,11 +172,33 @@ public function getByScreenName(string $screenName): array
148172
}
149173
return $author;
150174
}
175+
151176
/**
177+
* @param bool $extraData
152178
* @return array
153179
*/
154-
public function listAll(): array
180+
public function listAll(bool $extraData = false): array
155181
{
182+
if ($extraData) {
183+
$authors = $this->db->run(
184+
"SELECT a.*, fa.public_id AS owner_public_id
185+
FROM faqoff_author a
186+
LEFT JOIN faqoff_accounts fa ON a.ownerid = fa.accountid
187+
ORDER BY a.screenname ASC"
188+
);
189+
if (empty($authors)) {
190+
return [];
191+
}
192+
foreach ($authors as $i => $auth) {
193+
$authors[$i]['collections'] = $this->countCollections(
194+
(int) $auth['authorid']
195+
);
196+
$authors[$i]['contributors'] = $this->countContributors(
197+
(int) $auth['authorid']
198+
);
199+
}
200+
return $authors;
201+
}
156202
return $this->db->run(
157203
"SELECT authorid, screenname FROM faqoff_author ORDER BY screenname ASC"
158204
);
@@ -204,6 +250,19 @@ public function listContributors(int $authorId): array
204250
return $contributors;
205251
}
206252

253+
/**
254+
* @param int $authorId
255+
* @return array<int, int>
256+
*/
257+
public function getContributorIds(int $authorId): array
258+
{
259+
return $this->db->col(
260+
"SELECT accountid FROM faqoff_author_contributor WHERE authorid = ?",
261+
0,
262+
$authorId
263+
);
264+
}
265+
207266
/**
208267
* Revoke a user's access to this author profile
209268
*
@@ -250,6 +309,54 @@ public function screenNameIsTaken(string $screenName): bool
250309
);
251310
}
252311

312+
/**
313+
* @param int $authorId
314+
* @param array $post
315+
* @return bool
316+
*/
317+
public function updateAuthorByAdmin(int $authorId, array $post): bool
318+
{
319+
$this->db->beginTransaction();
320+
$oldContributors = $this->getContributorIds($authorId);
321+
$newContributors = $post['contributors'];
322+
323+
$deletes = array_diff($oldContributors, $newContributors);
324+
$inserts = array_diff($newContributors, $oldContributors);
325+
326+
if ($deletes) {
327+
$placeholders = implode(', ', array_fill(0, count($deletes), '?'));
328+
$params = $deletes;
329+
array_unshift($params, $authorId);
330+
$this->db->safeQuery(
331+
"DELETE FROM faqoff_author_contributor
332+
WHERE authorid = ? AND accountid IN ({$placeholders})",
333+
array_values($params)
334+
);
335+
}
336+
foreach ($inserts as $newId) {
337+
$this->db->insert(
338+
'faqoff_author_contributor',
339+
[
340+
'authorid' => $authorId,
341+
'accountid' => $newId
342+
]
343+
);
344+
}
345+
346+
$this->db->update(
347+
'faqoff_author',
348+
[
349+
'screenname' => $post['screenname'],
350+
'biography' => $post['biography']
351+
],
352+
[
353+
'authorid' => $authorId
354+
]
355+
);
356+
357+
return $this->db->commit();
358+
}
359+
253360
/**
254361
* Update biography.
255362
*

templates/admin/accounts-list.twig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<thead>
1111
<tr>
1212
<th></th>
13+
<th class="min-width">ID</th>
1314
<th>Public ID</th>
1415
<th>Username</th>
1516
<th>Active?</th>
@@ -36,6 +37,9 @@
3637
<i class="fas fa-pencil-alt"></i> Edit
3738
</a>
3839
</td>
40+
<td>
41+
{{ acc.accountid }}
42+
</td>
3943
<td>
4044
{{ acc.public_id|default("No public ID") }}
4145
</td>

0 commit comments

Comments
 (0)