1<?php
2
3require_once(__DIR__ . '/../ComboStrap/PluginUtility.php');
4
5use ComboStrap\DokuPath;
6use ComboStrap\ExceptionCombo;
7use ComboStrap\Image;
8use ComboStrap\LogUtility;
9use ComboStrap\Mime;
10use ComboStrap\Page;
11use ComboStrap\PageImageUsage;
12use ComboStrap\PageType;
13use ComboStrap\PluginUtility;
14use ComboStrap\Site;
15use ComboStrap\StringUtility;
16
17
18/**
19 *
20 * Implementation of the ogp protocol
21 *
22 * followed by:
23 *   * Facebook: https://developers.facebook.com/docs/sharing/webmasters
24 *   * Linkedin:  https://www.linkedin.com/help/linkedin/answer/a521928/making-your-website-shareable-on-linkedin?lang=en
25 *
26 * For the canonical meta, see {@link action_plugin_combo_metacanonical}
27 *
28 * Inspiration, reference:
29 * https://github.com/twbs/bootstrap/blob/v4-dev/site/layouts/partials/social.html
30 * https://github.com/mprins/dokuwiki-plugin-socialcards/blob/master/action.php
31 *
32 *
33 */
34class action_plugin_combo_metafacebook extends DokuWiki_Action_Plugin
35{
36
37    const FACEBOOK_APP_ID = "486120022012342";
38
39    /**
40     * The image
41     */
42    const CONF_DEFAULT_FACEBOOK_IMAGE = "defaultFacebookImage";
43
44
45    const CANONICAL = "facebook";
46
47
48    function __construct()
49    {
50        // enable direct access to language strings
51        // ie $this->lang
52        $this->setupLocale();
53    }
54
55    public function register(Doku_Event_Handler $controller)
56    {
57        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'metaFacebookProcessing', array());
58    }
59
60    /**
61     *
62     * @param $event
63     */
64    function metaFacebookProcessing($event)
65    {
66
67        global $ID;
68        if (empty($ID)) {
69            // $ID is null for media
70            return;
71        }
72
73
74        $page = Page::createPageFromId($ID);
75        if (!$page->exists()) {
76            return;
77        }
78
79        /**
80         * No social for bars
81         */
82        if ($page->isSecondarySlot()) {
83            return;
84        }
85
86
87        /**
88         * "og:url" is already created in the {@link action_plugin_combo_metacanonical}
89         * "og:description" is already created in the {@link action_plugin_combo_metadescription}
90         */
91        $facebookMeta = array(
92            "og:title" => StringUtility::truncateString($page->getTitleOrDefault(), 70)
93        );
94        $descriptionOrElseDokuWiki = $page->getDescriptionOrElseDokuWiki();
95        if (!empty($descriptionOrElseDokuWiki)) {
96            // happens in test with document without content
97            $facebookMeta["og:description"] = $descriptionOrElseDokuWiki;
98        }
99
100        $title = Site::getTitle();
101        if (!empty($title)) {
102            $facebookMeta["og:site_name"] = $title;
103        }
104
105        /**
106         * Type of page
107         */
108        $pageType = $page->getTypeOrDefault();
109        switch ($pageType) {
110            case PageType::ARTICLE_TYPE:
111                // https://ogp.me/#type_article
112                $facebookMeta["article:published_time"] = $page->getPublishedElseCreationTime()->format(DATE_ISO8601);
113                $modifiedTime = $page->getModifiedTimeOrDefault();
114                if ($modifiedTime !== null) {
115                    $facebookMeta["article:modified_time"] = $modifiedTime->format(DATE_ISO8601);
116                }
117                $facebookMeta["og:type"] = $pageType;
118                break;
119            default:
120                // The default facebook value
121                $facebookMeta["og:type"] = PageType::WEBSITE_TYPE;
122                break;
123        }
124
125
126        /**
127         * @var Image[]
128         */
129        $facebookImages = $page->getImagesOrDefaultForTheFollowingUsages([PageImageUsage::FACEBOOK, PageImageUsage::SOCIAL, PageImageUsage::ALL]);
130        if (empty($facebookImages)) {
131            $defaultFacebookImage = PluginUtility::getConfValue(self::CONF_DEFAULT_FACEBOOK_IMAGE);
132            if (!empty($defaultFacebookImage)) {
133                DokuPath::addRootSeparatorIfNotPresent($defaultFacebookImage);
134                $image = Image::createImageFromId($defaultFacebookImage);
135                if ($image->exists()) {
136                    $facebookImages[] = $image;
137                } else {
138                    if ($defaultFacebookImage != ":logo-facebook.png") {
139                        LogUtility::msg("The default facebook image ($defaultFacebookImage) does not exist", LogUtility::LVL_MSG_ERROR, self::CANONICAL);
140                    }
141                }
142            }
143        }
144        if (!empty($facebookImages)) {
145
146            /**
147             * One of image/jpeg, image/gif or image/png
148             * As stated here: https://developers.facebook.com/docs/sharing/webmasters#images
149             **/
150            $facebookMime = [Mime::JPEG, Mime::GIF, Mime::PNG];
151            foreach ($facebookImages as $facebookImage) {
152
153                if (!in_array($facebookImage->getPath()->getMime()->toString(), $facebookMime)) {
154                    continue;
155                }
156
157                /** @var Image $facebookImage */
158                if (!($facebookImage->isRaster())) {
159                    LogUtility::msg("Internal: The image ($facebookImage) is not a raster image and this should not be the case for facebook", LogUtility::LVL_MSG_ERROR);
160                    continue;
161                }
162
163                if (!$facebookImage->exists()) {
164                    LogUtility::msg("The image ($facebookImage) does not exist and was not added", LogUtility::LVL_MSG_ERROR, self::CANONICAL);
165                } else {
166
167                    $toSmall = false;
168
169                    // There is a minimum size constraint of 200px by 200px
170                    // The try is in case we can't get the width and height
171                    try {
172                        $intrinsicWidth = $facebookImage->getIntrinsicWidth();
173                        $intrinsicHeight = $facebookImage->getIntrinsicHeight();
174                    } catch (ExceptionCombo $e) {
175                        LogUtility::msg("No image was added for facebook. Error while retrieving the dimension of the image: {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, self::CANONICAL);
176                        break;
177                    }
178
179                    if ($intrinsicWidth < 200) {
180                        $toSmall = true;
181                    } else {
182                        $facebookMeta["og:image:width"] = $intrinsicWidth;
183                        if ($intrinsicHeight < 200) {
184                            $toSmall = true;
185                        } else {
186                            $facebookMeta["og:image:height"] = $intrinsicHeight;
187                        }
188                    }
189
190                    if ($toSmall) {
191                        $message = "The facebook image ($facebookImage) is too small (" . $intrinsicWidth . " x " . $intrinsicHeight . "). The minimum size constraint is 200px by 200px";
192                        if (
193                            $facebookImage->getPath()->toAbsolutePath()->toString()
194                            !==
195                            $page->getFirstImage()->getPath()->toAbsolutePath()->toString()
196                        ) {
197                            LogUtility::msg($message, LogUtility::LVL_MSG_ERROR, self::CANONICAL);
198                        } else {
199                            LogUtility::log2BrowserConsole($message);
200                        }
201                    }
202
203
204                    /**
205                     * We may don't known the dimensions
206                     */
207                    if (!$toSmall) {
208                        $mime = $facebookImage->getPath()->getMime()->toString();
209                        if (!empty($mime)) {
210                            $facebookMeta["og:image:type"] = $mime;
211                        }
212                        $facebookMeta["og:image"] = $facebookImage->getAbsoluteUrl();
213                        // One image only
214                        break;
215                    }
216                }
217
218            }
219        }
220
221
222        $facebookMeta["fb:app_id"] = self::FACEBOOK_APP_ID;
223
224        $facebookDefaultLocale = "en_US";
225        $locale = $page->getLocale($facebookDefaultLocale);
226        $facebookMeta["og:locale"] = $locale;
227
228
229        /**
230         * Add the properties
231         */
232        foreach ($facebookMeta as $property => $content) {
233            $event->data['meta'][] = array("property" => $property, "content" => $content);
234        }
235
236
237    }
238
239}
240