Passed
Push — master ( 1601e8...0a41a5 )
by Dispositif
05:46
created

GoogleLivresTemplate::arrayKeysToLower()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 8
rs 10
1
<?php
2
/**
3
 * This file is part of dispositif/wikibot application
4
 * 2019 : Philippe M. <[email protected]>
5
 * For the full copyright and MIT license information, please view the LICENSE file.
6
 */
7
8
declare(strict_types=1);
9
10
namespace App\Domain\Models\Wiki;
11
12
use DomainException;
13
use Exception;
14
15
/**
16
 * https://fr.wikipedia.org/wiki/Mod%C3%A8le:Google_Livres
17
 * Le premier paramètre (ou id) est obligatoire. L
18
 * Le deuxième (ou titre) est requis si on ne veut pas fabriquer le lien brut (inclusion {{ouvrage}} 'Lire en ligne')
19
 * Class GoogleLivresTemplate.
20
 */
21
class GoogleLivresTemplate extends AbstractWikiTemplate
22
{
23
    const DEFAULT_GOOGLEBOOK_URL = 'https://books.google.com/books';
24
25
    const ALLOW_USER_ORDER = false;
26
27
    const MODEL_NAME = 'Google Livres';
28
29
    const REQUIRED_PARAMETERS = ['id' => ''];
30
31
    const PARAM_ALIAS
32
        = [
33
            '1' => 'id',
34
            '2' => 'titre',
35
            'surligné' => 'surligne',
36
            'BuchID' => 'id',
37
        ];
38
39
    protected $parametersByOrder
40
        = ['id', 'titre', 'couv', 'page', 'romain', 'page autre', 'surligne'];
41
42
    /**
43
     * Serialize the wiki-template.
44
     * Improvement : force param order : id/titre/...
45
     *
46
     * @param bool|null $cleanOrder
47
     *
48
     * @return string
49
     */
50
    public function serialize(?bool $cleanOrder = true): string
51
    {
52
        $text = parent::serialize();
53
54
        // Documentation suggère non affichage de ces 2 paramètres
55
        return str_replace(['id=', 'titre='], '', $text);
56
    }
57
58
    /**
59
     * Create {Google Book} from URL.
60
     * See also https://fr.wikipedia.org/wiki/Utilisateur:Jack_ma/GB
61
     * https://stackoverflow.com/questions/11584551/need-information-on-query-parameters-for-google-books-e-g-difference-between-d.
62
     *
63
     * @param string $url
64
     *
65
     * @return GoogleLivresTemplate|null
66
     * @throws Exception
67
     */
68
    public static function createFromURL(string $url): ?self
69
    {
70
        if (!self::isGoogleBookURL($url)) {
71
            throw new DomainException('not a Google Book URL');
72
        }
73
        $gooDat = self::parseGoogleBookQuery($url);
74
75
        if (empty($gooDat['id'])) {
76
            throw new DomainException("no GoogleBook 'id' in URL");
77
        }
78
79
        $data = self::mapGooData($gooDat);
80
81
        $templ = new self();
82
        $templ->hydrate($data);
83
84
        return $templ;
85
    }
86
87
    /**
88
     * Mapping Google URL data to {Google Livres} data.
89
     *
90
     * @param array $gooData
91
     *
92
     * @return array
93
     */
94
    private static function mapGooData(array $gooData): array
95
    {
96
        $data = [];
97
        $data['id'] = $gooData['id'];
98
99
        // show cover ?
100
        if (isset($gooData['printsec']) && 'frontcover' === $gooData['printsec']) {
101
            $data['couv'] = '1';
102
        }
103
104
        // page number
105
        if (!empty($gooData['pg'])) {
106
            $data['page autre'] = $gooData['pg'];
107
108
            //  pg=PAx => "page=x"
109
            if (preg_match('/^PA([0-9]+)$/', $gooData['pg'], $matches) > 0) {
110
                $data['page'] = $matches[1];
111
                unset($data['page autre']);
112
            }
113
            //  pg=PRx => "page=x|romain=1"
114
            if (preg_match('/^PR([0-9]+)$/', $gooData['pg'], $matches) > 0) {
115
                $data['page'] = $matches[1];
116
                $data['romain'] = '1';
117
                unset($data['page autre']);
118
            }
119
        }
120
121
        if (!empty($gooData['dq']) || !empty($gooData['q'])) {
122
            $data['surligne'] = $gooData['dq'] ?? $gooData['q'];
123
            $data['surligne'] = self::googleUrlEncode($data['surligne']);
124
        }
125
126
        return $data;
127
    }
128
129
    /**
130
     * Instead of url_encode(). No UTF-8 encoding.
131
     */
132
    private static function googleUrlEncode(string $str): string
133
    {
134
        return str_replace(' ', '+', trim(urldecode($str)));
135
    }
136
137
    /**
138
     * Clean the google book URL from optional&tracking data.
139
     *
140
     * @param string $url
141
     *
142
     * @return string URL
143
     */
144
    public static function simplifyGoogleUrl(string $url): string
145
    {
146
        if (!self::isGoogleBookURL($url)) {
147
            throw new DomainException('not a Google Book URL');
148
        }
149
150
        $gooDat = self::parseGoogleBookQuery($url);
151
        if (empty($gooDat['id'])) {
152
            throw new DomainException("no GoogleBook 'id' in URL");
153
        }
154
155
        $dat = [];
156
        // keep only a few parameters (+'q' ?)
157
        $keeps = ['id', 'pg', 'printsec', 'dq'];
158
        foreach ($keeps as $keep) {
159
            if (!empty($gooDat[$keep])) {
160
                $dat[$keep] = $gooDat[$keep];
161
            }
162
        }
163
164
        $googleURL = self::DEFAULT_GOOGLEBOOK_URL;
165
166
        // domain .com .fr
167
        $gooDomain = self::parseGoogleDomain($url);
168
        if ($gooDomain) {
169
            $googleURL = str_replace('.com', $gooDomain, $googleURL);
170
        }
171
172
        return $googleURL.'?'.http_build_query($dat);
173
    }
174
175
    /**
176
     * Parse URL argument from ?query and #fragment.
177
     *
178
     * @param string $url
179
     *
180
     * @return array
181
     */
182
    private static function parseGoogleBookQuery(string $url): array
183
    {
184
        // Note : Also datas in URL after the '#' !!! (URL fragment)
185
        $queryData = parse_url($url, PHP_URL_QUERY); // after ?
186
        $fragmentData = parse_url($url, PHP_URL_FRAGMENT); // after #
187
        // queryData precedence over fragmentData
188
        parse_str(implode('&', [$fragmentData, $queryData]), $val);
189
190
        return self::arrayKeysToLower($val);
191
    }
192
193
    /**
194
     * Set all array keys to lower case.
195
     * TODO: move to ArrayProcessTrait ?
196
     *
197
     * @param array $array
198
     *
199
     * @return array
200
     */
201
    private static function arrayKeysToLower(array $array): array
202
    {
203
        $res = [];
204
        foreach ($array as $key => $val) {
205
            $res[strtolower($key)] = $val;
206
        }
207
208
        return $res;
209
    }
210
211
    /**
212
     * return '.fr' or '.com'.
213
     *
214
     * @param string $url
215
     *
216
     * @return string|null
217
     */
218
    private static function parseGoogleDomain(string $url): ?string
219
    {
220
        $host = parse_url($url, PHP_URL_HOST);
221
        if (!empty($host) && preg_match('#\.[a-z]{2,3}$#', $host, $matches) > 0) {
222
            return $matches[0]; // .fr
223
        }
224
225
        return null;
226
    }
227
228
    /**
229
     * Check google URL pattern.
230
     *
231
     * @param string $text
232
     *
233
     * @return bool
234
     */
235
    public static function isGoogleBookURL(string $text): bool
236
    {
237
        if (preg_match('#^https?://(books|play)\.google\.[a-z]{2,3}/(books)?(/reader)?\?id=#i', $text) > 0) {
238
            return true;
239
        }
240
241
        return false;
242
    }
243
244
    /**
245
     * Check if Google URL or wiki {Google Books} template.
246
     *
247
     * @param string $text
248
     *
249
     * @return bool
250
     */
251
    public static function isGoogleBookValue(string $text): bool
252
    {
253
        if (true === self::isGoogleBookURL($text)) {
254
            return true;
255
        }
256
        if (preg_match('#^{{[ \n]*Google (Livres|Books)[^}]+}}$#i', $text) > 0) {
257
            return true;
258
        }
259
260
        return false;
261
    }
262
}
263