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