1fdb4710bSMark Prins<?php 2*7e4aad61Sgithub-actions[bot] 3*7e4aad61Sgithub-actions[bot]use dokuwiki\Extension\ActionPlugin; 4*7e4aad61Sgithub-actions[bot]use dokuwiki\Extension\EventHandler; 5*7e4aad61Sgithub-actions[bot]use dokuwiki\Extension\Event; 6*7e4aad61Sgithub-actions[bot] 7635a5e2dSMark Prins/* 82765f2acSMark Prins * Copyright (c) 2013-2016 Mark C. Prins <mprins@users.sf.net> 9fdb4710bSMark Prins * 10fdb4710bSMark Prins * Permission to use, copy, modify, and distribute this software for any 11fdb4710bSMark Prins * purpose with or without fee is hereby granted, provided that the above 12fdb4710bSMark Prins * copyright notice and this permission notice appear in all copies. 13fdb4710bSMark Prins * 14fdb4710bSMark Prins * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 15fdb4710bSMark Prins * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 16fdb4710bSMark Prins * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 17fdb4710bSMark Prins * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 18fdb4710bSMark Prins * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 19fdb4710bSMark Prins * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 20fdb4710bSMark Prins * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 21635a5e2dSMark Prins */ 22ae2f5c78SMark Prins/** 23ae2f5c78SMark Prins * DokuWiki Plugin socialcards (Action Component). 24ae2f5c78SMark Prins * 25ae2f5c78SMark Prins * @license BSD license 26ae2f5c78SMark Prins * @author Mark C. Prins <mprins@users.sf.net> 27ae2f5c78SMark Prins */ 28*7e4aad61Sgithub-actions[bot]class action_plugin_socialcards extends ActionPlugin 29*7e4aad61Sgithub-actions[bot]{ 30ae2f5c78SMark Prins /** 31ae2f5c78SMark Prins * Register our callback for the TPL_METAHEADER_OUTPUT event. 32ae2f5c78SMark Prins * 33ae2f5c78SMark Prins * @param $controller Doku_Event_Handler 34ae2f5c78SMark Prins * @see DokuWiki_Action_Plugin::register() 35ae2f5c78SMark Prins */ 36*7e4aad61Sgithub-actions[bot] public function register(EventHandler $controller): void 37*7e4aad61Sgithub-actions[bot] { 383cc863c5SMark Prins $controller->register_hook( 393cc863c5SMark Prins 'TPL_METAHEADER_OUTPUT', 403cc863c5SMark Prins 'BEFORE', 413cc863c5SMark Prins $this, 423cc863c5SMark Prins 'handleTplMetaheaderOutput' 433cc863c5SMark Prins ); 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 * 50fdb4710bSMark Prins * @param Doku_Event $event the DokuWiki event. $event->data is a two-dimensional 51fdb4710bSMark Prins * array of all meta headers. The keys are meta, link and script. 522765f2acSMark Prins * @param mixed $param the parameters passed to register_hook when this 532765f2acSMark Prins * handler was registered (not used) 5489906951SMark Prins * 553cc863c5SMark Prins * @global array $INFO 563cc863c5SMark Prins * @global string $ID page id 573cc863c5SMark Prins * @global array $conf global wiki configuration 5889906951SMark Prins * @see http://www.dokuwiki.org/devel:event:tpl_metaheader_output 59fdb4710bSMark Prins */ 60*7e4aad61Sgithub-actions[bot] public function handleTplMetaheaderOutput(Event $event, $param): void 61*7e4aad61Sgithub-actions[bot] { 6289906951SMark Prins global $ID, $conf, $INFO; 63fdb4710bSMark Prins 643cc863c5SMark Prins if (!page_exists($ID)) { 653cc863c5SMark Prins return; 663cc863c5SMark Prins } 67a67fa79eSMark Prins if (auth_quickaclcheck($ID) < AUTH_READ) { 68a67fa79eSMark Prins return; 69a67fa79eSMark Prins } 70156465eeSMark Prins 712765f2acSMark Prins // twitter card, see https://dev.twitter.com/cards/markup 722765f2acSMark Prins // creat a summary card, see https://dev.twitter.com/cards/types/summary 73*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['name' => 'twitter:card', 'content' => "summary"]; 742765f2acSMark Prins 75*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['name' => 'twitter:site', 'content' => $this->getConf('twitterName')]; 762765f2acSMark Prins 77*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['name' => 'twitter:title', 'content' => p_get_metadata($ID, 'title', METADATA_RENDER_USING_SIMPLE_CACHE)]; 782765f2acSMark Prins 79eecf9074SMark Prins $desc = p_get_metadata($ID, 'description', METADATA_RENDER_USING_SIMPLE_CACHE); 80aeb704feSMark Prins if (!empty($desc)) { 8189906951SMark Prins $desc = str_replace("\n", " ", $desc['abstract']); 82*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['name' => 'twitter:description', 'content' => $desc]; 83fc03d0b3SMark Prins } 842765f2acSMark Prins 85fa5b18beSMark Prins if ($this->getConf('twitterUserName') !== '') { 86*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['name' => 'twitter:creator', 'content' => $this->getConf('twitterUserName')]; 87e0737b20SMark Prins } 882765f2acSMark Prins 89*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['name' => 'twitter:image', 'content' => $this->getImage()]; 90*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['name' => 'twitter:image:alt', 'content' => $this->getImageAlt()]; 9189906951SMark Prins 9289906951SMark Prins // opengraph, see http://ogp.me/ 9389906951SMark Prins // 9489906951SMark Prins // to make this work properly the template should be modified adding the 9589906951SMark Prins // namespaces for a (x)html 4 template make html tag: 9689906951SMark Prins // 9789906951SMark Prins // <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl" 9889906951SMark Prins // xmlns:og="http://ogp.me/ns#" xmlns:fb="http://ogp.me/ns/fb#" 9989906951SMark Prins // xmlns:article="http://ogp.me/ns/article#" xmlns:place="http://ogp.me/ns/place#"> 10089906951SMark Prins // 10189906951SMark Prins // and for a (x)html 5 template make head tag: 10289906951SMark Prins // 1035cd64445SMark Prins // <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# 1045cd64445SMark Prins // article: http://ogp.me/ns/article# place: http://ogp.me/ns/place#"> 10589906951SMark Prins 10689906951SMark Prins // og namespace http://ogp.me/ns# 107*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'og:locale', 'content' => $this->getConf('languageTerritory')]; 108*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'og:site_name', 'content' => $conf['title']]; 109*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'og:url', 'content' => wl($ID, '', true)]; 110*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'og:title', 'content' => p_get_metadata($ID, 'title', METADATA_RENDER_USING_SIMPLE_CACHE)]; 11189906951SMark Prins if (!empty($desc)) { 112*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'og:description', 'content' => $desc]; 113fdb4710bSMark Prins } 114*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'og:type', 'content' => "article"]; 115f3501156SLaurent Marquet $ogImage = $this->getImage(); 116fa5b18beSMark Prins $secure = strpos($ogImage, 'https') === 0 ? ':secure_url' : ''; 117*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'og:image' . $secure, 'content' => $ogImage]; 11889906951SMark Prins 11989906951SMark Prins // article namespace http://ogp.me/ns/article# 120eecf9074SMark Prins $_dates = p_get_metadata($ID, 'date', METADATA_RENDER_USING_SIMPLE_CACHE); 121*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'article:published_time', 'content' => dformat($_dates['created'])]; 122*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'article:modified_time', 'content' => dformat($_dates['modified'])]; 123*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'article:author', 'content' => $INFO['editor']]; 1243ab78d94SMark Prins// $event->data['meta'][] = array( 1253ab78d94SMark Prins// 'property' => 'article:author', 126eecf9074SMark Prins// 'content' => p_get_metadata($ID, 'creator', METADATA_RENDER_USING_SIMPLE_CACHE), 1273ab78d94SMark Prins// ); 1283ab78d94SMark Prins// $event->data['meta'][] = array( 1293ab78d94SMark Prins// 'property' => 'article:author', 130eecf9074SMark Prins// 'content' => p_get_metadata($ID, 'user', METADATA_RENDER_USING_SIMPLE_CACHE), 1313ab78d94SMark Prins// ); 132eecf9074SMark Prins $_subject = p_get_metadata($ID, 'subject', METADATA_RENDER_USING_SIMPLE_CACHE); 13389906951SMark Prins if (!empty($_subject)) { 13489906951SMark Prins if (!is_array($_subject)) { 135*7e4aad61Sgithub-actions[bot] $_subject = [$_subject]; 13689906951SMark Prins } 13789906951SMark Prins foreach ($_subject as $tag) { 138*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'article:tag', 'content' => $tag]; 13989906951SMark Prins } 14089906951SMark Prins } 14189906951SMark Prins 14289906951SMark Prins // place namespace http://ogp.me/ns/place# 143eecf9074SMark Prins $geotags = p_get_metadata($ID, 'geo', METADATA_RENDER_USING_SIMPLE_CACHE); 1442ebecb01SMark Prins if (is_array($geotags)) { 1452ebecb01SMark Prins $lat = $geotags['lat'] ?? 0; 1462ebecb01SMark Prins $lon = $geotags['lon'] ?? 0; 14789906951SMark Prins if (!(empty($lat) && empty($lon))) { 148*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'place:location:latitude', 'content' => $lat]; 149*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'place:location:longitude', 'content' => $lon]; 1503322a5daSMark Prins } 1513322a5daSMark Prins // see https://developers.facebook.com/docs/opengraph/property-types/#geopoint 1522ebecb01SMark Prins $alt = $geotags['alt'] ?? 0; 1533322a5daSMark Prins if (!empty($alt)) { 1543322a5daSMark Prins // facebook expects feet... 155fa5b18beSMark Prins $alt *= 3.2808; 156*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'place:location:altitude', 'content' => $alt]; 15789906951SMark Prins } 158ae2f5c78SMark Prins /* these are not valid for the GeoPoint type.. 15989906951SMark Prins $region = $geotags['region']; 16089906951SMark Prins $country = $geotags['country']; 16189906951SMark Prins $placename = $geotags['placename']; 1623ab78d94SMark Prins if(!empty($region)) { 1633ab78d94SMark Prins $event->data['meta'][] = array('property' => 'place:location:region', 'content' => $region,); 1643ab78d94SMark Prins } 1653ab78d94SMark Prins if(!empty($placename)) { 1663ab78d94SMark Prins $event->data['meta'][] = array('property' => 'place:location:locality', 'content' => $placename,); 1673ab78d94SMark Prins } 1683ab78d94SMark Prins if(!empty($country)) { 1693ab78d94SMark Prins $event->data['meta'][] = array('property' => 'place:location:country-name', 'content' => $country,); 1703ab78d94SMark Prins } 17189906951SMark Prins */ 1722ebecb01SMark Prins } 17389906951SMark Prins 17489906951SMark Prins // optional facebook app ID 17589906951SMark Prins $appId = $this->getConf('fbAppId'); 17689906951SMark Prins if (!empty($appId)) { 177*7e4aad61Sgithub-actions[bot] $event->data['meta'][] = ['property' => 'fb:app_id', 'content' => $appId]; 17889906951SMark Prins } 17989906951SMark Prins } 18089906951SMark Prins 18189906951SMark Prins /** 18289906951SMark Prins * Gets the canonical image path for this page. 18389906951SMark Prins * 18489906951SMark Prins * @return string the url to the image to use for this page 1853cc863c5SMark Prins * @global string $ID page id 18689906951SMark Prins */ 187*7e4aad61Sgithub-actions[bot] private function getImage(): string 188*7e4aad61Sgithub-actions[bot] { 18989906951SMark Prins global $ID; 190eecf9074SMark Prins $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_SIMPLE_CACHE); 19189906951SMark Prins $img = $rel['firstimage']; 192ae2f5c78SMark Prins 193ae2f5c78SMark Prins if (empty($img)) { 19493bf3c56SMark Prins $img = $this->getConf('fallbackImage'); 195fa5b18beSMark Prins if (strpos($img, "http") === 0) { 1963efb311dSMark Prins // don't use ml() as this results in a HTTP redirect after 1973efb311dSMark Prins // hitting the wiki making the card image fail. 19893bf3c56SMark Prins return $img; 19993bf3c56SMark Prins } 20093bf3c56SMark Prins } 201ae2f5c78SMark Prins 202*7e4aad61Sgithub-actions[bot] return ml($img, [], true, '&', true); 20389906951SMark Prins } 20489906951SMark Prins 205a543e5b7SMark Prins /** 206a543e5b7SMark Prins * Gets the alt text for this page image. 207a543e5b7SMark Prins * 208a543e5b7SMark Prins * @return string alt text 2093cc863c5SMark Prins * @global string $ID page id 210a543e5b7SMark Prins */ 211*7e4aad61Sgithub-actions[bot] private function getImageAlt(): string 212*7e4aad61Sgithub-actions[bot] { 213a543e5b7SMark Prins global $ID; 214eecf9074SMark Prins $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_SIMPLE_CACHE); 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)); 221*7e4aad61Sgithub-actions[bot] $tags = ['IPTC.Caption', 'EXIF.UserComment', 'EXIF.TIFFImageDescription', 'EXIF.TIFFUserComment', 'IPTC.Headline', 'Xmp.dc:title']; 222a543e5b7SMark Prins $alt = media_getTag($tags, $jpegmeta, ""); 223a543e5b7SMark Prins } 224a543e5b7SMark Prins return htmlspecialchars($alt); 225a543e5b7SMark Prins } 226fdb4710bSMark Prins} 227