xref: /plugin/socialcards/action.php (revision 3efb311dfd70e68e004fe942396e39385aea00f8)
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
18e0737b20SMark Prinsif (!defined('DOKU_INC')) {
1989906951SMark Prins    die();
20e0737b20SMark Prins}
21e0737b20SMark Prinsif (!defined('DOKU_PLUGIN')) {
2289906951SMark Prins    define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
23e0737b20SMark Prins}
24fdb4710bSMark Prins
25fdb4710bSMark Prinsrequire_once DOKU_PLUGIN . 'action.php';
26a543e5b7SMark Prinsrequire_once(DOKU_INC . 'inc/JpegMeta.php');
27ae2f5c78SMark Prins/**
28ae2f5c78SMark Prins * DokuWiki Plugin socialcards (Action Component).
29ae2f5c78SMark Prins *
30ae2f5c78SMark Prins * @license BSD license
31ae2f5c78SMark Prins * @author  Mark C. Prins <mprins@users.sf.net>
32ae2f5c78SMark Prins */
33fdb4710bSMark Prinsclass action_plugin_socialcards extends DokuWiki_Action_Plugin {
34fdb4710bSMark Prins
35ae2f5c78SMark Prins    /**
36ae2f5c78SMark Prins     * Register our callback for the TPL_METAHEADER_OUTPUT event.
37ae2f5c78SMark Prins     *
38ae2f5c78SMark Prins     * @param $controller Doku_Event_Handler
39ae2f5c78SMark Prins     * @see DokuWiki_Action_Plugin::register()
40ae2f5c78SMark Prins     */
41ae2f5c78SMark Prins    public function register(Doku_Event_Handler $controller) {
4289906951SMark Prins        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this,
4389906951SMark Prins                'handle_tpl_metaheader_output');
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     *
5089906951SMark Prins     * @global string $ID page id
5189906951SMark Prins     * @global array $conf global wiki configuration
5289906951SMark Prins     * @global array $INFO
53fdb4710bSMark Prins     * @param Doku_Event $event the DokuWiki event. $event->data is a two-dimensional
54fdb4710bSMark Prins     *             array of all meta headers. The keys are meta, link and script.
552765f2acSMark Prins     * @param mixed $param the parameters passed to register_hook when this
562765f2acSMark Prins     *             handler was registered (not used)
5789906951SMark Prins     *
5889906951SMark Prins     * @see http://www.dokuwiki.org/devel:event:tpl_metaheader_output
59fdb4710bSMark Prins     */
602765f2acSMark Prins    public function handle_tpl_metaheader_output(Doku_Event $event, $param) {
6189906951SMark Prins        global $ID, $conf, $INFO;
62fdb4710bSMark Prins
63e0737b20SMark Prins        if (!page_exists($ID)) { return; }
64156465eeSMark Prins
652765f2acSMark Prins        // twitter card, see https://dev.twitter.com/cards/markup
662765f2acSMark Prins        // creat a summary card, see https://dev.twitter.com/cards/types/summary
6789906951SMark Prins        $event->data['meta'][] = array('name' => 'twitter:card',
6889906951SMark Prins                'content' => "summary",);
692765f2acSMark Prins
702765f2acSMark Prins        $event->data['meta'][] = array('name' => 'twitter:site',
712765f2acSMark Prins                'content' => $this->getConf('twitterName'),);
722765f2acSMark Prins
7389906951SMark Prins        $event->data['meta'][] = array('name' => 'twitter:title',
7489906951SMark Prins                'content' => p_get_metadata($ID, 'title', true),);
752765f2acSMark Prins
76aeb704feSMark Prins        $desc = p_get_metadata($ID, 'description', true);
77aeb704feSMark Prins        if (!empty($desc)) {
7889906951SMark Prins            $desc = str_replace("\n", " ", $desc['abstract']);
79fc03d0b3SMark Prins            $event->data['meta'][] = array('name' => 'twitter:description',
8089906951SMark Prins                    'content' => $desc,);
81fc03d0b3SMark Prins        }
822765f2acSMark Prins
83e0737b20SMark Prins        if ($this->getConf('twitterUserName') != '') {
8489906951SMark Prins            $event->data['meta'][] = array('name' => 'twitter:creator',
85e0737b20SMark Prins                'content' => $this->getConf('twitterUserName'),);
86e0737b20SMark Prins        }
872765f2acSMark Prins
882765f2acSMark Prins        $event->data['meta'][] = array('name' => 'twitter:image',
8989906951SMark Prins                'content' => $this->getImage(),);
90a543e5b7SMark Prins        $event->data['meta'][] = array('name' => 'twitter:image:alt',
91a543e5b7SMark Prins                'content' => $this->getImageAlt(),);
9289906951SMark Prins
9389906951SMark Prins        // opengraph, see http://ogp.me/
9489906951SMark Prins        //
9589906951SMark Prins        // to make this work properly the template should be modified adding the
9689906951SMark Prins        // namespaces for a (x)html 4 template make html tag:
9789906951SMark Prins        //
9889906951SMark Prins        // <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl"
9989906951SMark Prins        //       xmlns:og="http://ogp.me/ns#" xmlns:fb="http://ogp.me/ns/fb#"
10089906951SMark Prins        //       xmlns:article="http://ogp.me/ns/article#" xmlns:place="http://ogp.me/ns/place#">
10189906951SMark Prins        //
10289906951SMark Prins        // and for a (x)html 5 template make head tag:
10389906951SMark Prins        //
10489906951SMark Prins        // <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article# place: http://ogp.me/ns/place#">
10589906951SMark Prins
10689906951SMark Prins        // og namespace http://ogp.me/ns#
10789906951SMark Prins        $event->data['meta'][] = array('property' => 'og:locale',
10889906951SMark Prins                'content' => $this->getConf('languageTerritory'),);
10989906951SMark Prins        $event->data['meta'][] = array('property' => 'og:site_name',
11089906951SMark Prins                'content' => $conf['title'],);
11189906951SMark Prins        $event->data['meta'][] = array('property' => 'og:url',
11289906951SMark Prins                'content' => wl($ID, '', true),);
11389906951SMark Prins        $event->data['meta'][] = array('property' => 'og:title',
11489906951SMark Prins                'content' => p_get_metadata($ID, 'title', true),);
11589906951SMark Prins        if (!empty($desc)) {
11689906951SMark Prins            $event->data['meta'][] = array('property' => 'og:description',
11789906951SMark Prins                    'content' => $desc,);
118fdb4710bSMark Prins        }
11989906951SMark Prins        $event->data['meta'][] = array('property' => 'og:type',
12089906951SMark Prins                'content' => "article",);
121f3501156SLaurent Marquet        $ogImage = $this->getImage();
122f3501156SLaurent Marquet        $secure = substr($ogImage, 0, 5) === 'https' ? ':secure_url' : '';
123f3501156SLaurent Marquet        $event->data['meta'][] = array('property' => 'og:image' . $secure,
124f3501156SLaurent Marquet                'content' => $ogImage,);
12589906951SMark Prins
12689906951SMark Prins        // article namespace http://ogp.me/ns/article#
12789906951SMark Prins        $_dates = p_get_metadata($ID, 'date', true);
12889906951SMark Prins        $event->data['meta'][] = array('property' => 'article:published_time',
12989906951SMark Prins                'content' => dformat($_dates['created']),);
13089906951SMark Prins        $event->data['meta'][] = array('property' => 'article:modified_time',
13189906951SMark Prins                'content' => dformat($_dates['modified']),);
13289906951SMark Prins        $event->data['meta'][] = array('property' => 'article:author',
13389906951SMark Prins                'content' => $INFO['editor'],);
13489906951SMark Prins        // $event->data['meta'][] = array('property' => 'article:author','content' => p_get_metadata($ID,'creator',true),);
13589906951SMark Prins        // $event->data['meta'][] = array('property' => 'article:author','content' => p_get_metadata($ID,'user',true),);
13689906951SMark Prins        $_subject = p_get_metadata($ID, 'subject', true);
13789906951SMark Prins        if (!empty($_subject)) {
13889906951SMark Prins            if (!is_array($_subject)) {
13989906951SMark Prins                $_subject = array($_subject);
14089906951SMark Prins            }
14189906951SMark Prins            foreach ($_subject as $tag) {
14289906951SMark Prins                $event->data['meta'][] = array('property' => 'article:tag',
14389906951SMark Prins                        'content' => $tag,);
14489906951SMark Prins            }
14589906951SMark Prins        }
14689906951SMark Prins
14789906951SMark Prins        // place namespace http://ogp.me/ns/place#
14889906951SMark Prins        $geotags = p_get_metadata($ID, 'geo', true);
14989906951SMark Prins        $lat = $geotags['lat'];
15089906951SMark Prins        $lon = $geotags['lon'];
15189906951SMark Prins        if (!(empty($lat) && empty($lon))) {
15289906951SMark Prins            $event->data['meta'][] = array('property' => 'place:location:latitude',
15389906951SMark Prins                    'content' => $lat,);
15489906951SMark Prins            $event->data['meta'][] = array('property' => 'place:location:longitude',
15589906951SMark Prins                    'content' => $lon,);
1563322a5daSMark Prins        }
1573322a5daSMark Prins        // see https://developers.facebook.com/docs/opengraph/property-types/#geopoint
1583322a5daSMark Prins        $alt = $geotags['alt'];
1593322a5daSMark Prins        if (!empty($alt)) {
1603322a5daSMark Prins            // facebook expects feet...
1613322a5daSMark Prins            $alt = $alt * 3.2808;
1623322a5daSMark Prins            $event->data['meta'][] = array('property' => 'place:location:altitude',
1633322a5daSMark Prins                    'content' => $alt,);
16489906951SMark Prins        }
16589906951SMark Prins
166ae2f5c78SMark Prins        /* these are not valid for the GeoPoint type..
16789906951SMark Prins        $region=$geotags['region'];
16889906951SMark Prins        $country=$geotags['country'];
16989906951SMark Prins        $placename=$geotags['placename'];
17089906951SMark Prins        if (!empty($region))    {$event->data['meta'][] = array('property' => 'place:location:region',      'content' => $region,);}
17189906951SMark Prins        if (!empty($placename)) {$event->data['meta'][] = array('property' => 'place:location:locality',    'content' => $placename,);}
17289906951SMark Prins        if (!empty($country))   {$event->data['meta'][] = array('property' => 'place:location:country-name','content' => $country,);}
17389906951SMark Prins        */
17489906951SMark Prins
17589906951SMark Prins        // optional facebook app ID
17689906951SMark Prins        $appId = $this->getConf('fbAppId');
17789906951SMark Prins        if (!empty($appId)) {
17889906951SMark Prins            $event->data['meta'][] = array('property' => 'fb:app_id',
17989906951SMark Prins                    'content' => $appId,);
18089906951SMark Prins        }
18189906951SMark Prins    }
18289906951SMark Prins
18389906951SMark Prins    /**
18489906951SMark Prins     * Gets the canonical image path for this page.
18589906951SMark Prins     *
18689906951SMark Prins     * @global string $ID page id
18789906951SMark Prins     * @return string the url to the image to use for this page
18889906951SMark Prins     */
18989906951SMark Prins    private function getImage() {
19089906951SMark Prins        global $ID;
19189906951SMark Prins        $rel = p_get_metadata($ID, 'relation', true);
19289906951SMark Prins        $img = $rel['firstimage'];
193ae2f5c78SMark Prins
194ae2f5c78SMark Prins        if (empty($img)) {
19593bf3c56SMark Prins            $img = $this->getConf('fallbackImage');
19693bf3c56SMark Prins            if (substr($img, 0, 4) === "http") {
197*3efb311dSMark Prins                // don't use ml() as this results in a HTTP redirect after
198*3efb311dSMark Prins                //   hitting the wiki making the card image fail.
19993bf3c56SMark Prins                return $img;
20093bf3c56SMark Prins            }
20193bf3c56SMark Prins        }
202ae2f5c78SMark Prins
203ae2f5c78SMark Prins        return ml($img, array(), true, '&amp;', true);
20489906951SMark Prins    }
20589906951SMark Prins
206a543e5b7SMark Prins    /**
207a543e5b7SMark Prins     * Gets the alt text for this page image.
208a543e5b7SMark Prins     *
209a543e5b7SMark Prins     * @global string $ID page id
210a543e5b7SMark Prins     * @return string alt text
211a543e5b7SMark Prins     */
212a543e5b7SMark Prins    private function getImageAlt() {
213a543e5b7SMark Prins        global $ID;
214a543e5b7SMark Prins        $rel = p_get_metadata($ID, 'relation', true);
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));
221a543e5b7SMark Prins            $tags = array('IPTC.Caption',
222a543e5b7SMark Prins                            'EXIF.UserComment',
223a543e5b7SMark Prins                            'EXIF.TIFFImageDescription',
224a543e5b7SMark Prins                            'EXIF.TIFFUserComment',
225a543e5b7SMark Prins                            'IPTC.Headline',
226a543e5b7SMark Prins                            'Xmp.dc:title'
227a543e5b7SMark Prins                            );
228a543e5b7SMark Prins            $alt = media_getTag($tags, $jpegmeta, "");
229a543e5b7SMark Prins        }
230a543e5b7SMark Prins        return htmlspecialchars($alt);
231a543e5b7SMark Prins    }
232a543e5b7SMark Prins
233fdb4710bSMark Prins}
234