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\Statistics\Repository; 21 22use Fisharebest\Webtrees\I18N; 23use Fisharebest\Webtrees\Statistics\Google\ChartMedia; 24use Fisharebest\Webtrees\Statistics\Repository\Interfaces\MediaRepositoryInterface; 25use Fisharebest\Webtrees\Tree; 26use Illuminate\Database\Capsule\Manager as DB; 27use Illuminate\Database\Query\Builder; 28 29use function array_slice; 30use function count; 31use function in_array; 32 33/** 34 * A repository providing methods for media type related statistics. 35 */ 36class MediaRepository implements MediaRepositoryInterface 37{ 38 /** 39 * @var Tree 40 */ 41 private $tree; 42 43 /** 44 * Available media types. 45 */ 46 private const MEDIA_TYPE_ALL = 'all'; 47 private const MEDIA_TYPE_AUDIO = 'audio'; 48 private const MEDIA_TYPE_BOOK = 'book'; 49 private const MEDIA_TYPE_CARD = 'card'; 50 private const MEDIA_TYPE_CERTIFICATE = 'certificate'; 51 private const MEDIA_TYPE_COAT = 'coat'; 52 private const MEDIA_TYPE_DOCUMENT = 'document'; 53 private const MEDIA_TYPE_ELECTRONIC = 'electronic'; 54 private const MEDIA_TYPE_FICHE = 'fiche'; 55 private const MEDIA_TYPE_FILM = 'film'; 56 private const MEDIA_TYPE_MAGAZINE = 'magazine'; 57 private const MEDIA_TYPE_MANUSCRIPT = 'manuscript'; 58 private const MEDIA_TYPE_MAP = 'map'; 59 private const MEDIA_TYPE_NEWSPAPER = 'newspaper'; 60 private const MEDIA_TYPE_PAINTING = 'painting'; 61 private const MEDIA_TYPE_PHOTO = 'photo'; 62 private const MEDIA_TYPE_TOMBSTONE = 'tombstone'; 63 private const MEDIA_TYPE_VIDEO = 'video'; 64 private const MEDIA_TYPE_OTHER = 'other'; 65 private const MEDIA_TYPE_UNKNOWN = 'unknown'; 66 67 /** 68 * List of GEDCOM media types. 69 * 70 * @var string[] 71 */ 72 private const MEDIA_TYPES = [ 73 self::MEDIA_TYPE_AUDIO, 74 self::MEDIA_TYPE_BOOK, 75 self::MEDIA_TYPE_CARD, 76 self::MEDIA_TYPE_CERTIFICATE, 77 self::MEDIA_TYPE_COAT, 78 self::MEDIA_TYPE_DOCUMENT, 79 self::MEDIA_TYPE_ELECTRONIC, 80 self::MEDIA_TYPE_FICHE, 81 self::MEDIA_TYPE_FILM, 82 self::MEDIA_TYPE_MAGAZINE, 83 self::MEDIA_TYPE_MANUSCRIPT, 84 self::MEDIA_TYPE_MAP, 85 self::MEDIA_TYPE_NEWSPAPER, 86 self::MEDIA_TYPE_PAINTING, 87 self::MEDIA_TYPE_PHOTO, 88 self::MEDIA_TYPE_TOMBSTONE, 89 self::MEDIA_TYPE_VIDEO, 90 self::MEDIA_TYPE_OTHER, 91 ]; 92 93 /** 94 * Constructor. 95 * 96 * @param Tree $tree 97 */ 98 public function __construct(Tree $tree) 99 { 100 $this->tree = $tree; 101 } 102 103 /** 104 * Returns the number of media records of the given type. 105 * 106 * @param string $type The media type to query 107 * 108 * @return int 109 */ 110 private function totalMediaTypeQuery(string $type): int 111 { 112 if ($type !== self::MEDIA_TYPE_ALL && $type !== self::MEDIA_TYPE_UNKNOWN && !in_array($type, self::MEDIA_TYPES, true)) { 113 return 0; 114 } 115 116 $query = DB::table('media') 117 ->where('m_file', '=', $this->tree->id()); 118 119 if ($type !== self::MEDIA_TYPE_ALL) { 120 if ($type === self::MEDIA_TYPE_UNKNOWN) { 121 // There has to be a better way then this :( 122 foreach (self::MEDIA_TYPES as $t) { 123 // Use function to add brackets 124 $query->where(static function (Builder $query) use ($t): void { 125 $query->where('m_gedcom', 'not like', '%3 TYPE ' . $t . '%') 126 ->where('m_gedcom', 'not like', '%1 _TYPE ' . $t . '%'); 127 }); 128 } 129 } else { 130 // Use function to add brackets 131 $query->where(static function (Builder $query) use ($type): void { 132 $query->where('m_gedcom', 'like', '%3 TYPE ' . $type . '%') 133 ->orWhere('m_gedcom', 'like', '%1 _TYPE ' . $type . '%'); 134 }); 135 } 136 } 137 138 return $query->count(); 139 } 140 141 /** 142 * @return string 143 */ 144 public function totalMedia(): string 145 { 146 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_ALL)); 147 } 148 149 /** 150 * @return string 151 */ 152 public function totalMediaAudio(): string 153 { 154 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_AUDIO)); 155 } 156 157 /** 158 * @return string 159 */ 160 public function totalMediaBook(): string 161 { 162 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_BOOK)); 163 } 164 165 /** 166 * @return string 167 */ 168 public function totalMediaCard(): string 169 { 170 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_CARD)); 171 } 172 173 /** 174 * @return string 175 */ 176 public function totalMediaCertificate(): string 177 { 178 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_CERTIFICATE)); 179 } 180 181 /** 182 * @return string 183 */ 184 public function totalMediaCoatOfArms(): string 185 { 186 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_COAT)); 187 } 188 189 /** 190 * @return string 191 */ 192 public function totalMediaDocument(): string 193 { 194 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_DOCUMENT)); 195 } 196 197 /** 198 * @return string 199 */ 200 public function totalMediaElectronic(): string 201 { 202 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_ELECTRONIC)); 203 } 204 205 /** 206 * @return string 207 */ 208 public function totalMediaFiche(): string 209 { 210 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_FICHE)); 211 } 212 213 /** 214 * @return string 215 */ 216 public function totalMediaFilm(): string 217 { 218 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_FILM)); 219 } 220 221 /** 222 * @return string 223 */ 224 public function totalMediaMagazine(): string 225 { 226 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_MAGAZINE)); 227 } 228 229 /** 230 * @return string 231 */ 232 public function totalMediaManuscript(): string 233 { 234 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_MANUSCRIPT)); 235 } 236 237 /** 238 * @return string 239 */ 240 public function totalMediaMap(): string 241 { 242 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_MAP)); 243 } 244 245 /** 246 * @return string 247 */ 248 public function totalMediaNewspaper(): string 249 { 250 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_NEWSPAPER)); 251 } 252 253 /** 254 * @return string 255 */ 256 public function totalMediaPainting(): string 257 { 258 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_PAINTING)); 259 } 260 261 /** 262 * @return string 263 */ 264 public function totalMediaPhoto(): string 265 { 266 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_PHOTO)); 267 } 268 269 /** 270 * @return string 271 */ 272 public function totalMediaTombstone(): string 273 { 274 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_TOMBSTONE)); 275 } 276 277 /** 278 * @return string 279 */ 280 public function totalMediaVideo(): string 281 { 282 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_VIDEO)); 283 } 284 285 /** 286 * @return string 287 */ 288 public function totalMediaOther(): string 289 { 290 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_OTHER)); 291 } 292 293 /** 294 * @return string 295 */ 296 public function totalMediaUnknown(): string 297 { 298 return I18N::number($this->totalMediaTypeQuery(self::MEDIA_TYPE_UNKNOWN)); 299 } 300 301 /** 302 * Returns a sorted list of media types and their total counts. 303 * 304 * @param int $tot The total number of media files 305 * 306 * @return array<string,int> 307 */ 308 private function getSortedMediaTypeList(int $tot): array 309 { 310 $media = []; 311 $c = 0; 312 $max = 0; 313 314 foreach (self::MEDIA_TYPES as $type) { 315 $count = $this->totalMediaTypeQuery($type); 316 317 if ($count > 0) { 318 $media[$type] = $count; 319 320 if ($count > $max) { 321 $max = $count; 322 } 323 324 $c += $count; 325 } 326 } 327 328 $count = $this->totalMediaTypeQuery(self::MEDIA_TYPE_UNKNOWN); 329 if ($count > 0) { 330 $media[self::MEDIA_TYPE_UNKNOWN] = $tot - $c; 331 if ($tot - $c > $max) { 332 $max = $count; 333 } 334 } 335 336 if (count($media) > 10 && ($max / $tot) > 0.6) { 337 arsort($media); 338 $media = array_slice($media, 0, 10); 339 $c = $tot; 340 341 foreach ($media as $cm) { 342 $c -= $cm; 343 } 344 345 if (isset($media[self::MEDIA_TYPE_OTHER])) { 346 $media[self::MEDIA_TYPE_OTHER] += $c; 347 } else { 348 $media[self::MEDIA_TYPE_OTHER] = $c; 349 } 350 } 351 352 asort($media); 353 354 return $media; 355 } 356 357 /** 358 * @param string|null $color_from 359 * @param string|null $color_to 360 * 361 * @return string 362 */ 363 public function chartMedia(string $color_from = null, string $color_to = null): string 364 { 365 $tot = $this->totalMediaTypeQuery(self::MEDIA_TYPE_ALL); 366 $media = $this->getSortedMediaTypeList($tot); 367 368 return (new ChartMedia()) 369 ->chartMedia($media, $color_from, $color_to); 370 } 371} 372