15f891b7eSNickeau<?php 25f891b7eSNickeau 3c3437056SNickeau 4*04fd306cSNickeauuse ComboStrap\ExecutionContext; 5*04fd306cSNickeauuse ComboStrap\SiteConfig; 6*04fd306cSNickeauuse ComboStrap\WikiPath; 7*04fd306cSNickeauuse ComboStrap\ExceptionCompile; 8*04fd306cSNickeauuse ComboStrap\ExceptionNotFound; 9*04fd306cSNickeauuse ComboStrap\IFetcherLocalImage; 10*04fd306cSNickeauuse ComboStrap\FileSystems; 115f891b7eSNickeauuse ComboStrap\LogUtility; 12c3437056SNickeauuse ComboStrap\Mime; 13*04fd306cSNickeauuse ComboStrap\MarkupPath; 14c3437056SNickeauuse ComboStrap\PageImageUsage; 15c3437056SNickeauuse ComboStrap\PageType; 16c3437056SNickeauuse ComboStrap\PluginUtility; 175f891b7eSNickeauuse ComboStrap\Site; 185f891b7eSNickeauuse ComboStrap\StringUtility; 195f891b7eSNickeau 205f891b7eSNickeau 215f891b7eSNickeau/** 225f891b7eSNickeau * 234cadd4f8SNickeau * Implementation of the ogp protocol 244cadd4f8SNickeau * 254cadd4f8SNickeau * followed by: 264cadd4f8SNickeau * * Facebook: https://developers.facebook.com/docs/sharing/webmasters 274cadd4f8SNickeau * * Linkedin: https://www.linkedin.com/help/linkedin/answer/a521928/making-your-website-shareable-on-linkedin?lang=en 284cadd4f8SNickeau * 295f891b7eSNickeau * For the canonical meta, see {@link action_plugin_combo_metacanonical} 305f891b7eSNickeau * 315f891b7eSNickeau * Inspiration, reference: 325f891b7eSNickeau * https://github.com/twbs/bootstrap/blob/v4-dev/site/layouts/partials/social.html 335f891b7eSNickeau * https://github.com/mprins/dokuwiki-plugin-socialcards/blob/master/action.php 344cadd4f8SNickeau * 354cadd4f8SNickeau * 365f891b7eSNickeau */ 375f891b7eSNickeauclass action_plugin_combo_metafacebook extends DokuWiki_Action_Plugin 385f891b7eSNickeau{ 395f891b7eSNickeau 405f891b7eSNickeau const FACEBOOK_APP_ID = "486120022012342"; 415f891b7eSNickeau 425f891b7eSNickeau /** 435f891b7eSNickeau * The image 445f891b7eSNickeau */ 455f891b7eSNickeau const CONF_DEFAULT_FACEBOOK_IMAGE = "defaultFacebookImage"; 465f891b7eSNickeau 475f891b7eSNickeau 485f891b7eSNickeau const CANONICAL = "facebook"; 495f891b7eSNickeau 505f891b7eSNickeau 515f891b7eSNickeau function __construct() 525f891b7eSNickeau { 535f891b7eSNickeau // enable direct access to language strings 545f891b7eSNickeau // ie $this->lang 555f891b7eSNickeau $this->setupLocale(); 565f891b7eSNickeau } 575f891b7eSNickeau 585f891b7eSNickeau public function register(Doku_Event_Handler $controller) 595f891b7eSNickeau { 605f891b7eSNickeau $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'metaFacebookProcessing', array()); 615f891b7eSNickeau } 625f891b7eSNickeau 635f891b7eSNickeau /** 645f891b7eSNickeau * 655f891b7eSNickeau * @param $event 665f891b7eSNickeau */ 675f891b7eSNickeau function metaFacebookProcessing($event) 685f891b7eSNickeau { 695f891b7eSNickeau 70*04fd306cSNickeau $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 71*04fd306cSNickeau try { 72*04fd306cSNickeau $templateForWebPage = $executionContext->getExecutingPageTemplate(); 73*04fd306cSNickeau } catch (ExceptionNotFound $e) { 745f891b7eSNickeau return; 755f891b7eSNickeau } 765f891b7eSNickeau 77*04fd306cSNickeau if (!$templateForWebPage->isSocial()) { 785f891b7eSNickeau return; 795f891b7eSNickeau } 805f891b7eSNickeau 815f891b7eSNickeau 825f891b7eSNickeau /** 835f891b7eSNickeau * "og:url" is already created in the {@link action_plugin_combo_metacanonical} 845f891b7eSNickeau * "og:description" is already created in the {@link action_plugin_combo_metadescription} 855f891b7eSNickeau */ 86*04fd306cSNickeau try { 87*04fd306cSNickeau $requestedPath = $templateForWebPage->getRequestedContextPath(); 88*04fd306cSNickeau } catch (ExceptionNotFound $e) { 89*04fd306cSNickeau return; 90*04fd306cSNickeau } 91*04fd306cSNickeau $page = MarkupPath::createPageFromPathObject($requestedPath); 925f891b7eSNickeau $facebookMeta = array( 93c3437056SNickeau "og:title" => StringUtility::truncateString($page->getTitleOrDefault(), 70) 945f891b7eSNickeau ); 95f788f694Sgerardnico $descriptionOrElseDokuWiki = $page->getDescriptionOrElseDokuWiki(); 96f788f694Sgerardnico if (!empty($descriptionOrElseDokuWiki)) { 97f788f694Sgerardnico // happens in test with document without content 98f788f694Sgerardnico $facebookMeta["og:description"] = $descriptionOrElseDokuWiki; 99f788f694Sgerardnico } 1005f891b7eSNickeau 101*04fd306cSNickeau 102*04fd306cSNickeau $title = Site::getName(); 1035f891b7eSNickeau if (!empty($title)) { 1045f891b7eSNickeau $facebookMeta["og:site_name"] = $title; 1055f891b7eSNickeau } 1065f891b7eSNickeau 1075f891b7eSNickeau /** 1085f891b7eSNickeau * Type of page 1095f891b7eSNickeau */ 110c3437056SNickeau $pageType = $page->getTypeOrDefault(); 111c3437056SNickeau switch ($pageType) { 112c3437056SNickeau case PageType::ARTICLE_TYPE: 1135f891b7eSNickeau // https://ogp.me/#type_article 114*04fd306cSNickeau try { 11585e82846SNickeau $facebookMeta["article:published_time"] = $page->getPublishedElseCreationTime()->format(DATE_ISO8601); 116*04fd306cSNickeau } catch (ExceptionNotFound $e) { 117*04fd306cSNickeau // Internal error, the page should exist 118*04fd306cSNickeau LogUtility::error("Internal Error: We were unable to define the publication date for the page ($page)", self::CANONICAL); 119*04fd306cSNickeau 12085e82846SNickeau } 121*04fd306cSNickeau try { 122*04fd306cSNickeau $modifiedTime = $page->getModifiedTimeOrDefault(); 123*04fd306cSNickeau $facebookMeta["article:modified_time"] = $modifiedTime->format(DATE_ISO8601); 124*04fd306cSNickeau } catch (ExceptionNotFound $e) { 125*04fd306cSNickeau // Internal error, the page should exist 126*04fd306cSNickeau LogUtility::error("Internal Error: We were unable to define the modification date for the page ($page)", self::CANONICAL); 127*04fd306cSNickeau } 128*04fd306cSNickeau 129*04fd306cSNickeau 130c3437056SNickeau $facebookMeta["og:type"] = $pageType; 131c3437056SNickeau break; 132c3437056SNickeau default: 133c3437056SNickeau // The default facebook value 134c3437056SNickeau $facebookMeta["og:type"] = PageType::WEBSITE_TYPE; 135c3437056SNickeau break; 1365f891b7eSNickeau } 1375f891b7eSNickeau 138c3437056SNickeau 139*04fd306cSNickeau $facebookImages = $page->getImagesForTheFollowingUsages([PageImageUsage::FACEBOOK, PageImageUsage::SOCIAL, PageImageUsage::ALL]); 1405f891b7eSNickeau if (empty($facebookImages)) { 141*04fd306cSNickeau $defaultFacebookImage = SiteConfig::getConfValue(self::CONF_DEFAULT_FACEBOOK_IMAGE); 1425f891b7eSNickeau if (!empty($defaultFacebookImage)) { 143*04fd306cSNickeau $dokuPath = WikiPath::createMediaPathFromId($defaultFacebookImage); 144*04fd306cSNickeau if (FileSystems::exists($dokuPath)) { 145*04fd306cSNickeau try { 146*04fd306cSNickeau $facebookImages[] = IFetcherLocalImage::createImageFetchFromPath($dokuPath); 147*04fd306cSNickeau } catch (ExceptionCompile $e) { 148*04fd306cSNickeau LogUtility::error("We were unable to add the default facebook image ($defaultFacebookImage) because of the following error: {$e->getMessage()}", self::CANONICAL); 149*04fd306cSNickeau } 1505f891b7eSNickeau } else { 1511fa8c418SNickeau if ($defaultFacebookImage != ":logo-facebook.png") { 152*04fd306cSNickeau LogUtility::error("The default facebook image ($defaultFacebookImage) does not exist", self::CANONICAL); 1535f891b7eSNickeau } 1545f891b7eSNickeau } 1555f891b7eSNickeau } 1565f891b7eSNickeau } 1575f891b7eSNickeau if (!empty($facebookImages)) { 15821913ab3SNickeau 15921913ab3SNickeau /** 16021913ab3SNickeau * One of image/jpeg, image/gif or image/png 16121913ab3SNickeau * As stated here: https://developers.facebook.com/docs/sharing/webmasters#images 16221913ab3SNickeau **/ 163*04fd306cSNickeau $facebookMimes = [Mime::JPEG, Mime::GIF, Mime::PNG]; 1645f891b7eSNickeau foreach ($facebookImages as $facebookImage) { 1655f891b7eSNickeau 166*04fd306cSNickeau $path = $facebookImage->getSourcePath(); 167*04fd306cSNickeau if (!FileSystems::exists($path)) { 168*04fd306cSNickeau LogUtility::error("The image ($path) does not exist and was not added", self::CANONICAL); 16921913ab3SNickeau continue; 17021913ab3SNickeau } 171*04fd306cSNickeau try { 172*04fd306cSNickeau $mime = FileSystems::getMime($path); 173*04fd306cSNickeau } catch (ExceptionNotFound $e) { 174*04fd306cSNickeau LogUtility::internalError($e->getMessage()); 17521913ab3SNickeau continue; 17621913ab3SNickeau } 177*04fd306cSNickeau if (!in_array($mime->toString(), $facebookMimes)) { 178*04fd306cSNickeau continue; 179*04fd306cSNickeau } 1805f891b7eSNickeau 1815f891b7eSNickeau $toSmall = false; 1825f891b7eSNickeau 1835f891b7eSNickeau // There is a minimum size constraint of 200px by 200px 18482a60d03SNickeau // The try is in case we can't get the width and height 18582a60d03SNickeau try { 18682a60d03SNickeau $intrinsicWidth = $facebookImage->getIntrinsicWidth(); 18782a60d03SNickeau $intrinsicHeight = $facebookImage->getIntrinsicHeight(); 188*04fd306cSNickeau } catch (ExceptionCompile $e) { 189*04fd306cSNickeau LogUtility::error("No image was added for facebook. Error while retrieving the dimension of the image: {$e->getMessage()}", self::CANONICAL); 19082a60d03SNickeau break; 1915f891b7eSNickeau } 19282a60d03SNickeau 19382a60d03SNickeau if ($intrinsicWidth < 200) { 19482a60d03SNickeau $toSmall = true; 19582a60d03SNickeau } else { 19682a60d03SNickeau $facebookMeta["og:image:width"] = $intrinsicWidth; 19782a60d03SNickeau if ($intrinsicHeight < 200) { 19882a60d03SNickeau $toSmall = true; 19982a60d03SNickeau } else { 20082a60d03SNickeau $facebookMeta["og:image:height"] = $intrinsicHeight; 2015f891b7eSNickeau } 2025f891b7eSNickeau } 2035f891b7eSNickeau 2045f891b7eSNickeau if ($toSmall) { 20582a60d03SNickeau $message = "The facebook image ($facebookImage) is too small (" . $intrinsicWidth . " x " . $intrinsicHeight . "). The minimum size constraint is 200px by 200px"; 206*04fd306cSNickeau try { 207*04fd306cSNickeau $firstImagePath = $page->getFirstImage()->getSourcePath(); 208c3437056SNickeau if ( 209*04fd306cSNickeau $path->toAbsolutePath()->toAbsoluteId() !== $firstImagePath->toAbsolutePath()->toAbsoluteId() 210c3437056SNickeau ) { 211*04fd306cSNickeau // specified image 212*04fd306cSNickeau LogUtility::error($message, self::CANONICAL); 2135f891b7eSNickeau } else { 214*04fd306cSNickeau // first image 2155f891b7eSNickeau LogUtility::log2BrowserConsole($message); 2165f891b7eSNickeau } 217*04fd306cSNickeau } catch (ExceptionNotFound $e) { 218*04fd306cSNickeau // no first image 219*04fd306cSNickeau LogUtility::error($message, self::CANONICAL); 220*04fd306cSNickeau } 2215f891b7eSNickeau } 2225f891b7eSNickeau 2235f891b7eSNickeau 2245f891b7eSNickeau /** 2255f891b7eSNickeau * We may don't known the dimensions 2265f891b7eSNickeau */ 2275f891b7eSNickeau if (!$toSmall) { 228*04fd306cSNickeau $facebookMeta["og:image:type"] = $mime->toString(); 229*04fd306cSNickeau try { 230*04fd306cSNickeau $facebookMeta["og:image"] = $facebookImage->getFetchUrl()->toAbsoluteUrlString(); 231*04fd306cSNickeau } catch (ExceptionCompile $e) { 232*04fd306cSNickeau // Oeps 233*04fd306cSNickeau LogUtility::internalError("Og Image could not be added. Error: {$e->getMessage()}", self::CANONICAL); 2345f891b7eSNickeau } 2355f891b7eSNickeau // One image only 2365f891b7eSNickeau break; 2375f891b7eSNickeau } 2385f891b7eSNickeau 2395f891b7eSNickeau } 2405f891b7eSNickeau } 2415f891b7eSNickeau 2425f891b7eSNickeau 2435f891b7eSNickeau $facebookMeta["fb:app_id"] = self::FACEBOOK_APP_ID; 2445f891b7eSNickeau 245*04fd306cSNickeau // FYI default if not set by facebook: "en_US" 246*04fd306cSNickeau $locale = ComboStrap\Locale::createForPage($page, "_")->getValueOrDefault(); 2471fa8c418SNickeau $facebookMeta["og:locale"] = $locale; 2485f891b7eSNickeau 2495f891b7eSNickeau 2505f891b7eSNickeau /** 2515f891b7eSNickeau * Add the properties 2525f891b7eSNickeau */ 2535f891b7eSNickeau foreach ($facebookMeta as $property => $content) { 2545f891b7eSNickeau $event->data['meta'][] = array("property" => $property, "content" => $content); 2555f891b7eSNickeau } 2565f891b7eSNickeau 2575f891b7eSNickeau 2585f891b7eSNickeau } 2595f891b7eSNickeau 2605f891b7eSNickeau} 261