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