1
+ <?php
2
+ /***************************************************************************
3
+ * Copyright (C) 2021 by Sergei V. Deriabin *
4
+ * *
5
+ * This program is free software; you can redistribute it and/or modify *
6
+ * it under the terms of the GNU Lesser General Public License as *
7
+ * published by the Free Software Foundation; either version 3 of the *
8
+ * License, or (at your option) any later version. *
9
+ * *
10
+ ***************************************************************************/
11
+
12
+ namespace OnPHP \Main \Markup \OGP ;
13
+
14
+ use OnPHP \Core \Base \Assert ;
15
+ use OnPHP \Core \Exception \WrongArgumentException ;
16
+ use OnPHP \Main \Markup \Html \HtmlAssembler ;
17
+ use OnPHP \Main \Markup \Html \SgmlOpenTag ;
18
+
19
+ /**
20
+ * The Open Graph protocol
21
+ * @see https://ogp.me/
22
+ * @see https://developers.facebook.com/docs/sharing/webmasters
23
+ * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards
24
+ *
25
+ * Validators:
26
+ * @see https://cards-dev.twitter.com/validator
27
+ * @see https://www.linkedin.com/post-inspector/inspect/
28
+ * @see https://developers.facebook.com/tools/debug/
29
+ *
30
+ * @ingroup Markup
31
+ * @ingroup OGP
32
+ */
33
+ class OpenGraph
34
+ {
35
+ const OGP_NAMESPACE = ['og ' , 'https://ogp.me/ns# ' ] ;
36
+ const FB_NAMESPACE = ['fb ' , 'https://ogp.me/ns/fb# ' ];
37
+
38
+ const ALLOWED_DATERMINE = ['a ' , 'an ' , 'the ' , 'auto ' ];
39
+
40
+ /**
41
+ * The title of your object as it should appear within the graph, e.g., "The Rock".
42
+ * @var ?string
43
+ */
44
+ protected ?string $ title = null ;
45
+ /**
46
+ * A one to two sentence description of your object.
47
+ * @var ?string
48
+ */
49
+ protected ?string $ description = null ;
50
+ /**
51
+ * The word that appears before this object's title in a sentence.
52
+ * An enum of (a, an, the, "", auto). If auto is chosen, the consumer of your
53
+ * data should chose between "a" or "an". Default is "" (blank).
54
+ * @var string
55
+ */
56
+ protected string $ determiner = '' ;
57
+ /**
58
+ * The locale these tags are marked up in.
59
+ * Of the format language_TERRITORY. Default is en_US.
60
+ * @var string
61
+ */
62
+ protected string $ locale = 'en_US ' ;
63
+ /**
64
+ * An array of other locales this page is available in.
65
+ * @var string[]
66
+ */
67
+ protected array $ localeAlternates = [];
68
+ /**
69
+ * If your object is part of a larger web site, the name which
70
+ * should be displayed for the overall site. e.g., "IMDb".
71
+ * @var ?string
72
+ */
73
+ protected ?string $ siteName = null ;
74
+ /**
75
+ * The type of your object, e.g., object OpenGraphVideo
76
+ * @var ?OpenGraphObject
77
+ */
78
+ protected ?OpenGraphObject $ type = null ;
79
+ /**
80
+ * An image which should represent your object within the graph.
81
+ * @var OpenGraphImage[]
82
+ */
83
+ protected array $ image = [];
84
+ /**
85
+ * @var ?OpenGraphVideo
86
+ */
87
+ protected ?OpenGraphVideo $ video = null ;
88
+ /**
89
+ * Object OpenGraphAudio an audio file to accompany this object.
90
+ * @var ?OpenGraphAudio
91
+ */
92
+ protected ?OpenGraphAudio $ audio = null ;
93
+ /**
94
+ * The canonical URL of your object that will be used as its permanent
95
+ * ID in the graph, e.g., "https://www.imdb.com/title/tt0117500/".
96
+ * @var ?string
97
+ */
98
+ protected ?string $ url = null ;
99
+ /**
100
+ * @var ?string
101
+ */
102
+ protected ?string $ vkImage = null ;
103
+ /**
104
+ * @var ?string
105
+ */
106
+ protected ?string $ appId = null ;
107
+ /**
108
+ * @var ?OpenGraphTwitterCard
109
+ */
110
+ protected ?OpenGraphTwitterCard $ twitterCard = null ;
111
+
112
+ /**
113
+ * @return static
114
+ */
115
+ public static function create (): static
116
+ {
117
+ return new static ;
118
+ }
119
+
120
+ /**
121
+ * @param string $title
122
+ * @return static
123
+ */
124
+ public function setTitle (string $ title ): static
125
+ {
126
+ $ this ->title = $ title ;
127
+
128
+ return $ this ;
129
+ }
130
+
131
+ /**
132
+ * @param string $description
133
+ * @return static
134
+ */
135
+ public function setDescription (string $ description ): static
136
+ {
137
+ $ this ->description = $ description ;
138
+
139
+ return $ this ;
140
+ }
141
+
142
+ /**
143
+ * @param string $daterminer
144
+ * @return static
145
+ * @throws WrongArgumentException
146
+ */
147
+ public function setDaterminer (string $ daterminer ): static
148
+ {
149
+ Assert::isTrue (
150
+ empty ($ daterminer ) || in_array ($ daterminer , self ::ALLOWED_DATERMINE ),
151
+ 'Only empty value or `a`, `an`, `the`, `auto` allowed '
152
+ );
153
+ $ this ->determiner = $ daterminer ;
154
+
155
+ return $ this ;
156
+ }
157
+
158
+ /**
159
+ * @param string $locale
160
+ * @return static
161
+ * @throws WrongArgumentException
162
+ */
163
+ public function setLocale (string $ locale ): static
164
+ {
165
+ Assert::isTrue (
166
+ preg_match ('/^[a-z]{2}_[A-Z]{2}$/iu ' , $ locale ) == 1 ,
167
+ 'wrong locale format '
168
+ );
169
+ $ this ->locale = $ locale ;
170
+
171
+ return $ this ;
172
+ }
173
+
174
+ /**
175
+ * @param string $locale
176
+ * @return static
177
+ * @throws WrongArgumentException
178
+ */
179
+ public function setLocaleAlternates (string $ locale ): static
180
+ {
181
+ Assert::isTrue (
182
+ preg_match ('/^[a-z]{2}_[A-Z]{2}$/iu ' , $ locale ) == 1 ,
183
+ 'wrong locale format '
184
+ );
185
+ $ this ->localeAlternates [] = $ locale ;
186
+
187
+ return $ this ;
188
+ }
189
+
190
+ /**
191
+ * @param string $siteName
192
+ * @return static
193
+ */
194
+ public function setSiteName (string $ siteName ): static
195
+ {
196
+ $ this ->siteName = $ siteName ;
197
+
198
+ return $ this ;
199
+ }
200
+
201
+ /**
202
+ * @param OpenGraphObject $type
203
+ * @return static
204
+ */
205
+ public function setType (OpenGraphObject $ type ): static
206
+ {
207
+ $ this ->type = $ type ;
208
+
209
+ return $ this ;
210
+ }
211
+
212
+ /**
213
+ * @param OpenGraphImage $image
214
+ * @return static
215
+ */
216
+ public function setImage (OpenGraphImage $ image ): static
217
+ {
218
+ $ this ->image [] = $ image ;
219
+
220
+ return $ this ;
221
+ }
222
+
223
+ /**
224
+ * @param mixed $appId
225
+ * @return static
226
+ */
227
+ public function setAppId (mixed $ appId ): static
228
+ {
229
+ $ this ->appId = (string )$ appId ;
230
+
231
+ return $ this ;
232
+ }
233
+
234
+ /**
235
+ * @param OpenGraphVideo $video
236
+ * @return static
237
+ */
238
+ public function setVideo (OpenGraphVideo $ video ): static
239
+ {
240
+ $ this ->video = $ video ;
241
+
242
+ return $ this ;
243
+ }
244
+
245
+ /**
246
+ * @param OpenGraphTwitterCard $twitterCard
247
+ * @return static
248
+ */
249
+ public function setTwitterCart (OpenGraphTwitterCard $ twitterCard ): static
250
+ {
251
+ $ this ->twitterCard = $ twitterCard ;
252
+
253
+ return $ this ;
254
+ }
255
+
256
+ /**
257
+ * @param OpenGraphAudio $audio
258
+ * @return static
259
+ */
260
+ public function setAudio (OpenGraphAudio $ audio ): static
261
+ {
262
+ $ this ->audio = $ audio ;
263
+
264
+ return $ this ;
265
+ }
266
+
267
+ /**
268
+ * @param string $url
269
+ * @return static
270
+ */
271
+ public function setUrl (string $ url ): static
272
+ {
273
+ $ this ->url = $ url ;
274
+
275
+ return $ this ;
276
+ }
277
+
278
+ /**
279
+ * Minimal image size - 160 x 160 px. Recommend greater than 510 x 228 px.
280
+ * @see https://vk.com/dev/publications
281
+ * @param string $vkImage
282
+ * @return static
283
+ */
284
+ public function setVkImage (string $ vkImage ): static
285
+ {
286
+ $ this ->vkImage = $ vkImage ;
287
+
288
+ return $ this ;
289
+ }
290
+
291
+ /**
292
+ * @param bool $full
293
+ * @return string
294
+ * @throws WrongArgumentException
295
+ */
296
+ public function getPrefix (bool $ full = true ): string
297
+ {
298
+ Assert::isNotEmpty ($ this ->type , 'type is required ' );
299
+
300
+ $ prefix = [
301
+ self ::OGP_NAMESPACE [0 ] . ': ' . self ::OGP_NAMESPACE [1 ],
302
+ $ this ->type ->getNamespace () . ': ' . $ this ->type ->getType ()->getNamespace (),
303
+ ];
304
+ if (!empty ($ this ->appId )) {
305
+ $ prefix [] = self ::FB_NAMESPACE [0 ] . ': ' . self ::FB_NAMESPACE [1 ];
306
+ }
307
+
308
+ return
309
+ ($ full ? 'prefix=" ' : '' )
310
+ . implode (" " , $ prefix )
311
+ . ($ full ? '" ' : '' );
312
+ }
313
+
314
+ /**
315
+ * @return string
316
+ * @throws WrongArgumentException
317
+ */
318
+ public function dump (): string
319
+ {
320
+ Assert::isNotEmpty ($ this ->title , 'title is required ' );
321
+ Assert::isNotEmpty ($ this ->type , 'type is required ' );
322
+ Assert::isNotEmpty ($ this ->url , 'url is required ' );
323
+ Assert::isNotEmpty ($ this ->image , 'image is required ' );
324
+ Assert::isNotEmpty ($ this ->description , 'description is required ' );
325
+
326
+ return
327
+ (new HtmlAssembler (
328
+ array_map (
329
+ function ($ item ) {
330
+ return (new SgmlOpenTag ())->setId ('meta ' )->setEmpty (true )
331
+ ->setAttribute ('property ' , $ item [0 ])
332
+ ->setAttribute ('content ' , $ item [1 ]);
333
+ },
334
+ $ this ->getList ()
335
+ )
336
+ )
337
+ )->getHtml ();
338
+ }
339
+
340
+ /**
341
+ * @return array
342
+ * @throws WrongArgumentException
343
+ */
344
+ protected function getList (): array
345
+ {
346
+ return array_merge ([
347
+ ['og:title ' , $ this ->title ],
348
+ ['og:url ' , $ this ->url ],
349
+ ['og:type ' , $ this ->type ->getType ()->getName ()],
350
+ ['og:locale ' , $ this ->locale ]
351
+ ],
352
+ array_map (
353
+ function ($ item ) { return ['og:locale:alternate ' , $ item ]; },
354
+ $ this ->localeAlternates
355
+ ),
356
+ array_reduce (
357
+ $ this ->image ,
358
+ function ($ result , OpenGraphImage $ image ) {
359
+ return array_merge ($ result , $ image ->getList ());
360
+ }, []
361
+ ),
362
+ $ this ->audio ?->getList() ?? [],
363
+ $ this ->video ?->getList() ?? [],
364
+ empty ($ this ->description ) ? [] : [ ['og:description ' , $ this ->description ] ],
365
+ empty ($ this ->determiner ) ? [] : [ ['og:determiner ' , $ this ->description ] ],
366
+ empty ($ this ->siteName ) ? [] : [ ['og:site_name ' , $ this ->description ] ],
367
+ empty ($ this ->appId ) ? [] : [ ['fb:app_id ' , $ this ->appId ] ],
368
+ empty ($ this ->vkImage ) ? [] : [ ['vk:image ' , $ this ->vkImage ] ],
369
+ $ this ->type ->getList (),
370
+ empty ($ this ->twitterCard ) ? [] : $ this ->twitterCard ->getList ()
371
+ );
372
+ }
373
+ }
0 commit comments