1fdb4710bSMark Prins<?php 2635a5e2dSMark Prins/* 32765f2acSMark Prins * Copyright (c) 2013-2016 Mark C. Prins <mprins@users.sf.net> 4fdb4710bSMark Prins * 5fdb4710bSMark Prins * Permission to use, copy, modify, and distribute this software for any 6fdb4710bSMark Prins * purpose with or without fee is hereby granted, provided that the above 7fdb4710bSMark Prins * copyright notice and this permission notice appear in all copies. 8fdb4710bSMark Prins * 9fdb4710bSMark Prins * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10fdb4710bSMark Prins * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11fdb4710bSMark Prins * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12fdb4710bSMark Prins * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13fdb4710bSMark Prins * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14fdb4710bSMark Prins * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15fdb4710bSMark Prins * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16635a5e2dSMark Prins */ 17635a5e2dSMark Prins 18e0737b20SMark Prinsif (!defined('DOKU_INC')) { 1989906951SMark Prins die(); 20e0737b20SMark Prins} 21e0737b20SMark Prinsif (!defined('DOKU_PLUGIN')) { 2289906951SMark Prins define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 23e0737b20SMark Prins} 24fdb4710bSMark Prins 25fdb4710bSMark Prinsrequire_once DOKU_PLUGIN . 'action.php'; 26a543e5b7SMark Prinsrequire_once(DOKU_INC . 'inc/JpegMeta.php'); 27ae2f5c78SMark Prins/** 28ae2f5c78SMark Prins * DokuWiki Plugin socialcards (Action Component). 29ae2f5c78SMark Prins * 30ae2f5c78SMark Prins * @license BSD license 31ae2f5c78SMark Prins * @author Mark C. Prins <mprins@users.sf.net> 32ae2f5c78SMark Prins */ 33fdb4710bSMark Prinsclass action_plugin_socialcards extends DokuWiki_Action_Plugin { 34fdb4710bSMark Prins 35ae2f5c78SMark Prins /** 36ae2f5c78SMark Prins * Register our callback for the TPL_METAHEADER_OUTPUT event. 37ae2f5c78SMark Prins * 38ae2f5c78SMark Prins * @param $controller Doku_Event_Handler 39ae2f5c78SMark Prins * @see DokuWiki_Action_Plugin::register() 40ae2f5c78SMark Prins */ 41ae2f5c78SMark Prins public function register(Doku_Event_Handler $controller) { 4289906951SMark Prins $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 4389906951SMark Prins 'handle_tpl_metaheader_output'); 44fdb4710bSMark Prins } 45fdb4710bSMark Prins 46fdb4710bSMark Prins /** 4789906951SMark Prins * Retrieve metadata and add to the head of the page using appropriate meta 48156465eeSMark Prins * tags unless the page does not exist. 4989906951SMark Prins * 5089906951SMark Prins * @global string $ID page id 5189906951SMark Prins * @global array $conf global wiki configuration 5289906951SMark Prins * @global array $INFO 53fdb4710bSMark Prins * @param Doku_Event $event the DokuWiki event. $event->data is a two-dimensional 54fdb4710bSMark Prins * array of all meta headers. The keys are meta, link and script. 552765f2acSMark Prins * @param mixed $param the parameters passed to register_hook when this 562765f2acSMark Prins * handler was registered (not used) 5789906951SMark Prins * 5889906951SMark Prins * @see http://www.dokuwiki.org/devel:event:tpl_metaheader_output 59fdb4710bSMark Prins */ 602765f2acSMark Prins public function handle_tpl_metaheader_output(Doku_Event $event, $param) { 6189906951SMark Prins global $ID, $conf, $INFO; 62fdb4710bSMark Prins 63e0737b20SMark Prins if (!page_exists($ID)) { return; } 64156465eeSMark Prins 652765f2acSMark Prins // twitter card, see https://dev.twitter.com/cards/markup 662765f2acSMark Prins // creat a summary card, see https://dev.twitter.com/cards/types/summary 6789906951SMark Prins $event->data['meta'][] = array('name' => 'twitter:card', 6889906951SMark Prins 'content' => "summary",); 692765f2acSMark Prins 702765f2acSMark Prins $event->data['meta'][] = array('name' => 'twitter:site', 712765f2acSMark Prins 'content' => $this->getConf('twitterName'),); 722765f2acSMark Prins 7389906951SMark Prins $event->data['meta'][] = array('name' => 'twitter:title', 7489906951SMark Prins 'content' => p_get_metadata($ID, 'title', true),); 752765f2acSMark Prins 76aeb704feSMark Prins $desc = p_get_metadata($ID, 'description', true); 77aeb704feSMark Prins if (!empty($desc)) { 7889906951SMark Prins $desc = str_replace("\n", " ", $desc['abstract']); 79fc03d0b3SMark Prins $event->data['meta'][] = array('name' => 'twitter:description', 8089906951SMark Prins 'content' => $desc,); 81fc03d0b3SMark Prins } 822765f2acSMark Prins 83e0737b20SMark Prins if ($this->getConf('twitterUserName') != '') { 8489906951SMark Prins $event->data['meta'][] = array('name' => 'twitter:creator', 85e0737b20SMark Prins 'content' => $this->getConf('twitterUserName'),); 86e0737b20SMark Prins } 872765f2acSMark Prins 882765f2acSMark Prins $event->data['meta'][] = array('name' => 'twitter:image', 8989906951SMark Prins 'content' => $this->getImage(),); 90a543e5b7SMark Prins $event->data['meta'][] = array('name' => 'twitter:image:alt', 91a543e5b7SMark Prins 'content' => $this->getImageAlt(),); 9289906951SMark Prins 9389906951SMark Prins // opengraph, see http://ogp.me/ 9489906951SMark Prins // 9589906951SMark Prins // to make this work properly the template should be modified adding the 9689906951SMark Prins // namespaces for a (x)html 4 template make html tag: 9789906951SMark Prins // 9889906951SMark Prins // <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl" 9989906951SMark Prins // xmlns:og="http://ogp.me/ns#" xmlns:fb="http://ogp.me/ns/fb#" 10089906951SMark Prins // xmlns:article="http://ogp.me/ns/article#" xmlns:place="http://ogp.me/ns/place#"> 10189906951SMark Prins // 10289906951SMark Prins // and for a (x)html 5 template make head tag: 10389906951SMark Prins // 10489906951SMark Prins // <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#"> 10589906951SMark Prins 10689906951SMark Prins // og namespace http://ogp.me/ns# 10789906951SMark Prins $event->data['meta'][] = array('property' => 'og:locale', 10889906951SMark Prins 'content' => $this->getConf('languageTerritory'),); 10989906951SMark Prins $event->data['meta'][] = array('property' => 'og:site_name', 11089906951SMark Prins 'content' => $conf['title'],); 11189906951SMark Prins $event->data['meta'][] = array('property' => 'og:url', 11289906951SMark Prins 'content' => wl($ID, '', true),); 11389906951SMark Prins $event->data['meta'][] = array('property' => 'og:title', 11489906951SMark Prins 'content' => p_get_metadata($ID, 'title', true),); 11589906951SMark Prins if (!empty($desc)) { 11689906951SMark Prins $event->data['meta'][] = array('property' => 'og:description', 11789906951SMark Prins 'content' => $desc,); 118fdb4710bSMark Prins } 11989906951SMark Prins $event->data['meta'][] = array('property' => 'og:type', 12089906951SMark Prins 'content' => "article",); 121f3501156SLaurent Marquet $ogImage = $this->getImage(); 122f3501156SLaurent Marquet $secure = substr($ogImage, 0, 5) === 'https' ? ':secure_url' : ''; 123f3501156SLaurent Marquet $event->data['meta'][] = array('property' => 'og:image' . $secure, 124f3501156SLaurent Marquet 'content' => $ogImage,); 12589906951SMark Prins 12689906951SMark Prins // article namespace http://ogp.me/ns/article# 12789906951SMark Prins $_dates = p_get_metadata($ID, 'date', true); 12889906951SMark Prins $event->data['meta'][] = array('property' => 'article:published_time', 12989906951SMark Prins 'content' => dformat($_dates['created']),); 13089906951SMark Prins $event->data['meta'][] = array('property' => 'article:modified_time', 13189906951SMark Prins 'content' => dformat($_dates['modified']),); 13289906951SMark Prins $event->data['meta'][] = array('property' => 'article:author', 13389906951SMark Prins 'content' => $INFO['editor'],); 13489906951SMark Prins // $event->data['meta'][] = array('property' => 'article:author','content' => p_get_metadata($ID,'creator',true),); 13589906951SMark Prins // $event->data['meta'][] = array('property' => 'article:author','content' => p_get_metadata($ID,'user',true),); 13689906951SMark Prins $_subject = p_get_metadata($ID, 'subject', true); 13789906951SMark Prins if (!empty($_subject)) { 13889906951SMark Prins if (!is_array($_subject)) { 13989906951SMark Prins $_subject = array($_subject); 14089906951SMark Prins } 14189906951SMark Prins foreach ($_subject as $tag) { 14289906951SMark Prins $event->data['meta'][] = array('property' => 'article:tag', 14389906951SMark Prins 'content' => $tag,); 14489906951SMark Prins } 14589906951SMark Prins } 14689906951SMark Prins 14789906951SMark Prins // place namespace http://ogp.me/ns/place# 14889906951SMark Prins $geotags = p_get_metadata($ID, 'geo', true); 14989906951SMark Prins $lat = $geotags['lat']; 15089906951SMark Prins $lon = $geotags['lon']; 15189906951SMark Prins if (!(empty($lat) && empty($lon))) { 15289906951SMark Prins $event->data['meta'][] = array('property' => 'place:location:latitude', 15389906951SMark Prins 'content' => $lat,); 15489906951SMark Prins $event->data['meta'][] = array('property' => 'place:location:longitude', 15589906951SMark Prins 'content' => $lon,); 1563322a5daSMark Prins } 1573322a5daSMark Prins // see https://developers.facebook.com/docs/opengraph/property-types/#geopoint 1583322a5daSMark Prins $alt = $geotags['alt']; 1593322a5daSMark Prins if (!empty($alt)) { 1603322a5daSMark Prins // facebook expects feet... 1613322a5daSMark Prins $alt = $alt * 3.2808; 1623322a5daSMark Prins $event->data['meta'][] = array('property' => 'place:location:altitude', 1633322a5daSMark Prins 'content' => $alt,); 16489906951SMark Prins } 16589906951SMark Prins 166ae2f5c78SMark Prins /* these are not valid for the GeoPoint type.. 16789906951SMark Prins $region=$geotags['region']; 16889906951SMark Prins $country=$geotags['country']; 16989906951SMark Prins $placename=$geotags['placename']; 17089906951SMark Prins if (!empty($region)) {$event->data['meta'][] = array('property' => 'place:location:region', 'content' => $region,);} 17189906951SMark Prins if (!empty($placename)) {$event->data['meta'][] = array('property' => 'place:location:locality', 'content' => $placename,);} 17289906951SMark Prins if (!empty($country)) {$event->data['meta'][] = array('property' => 'place:location:country-name','content' => $country,);} 17389906951SMark Prins */ 17489906951SMark Prins 17589906951SMark Prins // optional facebook app ID 17689906951SMark Prins $appId = $this->getConf('fbAppId'); 17789906951SMark Prins if (!empty($appId)) { 17889906951SMark Prins $event->data['meta'][] = array('property' => 'fb:app_id', 17989906951SMark Prins 'content' => $appId,); 18089906951SMark Prins } 18189906951SMark Prins } 18289906951SMark Prins 18389906951SMark Prins /** 18489906951SMark Prins * Gets the canonical image path for this page. 18589906951SMark Prins * 18689906951SMark Prins * @global string $ID page id 18789906951SMark Prins * @return string the url to the image to use for this page 18889906951SMark Prins */ 18989906951SMark Prins private function getImage() { 19089906951SMark Prins global $ID; 19189906951SMark Prins $rel = p_get_metadata($ID, 'relation', true); 19289906951SMark Prins $img = $rel['firstimage']; 193ae2f5c78SMark Prins 194ae2f5c78SMark Prins if (empty($img)) { 19593bf3c56SMark Prins $img = $this->getConf('fallbackImage'); 19693bf3c56SMark Prins if (substr($img, 0, 4) === "http") { 197*3efb311dSMark Prins // don't use ml() as this results in a HTTP redirect after 198*3efb311dSMark Prins // hitting the wiki making the card image fail. 19993bf3c56SMark Prins return $img; 20093bf3c56SMark Prins } 20193bf3c56SMark Prins } 202ae2f5c78SMark Prins 203ae2f5c78SMark Prins return ml($img, array(), true, '&', true); 20489906951SMark Prins } 20589906951SMark Prins 206a543e5b7SMark Prins /** 207a543e5b7SMark Prins * Gets the alt text for this page image. 208a543e5b7SMark Prins * 209a543e5b7SMark Prins * @global string $ID page id 210a543e5b7SMark Prins * @return string alt text 211a543e5b7SMark Prins */ 212a543e5b7SMark Prins private function getImageAlt() { 213a543e5b7SMark Prins global $ID; 214a543e5b7SMark Prins $rel = p_get_metadata($ID, 'relation', true); 215a543e5b7SMark Prins $imgID = $rel['firstimage']; 216a543e5b7SMark Prins $alt = ""; 217a543e5b7SMark Prins 218a543e5b7SMark Prins if (!empty($imgID)) { 219a543e5b7SMark Prins require_once(DOKU_INC . 'inc/JpegMeta.php'); 220a543e5b7SMark Prins $jpegmeta = new JpegMeta(mediaFN($imgID)); 221a543e5b7SMark Prins $tags = array('IPTC.Caption', 222a543e5b7SMark Prins 'EXIF.UserComment', 223a543e5b7SMark Prins 'EXIF.TIFFImageDescription', 224a543e5b7SMark Prins 'EXIF.TIFFUserComment', 225a543e5b7SMark Prins 'IPTC.Headline', 226a543e5b7SMark Prins 'Xmp.dc:title' 227a543e5b7SMark Prins ); 228a543e5b7SMark Prins $alt = media_getTag($tags, $jpegmeta, ""); 229a543e5b7SMark Prins } 230a543e5b7SMark Prins return htmlspecialchars($alt); 231a543e5b7SMark Prins } 232a543e5b7SMark Prins 233fdb4710bSMark Prins} 234