1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2021 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 <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Module; 21 22use Fisharebest\Webtrees\Auth; 23use Fisharebest\Webtrees\Http\Exceptions\HttpAccessDeniedException; 24use Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException; 25use Fisharebest\Webtrees\Http\RequestHandlers\UserPage; 26use Fisharebest\Webtrees\I18N; 27use Fisharebest\Webtrees\Registry; 28use Fisharebest\Webtrees\Services\HtmlService; 29use Fisharebest\Webtrees\Tree; 30use Illuminate\Database\Capsule\Manager as DB; 31use Illuminate\Database\Query\Expression; 32use Illuminate\Support\Str; 33use Psr\Http\Message\ResponseInterface; 34use Psr\Http\Message\ServerRequestInterface; 35 36use function assert; 37use function is_string; 38use function redirect; 39 40/** 41 * Class UserJournalModule 42 */ 43class UserJournalModule extends AbstractModule implements ModuleBlockInterface 44{ 45 use ModuleBlockTrait; 46 47 private HtmlService $html_service; 48 49 /** 50 * HtmlBlockModule constructor. 51 * 52 * @param HtmlService $html_service 53 */ 54 public function __construct(HtmlService $html_service) 55 { 56 $this->html_service = $html_service; 57 } 58 59 /** 60 * A sentence describing what this module does. 61 * 62 * @return string 63 */ 64 public function description(): string 65 { 66 /* I18N: Description of the “Journal” module */ 67 return I18N::translate('A private area to record notes or keep a journal.'); 68 } 69 70 /** 71 * Generate the HTML content of this block. 72 * 73 * @param Tree $tree 74 * @param int $block_id 75 * @param string $context 76 * @param array<string> $config 77 * 78 * @return string 79 */ 80 public function getBlock(Tree $tree, int $block_id, string $context, array $config = []): string 81 { 82 $articles = DB::table('news') 83 ->where('user_id', '=', Auth::id()) 84 ->orderByDesc('updated') 85 ->get() 86 ->map(static function (object $row): object { 87 $row->updated = Registry::timestampFactory()->fromString($row->updated); 88 89 return $row; 90 }); 91 92 $content = view('modules/user_blog/list', [ 93 'articles' => $articles, 94 'block_id' => $block_id, 95 'limit' => 5, 96 'tree' => $tree, 97 ]); 98 99 if ($context !== self::CONTEXT_EMBED) { 100 return view('modules/block-template', [ 101 'block' => Str::kebab($this->name()), 102 'id' => $block_id, 103 'config_url' => '', 104 'title' => $this->title(), 105 'content' => $content, 106 ]); 107 } 108 109 return $content; 110 } 111 112 /** 113 * How should this module be identified in the control panel, etc.? 114 * 115 * @return string 116 */ 117 public function title(): string 118 { 119 /* I18N: Name of a module */ 120 return I18N::translate('Journal'); 121 } 122 123 /** 124 * Should this block load asynchronously using AJAX? 125 * 126 * Simple blocks are faster in-line, more complex ones can be loaded later. 127 * 128 * @return bool 129 */ 130 public function loadAjax(): bool 131 { 132 return false; 133 } 134 135 /** 136 * Can this block be shown on the user’s home page? 137 * 138 * @return bool 139 */ 140 public function isUserBlock(): bool 141 { 142 return true; 143 } 144 145 /** 146 * Can this block be shown on the tree’s home page? 147 * 148 * @return bool 149 */ 150 public function isTreeBlock(): bool 151 { 152 return false; 153 } 154 155 /** 156 * @param ServerRequestInterface $request 157 * 158 * @return ResponseInterface 159 */ 160 public function getEditJournalAction(ServerRequestInterface $request): ResponseInterface 161 { 162 $tree = $request->getAttribute('tree'); 163 assert($tree instanceof Tree); 164 165 if (!Auth::check()) { 166 throw new HttpAccessDeniedException(); 167 } 168 169 $news_id = $request->getQueryParams()['news_id'] ?? ''; 170 171 if ($news_id !== '') { 172 $row = DB::table('news') 173 ->where('news_id', '=', $news_id) 174 ->where('user_id', '=', Auth::id()) 175 ->first(); 176 177 // Record was deleted before we could read it? 178 if ($row === null) { 179 throw new HttpNotFoundException(I18N::translate('%s does not exist.', 'news_id:' . $news_id)); 180 } 181 } else { 182 $row = (object)['body' => '', 'subject' => '']; 183 } 184 185 $title = I18N::translate('Add/edit a journal/news entry'); 186 187 return $this->viewResponse('modules/user_blog/edit', [ 188 'body' => $row->body, 189 'news_id' => $news_id, 190 'subject' => $row->subject, 191 'title' => $title, 192 'tree' => $tree, 193 ]); 194 } 195 196 /** 197 * @param ServerRequestInterface $request 198 * 199 * @return ResponseInterface 200 */ 201 public function postEditJournalAction(ServerRequestInterface $request): ResponseInterface 202 { 203 $tree = $request->getAttribute('tree'); 204 assert($tree instanceof Tree); 205 206 if (!Auth::check()) { 207 throw new HttpAccessDeniedException(); 208 } 209 210 $params = (array) $request->getParsedBody(); 211 212 $news_id = $request->getQueryParams()['news_id'] ?? ''; 213 $subject = $params['subject']; 214 $body = $params['body']; 215 216 $subject = $this->html_service->sanitize($subject); 217 $body = $this->html_service->sanitize($body); 218 219 if ($news_id !== '') { 220 DB::table('news') 221 ->where('news_id', '=', $news_id) 222 ->where('user_id', '=', Auth::id()) 223 ->update([ 224 'body' => $body, 225 'subject' => $subject, 226 'updated' => new Expression('updated'), // See issue #3208 227 ]); 228 } else { 229 DB::table('news')->insert([ 230 'body' => $body, 231 'subject' => $subject, 232 'user_id' => Auth::id(), 233 ]); 234 } 235 236 $url = route(UserPage::class, ['tree' => $tree->name()]); 237 238 return redirect($url); 239 } 240 241 /** 242 * @param ServerRequestInterface $request 243 * 244 * @return ResponseInterface 245 */ 246 public function postDeleteJournalAction(ServerRequestInterface $request): ResponseInterface 247 { 248 $tree = $request->getAttribute('tree'); 249 assert($tree instanceof Tree); 250 251 $news_id = $request->getQueryParams()['news_id']; 252 253 DB::table('news') 254 ->where('news_id', '=', $news_id) 255 ->where('user_id', '=', Auth::id()) 256 ->delete(); 257 258 $url = route(UserPage::class, ['tree' => $tree->name()]); 259 260 return redirect($url); 261 } 262} 263