diff --git a/bl-kernel/helpers/simple-image.class.php b/bl-kernel/helpers/simple-image.class.php index bfc89455..51e56733 100644 --- a/bl-kernel/helpers/simple-image.class.php +++ b/bl-kernel/helpers/simple-image.class.php @@ -1,6 +1,6 @@ flags = [ + "sslVerify" => true // Skip SSL peer validation + ]; + + // Override default flag values. + foreach($flags as $flag => $value) { + $this->setFlag($flag, $value); + } + // Load an image through the constructor if(preg_match('/^data:(.*?);/', $image)) { $this->fromDataUri($image); @@ -77,6 +89,37 @@ class SimpleImage { } } + ////////////////////////////////////////////////////////////////////////////////////////////////// + // Helper functions + ////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set flag value. + * + * @param string $flag Name of the flag to set. + * @param boolean $value State of the flag. + * @throws \Exception Thrown if flag does not exist (no default value). + */ + public function setFlag($flag, $value) { + // Throw if flag does not exist + if(!in_array($flag, array_keys($this->flags))) { + throw new \Exception('Invalid flag.', self::ERR_INVALID_FLAG); + } + + // Set flag value by name + $this->flags[$flag] = $value; + } + + /** + * Get flag value. + * + * @param string $flag Name of the flag to get. + * @return boolean|null + */ + public function getFlag($flag) { + return in_array($flag, array_keys($this->flags)) ? $this->flags[$flag] : null; + } + ////////////////////////////////////////////////////////////////////////////////////////////////// // Loaders ////////////////////////////////////////////////////////////////////////////////////////////////// @@ -118,70 +161,66 @@ class SimpleImage { * Loads an image from a file. * * @param string $file The image file to load. + * @param boolean $sslVerify Set to false to skip SSL validation. * @throws \Exception Thrown if file or image data is invalid. * @return \claviska\SimpleImage */ public function fromFile($file) { - // Check if the file exists and is readable. We're using fopen() instead of file_exists() - // because not all URL wrappers support the latter. - $handle = @fopen($file, 'r'); - if($handle === false) { + // Set fopen options. + $sslVerify = $this->getFlag("sslVerify"); // Don't perform peer validation when true + $opts = [ + "ssl" => [ + "verify_peer" => $sslVerify, + "verify_peer_name" => $sslVerify + ] + ]; + + // Check if the file exists and is readable. + $file = @file_get_contents($file, false, stream_context_create($opts)); + if($file === false) { throw new \Exception("File not found: $file", self::ERR_FILE_NOT_FOUND); } - fclose($handle); + + // Create image object from string + $this->image = imagecreatefromstring($file); // Get image info - $info = @getimagesize($file); + $info = @getimagesizefromstring($file); if($info === false) { throw new \Exception("Invalid image file: $file", self::ERR_INVALID_IMAGE); } $this->mimeType = $info['mime']; - // Create image object from file - switch($this->mimeType) { - case 'image/gif': - // Load the gif - $gif = imagecreatefromgif($file); - if($gif) { - // Copy the gif over to a true color image to preserve its transparency. This is a - // workaround to prevent imagepalettetruecolor() from borking transparency. - $width = imagesx($gif); - $height = imagesy($gif); - $this->image = imagecreatetruecolor($width, $height); - $transparentColor = imagecolorallocatealpha($this->image, 0, 0, 0, 127); - imagecolortransparent($this->image, $transparentColor); - imagefill($this->image, 0, 0, $transparentColor); - imagecopy($this->image, $gif, 0, 0, 0, 0, $width, $height); - imagedestroy($gif); - } - break; - case 'image/jpeg': - $this->image = imagecreatefromjpeg($file); - break; - case 'image/png': - $this->image = imagecreatefrompng($file); - break; - case 'image/webp': - $this->image = imagecreatefromwebp($file); - break; - case 'image/bmp': - case 'image/x-ms-bmp': - case 'image/x-windows-bmp': - $this->image = imagecreatefrombmp($file); - break; - } if(!$this->image) { throw new \Exception("Unsupported format: " . $this->mimeType, self::ERR_UNSUPPORTED_FORMAT); } + switch($this->mimeType) { + case 'image/gif': + // Copy the gif over to a true color image to preserve its transparency. This is a + // workaround to prevent imagepalettetotruecolor() from borking transparency. + $width = imagesx($this->image); + $height = imagesx($this->image); + + $gif = imagecreatetruecolor((int) $width, (int) $height); + $alpha = imagecolorallocatealpha($gif, 0, 0, 0, 127); + imagecolortransparent($gif, $alpha); + imagefill($gif, 0, 0, $alpha); + + imagecopy($this->image, $gif, 0, 0, 0, 0, $width, $height); + imagedestroy($gif); + break; + case 'image/jpeg': + // Load exif data from JPEG images + if(function_exists('exif_read_data')) { + $this->exif = @exif_read_data("data://image/jpeg;base64," . base64_encode($file)); + } + break; + } + // Convert pallete images to true color images imagepalettetotruecolor($this->image); - // Load exif data from JPEG images - if($this->mimeType === 'image/jpeg' && function_exists('exif_read_data')) { - $this->exif = @exif_read_data($file); - } - return $this; } @@ -194,7 +233,7 @@ class SimpleImage { * @return \claviska\SimpleImage */ public function fromNew($width, $height, $color = 'transparent') { - $this->image = imagecreatetruecolor($width, $height); + $this->image = imagecreatetruecolor((int) $width, (int) $height); // Use PNG for dynamically created images because it's lossless and supports transparency $this->mimeType = 'image/png'; @@ -490,7 +529,7 @@ class SimpleImage { imagefilter($srcIm, IMG_FILTER_COLORIZE, 0, 0, 0, 127 * ((100 - $pct) / 100)); } - imagecopy($dstIm, $srcIm, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH); + imagecopy($dstIm, $srcIm, (int) $dstX, (int) $dstY, (int) $srcX, (int) $srcY, (int) $srcW, (int) $srcH); return true; } @@ -593,7 +632,7 @@ class SimpleImage { // Avoid using native imagecrop() because of a bug with PNG transparency $dstW = abs($x2 - $x1); $dstH = abs($y2 - $y1); - $newImage = imagecreatetruecolor($dstW, $dstH); + $newImage = imagecreatetruecolor((int) $dstW, (int) $dstH); $transparentColor = imagecolorallocatealpha($newImage, 0, 0, 0, 127); imagecolortransparent($newImage, $transparentColor); imagefill($newImage, 0, 0, $transparentColor); @@ -603,10 +642,10 @@ class SimpleImage { $newImage, $this->image, 0, 0, min($x1, $x2), min($y1, $y2), - $dstW, - $dstH, - $dstW, - $dstH + (int) $dstW, + (int) $dstH, + (int) $dstW, + (int) $dstH ); // Swap out the new image @@ -802,7 +841,7 @@ class SimpleImage { // We can't use imagescale because it doesn't seem to preserve transparency properly. The // workaround is to create a new truecolor image, allocate a transparent color, and copy the // image over to it using imagecopyresampled. - $newImage = imagecreatetruecolor($width, $height); + $newImage = imagecreatetruecolor((int) $width, (int) $height); $transparentColor = imagecolorallocatealpha($newImage, 0, 0, 0, 127); imagecolortransparent($newImage, $transparentColor); imagefill($newImage, 0, 0, $transparentColor); @@ -810,8 +849,8 @@ class SimpleImage { $newImage, $this->image, 0, 0, 0, 0, - $width, - $height, + (int) $width, + (int) $height, $this->getWidth(), $this->getHeight() ); @@ -1885,7 +1924,7 @@ class SimpleImage { $color['red'], $color['green'], $color['blue'], - 127 - ($color['alpha'] * 127) + (int) (127 - ($color['alpha'] * 127)) ); if($index > -1) { // Yes, return this color index diff --git a/bl-plugins/twitter-cards/languages/cs_CZ.json b/bl-plugins/twitter-cards/languages/cs_CZ.json new file mode 100644 index 00000000..3b3edfcc --- /dev/null +++ b/bl-plugins/twitter-cards/languages/cs_CZ.json @@ -0,0 +1,13 @@ +{ + "plugin-data": + { + "name": "Twitter Cards", + "description": "With Twitter Cards, you can attach rich photos, videos and media experiences to Tweets, helping to drive traffic to your website." + }, + "default-image": "Výchozí obrázek", + "twitter-site-profile": "Profil stránky na Twitteru", + "twitter-author-profile": "Profil autora příspěvku na Twitteru", + "minimum-image-dimensions": "Minimální rozměry obrázku jsou 300x157px a maximum 4096x4096px.", + "image-must-be-less": "Obrázek musí být menší než 5MB.", + "formats-are-supported": "Jsou podporovány formáty pouze JPG, PNG, WEBP a GIF." +} diff --git a/bl-plugins/twitter-cards/languages/en.json b/bl-plugins/twitter-cards/languages/en.json index 5f98378d..f314a695 100644 --- a/bl-plugins/twitter-cards/languages/en.json +++ b/bl-plugins/twitter-cards/languages/en.json @@ -4,5 +4,10 @@ "name": "Twitter Cards", "description": "With Twitter Cards, you can attach rich photos, videos and media experiences to Tweets, helping to drive traffic to your website." }, - "default-image": "Default image" -} \ No newline at end of file + "default-image": "Default image", + "twitter-site-profile": "Twitter site profile", + "twitter-author-profile": "Twitter author profile", + "minimum-image-dimensions": "The minimum image dimensions are 300x157px and the maximum 4096x4096px.", + "image-must-be-less": "Image must be less than 5MB in size.", + "formats-are-supported": "JPG, PNG, WEBP and GIF formats are supported." +} diff --git a/bl-plugins/twitter-cards/metadata.json b/bl-plugins/twitter-cards/metadata.json index d62ed64e..5540bdca 100644 --- a/bl-plugins/twitter-cards/metadata.json +++ b/bl-plugins/twitter-cards/metadata.json @@ -3,8 +3,8 @@ "email": "", "website": "https://plugins.bludit.com", "version": "4.0.0", - "releaseDate": "2021-05-23", + "releaseDate": "2022-10-01", "license": "MIT", "compatible": "4.0.0", "notes": "" -} \ No newline at end of file +} diff --git a/bl-plugins/twitter-cards/plugin.php b/bl-plugins/twitter-cards/plugin.php index 26acdc80..754e3df1 100644 --- a/bl-plugins/twitter-cards/plugin.php +++ b/bl-plugins/twitter-cards/plugin.php @@ -6,7 +6,9 @@ class pluginTwitterCards extends Plugin { { // Fields and default values for the database of this plugin $this->dbFields = array( - 'defaultImage'=>'' + 'defaultImage' => '', + 'twitterSite' => '', + 'twitterCreator' => '' ); } @@ -14,9 +16,24 @@ class pluginTwitterCards extends Plugin { { global $L; - $html = '
'; + $html = ''; + + $html .= '
'; $html .= ''; $html .= ''; + $html .= '

' . $L->get('minimum-image-dimensions') . ' ' . $L->get('image-must-be-less') . ' ' . $L->get('formats-are-supported') . '

'; + $html .= '
'; + + $html .= '
'; + $html .= ''; + $html .= ''; + $html .= '
'; + + $html .= '
'; + $html .= ''; + $html .= ''; $html .= '
'; return $html; @@ -31,19 +48,20 @@ class pluginTwitterCards extends Plugin { global $page; $data = array( - 'card' =>'summary', - 'site' =>'', - 'title' =>$site->title(), - 'description' =>$site->description(), - 'image' =>'' + 'card' => 'summary_large_image', + 'twitterSite' => $this->getValue('twitterSite'), + 'twitterCreator' => $this->getValue('twitterCreator'), + 'title' => $site->title(), + 'description' => $site->description(), + 'image' => '' ); - switch($WHERE_AM_I) { + switch( $WHERE_AM_I ) { // The user filter by page case 'page': - $data['title'] = $page->title(); - $data['description'] = $page->description(); - $data['image'] = $page->coverImage($absolute=true); + $data['title'] = $page->title(); + $data['description'] = $page->description(); + $data['image'] = $page->coverImage( $absolute = true ); $pageContent = $page->content(); break; @@ -53,32 +71,75 @@ class pluginTwitterCards extends Plugin { $pageContent = ''; // The image it's from the first page if(isset($content[0]) ) { - $data['image'] = $content[0]->coverImage($absolute=true); + $data['image'] = $content[0]->coverImage( $absolute = true ); $pageContent = $content[0]->content(); } break; } $html = PHP_EOL.''.PHP_EOL; - $html .= ''.PHP_EOL; - $html .= ''.PHP_EOL; - $html .= ''.PHP_EOL; - $html .= ''.PHP_EOL; + $html .= ''.PHP_EOL; + $html .= ''.PHP_EOL; + $html .= ''.PHP_EOL; + $html .= ''.PHP_EOL; + // If the page doesn't have a description try to get excerpt from content + if( empty( $data['description'] ) ) + { + $data['description'] = $this->content_excerpt( $pageContent , 150, '...'); + } + $html .= ''.PHP_EOL; // If the page doesn't have a coverImage try to get an image from the HTML content - if( empty($data['image']) ) { + if( empty( $data['image'] ) ) { // Get the image from the content - $src = DOM::getFirstImage($pageContent); - if ($src!==false) { + $src = DOM::getFirstImage( $pageContent ); + if ( $src !== false ) { $data['image'] = $src; } else { - if (Text::isNotEmpty($this->getValue('defaultImage'))) { + if ( Text::isNotEmpty( $this->getValue('defaultImage') ) ) { $data['image'] = $this->getValue('defaultImage'); } } } - $html .= ''.PHP_EOL; + $html .= ''.PHP_EOL; + $html .= ''.PHP_EOL.PHP_EOL; + + unset( $pageContent ); + unset( $data ); + return $html; } -} \ No newline at end of file + + /** Return excerpt from full content of post + * + * @param string $str Post content + * @param int $n Number of characters + * @param string $endChar End char append to after excerpt (default "...") + * @return string + */ + private function content_excerpt( string $str, int $n = 500, string $endChar = '…' ): string + { + $str = strip_tags( $str ); + if ( mb_strlen( $str ) < $n ) + { + return $str; + } + $str = preg_replace('/ {2,}/', ' ', str_replace(["\r", "\n", "\t", "\x0B", "\x0C"], ' ', $str)); + if ( mb_strlen( $str ) <= $n ) + { + return $str; + } + $out = ''; + foreach ( explode( ' ', trim( $str ) ) as $val ) + { + $out .= $val . ' '; + if ( mb_strlen( $out ) >= $n ) + { + $out = trim( $out ); + break; + } + } + return ( mb_strlen( $out ) === mb_strlen( $str ) ) ? $out : $out . $endChar; + } +} diff --git a/install.php b/install.php index fa66707c..5eaae3f5 100644 --- a/install.php +++ b/install.php @@ -724,7 +724,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { - +
' . $error . '' . $L->get( $error ) . '