xref: /webtrees/app/Module/FamilyTreeNewsModule.php (revision 5229eade2a8cdd1381e19f96f67fc1c8b92ca95d)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2019 webtrees development team
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17declare(strict_types=1);
18
19namespace Fisharebest\Webtrees\Module;
20
21use Fisharebest\Webtrees\Auth;
22use Fisharebest\Webtrees\Carbon;
23use Fisharebest\Webtrees\I18N;
24use Fisharebest\Webtrees\Services\HtmlService;
25use Fisharebest\Webtrees\Tree;
26use Illuminate\Database\Capsule\Manager as DB;
27use Illuminate\Support\Str;
28use InvalidArgumentException;
29use Psr\Http\Message\ResponseInterface;
30use Psr\Http\Message\ServerRequestInterface;
31use stdClass;
32use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
33use function assert;
34
35/**
36 * Class FamilyTreeNewsModule
37 */
38class FamilyTreeNewsModule extends AbstractModule implements ModuleBlockInterface
39{
40    use ModuleBlockTrait;
41
42    /** @var HtmlService */
43    private $html_service;
44
45    /**
46     * HtmlBlockModule bootstrap.
47     *
48     * @param HtmlService $html_service
49     */
50    public function boot(HtmlService $html_service)
51    {
52        $this->html_service = $html_service;
53    }
54
55    /**
56     * A sentence describing what this module does.
57     *
58     * @return string
59     */
60    public function description(): string
61    {
62        /* I18N: Description of the “News” module */
63        return I18N::translate('Family news and site announcements.');
64    }
65
66    /**
67     * Generate the HTML content of this block.
68     *
69     * @param Tree     $tree
70     * @param int      $block_id
71     * @param string   $context
72     * @param string[] $config
73     *
74     * @return string
75     */
76    public function getBlock(Tree $tree, int $block_id, string $context, array $config = []): string
77    {
78        $articles = DB::table('news')
79            ->where('gedcom_id', '=', $tree->id())
80            ->orderByDesc('updated')
81            ->get()
82            ->map(static function (stdClass $row): stdClass {
83                $row->updated = Carbon::make($row->updated);
84
85                return $row;
86            });
87
88        $content = view('modules/gedcom_news/list', [
89            'articles' => $articles,
90            'block_id' => $block_id,
91            'limit'    => 5,
92        ]);
93
94        if ($context !== self::CONTEXT_EMBED) {
95            return view('modules/block-template', [
96                'block'      => Str::kebab($this->name()),
97                'id'         => $block_id,
98                'config_url' => '',
99                'title'      => $this->title(),
100                'content'    => $content,
101            ]);
102        }
103
104        return $content;
105    }
106
107    /**
108     * How should this module be identified in the control panel, etc.?
109     *
110     * @return string
111     */
112    public function title(): string
113    {
114        /* I18N: Name of a module */
115        return I18N::translate('News');
116    }
117
118    /**
119     * Should this block load asynchronously using AJAX?
120     *
121     * Simple blocks are faster in-line, more complex ones can be loaded later.
122     *
123     * @return bool
124     */
125    public function loadAjax(): bool
126    {
127        return false;
128    }
129
130    /**
131     * Can this block be shown on the user’s home page?
132     *
133     * @return bool
134     */
135    public function isUserBlock(): bool
136    {
137        return false;
138    }
139
140    /**
141     * Can this block be shown on the tree’s home page?
142     *
143     * @return bool
144     */
145    public function isTreeBlock(): bool
146    {
147        return true;
148    }
149
150    /**
151     * @param ServerRequestInterface $request
152     *
153     * @return ResponseInterface
154     */
155    public function getEditNewsAction(ServerRequestInterface $request): ResponseInterface
156    {
157        $tree = $request->getAttribute('tree');
158        assert($tree instanceof Tree, new InvalidArgumentException());
159
160        if (!Auth::isManager($tree)) {
161            throw new AccessDeniedHttpException();
162        }
163
164        $news_id = $request->getQueryParams()['news_id'] ?? '';
165
166        if ($news_id !== '') {
167            $row = DB::table('news')
168                ->where('news_id', '=', $news_id)
169                ->where('gedcom_id', '=', $tree->id())
170                ->first();
171        } else {
172            $row = (object) [
173                'body'    => '',
174                'subject' => '',
175            ];
176        }
177
178        $title = I18N::translate('Add/edit a journal/news entry');
179
180        return $this->viewResponse('modules/gedcom_news/edit', [
181            'body'    => $row->body,
182            'news_id' => $news_id,
183            'subject' => $row->subject,
184            'title'   => $title,
185        ]);
186    }
187
188    /**
189     * @param ServerRequestInterface $request
190     *
191     * @return ResponseInterface
192     */
193    public function postEditNewsAction(ServerRequestInterface $request): ResponseInterface
194    {
195        $tree = $request->getAttribute('tree');
196        assert($tree instanceof Tree, new InvalidArgumentException());
197
198        if (!Auth::isManager($tree)) {
199            throw new AccessDeniedHttpException();
200        }
201
202        $news_id = $request->getQueryParams()['news_id'] ?? '';
203        $subject = $request->getParsedBody()['subject'];
204        $body    = $request->getParsedBody()['body'];
205
206        $subject = $this->html_service->sanitize($subject);
207        $body    = $this->html_service->sanitize($body);
208
209        if ($news_id > 0) {
210            DB::table('news')
211                ->where('news_id', '=', $news_id)
212                ->where('gedcom_id', '=', $tree->id())
213                ->update([
214                    'body'    => $body,
215                    'subject' => $subject,
216                ]);
217        } else {
218            DB::table('news')->insert([
219                'body'      => $body,
220                'subject'   => $subject,
221                'gedcom_id' => $tree->id(),
222            ]);
223        }
224
225        $url = route('tree-page', ['tree' => $tree->name()]);
226
227        return redirect($url);
228    }
229
230    /**
231     * @param ServerRequestInterface $request
232     *
233     * @return ResponseInterface
234     */
235    public function postDeleteNewsAction(ServerRequestInterface $request): ResponseInterface
236    {
237        $tree    = $request->getAttribute('tree');
238        $news_id = $request->getQueryParams()['news_id'];
239
240        if (!Auth::isManager($tree)) {
241            throw new AccessDeniedHttpException();
242        }
243
244        DB::table('news')
245            ->where('news_id', '=', $news_id)
246            ->where('gedcom_id', '=', $tree->id())
247            ->delete();
248
249        $url = route('tree-page', ['tree' => $tree->name()]);
250
251        return redirect($url);
252    }
253}
254