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