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 18ae2f5c78SMark Prins/** 19ae2f5c78SMark Prins * DokuWiki Plugin socialcards (Action Component). 20ae2f5c78SMark Prins * 21ae2f5c78SMark Prins * @license BSD license 22ae2f5c78SMark Prins * @author Mark C. Prins <mprins@users.sf.net> 23ae2f5c78SMark Prins */ 243cc863c5SMark Prins 25fdb4710bSMark Prinsclass action_plugin_socialcards extends DokuWiki_Action_Plugin { 26fdb4710bSMark Prins 27ae2f5c78SMark Prins /** 28ae2f5c78SMark Prins * Register our callback for the TPL_METAHEADER_OUTPUT event. 29ae2f5c78SMark Prins * 30ae2f5c78SMark Prins * @param $controller Doku_Event_Handler 31ae2f5c78SMark Prins * @see DokuWiki_Action_Plugin::register() 32ae2f5c78SMark Prins */ 33*fa5b18beSMark Prins public function register(Doku_Event_Handler $controller): void { 343cc863c5SMark Prins $controller->register_hook( 353cc863c5SMark Prins 'TPL_METAHEADER_OUTPUT', 363cc863c5SMark Prins 'BEFORE', 373cc863c5SMark Prins $this, 383cc863c5SMark Prins 'handleTplMetaheaderOutput' 393cc863c5SMark Prins ); 40fdb4710bSMark Prins } 41fdb4710bSMark Prins 42fdb4710bSMark Prins /** 4389906951SMark Prins * Retrieve metadata and add to the head of the page using appropriate meta 44156465eeSMark Prins * tags unless the page does not exist. 4589906951SMark Prins * 46fdb4710bSMark Prins * @param Doku_Event $event the DokuWiki event. $event->data is a two-dimensional 47fdb4710bSMark Prins * array of all meta headers. The keys are meta, link and script. 482765f2acSMark Prins * @param mixed $param the parameters passed to register_hook when this 492765f2acSMark Prins * handler was registered (not used) 5089906951SMark Prins * 513cc863c5SMark Prins * @global array $INFO 523cc863c5SMark Prins * @global string $ID page id 533cc863c5SMark Prins * @global array $conf global wiki configuration 5489906951SMark Prins * @see http://www.dokuwiki.org/devel:event:tpl_metaheader_output 55fdb4710bSMark Prins */ 56*fa5b18beSMark Prins public function handleTplMetaheaderOutput(Doku_Event $event, $param): void { 5789906951SMark Prins global $ID, $conf, $INFO; 58fdb4710bSMark Prins 593cc863c5SMark Prins if(!page_exists($ID)) { 603cc863c5SMark Prins return; 613cc863c5SMark Prins } 62156465eeSMark Prins 632765f2acSMark Prins // twitter card, see https://dev.twitter.com/cards/markup 642765f2acSMark Prins // creat a summary card, see https://dev.twitter.com/cards/types/summary 653cc863c5SMark Prins $event->data['meta'][] = array( 663cc863c5SMark Prins 'name' => 'twitter:card', 673cc863c5SMark Prins 'content' => "summary", 683cc863c5SMark Prins ); 692765f2acSMark Prins 703cc863c5SMark Prins $event->data['meta'][] = array( 713cc863c5SMark Prins 'name' => 'twitter:site', 723cc863c5SMark Prins 'content' => $this->getConf('twitterName'), 733cc863c5SMark Prins ); 742765f2acSMark Prins 753cc863c5SMark Prins $event->data['meta'][] = array( 763cc863c5SMark Prins 'name' => 'twitter:title', 773cc863c5SMark Prins 'content' => p_get_metadata($ID, 'title', true), 783cc863c5SMark Prins ); 792765f2acSMark Prins 80aeb704feSMark Prins $desc = p_get_metadata($ID, 'description', true); 81aeb704feSMark Prins if(!empty($desc)) { 8289906951SMark Prins $desc = str_replace("\n", " ", $desc['abstract']); 833cc863c5SMark Prins $event->data['meta'][] = array( 843cc863c5SMark Prins 'name' => 'twitter:description', 853cc863c5SMark Prins 'content' => $desc, 863cc863c5SMark Prins ); 87fc03d0b3SMark Prins } 882765f2acSMark Prins 89*fa5b18beSMark Prins if($this->getConf('twitterUserName') !== '') { 903cc863c5SMark Prins $event->data['meta'][] = array( 913cc863c5SMark Prins 'name' => 'twitter:creator', 923cc863c5SMark Prins 'content' => $this->getConf('twitterUserName'), 933cc863c5SMark Prins ); 94e0737b20SMark Prins } 952765f2acSMark Prins 963cc863c5SMark Prins $event->data['meta'][] = array( 973cc863c5SMark Prins 'name' => 'twitter:image', 983cc863c5SMark Prins 'content' => $this->getImage(), 993cc863c5SMark Prins ); 1003cc863c5SMark Prins $event->data['meta'][] = array( 1013cc863c5SMark Prins 'name' => 'twitter:image:alt', 1023cc863c5SMark Prins 'content' => $this->getImageAlt(), 1033cc863c5SMark Prins ); 10489906951SMark Prins 10589906951SMark Prins // opengraph, see http://ogp.me/ 10689906951SMark Prins // 10789906951SMark Prins // to make this work properly the template should be modified adding the 10889906951SMark Prins // namespaces for a (x)html 4 template make html tag: 10989906951SMark Prins // 11089906951SMark Prins // <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl" 11189906951SMark Prins // xmlns:og="http://ogp.me/ns#" xmlns:fb="http://ogp.me/ns/fb#" 11289906951SMark Prins // xmlns:article="http://ogp.me/ns/article#" xmlns:place="http://ogp.me/ns/place#"> 11389906951SMark Prins // 11489906951SMark Prins // and for a (x)html 5 template make head tag: 11589906951SMark Prins // 1165cd64445SMark Prins // <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# 1175cd64445SMark Prins // article: http://ogp.me/ns/article# place: http://ogp.me/ns/place#"> 11889906951SMark Prins 11989906951SMark Prins // og namespace http://ogp.me/ns# 1203cc863c5SMark Prins $event->data['meta'][] = array( 1213cc863c5SMark Prins 'property' => 'og:locale', 1223cc863c5SMark Prins 'content' => $this->getConf('languageTerritory'), 1233cc863c5SMark Prins ); 1243cc863c5SMark Prins $event->data['meta'][] = array( 1253cc863c5SMark Prins 'property' => 'og:site_name', 1263cc863c5SMark Prins 'content' => $conf['title'], 1273cc863c5SMark Prins ); 1283cc863c5SMark Prins $event->data['meta'][] = array( 1293cc863c5SMark Prins 'property' => 'og:url', 1303cc863c5SMark Prins 'content' => wl($ID, '', true), 1313cc863c5SMark Prins ); 1323cc863c5SMark Prins $event->data['meta'][] = array( 1333cc863c5SMark Prins 'property' => 'og:title', 1343cc863c5SMark Prins 'content' => p_get_metadata($ID, 'title', true), 1353cc863c5SMark Prins ); 13689906951SMark Prins if(!empty($desc)) { 1373cc863c5SMark Prins $event->data['meta'][] = array( 1383cc863c5SMark Prins 'property' => 'og:description', 1393cc863c5SMark Prins 'content' => $desc, 1403cc863c5SMark Prins ); 141fdb4710bSMark Prins } 1423cc863c5SMark Prins $event->data['meta'][] = array( 1433cc863c5SMark Prins 'property' => 'og:type', 1443cc863c5SMark Prins 'content' => "article", 1453cc863c5SMark Prins ); 146f3501156SLaurent Marquet $ogImage = $this->getImage(); 147*fa5b18beSMark Prins $secure = strpos($ogImage, 'https') === 0 ? ':secure_url' : ''; 1483cc863c5SMark Prins $event->data['meta'][] = array( 1493cc863c5SMark Prins 'property' => 'og:image' . $secure, 1503cc863c5SMark Prins 'content' => $ogImage, 1513cc863c5SMark Prins ); 15289906951SMark Prins 15389906951SMark Prins // article namespace http://ogp.me/ns/article# 15489906951SMark Prins $_dates = p_get_metadata($ID, 'date', true); 1553cc863c5SMark Prins $event->data['meta'][] = array( 1563cc863c5SMark Prins 'property' => 'article:published_time', 1573cc863c5SMark Prins 'content' => dformat($_dates['created']), 1583cc863c5SMark Prins ); 1593cc863c5SMark Prins $event->data['meta'][] = array( 1603cc863c5SMark Prins 'property' => 'article:modified_time', 1613cc863c5SMark Prins 'content' => dformat($_dates['modified']), 1623cc863c5SMark Prins ); 1633cc863c5SMark Prins $event->data['meta'][] = array( 1643cc863c5SMark Prins 'property' => 'article:author', 1653cc863c5SMark Prins 'content' => $INFO['editor'], 1663cc863c5SMark Prins ); 1673ab78d94SMark Prins// $event->data['meta'][] = array( 1683ab78d94SMark Prins// 'property' => 'article:author', 1693ab78d94SMark Prins// 'content' => p_get_metadata($ID, 'creator', true), 1703ab78d94SMark Prins// ); 1713ab78d94SMark Prins// $event->data['meta'][] = array( 1723ab78d94SMark Prins// 'property' => 'article:author', 1733ab78d94SMark Prins// 'content' => p_get_metadata($ID, 'user', true), 1743ab78d94SMark Prins// ); 17589906951SMark Prins $_subject = p_get_metadata($ID, 'subject', true); 17689906951SMark Prins if(!empty($_subject)) { 17789906951SMark Prins if(!is_array($_subject)) { 17889906951SMark Prins $_subject = array($_subject); 17989906951SMark Prins } 18089906951SMark Prins foreach($_subject as $tag) { 1813cc863c5SMark Prins $event->data['meta'][] = array( 1823cc863c5SMark Prins 'property' => 'article:tag', 1833cc863c5SMark Prins 'content' => $tag, 1843cc863c5SMark Prins ); 18589906951SMark Prins } 18689906951SMark Prins } 18789906951SMark Prins 18889906951SMark Prins // place namespace http://ogp.me/ns/place# 18989906951SMark Prins $geotags = p_get_metadata($ID, 'geo', true); 19089906951SMark Prins $lat = $geotags['lat']; 19189906951SMark Prins $lon = $geotags['lon']; 19289906951SMark Prins if(!(empty($lat) && empty($lon))) { 1933cc863c5SMark Prins $event->data['meta'][] = array( 1943cc863c5SMark Prins 'property' => 'place:location:latitude', 1953cc863c5SMark Prins 'content' => $lat, 1963cc863c5SMark Prins ); 1973cc863c5SMark Prins $event->data['meta'][] = array( 1983cc863c5SMark Prins 'property' => 'place:location:longitude', 1993cc863c5SMark Prins 'content' => $lon, 2003cc863c5SMark Prins ); 2013322a5daSMark Prins } 2023322a5daSMark Prins // see https://developers.facebook.com/docs/opengraph/property-types/#geopoint 2033322a5daSMark Prins $alt = $geotags['alt']; 2043322a5daSMark Prins if(!empty($alt)) { 2053322a5daSMark Prins // facebook expects feet... 206*fa5b18beSMark Prins $alt *= 3.2808; 2073cc863c5SMark Prins $event->data['meta'][] = array( 2083cc863c5SMark Prins 'property' => 'place:location:altitude', 2093cc863c5SMark Prins 'content' => $alt, 2103cc863c5SMark Prins ); 21189906951SMark Prins } 21289906951SMark Prins 213ae2f5c78SMark Prins /* these are not valid for the GeoPoint type.. 21489906951SMark Prins $region = $geotags['region']; 21589906951SMark Prins $country = $geotags['country']; 21689906951SMark Prins $placename = $geotags['placename']; 2173ab78d94SMark Prins if(!empty($region)) { 2183ab78d94SMark Prins $event->data['meta'][] = array('property' => 'place:location:region', 'content' => $region,); 2193ab78d94SMark Prins } 2203ab78d94SMark Prins if(!empty($placename)) { 2213ab78d94SMark Prins $event->data['meta'][] = array('property' => 'place:location:locality', 'content' => $placename,); 2223ab78d94SMark Prins } 2233ab78d94SMark Prins if(!empty($country)) { 2243ab78d94SMark Prins $event->data['meta'][] = array('property' => 'place:location:country-name', 'content' => $country,); 2253ab78d94SMark Prins } 22689906951SMark Prins */ 22789906951SMark Prins 22889906951SMark Prins // optional facebook app ID 22989906951SMark Prins $appId = $this->getConf('fbAppId'); 23089906951SMark Prins if(!empty($appId)) { 2313cc863c5SMark Prins $event->data['meta'][] = array( 2323cc863c5SMark Prins 'property' => 'fb:app_id', 2333cc863c5SMark Prins 'content' => $appId, 2343cc863c5SMark Prins ); 23589906951SMark Prins } 23689906951SMark Prins } 23789906951SMark Prins 23889906951SMark Prins /** 23989906951SMark Prins * Gets the canonical image path for this page. 24089906951SMark Prins * 24189906951SMark Prins * @return string the url to the image to use for this page 2423cc863c5SMark Prins * @global string $ID page id 24389906951SMark Prins */ 244*fa5b18beSMark Prins private function getImage(): string { 24589906951SMark Prins global $ID; 24689906951SMark Prins $rel = p_get_metadata($ID, 'relation', true); 24789906951SMark Prins $img = $rel['firstimage']; 248ae2f5c78SMark Prins 249ae2f5c78SMark Prins if(empty($img)) { 25093bf3c56SMark Prins $img = $this->getConf('fallbackImage'); 251*fa5b18beSMark Prins if(strpos($img, "http") === 0) { 2523efb311dSMark Prins // don't use ml() as this results in a HTTP redirect after 2533efb311dSMark Prins // hitting the wiki making the card image fail. 25493bf3c56SMark Prins return $img; 25593bf3c56SMark Prins } 25693bf3c56SMark Prins } 257ae2f5c78SMark Prins 258ae2f5c78SMark Prins return ml($img, array(), true, '&', true); 25989906951SMark Prins } 26089906951SMark Prins 261a543e5b7SMark Prins /** 262a543e5b7SMark Prins * Gets the alt text for this page image. 263a543e5b7SMark Prins * 264a543e5b7SMark Prins * @return string alt text 2653cc863c5SMark Prins * @global string $ID page id 266a543e5b7SMark Prins */ 267*fa5b18beSMark Prins private function getImageAlt(): string { 268a543e5b7SMark Prins global $ID; 269a543e5b7SMark Prins $rel = p_get_metadata($ID, 'relation', true); 270a543e5b7SMark Prins $imgID = $rel['firstimage']; 271a543e5b7SMark Prins $alt = ""; 272a543e5b7SMark Prins 273a543e5b7SMark Prins if(!empty($imgID)) { 274a543e5b7SMark Prins require_once(DOKU_INC . 'inc/JpegMeta.php'); 275a543e5b7SMark Prins $jpegmeta = new JpegMeta(mediaFN($imgID)); 2763cc863c5SMark Prins $tags = array( 2773cc863c5SMark Prins 'IPTC.Caption', 278a543e5b7SMark Prins 'EXIF.UserComment', 279a543e5b7SMark Prins 'EXIF.TIFFImageDescription', 280a543e5b7SMark Prins 'EXIF.TIFFUserComment', 281a543e5b7SMark Prins 'IPTC.Headline', 282a543e5b7SMark Prins 'Xmp.dc:title' 283a543e5b7SMark Prins ); 284a543e5b7SMark Prins $alt = media_getTag($tags, $jpegmeta, ""); 285a543e5b7SMark Prins } 286a543e5b7SMark Prins return htmlspecialchars($alt); 287a543e5b7SMark Prins } 288fdb4710bSMark Prins} 289