LuckyCoinkydink/plugins/serendipity_event_responsiveimages/serendipity_event_responsiveimages.php
2021-06-06 18:23:06 +02:00

241 lines
10 KiB
PHP

<?php
if (IN_serendipity !== true) {
die ("Don't hack!");
}
@serendipity_plugin_api::load_language(dirname(__FILE__));
class serendipity_event_responsiveimages extends serendipity_event
{
var $title = PLUGIN_EVENT_RESPONSIVE_NAME;
function introspect(&$propbag)
{
global $serendipity;
$propbag->add('name', PLUGIN_EVENT_RESPONSIVE_NAME);
$propbag->add('description', PLUGIN_EVENT_RESPONSIVE_DESC);
$propbag->add('stackable', false);
$propbag->add('author', 'Serendipity Team');
$propbag->add('version', '0.5.2');
$propbag->add('requirements', array(
'serendipity' => '2.2',
));
$propbag->add('cachable_events', array('frontend_display' => true));
$propbag->add('event_hooks', array('frontend_display' => true,
'backend_media_makethumb' => true,
'frontend_display:unknown:per-entry' => true,
'frontend_display:opml-1.0:per_entry' => true,
'frontend_display:rss-0.91:per_entry' => true,
'frontend_display:rss-1.0:per_entry' => true,
'frontend_display:rss-2.0:per_entry' => true,
'frontend_display:atom-0.3:per_entry' => true,
'frontend_display:atom-1.0:per_entry' => true,
));
$propbag->add('groups', array('MARKUP'));
$this->markup_elements = array(
array(
'name' => 'ENTRY_BODY',
'element' => 'body',
),
array(
'name' => 'EXTENDED_BODY',
'element' => 'extended',
)
);
$conf_array = array();
foreach($this->markup_elements as $element) {
$conf_array[] = $element['name'];
}
$propbag->add('configuration', $conf_array);
}
function install()
{
serendipity_plugin_api::hook_event('backend_cache_entries', $this->title);
}
function uninstall(&$propbag)
{
serendipity_plugin_api::hook_event('backend_cache_purge', $this->title);
serendipity_plugin_api::hook_event('backend_cache_entries', $this->title);
}
function generate_content(&$title)
{
$title = $this->title;
}
function introspect_config_item($name, &$propbag)
{
$propbag->add('type', 'boolean');
$propbag->add('name', constant($name));
$propbag->add('description', sprintf(APPLY_MARKUP_TO, constant($name)));
$propbag->add('default', 'true');
return true;
}
function event_hook($event, &$bag, &$eventData, $addData = null)
{
global $serendipity;
$hooks = &$bag->get('event_hooks');
if (!isset($serendipity['smarty']) || ! is_object($serendipity['smarty'])) {
serendipity_smarty_init(); // if not set to avoid member function assign() on a non-object error, start Smarty templating
}
$this->breakpoints = $serendipity['smarty']->getTemplateVars('template_option')['breakpoints'] ?? false;
if (! $this->breakpoints) {
$this->breakpoints = [1600, 1200, 600]; # This can be overwritten by the theme
}
$this->thumbWidths = [1200, 800, 400];
if (isset($hooks[$event])) {
switch($event) {
case 'frontend_display':
foreach ($this->markup_elements as $temp) {
if (serendipity_db_bool($this->get_config($temp['name'], true)) && isset($eventData[$temp['element']]) &&
!($eventData['properties']['ep_disable_markup_' . $this->instance] ?? null) &&
!isset($serendipity['POST']['properties']['disable_markup_' . $this->instance])) {
$element = $temp['element'];
$eventData[$element] = $this->_responsive_markup($eventData[$element]);
}
}
break;
case 'backend_media_makethumb':
// $eventData is usually defined as:
// array(array(
// 'thumbSize' => $serendipity['thumbSize'],
// 'thumb' => $serendipity['thumbSuffix']
// ));
// We now just need to add the additional array elements, with the new sizes and suffix.
// We can use $addData, containing the path to the full size file, to get the starting width
$origSize = serendipity_getimagesize($addData);
for ($i = 0; $i < count($this->thumbWidths); $i++) {
$thumbWidth = $this->thumbWidths[$i];
if ($thumbWidth < $origSize[0]) {
$eventData[] = array(
'thumbSize' => ['width' => $thumbWidth, 'height' => $origSize[1] * ($thumbWidth / $origSize[0])],
'thumb' => $thumbWidth . 'W.' . $serendipity['thumbSuffix']
);
}
}
break;
case 'frontend_display:unknown:per-entry':
case 'frontend_display:opml-1.0:per_entry':
case 'frontend_display:rss-0.91:per_entry':
case 'frontend_display:rss-1.0:per_entry':
case 'frontend_display:rss-2.0:per_entry':
case 'frontend_display:atom-0.3:per_entry':
case 'frontend_display:atom-1.0:per_entry':
// We need to rewrite relative srcsets to absolute urls in the RSS feed, otherwise images won't be shown
$pattern = '@srcset=(["\'][^"\']+)@i';
$eventData['feed_body'] = preg_replace_callback($pattern, function($matches) {
global $serendipity;
$matches[1] = str_ireplace('"' . $serendipity['serendipityHTTPPath'], '"' . $serendipity['baseURL'], $matches[1]);
$matches[1] = str_ireplace(',' . $serendipity['serendipityHTTPPath'], ',' . $serendipity['baseURL'], $matches[1]);
return 'srcset=' . $matches[1];
}, $eventData['feed_body']);
break;
default:
return false;
}
return true;
} else {
return false;
}
}
/* Given an entry text, replace each image linked to the ML with an img element containing
* an srcset.
* */
function _responsive_markup($text)
{
preg_match_all('@<!-- s9ymdb:(?<id>\d+) -->@', $text, $matches);
foreach ($matches['id'] as $imgId) {
preg_match('@<!-- s9ymdb:\d+ --><img[^>]+width=["\'](\d+)["\']@', $text, $matches);
if (count($matches) > 0) {
$srcset = $this->createSrcset($imgId, $matches[1]);
} else {
$srcset = $this->createSrcset($imgId);
}
$callback = function($matches) use ($srcset) {
if (strpos($matches[1], "srcset") === false) {
// the image has not yet an srcset, at least at the position where we insert it normally
return "{$matches[1]} $srcset src=";
} else {
return "{$matches[1]} src=";
}
};
$text = preg_replace_callback("@(<!-- s9ymdb:$imgId -->.*?)src=@", $callback, $text);
}
return $text;
}
/* Given an id for a image in the ML, create an srcset using smaller thumbnail images and their width.
* Don't worry over thumbnail creation here, that's done on image upload and thumbnail creation.
* */
function createSrcset($id, $maxWidth = 20000) {
global $serendipity;
$origImage = serendipity_fetchImageFromDatabase($id);
if (! $origImage) {
return '';
}
$imagePath = $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $origImage['path'] . $origImage['realname'];
$thumbnails = serendipity_getThumbnails($id);
$srcset = "srcset=\"$imagePath {$origImage['dimensions_width']}w,";
if ($origImage['dimensions_width'] <= $this->breakpoints[0]) {
// don't set the original image as srcset source if its breakpoint would be too small
$srcset = 'srcset="';
}
for ($i = 0; $i < count($this->thumbWidths); $i++) {
$thumbWidth = $this->thumbWidths[$i];
$matchedThumbnail = false;
foreach ($thumbnails as $thumbnail) {
if (strpos($thumbnail, $thumbWidth . 'W') !== false) {
$matchedThumbnail = $thumbnail;
break;
}
}
if ($matchedThumbnail) {
$thumbnailHttp = str_replace($serendipity['serendipityPath'], $serendipity['serendipityHTTPPath'], $matchedThumbnail);
$breakpoint = $this->breakpoints[$i];
$srcset .= "{$thumbnailHttp} {$breakpoint}w,";
}
}
// 2 == there is the original thumbnail without a dimension, and one thumbnail for the smallest breakpoint
if (count($thumbnails) == 2) {
// When the smallest thumbnail is the only responsive thumbnail our original image will be needed to as part of the srcset, otherwise we too often upscale the small thumbnail
$srcset .= "$imagePath {$origImage['dimensions_width']}w,";
}
if (substr($srcset, -strlen(',')) === ',') {
// we don't want to have the trailing comma
$srcset = substr($srcset, 0, -1);
}
$srcset .= '"';
return $srcset;
}
}
/* vim: set sts=4 ts=4 expandtab : */
?>