xref: /plugin/socialcards/action.php (revision 7e4aad61c3058f8691fb1f53753e6e398739a165)
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, '&amp;', 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