* * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** * DokuWiki Plugin socialcards (Action Component). * * @license BSD license * @author Mark C. Prins */ class action_plugin_socialcards extends DokuWiki_Action_Plugin { /** * Register our callback for the TPL_METAHEADER_OUTPUT event. * * @param $controller Doku_Event_Handler * @see DokuWiki_Action_Plugin::register() */ public function register(Doku_Event_Handler $controller): void { $controller->register_hook( 'TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handleTplMetaheaderOutput' ); } /** * Retrieve metadata and add to the head of the page using appropriate meta * tags unless the page does not exist. * * @param Doku_Event $event the DokuWiki event. $event->data is a two-dimensional * array of all meta headers. The keys are meta, link and script. * @param mixed $param the parameters passed to register_hook when this * handler was registered (not used) * * @global array $INFO * @global string $ID page id * @global array $conf global wiki configuration * @see http://www.dokuwiki.org/devel:event:tpl_metaheader_output */ public function handleTplMetaheaderOutput(Doku_Event $event, $param): void { global $ID, $conf, $INFO; if(!page_exists($ID)) { return; } if(auth_quickaclcheck($ID) < AUTH_READ) { return; } // twitter card, see https://dev.twitter.com/cards/markup // creat a summary card, see https://dev.twitter.com/cards/types/summary $event->data['meta'][] = array( 'name' => 'twitter:card', 'content' => "summary", ); $event->data['meta'][] = array( 'name' => 'twitter:site', 'content' => $this->getConf('twitterName'), ); $event->data['meta'][] = array( 'name' => 'twitter:title', 'content' => p_get_metadata($ID, 'title', METADATA_RENDER_USING_SIMPLE_CACHE), ); $desc = p_get_metadata($ID, 'description', METADATA_RENDER_USING_SIMPLE_CACHE); if(!empty($desc)) { $desc = str_replace("\n", " ", $desc['abstract']); $event->data['meta'][] = array( 'name' => 'twitter:description', 'content' => $desc, ); } if($this->getConf('twitterUserName') !== '') { $event->data['meta'][] = array( 'name' => 'twitter:creator', 'content' => $this->getConf('twitterUserName'), ); } $event->data['meta'][] = array( 'name' => 'twitter:image', 'content' => $this->getImage(), ); $event->data['meta'][] = array( 'name' => 'twitter:image:alt', 'content' => $this->getImageAlt(), ); // opengraph, see http://ogp.me/ // // to make this work properly the template should be modified adding the // namespaces for a (x)html 4 template make html tag: // // // // and for a (x)html 5 template make head tag: // // // og namespace http://ogp.me/ns# $event->data['meta'][] = array( 'property' => 'og:locale', 'content' => $this->getConf('languageTerritory'), ); $event->data['meta'][] = array( 'property' => 'og:site_name', 'content' => $conf['title'], ); $event->data['meta'][] = array( 'property' => 'og:url', 'content' => wl($ID, '', true), ); $event->data['meta'][] = array( 'property' => 'og:title', 'content' => p_get_metadata($ID, 'title', METADATA_RENDER_USING_SIMPLE_CACHE), ); if(!empty($desc)) { $event->data['meta'][] = array( 'property' => 'og:description', 'content' => $desc, ); } $event->data['meta'][] = array( 'property' => 'og:type', 'content' => "article", ); $ogImage = $this->getImage(); $secure = strpos($ogImage, 'https') === 0 ? ':secure_url' : ''; $event->data['meta'][] = array( 'property' => 'og:image' . $secure, 'content' => $ogImage, ); // article namespace http://ogp.me/ns/article# $_dates = p_get_metadata($ID, 'date', METADATA_RENDER_USING_SIMPLE_CACHE); $event->data['meta'][] = array( 'property' => 'article:published_time', 'content' => dformat($_dates['created']), ); $event->data['meta'][] = array( 'property' => 'article:modified_time', 'content' => dformat($_dates['modified']), ); $event->data['meta'][] = array( 'property' => 'article:author', 'content' => $INFO['editor'], ); // $event->data['meta'][] = array( // 'property' => 'article:author', // 'content' => p_get_metadata($ID, 'creator', METADATA_RENDER_USING_SIMPLE_CACHE), // ); // $event->data['meta'][] = array( // 'property' => 'article:author', // 'content' => p_get_metadata($ID, 'user', METADATA_RENDER_USING_SIMPLE_CACHE), // ); $_subject = p_get_metadata($ID, 'subject', METADATA_RENDER_USING_SIMPLE_CACHE); if(!empty($_subject)) { if(!is_array($_subject)) { $_subject = array($_subject); } foreach($_subject as $tag) { $event->data['meta'][] = array( 'property' => 'article:tag', 'content' => $tag, ); } } // place namespace http://ogp.me/ns/place# $geotags = p_get_metadata($ID, 'geo', METADATA_RENDER_USING_SIMPLE_CACHE); if(is_array($geotags)) { $lat = $geotags['lat'] ?? 0; $lon = $geotags['lon'] ?? 0; if(!(empty($lat) && empty($lon))) { $event->data['meta'][] = array( 'property' => 'place:location:latitude', 'content' => $lat, ); $event->data['meta'][] = array( 'property' => 'place:location:longitude', 'content' => $lon, ); } // see https://developers.facebook.com/docs/opengraph/property-types/#geopoint $alt = $geotags['alt'] ?? 0; if(!empty($alt)) { // facebook expects feet... $alt *= 3.2808; $event->data['meta'][] = array( 'property' => 'place:location:altitude', 'content' => $alt, ); } /* these are not valid for the GeoPoint type.. $region = $geotags['region']; $country = $geotags['country']; $placename = $geotags['placename']; if(!empty($region)) { $event->data['meta'][] = array('property' => 'place:location:region', 'content' => $region,); } if(!empty($placename)) { $event->data['meta'][] = array('property' => 'place:location:locality', 'content' => $placename,); } if(!empty($country)) { $event->data['meta'][] = array('property' => 'place:location:country-name', 'content' => $country,); } */ } // optional facebook app ID $appId = $this->getConf('fbAppId'); if(!empty($appId)) { $event->data['meta'][] = array( 'property' => 'fb:app_id', 'content' => $appId, ); } } /** * Gets the canonical image path for this page. * * @return string the url to the image to use for this page * @global string $ID page id */ private function getImage(): string { global $ID; $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_SIMPLE_CACHE); $img = $rel['firstimage']; if(empty($img)) { $img = $this->getConf('fallbackImage'); if(strpos($img, "http") === 0) { // don't use ml() as this results in a HTTP redirect after // hitting the wiki making the card image fail. return $img; } } return ml($img, array(), true, '&', true); } /** * Gets the alt text for this page image. * * @return string alt text * @global string $ID page id */ private function getImageAlt(): string { global $ID; $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_SIMPLE_CACHE); $imgID = $rel['firstimage']; $alt = ""; if(!empty($imgID)) { require_once(DOKU_INC . 'inc/JpegMeta.php'); $jpegmeta = new JpegMeta(mediaFN($imgID)); $tags = array( 'IPTC.Caption', 'EXIF.UserComment', 'EXIF.TIFFImageDescription', 'EXIF.TIFFUserComment', 'IPTC.Headline', 'Xmp.dc:title' ); $alt = media_getTag($tags, $jpegmeta, ""); } return htmlspecialchars($alt); } }