xref: /webtrees/app/Statistics/Repository/MediaRepository.php (revision 2ebcf907ed34213f816592af04e6c160335d6311)
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\Statistics\Service\ColorService;
26use Fisharebest\Webtrees\Tree;
27use Illuminate\Database\Capsule\Manager as DB;
28use Illuminate\Database\Query\Builder;
29
30use function array_slice;
31use function arsort;
32use function asort;
33use function count;
34use function in_array;
35
36/**
37 * A repository providing methods for media type related statistics.
38 */
39class MediaRepository implements MediaRepositoryInterface
40{
41    private ColorService $color_service;
42
43    private Tree $tree;
44
45    /**
46     * Available media types.
47     */
48    private const MEDIA_TYPE_ALL         = 'all';
49    private const MEDIA_TYPE_AUDIO       = 'audio';
50    private const MEDIA_TYPE_BOOK        = 'book';
51    private const MEDIA_TYPE_CARD        = 'card';
52    private const MEDIA_TYPE_CERTIFICATE = 'certificate';
53    private const MEDIA_TYPE_COAT        = 'coat';
54    private const MEDIA_TYPE_DOCUMENT    = 'document';
55    private const MEDIA_TYPE_ELECTRONIC  = 'electronic';
56    private const MEDIA_TYPE_FICHE       = 'fiche';
57    private const MEDIA_TYPE_FILM        = 'film';
58    private const MEDIA_TYPE_MAGAZINE    = 'magazine';
59    private const MEDIA_TYPE_MANUSCRIPT  = 'manuscript';
60    private const MEDIA_TYPE_MAP         = 'map';
61    private const MEDIA_TYPE_NEWSPAPER   = 'newspaper';
62    private const MEDIA_TYPE_PAINTING    = 'painting';
63    private const MEDIA_TYPE_PHOTO       = 'photo';
64    private const MEDIA_TYPE_TOMBSTONE   = 'tombstone';
65    private const MEDIA_TYPE_VIDEO       = 'video';
66    private const MEDIA_TYPE_OTHER       = 'other';
67    private const MEDIA_TYPE_UNKNOWN     = 'unknown';
68
69    /**
70     * List of GEDCOM media types.
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     * @param ColorService $color_service
95     * @param Tree         $tree
96     */
97    public function __construct(ColorService $color_service, Tree $tree)
98    {
99        $this->color_service = $color_service;
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($this->color_service))
369            ->chartMedia($media, $color_from, $color_to);
370    }
371}
372