1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* Copyright (c) 2013-2016 Mark C. Prins <[email protected]> |
4
|
|
|
* |
5
|
|
|
* Permission to use, copy, modify, and distribute this software for any |
6
|
|
|
* purpose with or without fee is hereby granted, provided that the above |
7
|
|
|
* copyright notice and this permission notice appear in all copies. |
8
|
|
|
* |
9
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
10
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
11
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
12
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
13
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
14
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
15
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* DokuWiki Plugin socialcards (Action Component). |
20
|
|
|
* |
21
|
|
|
* @license BSD license |
22
|
|
|
* @author Mark C. Prins <[email protected]> |
23
|
|
|
*/ |
24
|
|
|
|
25
|
|
|
class action_plugin_socialcards extends DokuWiki_Action_Plugin { |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Register our callback for the TPL_METAHEADER_OUTPUT event. |
29
|
|
|
* |
30
|
|
|
* @param $controller Doku_Event_Handler |
31
|
|
|
* @see DokuWiki_Action_Plugin::register() |
32
|
|
|
*/ |
33
|
|
|
public function register(Doku_Event_Handler $controller): void { |
34
|
|
|
$controller->register_hook( |
35
|
|
|
'TPL_METAHEADER_OUTPUT', |
36
|
|
|
'BEFORE', |
37
|
|
|
$this, |
38
|
|
|
'handleTplMetaheaderOutput' |
39
|
|
|
); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Retrieve metadata and add to the head of the page using appropriate meta |
44
|
|
|
* tags unless the page does not exist. |
45
|
|
|
* |
46
|
|
|
* @param Doku_Event $event the DokuWiki event. $event->data is a two-dimensional |
47
|
|
|
* array of all meta headers. The keys are meta, link and script. |
48
|
|
|
* @param mixed $param the parameters passed to register_hook when this |
49
|
|
|
* handler was registered (not used) |
50
|
|
|
* |
51
|
|
|
* @global array $INFO |
52
|
|
|
* @global string $ID page id |
53
|
|
|
* @global array $conf global wiki configuration |
54
|
|
|
* @see http://www.dokuwiki.org/devel:event:tpl_metaheader_output |
55
|
|
|
*/ |
56
|
|
|
public function handleTplMetaheaderOutput(Doku_Event $event, $param): void { |
57
|
|
|
global $ID, $conf, $INFO; |
58
|
|
|
|
59
|
|
|
if(!page_exists($ID)) { |
60
|
|
|
return; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
// twitter card, see https://dev.twitter.com/cards/markup |
64
|
|
|
// creat a summary card, see https://dev.twitter.com/cards/types/summary |
65
|
|
|
$event->data['meta'][] = array( |
66
|
|
|
'name' => 'twitter:card', |
67
|
|
|
'content' => "summary", |
68
|
|
|
); |
69
|
|
|
|
70
|
|
|
$event->data['meta'][] = array( |
71
|
|
|
'name' => 'twitter:site', |
72
|
|
|
'content' => $this->getConf('twitterName'), |
73
|
|
|
); |
74
|
|
|
|
75
|
|
|
$event->data['meta'][] = array( |
76
|
|
|
'name' => 'twitter:title', |
77
|
|
|
'content' => p_get_metadata($ID, 'title', true), |
78
|
|
|
); |
79
|
|
|
|
80
|
|
|
$desc = p_get_metadata($ID, 'description', true); |
81
|
|
|
if(!empty($desc)) { |
82
|
|
|
$desc = str_replace("\n", " ", $desc['abstract']); |
83
|
|
|
$event->data['meta'][] = array( |
84
|
|
|
'name' => 'twitter:description', |
85
|
|
|
'content' => $desc, |
86
|
|
|
); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
if($this->getConf('twitterUserName') !== '') { |
90
|
|
|
$event->data['meta'][] = array( |
91
|
|
|
'name' => 'twitter:creator', |
92
|
|
|
'content' => $this->getConf('twitterUserName'), |
93
|
|
|
); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$event->data['meta'][] = array( |
97
|
|
|
'name' => 'twitter:image', |
98
|
|
|
'content' => $this->getImage(), |
99
|
|
|
); |
100
|
|
|
$event->data['meta'][] = array( |
101
|
|
|
'name' => 'twitter:image:alt', |
102
|
|
|
'content' => $this->getImageAlt(), |
103
|
|
|
); |
104
|
|
|
|
105
|
|
|
// opengraph, see http://ogp.me/ |
106
|
|
|
// |
107
|
|
|
// to make this work properly the template should be modified adding the |
108
|
|
|
// namespaces for a (x)html 4 template make html tag: |
109
|
|
|
// |
110
|
|
|
// <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl" |
111
|
|
|
// xmlns:og="http://ogp.me/ns#" xmlns:fb="http://ogp.me/ns/fb#" |
112
|
|
|
// xmlns:article="http://ogp.me/ns/article#" xmlns:place="http://ogp.me/ns/place#"> |
113
|
|
|
// |
114
|
|
|
// and for a (x)html 5 template make head tag: |
115
|
|
|
// |
116
|
|
|
// <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# |
117
|
|
|
// article: http://ogp.me/ns/article# place: http://ogp.me/ns/place#"> |
118
|
|
|
|
119
|
|
|
// og namespace http://ogp.me/ns# |
120
|
|
|
$event->data['meta'][] = array( |
121
|
|
|
'property' => 'og:locale', |
122
|
|
|
'content' => $this->getConf('languageTerritory'), |
123
|
|
|
); |
124
|
|
|
$event->data['meta'][] = array( |
125
|
|
|
'property' => 'og:site_name', |
126
|
|
|
'content' => $conf['title'], |
127
|
|
|
); |
128
|
|
|
$event->data['meta'][] = array( |
129
|
|
|
'property' => 'og:url', |
130
|
|
|
'content' => wl($ID, '', true), |
131
|
|
|
); |
132
|
|
|
$event->data['meta'][] = array( |
133
|
|
|
'property' => 'og:title', |
134
|
|
|
'content' => p_get_metadata($ID, 'title', true), |
135
|
|
|
); |
136
|
|
|
if(!empty($desc)) { |
137
|
|
|
$event->data['meta'][] = array( |
138
|
|
|
'property' => 'og:description', |
139
|
|
|
'content' => $desc, |
140
|
|
|
); |
141
|
|
|
} |
142
|
|
|
$event->data['meta'][] = array( |
143
|
|
|
'property' => 'og:type', |
144
|
|
|
'content' => "article", |
145
|
|
|
); |
146
|
|
|
$ogImage = $this->getImage(); |
147
|
|
|
$secure = strpos($ogImage, 'https') === 0 ? ':secure_url' : ''; |
148
|
|
|
$event->data['meta'][] = array( |
149
|
|
|
'property' => 'og:image' . $secure, |
150
|
|
|
'content' => $ogImage, |
151
|
|
|
); |
152
|
|
|
|
153
|
|
|
// article namespace http://ogp.me/ns/article# |
154
|
|
|
$_dates = p_get_metadata($ID, 'date', true); |
155
|
|
|
$event->data['meta'][] = array( |
156
|
|
|
'property' => 'article:published_time', |
157
|
|
|
'content' => dformat($_dates['created']), |
158
|
|
|
); |
159
|
|
|
$event->data['meta'][] = array( |
160
|
|
|
'property' => 'article:modified_time', |
161
|
|
|
'content' => dformat($_dates['modified']), |
162
|
|
|
); |
163
|
|
|
$event->data['meta'][] = array( |
164
|
|
|
'property' => 'article:author', |
165
|
|
|
'content' => $INFO['editor'], |
166
|
|
|
); |
167
|
|
|
// $event->data['meta'][] = array( |
168
|
|
|
// 'property' => 'article:author', |
169
|
|
|
// 'content' => p_get_metadata($ID, 'creator', true), |
170
|
|
|
// ); |
171
|
|
|
// $event->data['meta'][] = array( |
172
|
|
|
// 'property' => 'article:author', |
173
|
|
|
// 'content' => p_get_metadata($ID, 'user', true), |
174
|
|
|
// ); |
175
|
|
|
$_subject = p_get_metadata($ID, 'subject', true); |
176
|
|
|
if(!empty($_subject)) { |
177
|
|
|
if(!is_array($_subject)) { |
178
|
|
|
$_subject = array($_subject); |
179
|
|
|
} |
180
|
|
|
foreach($_subject as $tag) { |
181
|
|
|
$event->data['meta'][] = array( |
182
|
|
|
'property' => 'article:tag', |
183
|
|
|
'content' => $tag, |
184
|
|
|
); |
185
|
|
|
} |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
// place namespace http://ogp.me/ns/place# |
189
|
|
|
$geotags = p_get_metadata($ID, 'geo', true); |
190
|
|
|
$lat = $geotags['lat']; |
191
|
|
|
$lon = $geotags['lon']; |
192
|
|
|
if(!(empty($lat) && empty($lon))) { |
193
|
|
|
$event->data['meta'][] = array( |
194
|
|
|
'property' => 'place:location:latitude', |
195
|
|
|
'content' => $lat, |
196
|
|
|
); |
197
|
|
|
$event->data['meta'][] = array( |
198
|
|
|
'property' => 'place:location:longitude', |
199
|
|
|
'content' => $lon, |
200
|
|
|
); |
201
|
|
|
} |
202
|
|
|
// see https://developers.facebook.com/docs/opengraph/property-types/#geopoint |
203
|
|
|
$alt = $geotags['alt']; |
204
|
|
|
if(!empty($alt)) { |
205
|
|
|
// facebook expects feet... |
206
|
|
|
$alt *= 3.2808; |
207
|
|
|
$event->data['meta'][] = array( |
208
|
|
|
'property' => 'place:location:altitude', |
209
|
|
|
'content' => $alt, |
210
|
|
|
); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/* these are not valid for the GeoPoint type.. |
214
|
|
|
$region = $geotags['region']; |
215
|
|
|
$country = $geotags['country']; |
216
|
|
|
$placename = $geotags['placename']; |
217
|
|
|
if(!empty($region)) { |
218
|
|
|
$event->data['meta'][] = array('property' => 'place:location:region', 'content' => $region,); |
219
|
|
|
} |
220
|
|
|
if(!empty($placename)) { |
221
|
|
|
$event->data['meta'][] = array('property' => 'place:location:locality', 'content' => $placename,); |
222
|
|
|
} |
223
|
|
|
if(!empty($country)) { |
224
|
|
|
$event->data['meta'][] = array('property' => 'place:location:country-name', 'content' => $country,); |
225
|
|
|
} |
226
|
|
|
*/ |
227
|
|
|
|
228
|
|
|
// optional facebook app ID |
229
|
|
|
$appId = $this->getConf('fbAppId'); |
230
|
|
|
if(!empty($appId)) { |
231
|
|
|
$event->data['meta'][] = array( |
232
|
|
|
'property' => 'fb:app_id', |
233
|
|
|
'content' => $appId, |
234
|
|
|
); |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Gets the canonical image path for this page. |
240
|
|
|
* |
241
|
|
|
* @return string the url to the image to use for this page |
242
|
|
|
* @global string $ID page id |
243
|
|
|
*/ |
244
|
|
|
private function getImage(): string { |
245
|
|
|
global $ID; |
246
|
|
|
$rel = p_get_metadata($ID, 'relation', true); |
247
|
|
|
$img = $rel['firstimage']; |
248
|
|
|
|
249
|
|
|
if(empty($img)) { |
250
|
|
|
$img = $this->getConf('fallbackImage'); |
251
|
|
|
if(strpos($img, "http") === 0) { |
252
|
|
|
// don't use ml() as this results in a HTTP redirect after |
253
|
|
|
// hitting the wiki making the card image fail. |
254
|
|
|
return $img; |
255
|
|
|
} |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
return ml($img, array(), true, '&', true); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Gets the alt text for this page image. |
263
|
|
|
* |
264
|
|
|
* @return string alt text |
265
|
|
|
* @global string $ID page id |
266
|
|
|
*/ |
267
|
|
|
private function getImageAlt(): string { |
268
|
|
|
global $ID; |
269
|
|
|
$rel = p_get_metadata($ID, 'relation', true); |
270
|
|
|
$imgID = $rel['firstimage']; |
271
|
|
|
$alt = ""; |
272
|
|
|
|
273
|
|
|
if(!empty($imgID)) { |
274
|
|
|
require_once(DOKU_INC . 'inc/JpegMeta.php'); |
275
|
|
|
$jpegmeta = new JpegMeta(mediaFN($imgID)); |
276
|
|
|
$tags = array( |
277
|
|
|
'IPTC.Caption', |
278
|
|
|
'EXIF.UserComment', |
279
|
|
|
'EXIF.TIFFImageDescription', |
280
|
|
|
'EXIF.TIFFUserComment', |
281
|
|
|
'IPTC.Headline', |
282
|
|
|
'Xmp.dc:title' |
283
|
|
|
); |
284
|
|
|
$alt = media_getTag($tags, $jpegmeta, ""); |
285
|
|
|
} |
286
|
|
|
return htmlspecialchars($alt); |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
|