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    /**
41     * @var Tree
42     */
43    private $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 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