xref: /plugin/socialcards/action.php (revision ab09af334595480950a62a806a558f0cf93e224c) !
1<?php
2/*
3 * Copyright (c) 2013-2016 Mark C. Prins <mprins@users.sf.net>
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
18if (!defined('DOKU_INC')){
19	die();
20}
21if (!defined('DOKU_PLUGIN')) {
22	define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
23}
24
25require_once DOKU_PLUGIN . 'action.php';
26require_once(DOKU_INC.'inc/JpegMeta.php');
27/**
28 * DokuWiki Plugin socialcards (Action Component).
29 *
30 * @license BSD license
31 * @author  Mark C. Prins <mprins@users.sf.net>
32 */
33class action_plugin_socialcards extends DokuWiki_Action_Plugin {
34
35	/**
36	 * Register our callback for the TPL_METAHEADER_OUTPUT event.
37	 *
38	 * @param $controller Doku_Event_Handler
39	 * @see DokuWiki_Action_Plugin::register()
40	 */
41	public function register(Doku_Event_Handler $controller) {
42		$controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this,
43				'handle_tpl_metaheader_output');
44	}
45
46	/**
47	 * Retrieve metadata and add to the head of the page using appropriate meta
48	 * tags unless the page does not exist.
49	 *
50	 * @global string $ID page id
51	 * @global array $conf global wiki configuration
52	 * @global array $INFO
53	 * @param Doku_Event $event the DokuWiki event. $event->data is a two-dimensional
54	 *             array of all meta headers. The keys are meta, link and script.
55	 * @param mixed $param the parameters passed to register_hook when this
56	 *             handler was registered (not used)
57	 *
58	 * @see http://www.dokuwiki.org/devel:event:tpl_metaheader_output
59	 */
60	public function handle_tpl_metaheader_output(Doku_Event $event, $param) {
61		global $ID, $conf, $INFO;
62
63		if (!page_exists($ID)) { return; }
64
65		// twitter card, see https://dev.twitter.com/cards/markup
66		// creat a summary card, see https://dev.twitter.com/cards/types/summary
67		$event->data['meta'][] = array('name' => 'twitter:card',
68				'content' => "summary",);
69
70		$event->data['meta'][] = array('name' => 'twitter:site',
71				'content' => $this->getConf('twitterName'),);
72
73		$event->data['meta'][] = array('name' => 'twitter:title',
74				'content' => p_get_metadata($ID, 'title', true),);
75
76		$desc = p_get_metadata($ID, 'description', true);
77		if (!empty($desc)) {
78			$desc = str_replace("\n", " ", $desc['abstract']);
79			$event->data['meta'][] = array('name' => 'twitter:description',
80					'content' => $desc,);
81		}
82
83		if ($this->getConf('twitterUserName') != '') {
84			$event->data['meta'][] = array('name' => 'twitter:creator',
85				'content' => $this->getConf('twitterUserName'),);
86		}
87
88		$event->data['meta'][] = array('name' => 'twitter:image',
89				'content' => $this->getImage(),);
90		$event->data['meta'][] = array('name' => 'twitter:image:alt',
91				'content' => $this->getImageAlt(),);
92
93		// opengraph, see http://ogp.me/
94		//
95		// to make this work properly the template should be modified adding the
96		// namespaces for a (x)html 4 template make html tag:
97		//
98		// <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl"
99		//       xmlns:og="http://ogp.me/ns#" xmlns:fb="http://ogp.me/ns/fb#"
100		//       xmlns:article="http://ogp.me/ns/article#" xmlns:place="http://ogp.me/ns/place#">
101		//
102		// and for a (x)html 5 template make head tag:
103		//
104		// <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article# place: http://ogp.me/ns/place#">
105
106		// og namespace http://ogp.me/ns#
107		$event->data['meta'][] = array('property' => 'og:locale',
108				'content' => $this->getConf('languageTerritory'),);
109		$event->data['meta'][] = array('property' => 'og:site_name',
110				'content' => $conf['title'],);
111		$event->data['meta'][] = array('property' => 'og:url',
112				'content' => wl($ID, '', true),);
113		$event->data['meta'][] = array('property' => 'og:title',
114				'content' => p_get_metadata($ID, 'title', true),);
115		if (!empty($desc)) {
116			$event->data['meta'][] = array('property' => 'og:description',
117					'content' => $desc,);
118		}
119		$event->data['meta'][] = array('property' => 'og:type',
120				'content' => "article",);
121		$ogImage = $this->getImage();
122		$secure = substr($ogImage, 0, 5) === 'https' ? ':secure_url' : '';
123		$event->data['meta'][] = array('property' => 'og:image' . $secure,
124				'content' => $ogImage,);
125
126		// article namespace http://ogp.me/ns/article#
127		$_dates = p_get_metadata($ID, 'date', true);
128		$event->data['meta'][] = array('property' => 'article:published_time',
129				'content' => dformat($_dates['created']),);
130		$event->data['meta'][] = array('property' => 'article:modified_time',
131				'content' => dformat($_dates['modified']),);
132		$event->data['meta'][] = array('property' => 'article:author',
133				'content' => $INFO['editor'],);
134		// $event->data['meta'][] = array('property' => 'article:author','content' => p_get_metadata($ID,'creator',true),);
135		// $event->data['meta'][] = array('property' => 'article:author','content' => p_get_metadata($ID,'user',true),);
136		$_subject = p_get_metadata($ID, 'subject', true);
137		if (!empty($_subject)) {
138			if (!is_array($_subject)) {
139				$_subject = array($_subject);
140			}
141			foreach ($_subject as $tag) {
142				$event->data['meta'][] = array('property' => 'article:tag',
143						'content' => $tag,);
144			}
145		}
146
147		// place namespace http://ogp.me/ns/place#
148		$geotags = p_get_metadata($ID, 'geo', true);
149		$lat = $geotags['lat'];
150		$lon = $geotags['lon'];
151		if (!(empty($lat) && empty($lon))) {
152			$event->data['meta'][] = array('property' => 'place:location:latitude',
153					'content' => $lat,);
154			$event->data['meta'][] = array('property' => 'place:location:longitude',
155					'content' => $lon,);
156		}
157		// see https://developers.facebook.com/docs/opengraph/property-types/#geopoint
158		$alt = $geotags['alt'];
159		if (!empty($alt)) {
160			// facebook expects feet...
161			$alt = $alt * 3.2808;
162			$event->data['meta'][] = array('property' => 'place:location:altitude',
163					'content' => $alt,);
164		}
165
166		/* these are not valid for the GeoPoint type..
167
168		$region=$geotags['region'];
169		$country=$geotags['country'];
170		$placename=$geotags['placename'];
171		if (!empty($region))    {$event->data['meta'][] = array('property' => 'place:location:region',		'content' => $region,);}
172		if (!empty($placename)) {$event->data['meta'][] = array('property' => 'place:location:locality',	'content' => $placename,);}
173		if (!empty($country))   {$event->data['meta'][] = array('property' => 'place:location:country-name','content' => $country,);}
174		*/
175
176		// optional facebook app ID
177		$appId = $this->getConf('fbAppId');
178		if (!empty($appId)) {
179			$event->data['meta'][] = array('property' => 'fb:app_id',
180					'content' => $appId,);
181		}
182	}
183
184	/**
185	 * Gets the canonical image path for this page.
186	 *
187	 * @global string $ID page id
188	 * @return string the url to the image to use for this page
189	 */
190	private function getImage() {
191		global $ID;
192		$rel = p_get_metadata($ID, 'relation', true);
193		$img = $rel['firstimage'];
194
195		if (empty($img)) {
196			$img = $this->getConf('fallbackImage');
197			if (substr($img, 0, 4 ) === "http") {
198				// don't use ml() as this results in a HTTP redirect after hitting the wiki
199				return $img;
200			}
201		}
202
203		return ml($img, array(), true, '&amp;', true);
204	}
205
206	/**
207	 * Gets the alt text for this page image.
208	 *
209	 * @global string $ID page id
210	 * @return string alt text
211	 */
212	private function getImageAlt() {
213		global $ID;
214		$rel = p_get_metadata($ID, 'relation', true);
215		$imgID = $rel['firstimage'];
216		$alt = "";
217
218		if (!empty($imgID)) {
219			require_once(DOKU_INC.'inc/JpegMeta.php');
220			$jpegmeta = new JpegMeta(mediaFN($imgID));
221			$tags = array('IPTC.Caption',
222							'EXIF.UserComment',
223							'EXIF.TIFFImageDescription',
224							'EXIF.TIFFUserComment',
225							'IPTC.Headline',
226							'Xmp.dc:title'
227							);
228			$alt = media_getTag( $tags,$jpegmeta,"");
229		}
230		return htmlspecialchars($alt);
231	}
232
233}
234