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