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\Http\RequestHandlers; 21 22use Fisharebest\Webtrees\Http\ViewResponseTrait; 23use Fisharebest\Webtrees\I18N; 24use Fisharebest\Webtrees\Module\FamilyListModule; 25use Fisharebest\Webtrees\Module\IndividualListModule; 26use Fisharebest\Webtrees\Module\MediaListModule; 27use Fisharebest\Webtrees\Module\ModuleAnalyticsInterface; 28use Fisharebest\Webtrees\Module\ModuleBlockInterface; 29use Fisharebest\Webtrees\Module\ModuleChartInterface; 30use Fisharebest\Webtrees\Module\ModuleCustomInterface; 31use Fisharebest\Webtrees\Module\ModuleDataFixInterface; 32use Fisharebest\Webtrees\Module\ModuleFooterInterface; 33use Fisharebest\Webtrees\Module\ModuleHistoricEventsInterface; 34use Fisharebest\Webtrees\Module\ModuleLanguageInterface; 35use Fisharebest\Webtrees\Module\ModuleListInterface; 36use Fisharebest\Webtrees\Module\ModuleMenuInterface; 37use Fisharebest\Webtrees\Module\ModuleReportInterface; 38use Fisharebest\Webtrees\Module\ModuleSidebarInterface; 39use Fisharebest\Webtrees\Module\ModuleTabInterface; 40use Fisharebest\Webtrees\Module\ModuleThemeInterface; 41use Fisharebest\Webtrees\Module\NoteListModule; 42use Fisharebest\Webtrees\Module\RepositoryListModule; 43use Fisharebest\Webtrees\Module\SourceListModule; 44use Fisharebest\Webtrees\Module\SubmitterListModule; 45use Fisharebest\Webtrees\Note; 46use Fisharebest\Webtrees\Repository; 47use Fisharebest\Webtrees\Services\HousekeepingService; 48use Fisharebest\Webtrees\Services\ModuleService; 49use Fisharebest\Webtrees\Services\ServerCheckService; 50use Fisharebest\Webtrees\Services\TreeService; 51use Fisharebest\Webtrees\Services\UpgradeService; 52use Fisharebest\Webtrees\Services\UserService; 53use Fisharebest\Webtrees\Submitter; 54use Fisharebest\Webtrees\Webtrees; 55use Illuminate\Database\Capsule\Manager as DB; 56use Illuminate\Database\Query\Expression; 57use Illuminate\Database\Query\JoinClause; 58use Illuminate\Support\Collection; 59use League\Flysystem\Adapter\Local; 60use League\Flysystem\Filesystem; 61use Psr\Http\Message\ResponseInterface; 62use Psr\Http\Message\ServerRequestInterface; 63use Psr\Http\Server\RequestHandlerInterface; 64 65/** 66 * The control panel shows a summary of the site and links to admin functions. 67 */ 68class ControlPanel implements RequestHandlerInterface 69{ 70 use ViewResponseTrait; 71 72 /** @var ModuleService */ 73 private $module_service; 74 75 /** @var HousekeepingService */ 76 private $housekeeping_service; 77 78 /** @var ServerCheckService */ 79 private $server_check_service; 80 81 /** @var TreeService */ 82 private $tree_service; 83 84 /** @var UpgradeService */ 85 private $upgrade_service; 86 87 /** @var UserService */ 88 private $user_service; 89 90 /** 91 * ControlPanel constructor. 92 * 93 * @param HousekeepingService $housekeeping_service 94 * @param ModuleService $module_service 95 * @param ServerCheckService $server_check_service 96 * @param TreeService $tree_service 97 * @param UpgradeService $upgrade_service 98 * @param UserService $user_service 99 */ 100 public function __construct( 101 HousekeepingService $housekeeping_service, 102 ModuleService $module_service, 103 ServerCheckService $server_check_service, 104 TreeService $tree_service, 105 UpgradeService $upgrade_service, 106 UserService $user_service 107 ) { 108 $this->module_service = $module_service; 109 $this->housekeeping_service = $housekeeping_service; 110 $this->server_check_service = $server_check_service; 111 $this->tree_service = $tree_service; 112 $this->upgrade_service = $upgrade_service; 113 $this->user_service = $user_service; 114 } 115 116 /** 117 * @param ServerRequestInterface $request 118 * 119 * @return ResponseInterface 120 */ 121 public function handle(ServerRequestInterface $request): ResponseInterface 122 { 123 $this->layout = 'layouts/administration'; 124 125 $filesystem = new Filesystem(new Local(Webtrees::ROOT_DIR)); 126 $files_to_delete = $this->housekeeping_service->deleteOldWebtreesFiles($filesystem); 127 128 $custom_updates = $this->module_service 129 ->findByInterface(ModuleCustomInterface::class) 130 ->filter(static function (ModuleCustomInterface $module): bool { 131 return version_compare($module->customModuleLatestVersion(), $module->customModuleVersion()) > 0; 132 }); 133 134 return $this->viewResponse('admin/control-panel', [ 135 'title' => I18N::translate('Control panel'), 136 'server_errors' => $this->server_check_service->serverErrors(), 137 'server_warnings' => $this->server_check_service->serverWarnings(), 138 'latest_version' => $this->upgrade_service->latestVersion(), 139 'all_users' => $this->user_service->all(), 140 'administrators' => $this->user_service->administrators(), 141 'managers' => $this->user_service->managers(), 142 'moderators' => $this->user_service->moderators(), 143 'unapproved' => $this->user_service->unapproved(), 144 'unverified' => $this->user_service->unverified(), 145 'all_trees' => $this->tree_service->all(), 146 'changes' => $this->totalChanges(), 147 'individuals' => $this->totalIndividuals(), 148 'families' => $this->totalFamilies(), 149 'sources' => $this->totalSources(), 150 'media' => $this->totalMediaObjects(), 151 'repositories' => $this->totalRepositories(), 152 'notes' => $this->totalNotes(), 153 'submitters' => $this->totalSubmitters(), 154 'individual_list_module' => $this->module_service->findByInterface(IndividualListModule::class)->first(), 155 'family_list_module' => $this->module_service->findByInterface(FamilyListModule::class)->first(), 156 'media_list_module' => $this->module_service->findByInterface(MediaListModule::class)->first(), 157 'note_list_module' => $this->module_service->findByInterface(NoteListModule::class)->first(), 158 'repository_list_module' => $this->module_service->findByInterface(RepositoryListModule::class)->first(), 159 'source_list_module' => $this->module_service->findByInterface(SourceListModule::class)->first(), 160 'submitter_list_module' => $this->module_service->findByInterface(SubmitterListModule::class)->first(), 161 'files_to_delete' => $files_to_delete, 162 'all_modules_disabled' => $this->module_service->all(true), 163 'all_modules_enabled' => $this->module_service->all(), 164 'deleted_modules' => $this->module_service->deletedModules(), 165 'analytics_modules_disabled' => $this->module_service->findByInterface(ModuleAnalyticsInterface::class, true), 166 'analytics_modules_enabled' => $this->module_service->findByInterface(ModuleAnalyticsInterface::class), 167 'block_modules_disabled' => $this->module_service->findByInterface(ModuleBlockInterface::class, true), 168 'block_modules_enabled' => $this->module_service->findByInterface(ModuleBlockInterface::class), 169 'chart_modules_disabled' => $this->module_service->findByInterface(ModuleChartInterface::class, true), 170 'chart_modules_enabled' => $this->module_service->findByInterface(ModuleChartInterface::class), 171 'custom_updates' => $custom_updates, 172 'data_fix_modules_disabled' => $this->module_service->findByInterface(ModuleDataFixInterface::class, true), 173 'data_fix_modules_enabled' => $this->module_service->findByInterface(ModuleDataFixInterface::class), 174 'other_modules' => $this->module_service->otherModules(true), 175 'footer_modules_disabled' => $this->module_service->findByInterface(ModuleFooterInterface::class, true), 176 'footer_modules_enabled' => $this->module_service->findByInterface(ModuleFooterInterface::class), 177 'history_modules_disabled' => $this->module_service->findByInterface(ModuleHistoricEventsInterface::class, true), 178 'history_modules_enabled' => $this->module_service->findByInterface(ModuleHistoricEventsInterface::class), 179 'language_modules_disabled' => $this->module_service->findByInterface(ModuleLanguageInterface::class, true), 180 'language_modules_enabled' => $this->module_service->findByInterface(ModuleLanguageInterface::class), 181 'list_modules_disabled' => $this->module_service->findByInterface(ModuleListInterface::class, true), 182 'list_modules_enabled' => $this->module_service->findByInterface(ModuleListInterface::class), 183 'menu_modules_disabled' => $this->module_service->findByInterface(ModuleMenuInterface::class, true), 184 'menu_modules_enabled' => $this->module_service->findByInterface(ModuleMenuInterface::class), 185 'report_modules_disabled' => $this->module_service->findByInterface(ModuleReportInterface::class, true), 186 'report_modules_enabled' => $this->module_service->findByInterface(ModuleReportInterface::class), 187 'sidebar_modules_disabled' => $this->module_service->findByInterface(ModuleSidebarInterface::class, true), 188 'sidebar_modules_enabled' => $this->module_service->findByInterface(ModuleSidebarInterface::class), 189 'tab_modules_disabled' => $this->module_service->findByInterface(ModuleTabInterface::class, true), 190 'tab_modules_enabled' => $this->module_service->findByInterface(ModuleTabInterface::class), 191 'theme_modules_disabled' => $this->module_service->findByInterface(ModuleThemeInterface::class, true), 192 'theme_modules_enabled' => $this->module_service->findByInterface(ModuleThemeInterface::class), 193 ]); 194 } 195 196 /** 197 * Count the number of pending changes in each tree. 198 * 199 * @return string[] 200 */ 201 private function totalChanges(): array 202 { 203 return DB::table('gedcom') 204 ->leftJoin('change', static function (JoinClause $join): void { 205 $join 206 ->on('change.gedcom_id', '=', 'gedcom.gedcom_id') 207 ->where('change.status', '=', 'pending'); 208 }) 209 ->groupBy(['gedcom.gedcom_id']) 210 ->pluck(new Expression('COUNT(change_id) AS aggregate'), 'gedcom.gedcom_id') 211 ->all(); 212 } 213 214 /** 215 * Count the number of individuals in each tree. 216 * 217 * @return Collection<string,int> 218 */ 219 private function totalIndividuals(): Collection 220 { 221 return DB::table('gedcom') 222 ->leftJoin('individuals', 'i_file', '=', 'gedcom_id') 223 ->groupBy(['gedcom_id']) 224 ->pluck(new Expression('COUNT(i_id) AS aggregate'), 'gedcom_id') 225 ->map(static function (string $count) { 226 return (int) $count; 227 }); 228 } 229 230 /** 231 * Count the number of families in each tree. 232 * 233 * @return Collection<string,int> 234 */ 235 private function totalFamilies(): Collection 236 { 237 return DB::table('gedcom') 238 ->leftJoin('families', 'f_file', '=', 'gedcom_id') 239 ->groupBy(['gedcom_id']) 240 ->pluck(new Expression('COUNT(f_id) AS aggregate'), 'gedcom_id') 241 ->map(static function (string $count) { 242 return (int) $count; 243 }); 244 } 245 246 /** 247 * Count the number of sources in each tree. 248 * 249 * @return Collection<string,int> 250 */ 251 private function totalSources(): Collection 252 { 253 return DB::table('gedcom') 254 ->leftJoin('sources', 's_file', '=', 'gedcom_id') 255 ->groupBy(['gedcom_id']) 256 ->pluck(new Expression('COUNT(s_id) AS aggregate'), 'gedcom_id') 257 ->map(static function (string $count) { 258 return (int) $count; 259 }); 260 } 261 262 /** 263 * Count the number of media objects in each tree. 264 * 265 * @return Collection<string,int> 266 */ 267 private function totalMediaObjects(): Collection 268 { 269 return DB::table('gedcom') 270 ->leftJoin('media', 'm_file', '=', 'gedcom_id') 271 ->groupBy(['gedcom_id']) 272 ->pluck(new Expression('COUNT(m_id) AS aggregate'), 'gedcom_id') 273 ->map(static function (string $count) { 274 return (int) $count; 275 }); 276 } 277 278 /** 279 * Count the number of repositorie in each tree. 280 * 281 * @return Collection<string,int> 282 */ 283 private function totalRepositories(): Collection 284 { 285 return DB::table('gedcom') 286 ->leftJoin('other', static function (JoinClause $join): void { 287 $join 288 ->on('o_file', '=', 'gedcom_id') 289 ->where('o_type', '=', Repository::RECORD_TYPE); 290 }) 291 ->groupBy(['gedcom_id']) 292 ->pluck(new Expression('COUNT(o_id) AS aggregate'), 'gedcom_id') 293 ->map(static function (string $count) { 294 return (int) $count; 295 }); 296 } 297 298 /** 299 * Count the number of notes in each tree. 300 * 301 * @return Collection<string,int> 302 */ 303 private function totalNotes(): Collection 304 { 305 return DB::table('gedcom') 306 ->leftJoin('other', static function (JoinClause $join): void { 307 $join 308 ->on('o_file', '=', 'gedcom_id') 309 ->where('o_type', '=', Note::RECORD_TYPE); 310 }) 311 ->groupBy(['gedcom_id']) 312 ->pluck(new Expression('COUNT(o_id) AS aggregate'), 'gedcom_id') 313 ->map(static function (string $count) { 314 return (int) $count; 315 }); 316 } 317 318 /** 319 * Count the number of submitters in each tree. 320 * 321 * @return Collection<string,int> 322 */ 323 private function totalSubmitters(): Collection 324 { 325 return DB::table('gedcom') 326 ->leftJoin('other', static function (JoinClause $join): void { 327 $join 328 ->on('o_file', '=', 'gedcom_id') 329 ->where('o_type', '=', Submitter::RECORD_TYPE); 330 }) 331 ->groupBy(['gedcom_id']) 332 ->pluck(new Expression('COUNT(o_id) AS aggregate'), 'gedcom_id') 333 ->map(static function (string $count) { 334 return (int) $count; 335 }); 336 } 337} 338