]> _ Git - cubeextranet.git/commitdiff
WIP #3881 @8
authorstephen@cubedesigners.com <stephen@cubedesigners.com@f5622870-0f3c-0410-866d-9cb505b7a8ef>
Thu, 24 Sep 2020 17:33:14 +0000 (17:33 +0000)
committerstephen@cubedesigners.com <stephen@cubedesigners.com@f5622870-0f3c-0410-866d-9cb505b7a8ef>
Thu, 24 Sep 2020 17:33:14 +0000 (17:33 +0000)
inc/ws/Util/html5/master/class.ws.html5.links.php
inc/ws/Util/html5/slideshow/class.ws.html5.links.php

index 7de817732ae56a8675a6d8e1cdf3616c6945be5f..57d19a6a06841dd16486e5a942ae2eacf99d7f15 100644 (file)
@@ -1177,7 +1177,7 @@ class webVideoLink extends videoLink
 
     public function getEmbed()
     {
-        return '<iframe width="' . $this->width . '" height="' . $this->height . '" src="' . $this->getEmbedURL() . '" frameborder="0"  allowfullscreen></iframe>';
+        return '<iframe width="' . $this->width . '" height="' . $this->height . '" src="' . $this->getEmbedURL() . '" frameborder="0" allowfullscreen></iframe>';
     }
 
     public function getEmbedURL()
@@ -2202,6 +2202,7 @@ class slideshowLink extends normalLink
 
     protected $path;
     protected $path_absolute;
+    protected $allowed_extensions;
 
     public function getURL()
     {
@@ -2240,46 +2241,60 @@ class slideshowLink extends normalLink
         $this->compiler->addLess('slick/slick-bundle');
         $this->compiler->addLess('fluidbook.slideshow');
 
-        $extensions = ['jpg', 'png', 'jpeg', 'gif'];
+        $this->allowed_extensions = ['jpg', 'png', 'jpeg', 'gif'];
 
         $slideshowID = 'slideshow_' . $this->uid;
         $XML_path = $this->path_absolute . '/slideshow.xml'; // Optional file so it may not exist
 
+        // Default Slick settings (can be overridden by slideshow.xml)
+        $slick_settings = [
+            'autoplay' => false,
+            'fade' => false,
+        ];
+
         $this->getURL();
 
         $slides = [];
+
         // If the zip file contained a slideshow.xml file, use that for fetching images and their captions
-        $thumbnails = true;
         if (file_exists($XML_path)) {
             $slideshow_XML = simplexml_load_string(file_get_contents($XML_path));
             $slideshowData = CubeIT_Util_Xml::toObject($slideshow_XML);
-            $images = [];
-            if (is_array($slideshowData->image)) {
-                $images = $slideshowData->image;
-            } else if (is_object($slideshowData->image)) {
-                $images = [$slideshowData->image];
+            $thumbnails = isset($slideshowData->_thumbnails) && $slideshowData->_thumbnails !== 'false';
+
+            // Allow "fade" transition to be enabled
+            if (isset($slideshowData->_effect) && $slideshowData->_effect == 'fade') {
+                $slick_settings['fade'] = true;
             }
-            foreach ($images as $img) {
-                $full_path = $this->path_absolute . '/' . $img->_name;
-                $slides[] = ['caption' => $img->_caption, 'path' => $full_path];
+            if (isset($slideshowData->_autoplay) && $slideshowData->_autoplay == 'true') {
+                $slick_settings['autoplay'] = true;
             }
-            $thumbnails = $slideshowData->_thumbnails !== 'false' && $slideshowData->_thumbnails;
-        } else {
-            // Or by default, just get all the images that were in the zip file...
-            $afiles = CubeIT_Files::getRecursiveDirectoryIterator($this->path_absolute);
 
-            foreach ($afiles as $afile) {
-                /** @var SplFileInfo $afile */
-                if (!$afile->isFile()) {
-                    continue;
+            if (isset($slideshowData->image)) {
+                $images = [];
+                if (is_array($slideshowData->image)) {
+                    $images = $slideshowData->image;
+                } else if (is_object($slideshowData->image)) {
+                    $images = [$slideshowData->image];
                 }
-                $ext = mb_strtolower($afile->getExtension());
-                if (!in_array($ext, $extensions)) {
-                    continue;
+                foreach ($images as $img) {
+                    $full_path = $this->path_absolute . '/' . $img->_name;
+                    $slides[] = ['caption' => $img->_caption, 'path' => $full_path];
                 }
-                $slides[] = ['path' => $afile->getPathname(), 'caption' => null];
-                uasort($slides, [$this, '_orderSlidesByFilename']);
             }
+
+            // It's possible that images are not defined in the slideshow.xml structure.
+            // In this case, we attempt to read the images from the directory...
+            if (empty($slides)) {
+                $slides = $this->_getSlidesFromDirectory($this->path_absolute);
+            }
+
+
+        } else {
+            // Or by default, just get all the images that were in the zip file...
+            $slides = $this->_getSlidesFromDirectory($this->path_absolute);
+
+            $thumbnails = (count($slides) > 1);
         }
 
         $res = '';
@@ -2292,7 +2307,7 @@ class slideshowLink extends normalLink
             $res .= '</div>'; // .fb-slideshow-slide
         }
 
-        $res = '<div class="fb-slideshow" id="' . $slideshowID . '" data-open-index="' . $this->extra . '" data-thumbnails="' . ($thumbnails ? '1' : '0') . '">' . $res . '</div>';
+        $res = '<div class="fb-slideshow" id="' . $slideshowID . '" data-open-index="' . $this->extra . '" data-thumbnails="' . ($thumbnails ? '1' : '0') . '" data-slick=\''. json_encode($slick_settings) .'\'>' . $res . '</div>';
 
         $res .= '<script>';
         $res .= 'fluidbook.slideshow.initSlideshow("' . $slideshowID . '");';
@@ -2301,6 +2316,29 @@ class slideshowLink extends normalLink
         return $res;
     }
 
+    protected function _getSlidesFromDirectory($path) {
+        // Previously this was getting all files recursively but it caused problems
+        // when there was a __MACOSX sub directory inside the zip file containing
+        // resource forks for the JPGs. There's no need to support nested directories
+        // in the zip so we only look at the zip's root directory...
+        $files = CubeIT_Files::getDirectoryIterator($path);
+        $slides = [];
+
+        foreach ($files as $file) {
+            /** @var SplFileInfo $file */
+            if (!$file->isFile()) {
+                continue;
+            }
+            $ext = mb_strtolower($file->getExtension());
+            if (!in_array($ext, $this->allowed_extensions)) {
+                continue;
+            }
+            $slides[] = ['path' => $file->getPathname(), 'caption' => null];
+            uasort($slides, [$this, '_orderSlidesByFilename']);
+        }
+
+        return $slides;
+    }
 
     protected function _orderSlidesByFilename($a, $b)
     {
index 2a4a61f780c88469a09da8e277911f84d20660ea..e16175172faab71548bf328c9c039adcb9cb023b 100644 (file)
@@ -38,6 +38,10 @@ class wsHTML5Link
     public $zindex = 4;
     public $rightClone = false;
     public $iframeType = "none";
+    public $border = 0;
+    public $borderColor = '#ffffff';
+    public $maxWidth = 0;
+    protected $role = 'button';
 
     protected $_init;
 
@@ -192,11 +196,71 @@ class wsHTML5Link
                 break;
             case 37:
                 return new downloadPortionLink($id, $init, $compiler);
+            case 38:
+                $compiler->addTriggersLink($init['page'], $init['to']);
+                break;
+            case 39:
+                return new layerLink($id, $init, $compiler);
             default:
                 return null;
         }
     }
 
+    public static function parseExtras($extras, $normalizeKey = false)
+    {
+        $extras = trim($extras);
+        if ($extras === '') {
+            return [];
+        }
+        $res = [];
+        $lines = CubeIT_Text::splitLines($extras);
+        foreach ($lines as $line) {
+            $e = explode('=', $line);
+            if (count($e) < 2) {
+                continue;
+            }
+            $v = trim($e[1]);
+            // Handle values surronded by quotes
+            if (preg_match('|^\"([^\"]+)\"$|', $v, $matches)) {
+                $v = $matches[1];
+            }
+            $k = trim($e[0]);
+            if ($normalizeKey) {
+                $k = mb_strtolower($k);
+            }
+            $res[$k] = $v;
+        }
+
+        return $res;
+    }
+
+    public static function parseAnimations($animations)
+    {
+        $anims = explode('---', $animations);
+        $res = [];
+
+        foreach ($anims as $animation) {
+            $animation = trim($animation);
+            if (!$animation) {
+                continue;
+            }
+            $extras = self::parseExtras($animation, true);
+            if (count($extras) > 0) {
+                if (!isset($extras['direction'])) {
+                    $extras['direction'] = 'right';
+                }
+                if ($extras['direction'] === 'top') {
+                    $extras['direction'] = 'up';
+                }
+                if ($extras['direction'] === 'bottom') {
+                    $extras['direction'] = 'down';
+                }
+            }
+            $res[] = $extras;
+        }
+        return $res;
+    }
+
     public static function replaceCustomURL($url)
     {
         $url = trim($url);
@@ -244,11 +308,17 @@ class wsHTML5Link
             if ($k == 'extra') {
                 if (CubeIT_Util_Json::isJson($v)) {
                     $v = CubeIT_Util_Json::decode($v);
-                } else if (stristr($v, '=')) {
+                } else if (strpos($v, '=') !== false && strpos($v, '&') !== false) {
                     $vv = $v;
                     $v = [];
                     parse_str($vv, $v);
                     $v = CubeIT_Util_Object::asObject($v);
+                } else if (strpos($v, '=') !== false) {
+                    $extras = self::parseExtras($v);
+                    foreach ($extras as $extrak => $extrav) {
+                        $this->$extrak = $extrav;
+                    }
+                    continue;
                 }
             }
             $this->$k = $v;
@@ -268,6 +338,21 @@ class wsHTML5Link
         $this->init();
     }
 
+    public function getTooltipAttribute($t = null)
+    {
+        if (null === $t) {
+            $t = $this->getTooltip();
+        }
+        if ($t !== false) {
+            $escaped = htmlspecialchars($t, ENT_QUOTES);
+            $tooltip = ' data-tooltip="' . $escaped . '"';
+            $tooltip .= ' aria-label="' . $escaped . '"';
+            return $tooltip;
+        } else {
+            return '';
+        }
+    }
+
     public function overlapDoublePage()
     {
         return ($this->page % 2 == 0 && $this->left + $this->width > $this->compiler->width);
@@ -330,7 +415,15 @@ class wsHTML5Link
 
     public function getAdditionnalContent()
     {
-        return '';
+        $res = '';
+        if ($this->role !== '') {
+            $res .= ' role="' . $this->role . '"';
+        }
+        if ($this->maxWidth > 0) {
+            $res .= ' data-max-width="' . $this->maxWidth . '"';
+        }
+
+        return $res;
 
     }
 
@@ -395,8 +488,12 @@ class wsHTML5Link
             $css .= wsHTML5::writeCSSUA('transform', 'rotate(' . $this->rot . 'deg)');
             $origin = true;
         }
-        if (isset($this->extra->skewX)) {
-            $css .= wsHTML5::writeCSSUA('transform', 'skewX(' . $this->extra->skewX . 'deg)');
+        if (isset($this->skewX)) {
+            $css .= wsHTML5::writeCSSUA('transform', 'skewX(' . $this->skewX . 'deg)');
+            $origin = true;
+        }
+        if (isset($this->skew)) {
+            $css .= wsHTML5::writeCSSUA('transform', 'skew(' . $this->skew . ')');
             $origin = true;
         }
 
@@ -473,6 +570,7 @@ class wsHTML5Link
 
 class normalLink extends wsHTML5Link
 {
+    protected $role = 'link';
 
     public function getHTMLContent()
     {
@@ -484,16 +582,14 @@ class normalLink extends wsHTML5Link
         if (count($class)) {
             $attrs .= ' class="' . implode(' ', $class) . '"';
         }
-        $t = $this->getTooltip();
-        if ($t !== false) {
-            $attrs .= ' data-tooltip="' . htmlentities($t, ENT_QUOTES) . '"';
-        }
+        $attrs .= $this->getTooltipAttribute();
         if (isset($this->extra->blinkdelay)) {
             $attrs .= ' data-blinkdelay="' . intval($this->extra->blinkdelay) . '"';
         }
         return '<a href="' . $this->getURL() . '" data-type="' . $this->type . '" target="' . $this->getTarget() . '"' . $attrs . $this->getAdditionnalContent() . $this->getTrack() . '></a>';
     }
 
+
     public function getTrack()
     {
         return '';
@@ -600,6 +696,11 @@ class htmlMultimediaImage extends wsHTML5Link
         return $alt;
     }
 
+    public function getAdditionnalContent()
+    {
+        return parent::getAdditionnalContent() . ' aria-hidden="true"';
+    }
+
 }
 
 class htmlMultimediaPopupLink extends htmlMultimediaPopupImage
@@ -689,49 +790,29 @@ class contentLink extends wsHTML5Link
     public function getAdditionnalContent()
     {
         $res = parent::getAdditionnalContent();
-        $variables = self::parseAnimation($this->image_rollover);
-
-        if (!isset($variables['type']) || !$variables['type']) {
-            $variables['type'] = 'none';
+        $animations = self::parseAnimations($this->image_rollover);
+        foreach ($animations as $animation) {
+            if (isset($animation['zindex'])) {
+                $this->zindex = $animation['zindex'];
+            }
         }
-        if (isset($variables['zindex'])) {
-            $this->zindex = $variables['zindex'];
+        $res .= ' data-animations="' . htmlspecialchars(json_encode($animations), ENT_QUOTES) . '" ';
+        if ($this->_isHiddenFirst($animations)) {
+            $res .= ' data-animation-hide ';
         }
-        $res .= ' data-animation-type="' . $variables['type'] . '" data-animation="' . htmlspecialchars(json_encode($variables), ENT_QUOTES) . '" ';
-
 
         return $res;
     }
 
-    public static function parseAnimation($animation)
+    protected function _isHiddenFirst($animations)
     {
-        $animation = trim($animation);
-        $variables = [];
-        if ($animation != '') {
-            $lines = CubeIT_Text::splitLines($animation);
-            foreach ($lines as $line) {
-                $e = explode('=', $line);
-                if (count($e) < 2) {
-                    continue;
-                }
-                $v = trim($e[1]);
-                // Handle values surronded by quotes
-                if (preg_match('|^\"([^\"]+)\"$|', $v, $matches)) {
-                    $v = $matches[1];
-                }
-                $variables[trim($e[0])] = $v;
-            }
-            if (!isset($variables['direction'])) {
-                $variables['direction'] = 'right';
-            }
-            if ($variables['direction'] == 'top') {
-                $variables['direction'] = 'up';
-            }
-            if ($variables['direction'] == 'bottom') {
-                $variables['direction'] = 'down';
+        $hiddenAnimations = ['reveal', 'fadein', 'translatefrom'];
+        foreach ($animations as $animation) {
+            if (in_array($animation['type'], $hiddenAnimations)) {
+                return true;
             }
         }
-        return $variables;
+        return false;
     }
 
     public function getCSSZIndex()
@@ -760,6 +841,8 @@ class eventOverlayLink extends wsHTML5Link
 
 class webLink extends normalLink
 {
+    protected $role = 'link';
+
     public function getURL()
     {
         $res = str_replace('"', '\'', wsHTML5Link::getUniversalLocation($this->to));
@@ -793,6 +876,7 @@ class webLink extends normalLink
 
 class mailLink extends normalLink
 {
+    protected $role = 'link';
 
     public function getURL()
     {
@@ -818,6 +902,7 @@ class mailLink extends normalLink
 
 class phoneLink extends mailLink
 {
+    protected $role = 'link';
 
     public function getURL()
     {
@@ -853,6 +938,11 @@ class internalLink extends normalLink
         }
     }
 
+    public function getAdditionnalContent()
+    {
+        return parent::getAdditionnalContent() . ' role="button"';
+    }
+
     public function getDefaultTooltip()
     {
         return 'go to page';
@@ -876,8 +966,6 @@ class videoLink extends wsHTML5Link
 
     public function getHTMLContent()
     {
-
-
         $this->copyExternalFile($this->to, true);
 
         $w = round($this->width * $this->getCssScale());
@@ -1186,8 +1274,35 @@ class remarkableCartLink extends cartLink
 
 }
 
+class layerLink extends imageLink
+{
+    protected $maxzoom_default = 4;
+
+    public function getCSS()
+    {
+        $attributes = $this->getZoomAttributes();
+        zoomLink::generateImage($attributes, $this->compiler, 'layerlink', 'layer');
+        return 'background-image:url(../links/layer_' . $attributes['id'] . '.jpg);background-size:100% 100%;background-repeat:no-repeat;';
+    }
+
+    public function getZoomAttributes()
+    {
+        return [
+            'id' => $this->uid,
+            'page' => $this->page,
+            'maxzoom' => $this->maxzoom_default,
+            'width' => round($this->width),
+            'height' => round($this->height),
+            'x' => round($this->left),
+            'y' => round($this->top),
+        ];
+    }
+}
+
 class colorLink extends contentLink
 {
+    protected $role = '';
+
     public function getCSS()
     {
         return 'background-color:' . wsHTML5::colorToCSS($this->to, 1) . ';';
@@ -1223,6 +1338,7 @@ class textLink extends contentLink
     public function getAdditionnalContent()
     {
         $res = parent::getAdditionnalContent();
+
         return $res;
     }
 
@@ -1273,6 +1389,7 @@ class inlineSlideshowLink extends contentLink
 
 class fileLink extends normalLink
 {
+    protected $role = 'link';
 
     public function getURL()
     {
@@ -1299,8 +1416,9 @@ class downloadPortionLink extends fileLink
 {
     public function getURL()
     {
-        zoomLink::generateImage($this->getZoomAttributes(), $this->compiler, 'downloadportion', 'downloadportion');
-        return 'data/links/downloadportion_' . $this->id . '.jpg';
+        $attributes = $this->getZoomAttributes();
+        zoomLink::generateImage($attributes, $this->compiler, 'downloadportion', 'downloadportion');
+        return 'data/links/downloadportion_' . $attributes['id'] . '.jpg';
     }
 
     public function getZoomAttributes()
@@ -1321,6 +1439,8 @@ class downloadPortionLink extends fileLink
             'x' => round($this->left),
             'y' => round($this->top),
             'pdf' => $pdf,
+            'border' => $this->border,
+            'borderColor' => $this->borderColor,
         ];
         return $res;
     }
@@ -1347,6 +1467,8 @@ class downloadPortionLink extends fileLink
 
 class facebookLikeLink extends wsHTML5Link
 {
+    protected $role = '';
+
     public function getHTMLContent()
     {
         $this->compiler->addFacebookSDK();
@@ -1357,6 +1479,7 @@ class facebookLikeLink extends wsHTML5Link
 class htmlMultimediaLink extends wsHTML5Link
 {
 
+    protected $role = '';
     protected $_config = null;
     protected $_content = '';
     protected $_url;
@@ -1420,12 +1543,12 @@ class htmlMultimediaLink extends wsHTML5Link
                     $ld = ' data-ld="' . str_replace('index.html', $this->_config['lowDef'], $this->_url) . '" ';
                 }
 
-                $res = '<iframe' . $ld . 'data-scale="' . $s . '" data-width="' . $iw . '" data-height="' . $ih . '" width="' . $iw . '" height="' . $ih . '" src="' . $this->_url . '" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" allowfullscreen mozallowfullscreen="true" webkitallowfullscreen="true" onmousewheel="" style="visibility:hidden;" onload="this.style.visibility=\'visible\';"></iframe>';
+                $res = '<iframe' . $ld . 'data-scale="' . $s . '" data-width="' . $iw . '" data-height="' . $ih . '" width="' . $iw . '" height="' . $ih . '" src="' . $this->_url . '" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" allowfullscreen mozallowfullscreen="true" webkitallowfullscreen="true" onmousewheel="" style="visibility:hidden;" tabindex="-1" onload="this.style.visibility=\'visible\';"></iframe>';
             }
             if ($this->_externalIframe !== false) {
                 $iw = $this->_config['width'] * $s;
                 $ih = $this->_config['height'] * $s;
-                $res = '<iframe data-scale="' . $s . '" data-width="' . $iw . '" data-height="' . $ih . '"  width="' . $iw . '" height="' . $ih . '" src="' . $this->_externalIframe . '" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" allowfullscreen mozallowfullscreen="true" webkitallowfullscreen="true" onmousewheel="" style="visibility:hidden;" onload="this.style.visibility=\'visible\';"></iframe>';
+                $res = '<iframe data-scale="' . $s . '" data-width="' . $iw . '" data-height="' . $ih . '"  width="' . $iw . '" height="' . $ih . '" src="' . $this->_externalIframe . '" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" allowfullscreen mozallowfullscreen="true" webkitallowfullscreen="true" onmousewheel="" style="visibility:hidden;" tabindex="-1" onload="this.style.visibility=\'visible\';"></iframe>';
             }
 
             foreach ($this->_config['inject'] as $i) {
@@ -1613,6 +1736,8 @@ class audioLink extends wsHTML5Link
 
 class wescoLink extends normalLink
 {
+    protected $role = 'link';
+
     public static function _getURL($to)
     {
         return self::_getURLOfType('wesco', $to);
@@ -1648,6 +1773,7 @@ class wescoLink extends normalLink
 
 class pierronLink extends normalLink
 {
+    protected $role = 'link';
 
     public function getURL()
     {
@@ -1726,11 +1852,8 @@ class inpesPopinLink extends htmlMultimediaLink
         if (count($class)) {
             $c = ' class="' . implode(' ', $class) . '"';
         }
-        $tooltip = '';
-        $t = $this->getTooltip();
-        if ($t !== false) {
-            $tooltip = ' data-tooltip="' . htmlspecialchars($t, ENT_QUOTES) . '"';
-        }
+        $tooltip = $this->getTooltipAttribute();
+
         return '<a href="#" ' . $tooltip . $c . $this->getAdditionnalContent() . '></a>';
     }
 
@@ -1792,6 +1915,7 @@ class statsTagLink extends wsHTML5Link
 
 class flfLink extends wescoLink
 {
+    protected $role = 'link';
 
     public function getURL()
     {
@@ -1827,6 +1951,8 @@ class haguenauManifLink extends internalLink
 
 class customLink extends wescoLink
 {
+    protected $role = 'link';
+
     public static function getCustomInstance($id, $init, &$compiler)
     {
         $e = explode(':', $init['to']);
@@ -1922,7 +2048,7 @@ class zoomLink extends normalLink
     public function getZoomAttributes()
     {
         return [
-            'id' => $this->id,
+            'id' => $this->uid,
             'page' => $this->page,
             'maxzoom' => empty($this->to) ? $this->maxzoom_default : $this->to,
             'group' => implode(',', $this->getGroups()),
@@ -1930,7 +2056,9 @@ class zoomLink extends normalLink
             'width' => round($this->width),
             'height' => round($this->height),
             'x' => round($this->left),
-            'y' => round($this->top)
+            'y' => round($this->top),
+            'border' => $this->border,
+            'borderColor' => $this->borderColor,
         ];
     }
 
@@ -1949,7 +2077,7 @@ class zoomLink extends normalLink
             // The Poppler::extractArea function accepts a resolution setting and uses that to determine the
             // scale factor on the extracted images. It does so by dividing by 72, so we can pass our own scale
             // factor by setting the resolution to 72 * $maxzoom
-            'resolution' => 72 * $maxzoom
+            'resolution' => 150 * $maxzoom
         ];
 
         // Round all link co-ordinates because there seems to be a problem with the the Workshop link editor
@@ -1959,7 +2087,7 @@ class zoomLink extends normalLink
         $y = $attributes['y'];
         $w = $attributes['width'];
         $h = $attributes['height'];
-        $bookwidth = round($compiler->book->parametres->width);
+        $bookwidth = round($compiler->width);
 
         //error_log("--- Book Width: $bookwidth ---");
 
@@ -2001,6 +2129,13 @@ class zoomLink extends normalLink
             $both = $leftfile;
         }
 
+        if (isset($attributes['border']) && $attributes['border'] > 0) {
+            $tmp = CubeIT_Files::tempnam() . '.jpg';
+            CubeIT_CommandLine_Imagemagick::addBorder($both, $tmp, $attributes['border'], $attributes['borderColor']);
+            $compiler->vdir->addTemp($both);
+            $both = $tmp;
+        }
+
         $compiler->simpleCopyLinkFile($both, 'data/links/' . $save . '_' . $attributes['id'] . '.jpg');
 
         // Perform tidy up and delete temporary files if they exist
@@ -2067,6 +2202,8 @@ class slideshowLink extends normalLink
 
     protected $path;
     protected $path_absolute;
+    protected $allowed_extensions;
+    protected $thumbnail_height = 80; // Height in px of thumbnail slider
 
     public function getURL()
     {
@@ -2106,11 +2243,17 @@ class slideshowLink extends normalLink
         $this->compiler->addJsLib('splide', 'js/libs/splide/splide.js');
         $this->compiler->addLess('fluidbook.slideshow');
 
-        $extensions = ['jpg', 'png', 'jpeg', 'gif'];
+        $this->allowed_extensions = ['jpg', 'png', 'jpeg', 'gif'];
 
         $slideshowID = 'slideshow_' . $this->uid;
         $XML_path = $this->path_absolute . '/slideshow.xml'; // Optional file so it may not exist
 
+        // Default Slick settings (can be overridden by slideshow.xml)
+        $slideshow_settings = [
+            'autoplay' => false,
+            'fade' => false,
+        ];
+
         $this->getURL();
 
         $slides = [];
@@ -2119,48 +2262,50 @@ class slideshowLink extends normalLink
         if (file_exists($XML_path)) {
             $slideshow_XML = simplexml_load_string(file_get_contents($XML_path));
             $slideshowData = CubeIT_Util_Xml::toObject($slideshow_XML);
-            $images = [];
-            if (is_array($slideshowData->image)) {
-                $images = $slideshowData->image;
-            } else if (is_object($slideshowData->image)) {
-                $images = [$slideshowData->image];
+            $thumbnails = isset($slideshowData->_thumbnails) && $slideshowData->_thumbnails !== 'false';
+
+            // Allow "fade" transition to be enabled
+            if (isset($slideshowData->_effect) && $slideshowData->_effect == 'fade') {
+                $slideshow_settings['type'] = 'fade';
+                $slideshow_settings['rewind'] = true; // Loop infinitely
             }
-            foreach ($images as $img) {
-                $full_path = $this->path_absolute . '/' . $img->_name;
-                $slides[] = ['caption' => $img->_caption, 'path' => $full_path];
+            if (isset($slideshowData->_autoplay) && $slideshowData->_autoplay == 'true') {
+                $slideshow_settings['autoplay'] = true;
             }
-            $thumbnails = $slideshowData->_thumbnails !== 'false' && $slideshowData->_thumbnails;
-        } else {
-            // Or by default, just get all the images that were in the zip file...
 
-            // Previously this was getting all files recursively but it caused problems
-            // when there was a __MACOSX sub directory inside the zip file containing
-            // resource forks for the JPGs. There's no need to support nested directories
-            // in the zip so we only look at the zip's root directory...
-            $afiles = CubeIT_Files::getDirectoryIterator($this->path_absolute);
-
-            foreach ($afiles as $afile) {
-                /** @var SplFileInfo $afile */
-                if (!$afile->isFile()) {
-                    continue;
+            if (isset($slideshowData->image)) {
+                $images = [];
+                if (is_array($slideshowData->image)) {
+                    $images = $slideshowData->image;
+                } else if (is_object($slideshowData->image)) {
+                    $images = [$slideshowData->image];
                 }
-                $ext = mb_strtolower($afile->getExtension());
-                if (!in_array($ext, $extensions)) {
-                    continue;
+                foreach ($images as $img) {
+                    $full_path = $this->path_absolute . '/' . $img->_name;
+                    $slides[] = ['caption' => $img->_caption, 'path' => $full_path];
                 }
-                $slides[] = ['path' => $afile->getPathname(), 'caption' => null];
-                uasort($slides, [$this, '_orderSlidesByFilename']);
             }
 
+            // It's possible that images are not defined in the slideshow.xml structure.
+            // In this case, we attempt to read the images from the directory...
+            if (empty($slides)) {
+                $slides = $this->_getSlidesFromDirectory($this->path_absolute);
+            }
+
+
+        } else {
+            // Or by default, just get all the images that were in the zip file...
+            $slides = $this->_getSlidesFromDirectory($this->path_absolute);
+
             $thumbnails = (count($slides) > 1);
         }
 
         // Main slider
-        $res = '<div class="fb-slideshow splide" id="' . $slideshowID . '" data-open-index="' . $this->extra . '" data-thumbnails="' . ($thumbnails ? '1' : '0') . '">' . $this->_slides($slides) . '</div>';
+        $res = '<div class="fb-slideshow splide" id="' . $slideshowID . '" data-open-index="' . $this->extra . '" data-thumbnails="' . ($thumbnails ? '1' : '0') . '" data-splide=\''. json_encode($slideshow_settings) .'\'>' . $this->_slides($slides) . '</div>';
 
         // Thumbnails slider
         if ($thumbnails) {
-            $res .= '<div class="fb-slideshow-thumbnails splide" id="' . $slideshowID . '_thumbnails">' . $this->_slides($slides, false) . '</div>';
+            $res .= '<div class="fb-slideshow-thumbnails splide" id="' . $slideshowID . '_thumbnails">' . $this->_slides($slides, false, $this->thumbnail_height) . '</div>';
         }
 
 //        $res .= '<script>';
@@ -2170,7 +2315,31 @@ class slideshowLink extends normalLink
         return $res;
     }
 
-    protected function _slides($slides, $show_captions = true) {
+    protected function _getSlidesFromDirectory($path) {
+        // Previously this was getting all files recursively but it caused problems
+        // when there was a __MACOSX sub directory inside the zip file containing
+        // resource forks for the JPGs. There's no need to support nested directories
+        // in the zip so we only look at the zip's root directory...
+        $files = CubeIT_Files::getDirectoryIterator($path);
+        $slides = [];
+
+        foreach ($files as $file) {
+            /** @var SplFileInfo $file */
+            if (!$file->isFile()) {
+                continue;
+            }
+            $ext = mb_strtolower($file->getExtension());
+            if (!in_array($ext, $this->allowed_extensions)) {
+                continue;
+            }
+            $slides[] = ['path' => $file->getPathname(), 'caption' => null];
+            uasort($slides, [$this, '_orderSlidesByFilename']);
+        }
+
+        return $slides;
+    }
+
+    protected function _slides($slides, $show_captions = true, $max_height = null) {
 
         $res  = '<div class="splide__track">';
         $res .= '<ul class="splide__list">';
@@ -2181,10 +2350,17 @@ class slideshowLink extends normalLink
             $image_info_json = ($image_info) ? json_encode(['width' => $image_info[0], 'height' => $image_info[1]]) : '';
             $image_dimensions = ($image_info) ? $image_info[3] : '';
 
+            // When displaying thumbnails, they are a fixed size, based on height
+            // We set dimensions here to avoid extra work on the client side
+            if ($max_height && $image_info) {
+                $thumb_width = round($max_height / $image_info[1] * $image_info[0]); // max_height / image_height * image_width
+                $image_dimensions = 'style="width:'. $thumb_width .'px; height:'. $max_height .'px"';
+            }
+
             $res .= '<li class="fb-slideshow-slide splide__slide">';
-            $res .= '<div class="splide__slide__container">';
+            //$res .= '<div class="splide__slide__container">';
             $res .= '<img class="fb-slideshow-slide-image" src="' . $image_path_relative . '" data-meta="'. htmlspecialchars($image_info_json, ENT_QUOTES) .'" '. $image_dimensions .'>';
-            $res .= '</div>'; // .splide__slide__container
+            //$res .= '</div>'; // .splide__slide__container
 
             if ($show_captions && null !== $slide['caption']) {
                 $res .= '<p class="fb-slideshow-slide-caption">' . $slide['caption'] . '</p>';
@@ -2259,8 +2435,10 @@ class articleLink extends normalLink
     public function init()
     {
         parent::init();
-        $this->compiler->config->articlesList[$this->to]['page'] = $this->page;
         $this->article = $this->compiler->config->articlesList[$this->to];
+        if (!isset($this->compiler->config->articlesList[$this->to]['page'])) {
+            $this->compiler->config->articlesList[$this->to]['page'] = $this->page;
+        }
     }
 
     public function getURL()