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 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Module; 21 22use Fisharebest\Localization\Locale\LocaleInterface; 23use Fisharebest\Webtrees\Auth; 24use Fisharebest\Webtrees\Carbon; 25use Fisharebest\Webtrees\GedcomRecord; 26use Fisharebest\Webtrees\Http\RequestHandlers\PendingChanges; 27use Fisharebest\Webtrees\I18N; 28use Fisharebest\Webtrees\Services\EmailService; 29use Fisharebest\Webtrees\Services\TreeService; 30use Fisharebest\Webtrees\Services\UserService; 31use Fisharebest\Webtrees\Site; 32use Fisharebest\Webtrees\SiteUser; 33use Fisharebest\Webtrees\Tree; 34use Fisharebest\Webtrees\TreeUser; 35use Fisharebest\Webtrees\User; 36use Illuminate\Database\Capsule\Manager as DB; 37use Illuminate\Support\Str; 38use Psr\Http\Message\ServerRequestInterface; 39 40use function assert; 41 42/** 43 * Class ReviewChangesModule 44 */ 45class ReviewChangesModule extends AbstractModule implements ModuleBlockInterface 46{ 47 use ModuleBlockTrait; 48 49 /** @var EmailService */ 50 private $email_service; 51 52 /** @var UserService */ 53 private $user_service; 54 55 /** @var TreeService */ 56 private $tree_service; 57 58 /** 59 * ReviewChangesModule constructor. 60 * 61 * @param EmailService $email_service 62 * @param TreeService $tree_service 63 * @param UserService $user_service 64 */ 65 public function __construct( 66 EmailService $email_service, 67 TreeService $tree_service, 68 UserService $user_service 69 ) { 70 $this->email_service = $email_service; 71 $this->tree_service = $tree_service; 72 $this->user_service = $user_service; 73 } 74 75 /** 76 * How should this module be identified in the control panel, etc.? 77 * 78 * @return string 79 */ 80 public function title(): string 81 { 82 /* I18N: Name of a module */ 83 return I18N::translate('Pending changes'); 84 } 85 86 /** 87 * A sentence describing what this module does. 88 * 89 * @return string 90 */ 91 public function description(): string 92 { 93 /* I18N: Description of the “Pending changes” module */ 94 return I18N::translate('A list of changes that need to be reviewed by a moderator, and email notifications.'); 95 } 96 97 /** 98 * Generate the HTML content of this block. 99 * 100 * @param Tree $tree 101 * @param int $block_id 102 * @param string $context 103 * @param string[] $config 104 * 105 * @return string 106 */ 107 public function getBlock(Tree $tree, int $block_id, string $context, array $config = []): string 108 { 109 $locale = app(ServerRequestInterface::class)->getAttribute('locale'); 110 assert($locale instanceof LocaleInterface); 111 112 $sendmail = (bool) $this->getBlockSetting($block_id, 'sendmail', '1'); 113 $days = (int) $this->getBlockSetting($block_id, 'days', '1'); 114 115 extract($config, EXTR_OVERWRITE); 116 117 $changes_exist = DB::table('change') 118 ->where('status', 'pending') 119 ->exists(); 120 121 if ($changes_exist && $sendmail) { 122 $last_email_timestamp = Carbon::createFromTimestamp((int) Site::getPreference('LAST_CHANGE_EMAIL')); 123 $next_email_timestamp = $last_email_timestamp->addDays($days); 124 125 // There are pending changes - tell moderators/managers/administrators about them. 126 if ($next_email_timestamp < Carbon::now()) { 127 // Which users have pending changes? 128 foreach ($this->user_service->all() as $user) { 129 if ($user->getPreference(User::PREF_CONTACT_METHOD) !== 'none') { 130 foreach ($this->tree_service->all() as $tmp_tree) { 131 if ($tmp_tree->hasPendingEdit() && Auth::isManager($tmp_tree, $user)) { 132 I18N::init($user->getPreference(User::PREF_LANGUAGE)); 133 134 $this->email_service->send( 135 new SiteUser(), 136 $user, 137 new TreeUser($tmp_tree), 138 I18N::translate('Pending changes'), 139 view('emails/pending-changes-text', [ 140 'tree' => $tmp_tree, 141 'user' => $user, 142 ]), 143 view('emails/pending-changes-html', [ 144 'tree' => $tmp_tree, 145 'user' => $user, 146 ]) 147 ); 148 I18N::init($locale->languageTag()); 149 } 150 } 151 } 152 } 153 Site::setPreference('LAST_CHANGE_EMAIL', (string) Carbon::now()->unix()); 154 } 155 } 156 if (Auth::isEditor($tree) && $tree->hasPendingEdit()) { 157 $content = ''; 158 if (Auth::isModerator($tree)) { 159 $content .= '<a href="' . e(route(PendingChanges::class, ['tree' => $tree->name()])) . '">' . I18N::translate('There are pending changes for you to moderate.') . '</a><br>'; 160 } 161 if ($sendmail) { 162 $last_email_timestamp = Carbon::createFromTimestamp((int) Site::getPreference('LAST_CHANGE_EMAIL')); 163 $next_email_timestamp = $last_email_timestamp->copy()->addDays($days); 164 165 $content .= I18N::translate('Last email reminder was sent ') . view('components/datetime', ['timestamp' => $last_email_timestamp]) . '<br>'; 166 $content .= I18N::translate('Next email reminder will be sent after ') . view('components/datetime', ['timestamp' => $next_email_timestamp]) . '<br><br>'; 167 } 168 $content .= '<ul>'; 169 170 $changes = DB::table('change') 171 ->where('gedcom_id', '=', $tree->id()) 172 ->where('status', '=', 'pending') 173 ->select(['xref']) 174 ->get(); 175 176 foreach ($changes as $change) { 177 $record = GedcomRecord::getInstance($change->xref, $tree); 178 if ($record->canShow()) { 179 $content .= '<li><a href="' . e($record->url()) . '">' . $record->fullName() . '</a></li>'; 180 } 181 } 182 $content .= '</ul>'; 183 184 if ($context !== self::CONTEXT_EMBED) { 185 return view('modules/block-template', [ 186 'block' => Str::kebab($this->name()), 187 'id' => $block_id, 188 'config_url' => $this->configUrl($tree, $context, $block_id), 189 'title' => $this->title(), 190 'content' => $content, 191 ]); 192 } 193 194 return $content; 195 } 196 197 return ''; 198 } 199 200 /** 201 * Should this block load asynchronously using AJAX? 202 * 203 * Simple blocks are faster in-line, more complex ones can be loaded later. 204 * 205 * @return bool 206 */ 207 public function loadAjax(): bool 208 { 209 return false; 210 } 211 212 /** 213 * Can this block be shown on the user’s home page? 214 * 215 * @return bool 216 */ 217 public function isUserBlock(): bool 218 { 219 return true; 220 } 221 222 /** 223 * Can this block be shown on the tree’s home page? 224 * 225 * @return bool 226 */ 227 public function isTreeBlock(): bool 228 { 229 return true; 230 } 231 232 /** 233 * Update the configuration for a block. 234 * 235 * @param ServerRequestInterface $request 236 * @param int $block_id 237 * 238 * @return void 239 */ 240 public function saveBlockConfiguration(ServerRequestInterface $request, int $block_id): void 241 { 242 $params = $request->getParsedBody(); 243 244 $this->setBlockSetting($block_id, 'days', $params['days']); 245 $this->setBlockSetting($block_id, 'sendmail', $params['sendmail']); 246 } 247 248 /** 249 * An HTML form to edit block settings 250 * 251 * @param Tree $tree 252 * @param int $block_id 253 * 254 * @return string 255 */ 256 public function editBlockConfiguration(Tree $tree, int $block_id): string 257 { 258 $sendmail = $this->getBlockSetting($block_id, 'sendmail', '1'); 259 $days = $this->getBlockSetting($block_id, 'days', '1'); 260 261 return view('modules/review_changes/config', [ 262 'days' => $days, 263 'sendmail' => $sendmail, 264 ]); 265 } 266} 267