1<?php 2 3use dokuwiki\Extension\ActionPlugin; 4use dokuwiki\Extension\EventHandler; 5use dokuwiki\Extension\Event; 6 7/* 8 * Copyright (c) 2013-2016 Mark C. Prins <mprins@users.sf.net> 9 * 10 * Permission to use, copy, modify, and distribute this software for any 11 * purpose with or without fee is hereby granted, provided that the above 12 * copyright notice and this permission notice appear in all copies. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 21 */ 22/** 23 * DokuWiki Plugin socialcards (Action Component). 24 * 25 * @license BSD license 26 * @author Mark C. Prins <mprins@users.sf.net> 27 */ 28class action_plugin_socialcards extends ActionPlugin 29{ 30 /** 31 * Register our callback for the TPL_METAHEADER_OUTPUT event. 32 * 33 * @param $controller Doku_Event_Handler 34 * @see DokuWiki_Action_Plugin::register() 35 */ 36 public function register(EventHandler $controller): void 37 { 38 $controller->register_hook( 39 'TPL_METAHEADER_OUTPUT', 40 'BEFORE', 41 $this, 42 'handleTplMetaheaderOutput' 43 ); 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 * @param Event $event the DokuWiki event. $event->data is a two-dimensional 51 * array of all meta headers. The keys are meta, link and script. 52 * @param mixed $param the parameters passed to register_hook when this 53 * handler was registered (not used) 54 * 55 * @global array $INFO 56 * @global string $ID page id 57 * @global array $conf global wiki configuration 58 * @see http://www.dokuwiki.org/devel:event:tpl_metaheader_output 59 */ 60 public function handleTplMetaheaderOutput(Event $event, $param): void 61 { 62 global $ID, $conf, $INFO; 63 64 if (!page_exists($ID)) { 65 return; 66 } 67 if (auth_quickaclcheck($ID) < AUTH_READ) { 68 return; 69 } 70 71 // twitter card, see https://dev.twitter.com/cards/markup 72 // creat a summary card, see https://dev.twitter.com/cards/types/summary 73 $event->data['meta'][] = ['name' => 'twitter:card', 'content' => "summary"]; 74 75 $event->data['meta'][] = ['name' => 'twitter:site', 'content' => $this->getConf('twitterName')]; 76 77 $event->data['meta'][] = ['name' => 'twitter:title', 'content' => p_get_metadata($ID, 'title', METADATA_RENDER_USING_SIMPLE_CACHE)]; 78 79 $desc = p_get_metadata($ID, 'description', METADATA_RENDER_USING_SIMPLE_CACHE); 80 if (!empty($desc)) { 81 $desc = str_replace("\n", " ", $desc['abstract']); 82 $event->data['meta'][] = ['name' => 'twitter:description', 'content' => $desc]; 83 } 84 85 if ($this->getConf('twitterUserName') !== '') { 86 $event->data['meta'][] = ['name' => 'twitter:creator', 'content' => $this->getConf('twitterUserName')]; 87 } 88 89 $event->data['meta'][] = ['name' => 'twitter:image', 'content' => $this->getImage()]; 90 $event->data['meta'][] = ['name' => 'twitter:image:alt', 'content' => $this->getImageAlt()]; 91 92 // opengraph, see http://ogp.me/ 93 // 94 // to make this work properly the template should be modified adding the 95 // namespaces for a (x)html 4 template make html tag: 96 // 97 // <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl" 98 // xmlns:og="http://ogp.me/ns#" xmlns:fb="http://ogp.me/ns/fb#" 99 // xmlns:article="http://ogp.me/ns/article#" xmlns:place="http://ogp.me/ns/place#"> 100 // 101 // and for a (x)html 5 template make head tag: 102 // 103 // <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# 104 // article: http://ogp.me/ns/article# place: http://ogp.me/ns/place#"> 105 106 // og namespace http://ogp.me/ns# 107 $event->data['meta'][] = ['property' => 'og:locale', 'content' => $this->getConf('languageTerritory')]; 108 $event->data['meta'][] = ['property' => 'og:site_name', 'content' => $conf['title']]; 109 $event->data['meta'][] = ['property' => 'og:url', 'content' => wl($ID, '', true)]; 110 $event->data['meta'][] = ['property' => 'og:title', 'content' => p_get_metadata($ID, 'title', METADATA_RENDER_USING_SIMPLE_CACHE)]; 111 if (!empty($desc)) { 112 $event->data['meta'][] = ['property' => 'og:description', 'content' => $desc]; 113 } 114 $event->data['meta'][] = ['property' => 'og:type', 'content' => "article"]; 115 $ogImage = $this->getImage(); 116 $secure = strpos($ogImage, 'https') === 0 ? ':secure_url' : ''; 117 $event->data['meta'][] = ['property' => 'og:image' . $secure, 'content' => $ogImage]; 118 119 // article namespace http://ogp.me/ns/article# 120 $_dates = p_get_metadata($ID, 'date', METADATA_RENDER_USING_SIMPLE_CACHE); 121 $event->data['meta'][] = ['property' => 'article:published_time', 'content' => dformat($_dates['created'])]; 122 $event->data['meta'][] = ['property' => 'article:modified_time', 'content' => dformat($_dates['modified'])]; 123 $event->data['meta'][] = ['property' => 'article:author', 'content' => $INFO['editor']]; 124// $event->data['meta'][] = array( 125// 'property' => 'article:author', 126// 'content' => p_get_metadata($ID, 'creator', METADATA_RENDER_USING_SIMPLE_CACHE), 127// ); 128// $event->data['meta'][] = array( 129// 'property' => 'article:author', 130// 'content' => p_get_metadata($ID, 'user', METADATA_RENDER_USING_SIMPLE_CACHE), 131// ); 132 $_subject = p_get_metadata($ID, 'subject', METADATA_RENDER_USING_SIMPLE_CACHE); 133 if (!empty($_subject)) { 134 if (!is_array($_subject)) { 135 $_subject = [$_subject]; 136 } 137 foreach ($_subject as $tag) { 138 $event->data['meta'][] = ['property' => 'article:tag', 'content' => $tag]; 139 } 140 } 141 142 // place namespace http://ogp.me/ns/place# 143 $geotags = p_get_metadata($ID, 'geo', METADATA_RENDER_USING_SIMPLE_CACHE); 144 if (is_array($geotags)) { 145 $lat = $geotags['lat'] ?? 0; 146 $lon = $geotags['lon'] ?? 0; 147 if (!(empty($lat) && empty($lon))) { 148 $event->data['meta'][] = ['property' => 'place:location:latitude', 'content' => $lat]; 149 $event->data['meta'][] = ['property' => 'place:location:longitude', 'content' => $lon]; 150 } 151 // see https://developers.facebook.com/docs/opengraph/property-types/#geopoint 152 $alt = $geotags['alt'] ?? 0; 153 if (!empty($alt)) { 154 // facebook expects feet... 155 $alt *= 3.2808; 156 $event->data['meta'][] = ['property' => 'place:location:altitude', 'content' => $alt]; 157 } 158 /* these are not valid for the GeoPoint type.. 159 $region = $geotags['region']; 160 $country = $geotags['country']; 161 $placename = $geotags['placename']; 162 if(!empty($region)) { 163 $event->data['meta'][] = array('property' => 'place:location:region', 'content' => $region,); 164 } 165 if(!empty($placename)) { 166 $event->data['meta'][] = array('property' => 'place:location:locality', 'content' => $placename,); 167 } 168 if(!empty($country)) { 169 $event->data['meta'][] = array('property' => 'place:location:country-name', 'content' => $country,); 170 } 171 */ 172 } 173 174 // optional facebook app ID 175 $appId = $this->getConf('fbAppId'); 176 if (!empty($appId)) { 177 $event->data['meta'][] = ['property' => 'fb:app_id', 'content' => $appId]; 178 } 179 } 180 181 /** 182 * Gets the canonical image path for this page. 183 * 184 * @return string the url to the image to use for this page 185 * @global string $ID page id 186 */ 187 private function getImage(): string 188 { 189 global $ID; 190 $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_SIMPLE_CACHE); 191 $img = $rel['firstimage']; 192 193 if (empty($img)) { 194 $img = $this->getConf('fallbackImage'); 195 if (strpos($img, "http") === 0) { 196 // don't use ml() as this results in a HTTP redirect after 197 // hitting the wiki making the card image fail. 198 return $img; 199 } 200 } 201 202 return ml($img, [], true, '&', true); 203 } 204 205 /** 206 * Gets the alt text for this page image. 207 * 208 * @return string alt text 209 * @global string $ID page id 210 */ 211 private function getImageAlt(): string 212 { 213 global $ID; 214 $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_SIMPLE_CACHE); 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 = ['IPTC.Caption', 'EXIF.UserComment', 'EXIF.TIFFImageDescription', 'EXIF.TIFFUserComment', 'IPTC.Headline', 'Xmp.dc:title']; 222 $alt = media_getTag($tags, $jpegmeta, ""); 223 } 224 return htmlspecialchars($alt); 225 } 226} 227