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, '&', 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