xref: /plugin/socialcards/action.php (revision eecf90743071299a8723e00ae72ec7a528cc3ace)
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 */
24fdb4710bSMark Prinsclass action_plugin_socialcards extends DokuWiki_Action_Plugin {
25fdb4710bSMark Prins
26ae2f5c78SMark Prins    /**
27ae2f5c78SMark Prins     * Register our callback for the TPL_METAHEADER_OUTPUT event.
28ae2f5c78SMark Prins     *
29ae2f5c78SMark Prins     * @param $controller Doku_Event_Handler
30ae2f5c78SMark Prins     * @see DokuWiki_Action_Plugin::register()
31ae2f5c78SMark Prins     */
32fa5b18beSMark Prins    public function register(Doku_Event_Handler $controller): void {
333cc863c5SMark Prins        $controller->register_hook(
343cc863c5SMark Prins            'TPL_METAHEADER_OUTPUT',
353cc863c5SMark Prins            'BEFORE',
363cc863c5SMark Prins            $this,
373cc863c5SMark Prins            'handleTplMetaheaderOutput'
383cc863c5SMark Prins        );
39fdb4710bSMark Prins    }
40fdb4710bSMark Prins
41fdb4710bSMark Prins    /**
4289906951SMark Prins     * Retrieve metadata and add to the head of the page using appropriate meta
43156465eeSMark Prins     * tags unless the page does not exist.
4489906951SMark Prins     *
45fdb4710bSMark Prins     * @param Doku_Event $event the DokuWiki event. $event->data is a two-dimensional
46fdb4710bSMark Prins     *                          array of all meta headers. The keys are meta, link and script.
472765f2acSMark Prins     * @param mixed      $param the parameters passed to register_hook when this
482765f2acSMark Prins     *                          handler was registered (not used)
4989906951SMark Prins     *
503cc863c5SMark Prins     * @global array     $INFO
513cc863c5SMark Prins     * @global string    $ID    page id
523cc863c5SMark Prins     * @global array     $conf  global wiki configuration
5389906951SMark Prins     * @see http://www.dokuwiki.org/devel:event:tpl_metaheader_output
54fdb4710bSMark Prins     */
55fa5b18beSMark Prins    public function handleTplMetaheaderOutput(Doku_Event $event, $param): void {
5689906951SMark Prins        global $ID, $conf, $INFO;
57fdb4710bSMark Prins
583cc863c5SMark Prins        if(!page_exists($ID)) {
593cc863c5SMark Prins            return;
603cc863c5SMark Prins        }
61156465eeSMark Prins
622765f2acSMark Prins        // twitter card, see https://dev.twitter.com/cards/markup
632765f2acSMark Prins        // creat a summary card, see https://dev.twitter.com/cards/types/summary
643cc863c5SMark Prins        $event->data['meta'][] = array(
653cc863c5SMark Prins            'name'    => 'twitter:card',
663cc863c5SMark Prins            'content' => "summary",
673cc863c5SMark Prins        );
682765f2acSMark Prins
693cc863c5SMark Prins        $event->data['meta'][] = array(
703cc863c5SMark Prins            'name'    => 'twitter:site',
713cc863c5SMark Prins            'content' => $this->getConf('twitterName'),
723cc863c5SMark Prins        );
732765f2acSMark Prins
743cc863c5SMark Prins        $event->data['meta'][] = array(
753cc863c5SMark Prins            'name'    => 'twitter:title',
76*eecf9074SMark Prins            'content' => p_get_metadata($ID, 'title', METADATA_RENDER_USING_SIMPLE_CACHE),
773cc863c5SMark Prins        );
782765f2acSMark Prins
79*eecf9074SMark 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']);
823cc863c5SMark Prins            $event->data['meta'][] = array(
833cc863c5SMark Prins                'name'    => 'twitter:description',
843cc863c5SMark Prins                'content' => $desc,
853cc863c5SMark Prins            );
86fc03d0b3SMark Prins        }
872765f2acSMark Prins
88fa5b18beSMark Prins        if($this->getConf('twitterUserName') !== '') {
893cc863c5SMark Prins            $event->data['meta'][] = array(
903cc863c5SMark Prins                'name'    => 'twitter:creator',
913cc863c5SMark Prins                'content' => $this->getConf('twitterUserName'),
923cc863c5SMark Prins            );
93e0737b20SMark Prins        }
942765f2acSMark Prins
953cc863c5SMark Prins        $event->data['meta'][] = array(
963cc863c5SMark Prins            'name'    => 'twitter:image',
973cc863c5SMark Prins            'content' => $this->getImage(),
983cc863c5SMark Prins        );
993cc863c5SMark Prins        $event->data['meta'][] = array(
1003cc863c5SMark Prins            'name'    => 'twitter:image:alt',
1013cc863c5SMark Prins            'content' => $this->getImageAlt(),
1023cc863c5SMark Prins        );
10389906951SMark Prins
10489906951SMark Prins        // opengraph, see http://ogp.me/
10589906951SMark Prins        //
10689906951SMark Prins        // to make this work properly the template should be modified adding the
10789906951SMark Prins        // namespaces for a (x)html 4 template make html tag:
10889906951SMark Prins        //
10989906951SMark Prins        // <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl"
11089906951SMark Prins        //       xmlns:og="http://ogp.me/ns#" xmlns:fb="http://ogp.me/ns/fb#"
11189906951SMark Prins        //       xmlns:article="http://ogp.me/ns/article#" xmlns:place="http://ogp.me/ns/place#">
11289906951SMark Prins        //
11389906951SMark Prins        // and for a (x)html 5 template make head tag:
11489906951SMark Prins        //
1155cd64445SMark Prins        // <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#
1165cd64445SMark Prins        //    article: http://ogp.me/ns/article# place: http://ogp.me/ns/place#">
11789906951SMark Prins
11889906951SMark Prins        // og namespace http://ogp.me/ns#
1193cc863c5SMark Prins        $event->data['meta'][] = array(
1203cc863c5SMark Prins            'property' => 'og:locale',
1213cc863c5SMark Prins            'content'  => $this->getConf('languageTerritory'),
1223cc863c5SMark Prins        );
1233cc863c5SMark Prins        $event->data['meta'][] = array(
1243cc863c5SMark Prins            'property' => 'og:site_name',
1253cc863c5SMark Prins            'content'  => $conf['title'],
1263cc863c5SMark Prins        );
1273cc863c5SMark Prins        $event->data['meta'][] = array(
1283cc863c5SMark Prins            'property' => 'og:url',
1293cc863c5SMark Prins            'content'  => wl($ID, '', true),
1303cc863c5SMark Prins        );
1313cc863c5SMark Prins        $event->data['meta'][] = array(
1323cc863c5SMark Prins            'property' => 'og:title',
133*eecf9074SMark Prins            'content'  => p_get_metadata($ID, 'title', METADATA_RENDER_USING_SIMPLE_CACHE),
1343cc863c5SMark Prins        );
13589906951SMark Prins        if(!empty($desc)) {
1363cc863c5SMark Prins            $event->data['meta'][] = array(
1373cc863c5SMark Prins                'property' => 'og:description',
1383cc863c5SMark Prins                'content'  => $desc,
1393cc863c5SMark Prins            );
140fdb4710bSMark Prins        }
1413cc863c5SMark Prins        $event->data['meta'][] = array(
1423cc863c5SMark Prins            'property' => 'og:type',
1433cc863c5SMark Prins            'content'  => "article",
1443cc863c5SMark Prins        );
145f3501156SLaurent Marquet        $ogImage               = $this->getImage();
146fa5b18beSMark Prins        $secure                = strpos($ogImage, 'https') === 0 ? ':secure_url' : '';
1473cc863c5SMark Prins        $event->data['meta'][] = array(
1483cc863c5SMark Prins            'property' => 'og:image' . $secure,
1493cc863c5SMark Prins            'content'  => $ogImage,
1503cc863c5SMark Prins        );
15189906951SMark Prins
15289906951SMark Prins        // article namespace http://ogp.me/ns/article#
153*eecf9074SMark Prins        $_dates                = p_get_metadata($ID, 'date', METADATA_RENDER_USING_SIMPLE_CACHE);
1543cc863c5SMark Prins        $event->data['meta'][] = array(
1553cc863c5SMark Prins            'property' => 'article:published_time',
1563cc863c5SMark Prins            'content'  => dformat($_dates['created']),
1573cc863c5SMark Prins        );
1583cc863c5SMark Prins        $event->data['meta'][] = array(
1593cc863c5SMark Prins            'property' => 'article:modified_time',
1603cc863c5SMark Prins            'content'  => dformat($_dates['modified']),
1613cc863c5SMark Prins        );
1623cc863c5SMark Prins        $event->data['meta'][] = array(
1633cc863c5SMark Prins            'property' => 'article:author',
1643cc863c5SMark Prins            'content'  => $INFO['editor'],
1653cc863c5SMark Prins        );
1663ab78d94SMark Prins//        $event->data['meta'][] = array(
1673ab78d94SMark Prins//            'property' => 'article:author',
168*eecf9074SMark Prins//            'content'  => p_get_metadata($ID, 'creator', METADATA_RENDER_USING_SIMPLE_CACHE),
1693ab78d94SMark Prins//        );
1703ab78d94SMark Prins//        $event->data['meta'][] = array(
1713ab78d94SMark Prins//            'property' => 'article:author',
172*eecf9074SMark Prins//            'content'  => p_get_metadata($ID, 'user', METADATA_RENDER_USING_SIMPLE_CACHE),
1733ab78d94SMark Prins//        );
174*eecf9074SMark Prins        $_subject = p_get_metadata($ID, 'subject', METADATA_RENDER_USING_SIMPLE_CACHE);
17589906951SMark Prins        if(!empty($_subject)) {
17689906951SMark Prins            if(!is_array($_subject)) {
17789906951SMark Prins                $_subject = array($_subject);
17889906951SMark Prins            }
17989906951SMark Prins            foreach($_subject as $tag) {
1803cc863c5SMark Prins                $event->data['meta'][] = array(
1813cc863c5SMark Prins                    'property' => 'article:tag',
1823cc863c5SMark Prins                    'content'  => $tag,
1833cc863c5SMark Prins                );
18489906951SMark Prins            }
18589906951SMark Prins        }
18689906951SMark Prins
18789906951SMark Prins        // place namespace http://ogp.me/ns/place#
188*eecf9074SMark Prins        $geotags = p_get_metadata($ID, 'geo', METADATA_RENDER_USING_SIMPLE_CACHE);
1892ebecb01SMark Prins        if(is_array($geotags)) {
1902ebecb01SMark Prins            $lat = $geotags['lat'] ?? 0;
1912ebecb01SMark Prins            $lon = $geotags['lon'] ?? 0;
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
2032ebecb01SMark Prins            $alt = $geotags['alt'] ?? 0;
2043322a5daSMark Prins            if(!empty($alt)) {
2053322a5daSMark Prins                // facebook expects feet...
206fa5b18beSMark Prins                $alt                   *= 3.2808;
2073cc863c5SMark Prins                $event->data['meta'][] = array(
2083cc863c5SMark Prins                    'property' => 'place:location:altitude',
2093cc863c5SMark Prins                    'content'  => $alt,
2103cc863c5SMark Prins                );
21189906951SMark Prins            }
212ae2f5c78SMark Prins            /* these are not valid for the GeoPoint type..
21389906951SMark Prins            $region    = $geotags['region'];
21489906951SMark Prins            $country   = $geotags['country'];
21589906951SMark Prins            $placename = $geotags['placename'];
2163ab78d94SMark Prins            if(!empty($region)) {
2173ab78d94SMark Prins                $event->data['meta'][] = array('property' => 'place:location:region', 'content' => $region,);
2183ab78d94SMark Prins            }
2193ab78d94SMark Prins            if(!empty($placename)) {
2203ab78d94SMark Prins                $event->data['meta'][] = array('property' => 'place:location:locality', 'content' => $placename,);
2213ab78d94SMark Prins            }
2223ab78d94SMark Prins            if(!empty($country)) {
2233ab78d94SMark Prins                $event->data['meta'][] = array('property' => 'place:location:country-name', 'content' => $country,);
2243ab78d94SMark Prins            }
22589906951SMark Prins            */
2262ebecb01SMark 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     */
244fa5b18beSMark Prins    private function getImage(): string {
24589906951SMark Prins        global $ID;
246*eecf9074SMark Prins        $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_SIMPLE_CACHE);
24789906951SMark Prins        $img = $rel['firstimage'];
248ae2f5c78SMark Prins
249ae2f5c78SMark Prins        if(empty($img)) {
25093bf3c56SMark Prins            $img = $this->getConf('fallbackImage');
251fa5b18beSMark 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, '&amp;', 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     */
267fa5b18beSMark Prins    private function getImageAlt(): string {
268a543e5b7SMark Prins        global $ID;
269*eecf9074SMark Prins        $rel   = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_SIMPLE_CACHE);
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