xref: /plugin/socialcards/action.php (revision 03499faa54552d0a8e07c428e654db5f00cc7412)
1fdb4710bSMark Prins<?php
27e4aad61Sgithub-actions[bot]
37e4aad61Sgithub-actions[bot]use dokuwiki\Extension\ActionPlugin;
47e4aad61Sgithub-actions[bot]use dokuwiki\Extension\EventHandler;
57e4aad61Sgithub-actions[bot]use dokuwiki\Extension\Event;
67e4aad61Sgithub-actions[bot]
7635a5e2dSMark Prins/*
8*03499faaSMark Prins * Copyright (c) 2013-2026 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>
27*03499faaSMark Prins *
28*03499faaSMark Prins * @phpcs:disable Squiz.Classes.ValidClassName.NotPascalCase
29ae2f5c78SMark Prins */
307e4aad61Sgithub-actions[bot]class action_plugin_socialcards extends ActionPlugin
317e4aad61Sgithub-actions[bot]{
32ae2f5c78SMark Prins    /**
33ae2f5c78SMark Prins     * Register our callback for the TPL_METAHEADER_OUTPUT event.
34ae2f5c78SMark Prins     *
35ae2f5c78SMark Prins     * @param $controller Doku_Event_Handler
36ae2f5c78SMark Prins     * @see DokuWiki_Action_Plugin::register()
37ae2f5c78SMark Prins     */
387e4aad61Sgithub-actions[bot]    public function register(EventHandler $controller): void
397e4aad61Sgithub-actions[bot]    {
403cc863c5SMark Prins        $controller->register_hook(
413cc863c5SMark Prins            'TPL_METAHEADER_OUTPUT',
423cc863c5SMark Prins            'BEFORE',
433cc863c5SMark Prins            $this,
443cc863c5SMark Prins            'handleTplMetaheaderOutput'
453cc863c5SMark Prins        );
46fdb4710bSMark Prins    }
47fdb4710bSMark Prins
48fdb4710bSMark Prins    /**
4989906951SMark Prins     * Retrieve metadata and add to the head of the page using appropriate meta
50156465eeSMark Prins     * tags unless the page does not exist.
5189906951SMark Prins     *
52c3673c20Sgithub-actions[bot]     * @param Event $event the DokuWiki event. $event->data is a two-dimensional
53fdb4710bSMark Prins     *                          array of all meta headers. The keys are meta, link and script.
542765f2acSMark Prins     *                          handler was registered (not used)
5589906951SMark Prins     *
563cc863c5SMark Prins     * @global array     $INFO
573cc863c5SMark Prins     * @global string    $ID    page id
583cc863c5SMark Prins     * @global array     $conf  global wiki configuration
5989906951SMark Prins     * @see http://www.dokuwiki.org/devel:event:tpl_metaheader_output
60fdb4710bSMark Prins     */
61*03499faaSMark Prins    public function handleTplMetaheaderOutput(Event $event): void
627e4aad61Sgithub-actions[bot]    {
6389906951SMark Prins        global $ID, $conf, $INFO;
64fdb4710bSMark Prins
653cc863c5SMark Prins        if (!page_exists($ID)) {
663cc863c5SMark Prins            return;
673cc863c5SMark Prins        }
68a67fa79eSMark Prins        if (auth_quickaclcheck($ID) < AUTH_READ) {
69a67fa79eSMark Prins            return;
70a67fa79eSMark Prins        }
71156465eeSMark Prins
722765f2acSMark Prins        // twitter card, see https://dev.twitter.com/cards/markup
732765f2acSMark Prins        // creat a summary card, see https://dev.twitter.com/cards/types/summary
747e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['name'    => 'twitter:card', 'content' => "summary"];
757e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['name'    => 'twitter:site', 'content' => $this->getConf('twitterName')];
76*03499faaSMark Prins        $event->data['meta'][] = ['name'    => 'twitter:title',
77*03499faaSMark Prins            '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']);
827e4aad61Sgithub-actions[bot]            $event->data['meta'][] = ['name'    => 'twitter:description', 'content' => $desc];
83fc03d0b3SMark Prins        }
842765f2acSMark Prins
85fa5b18beSMark Prins        if ($this->getConf('twitterUserName') !== '') {
86*03499faaSMark Prins            $event->data['meta'][] = [
87*03499faaSMark Prins                'name' => 'twitter:creator',
88*03499faaSMark Prins                'content' => $this->getConf('twitterUserName')
89*03499faaSMark Prins            ];
90e0737b20SMark Prins        }
912765f2acSMark Prins
927e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['name'    => 'twitter:image', 'content' => $this->getImage()];
937e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['name'    => 'twitter:image:alt', 'content' => $this->getImageAlt()];
9489906951SMark Prins
9589906951SMark Prins        // opengraph, see http://ogp.me/
9689906951SMark Prins        //
9789906951SMark Prins        // to make this work properly the template should be modified adding the
9889906951SMark Prins        // namespaces for a (x)html 4 template make html tag:
9989906951SMark Prins        //
10089906951SMark Prins        // <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl"
10189906951SMark Prins        //       xmlns:og="http://ogp.me/ns#" xmlns:fb="http://ogp.me/ns/fb#"
10289906951SMark Prins        //       xmlns:article="http://ogp.me/ns/article#" xmlns:place="http://ogp.me/ns/place#">
10389906951SMark Prins        //
10489906951SMark Prins        // and for a (x)html 5 template make head tag:
10589906951SMark Prins        //
1065cd64445SMark Prins        // <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#
1075cd64445SMark Prins        //    article: http://ogp.me/ns/article# place: http://ogp.me/ns/place#">
10889906951SMark Prins
10989906951SMark Prins        // og namespace http://ogp.me/ns#
1107e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['property' => 'og:locale', 'content'  => $this->getConf('languageTerritory')];
1117e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['property' => 'og:site_name', 'content'  => $conf['title']];
1127e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['property' => 'og:url', 'content'  => wl($ID, '', true)];
113*03499faaSMark Prins        $event->data['meta'][] = ['property' => 'og:title',
114*03499faaSMark Prins            'content'  => p_get_metadata($ID, 'title', METADATA_RENDER_USING_SIMPLE_CACHE)];
11589906951SMark Prins        if (!empty($desc)) {
1167e4aad61Sgithub-actions[bot]            $event->data['meta'][] = ['property' => 'og:description', 'content'  => $desc];
117fdb4710bSMark Prins        }
1187e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['property' => 'og:type', 'content'  => "article"];
119f3501156SLaurent Marquet        $ogImage               = $this->getImage();
120*03499faaSMark Prins        $secure                = str_starts_with($ogImage, 'https') ? ':secure_url' : '';
1217e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['property' => 'og:image' . $secure, 'content'  => $ogImage];
12289906951SMark Prins
12389906951SMark Prins        // article namespace http://ogp.me/ns/article#
124eecf9074SMark Prins        $_dates                = p_get_metadata($ID, 'date', METADATA_RENDER_USING_SIMPLE_CACHE);
1257e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['property' => 'article:published_time', 'content'  => dformat($_dates['created'])];
1267e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['property' => 'article:modified_time', 'content'  => dformat($_dates['modified'])];
1277e4aad61Sgithub-actions[bot]        $event->data['meta'][] = ['property' => 'article:author', 'content'  => $INFO['editor']];
1283ab78d94SMark Prins//        $event->data['meta'][] = array(
1293ab78d94SMark Prins//            'property' => 'article:author',
130eecf9074SMark Prins//            'content'  => p_get_metadata($ID, 'creator', METADATA_RENDER_USING_SIMPLE_CACHE),
1313ab78d94SMark Prins//        );
1323ab78d94SMark Prins//        $event->data['meta'][] = array(
1333ab78d94SMark Prins//            'property' => 'article:author',
134eecf9074SMark Prins//            'content'  => p_get_metadata($ID, 'user', METADATA_RENDER_USING_SIMPLE_CACHE),
1353ab78d94SMark Prins//        );
136eecf9074SMark Prins        $_subject = p_get_metadata($ID, 'subject', METADATA_RENDER_USING_SIMPLE_CACHE);
13789906951SMark Prins        if (!empty($_subject)) {
13889906951SMark Prins            if (!is_array($_subject)) {
1397e4aad61Sgithub-actions[bot]                $_subject = [$_subject];
14089906951SMark Prins            }
14189906951SMark Prins            foreach ($_subject as $tag) {
1427e4aad61Sgithub-actions[bot]                $event->data['meta'][] = ['property' => 'article:tag', 'content'  => $tag];
14389906951SMark Prins            }
14489906951SMark Prins        }
14589906951SMark Prins
14689906951SMark Prins        // place namespace http://ogp.me/ns/place#
147eecf9074SMark Prins        $geotags = p_get_metadata($ID, 'geo', METADATA_RENDER_USING_SIMPLE_CACHE);
1482ebecb01SMark Prins        if (is_array($geotags)) {
1492ebecb01SMark Prins            $lat = $geotags['lat'] ?? 0;
1502ebecb01SMark Prins            $lon = $geotags['lon'] ?? 0;
15189906951SMark Prins            if (!(empty($lat) && empty($lon))) {
1527e4aad61Sgithub-actions[bot]                $event->data['meta'][] = ['property' => 'place:location:latitude', 'content'  => $lat];
1537e4aad61Sgithub-actions[bot]                $event->data['meta'][] = ['property' => 'place:location:longitude', 'content'  => $lon];
1543322a5daSMark Prins            }
1553322a5daSMark Prins            // see https://developers.facebook.com/docs/opengraph/property-types/#geopoint
1562ebecb01SMark Prins            $alt = $geotags['alt'] ?? 0;
1573322a5daSMark Prins            if (!empty($alt)) {
1583322a5daSMark Prins                // facebook expects feet...
159fa5b18beSMark Prins                $alt                   *= 3.2808;
1607e4aad61Sgithub-actions[bot]                $event->data['meta'][] = ['property' => 'place:location:altitude', 'content'  => $alt];
16189906951SMark Prins            }
162ae2f5c78SMark Prins            /* these are not valid for the GeoPoint type..
16389906951SMark Prins            $region    = $geotags['region'];
16489906951SMark Prins            $country   = $geotags['country'];
16589906951SMark Prins            $placename = $geotags['placename'];
1663ab78d94SMark Prins            if(!empty($region)) {
1673ab78d94SMark Prins                $event->data['meta'][] = array('property' => 'place:location:region', 'content' => $region,);
1683ab78d94SMark Prins            }
1693ab78d94SMark Prins            if(!empty($placename)) {
1703ab78d94SMark Prins                $event->data['meta'][] = array('property' => 'place:location:locality', 'content' => $placename,);
1713ab78d94SMark Prins            }
1723ab78d94SMark Prins            if(!empty($country)) {
1733ab78d94SMark Prins                $event->data['meta'][] = array('property' => 'place:location:country-name', 'content' => $country,);
1743ab78d94SMark Prins            }
17589906951SMark Prins            */
1762ebecb01SMark Prins        }
17789906951SMark Prins
17889906951SMark Prins        // optional facebook app ID
17989906951SMark Prins        $appId = $this->getConf('fbAppId');
18089906951SMark Prins        if (!empty($appId)) {
1817e4aad61Sgithub-actions[bot]            $event->data['meta'][] = ['property' => 'fb:app_id', 'content'  => $appId];
18289906951SMark Prins        }
18389906951SMark Prins    }
18489906951SMark Prins
18589906951SMark Prins    /**
18689906951SMark Prins     * Gets the canonical image path for this page.
18789906951SMark Prins     *
18889906951SMark Prins     * @return string the url to the image to use for this page
1893cc863c5SMark Prins     * @global string $ID page id
19089906951SMark Prins     */
1917e4aad61Sgithub-actions[bot]    private function getImage(): string
1927e4aad61Sgithub-actions[bot]    {
19389906951SMark Prins        global $ID;
194eecf9074SMark Prins        $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_SIMPLE_CACHE);
19589906951SMark Prins        $img = $rel['firstimage'];
196ae2f5c78SMark Prins
197ae2f5c78SMark Prins        if (empty($img)) {
19893bf3c56SMark Prins            $img = $this->getConf('fallbackImage');
199*03499faaSMark Prins            if (str_starts_with($img, "http")) {
2003efb311dSMark Prins                // don't use ml() as this results in a HTTP redirect after
2013efb311dSMark Prins                //   hitting the wiki making the card image fail.
20293bf3c56SMark Prins                return $img;
20393bf3c56SMark Prins            }
20493bf3c56SMark Prins        }
205ae2f5c78SMark Prins
2067e4aad61Sgithub-actions[bot]        return ml($img, [], true, '&amp;', true);
20789906951SMark Prins    }
20889906951SMark Prins
209a543e5b7SMark Prins    /**
210a543e5b7SMark Prins     * Gets the alt text for this page image.
211a543e5b7SMark Prins     *
212a543e5b7SMark Prins     * @return string alt text
2133cc863c5SMark Prins     * @global string $ID page id
214a543e5b7SMark Prins     */
2157e4aad61Sgithub-actions[bot]    private function getImageAlt(): string
2167e4aad61Sgithub-actions[bot]    {
217a543e5b7SMark Prins        global $ID;
218eecf9074SMark Prins        $rel   = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_SIMPLE_CACHE);
219a543e5b7SMark Prins        $imgID = $rel['firstimage'];
220a543e5b7SMark Prins        $alt   = "";
221a543e5b7SMark Prins
222a543e5b7SMark Prins        if (!empty($imgID)) {
223a543e5b7SMark Prins            require_once(DOKU_INC . 'inc/JpegMeta.php');
224a543e5b7SMark Prins            $jpegmeta = new JpegMeta(mediaFN($imgID));
225*03499faaSMark Prins            $tags     = ['IPTC.Caption', 'EXIF.UserComment', 'EXIF.TIFFImageDescription',
226*03499faaSMark Prins                'EXIF.TIFFUserComment', 'IPTC.Headline', 'Xmp.dc:title'];
227*03499faaSMark Prins            $alt      = media_getTag($tags, $jpegmeta);
228a543e5b7SMark Prins        }
229a543e5b7SMark Prins        return htmlspecialchars($alt);
230a543e5b7SMark Prins    }
231fdb4710bSMark Prins}
232