mirror of
https://github.com/splitbrain/dokuwiki-plugin-gallery.git
synced 2025-07-24 09:59:17 +00:00
WIP refactored backend
This splits up all the code in classes and cleans everything up. A very few tests have been added. HTML has been simplified. Next up: frontend refactoring
This commit is contained in:
52
.github/workflows/phpTestLinux.yml
vendored
Normal file
52
.github/workflows/phpTestLinux.yml
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
name: PHP Tests on Linux
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
testLinux:
|
||||
name: PHP ${{ matrix.php-versions }} DokuWiki ${{ matrix.dokuwiki-branch }}
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.2', '7.3', '7.4', '8.0']
|
||||
dokuwiki-branch: [ 'master', 'stable']
|
||||
exclude:
|
||||
- dokuwiki-branch: 'stable'
|
||||
php-versions: '8.0'
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: mbstring, intl, PDO, pdo_sqlite, bz2
|
||||
|
||||
- name: Setup problem matchers
|
||||
run: |
|
||||
echo ::add-matcher::${{ runner.tool_cache }}/php.json
|
||||
echo ::add-matcher::${{ runner.tool_cache }}/phpunit.json
|
||||
|
||||
- name: Download DokuWiki Test-setup
|
||||
run: wget https://raw.github.com/splitbrain/dokuwiki-travis/master/travis.sh
|
||||
|
||||
- name: Run DokuWiki Test-setup
|
||||
env:
|
||||
CI_SERVER: 1
|
||||
DOKUWIKI: ${{ matrix.dokuwiki-branch }}
|
||||
run: sh travis.sh
|
||||
|
||||
- name: Setup PHPUnit
|
||||
run: |
|
||||
php _test/fetchphpunit.php
|
||||
cd _test
|
||||
|
||||
- name: Run PHPUnit
|
||||
run: |
|
||||
cd _test
|
||||
php phpunit.phar --verbose --stderr --group plugin_gallery
|
28
_test/FeedGalleryTest.php
Normal file
28
_test/FeedGalleryTest.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\gallery\test;
|
||||
|
||||
use dokuwiki\plugin\gallery\classes\FeedGallery;
|
||||
use dokuwiki\plugin\gallery\classes\Options;
|
||||
use DokuWikiTest;
|
||||
|
||||
/**
|
||||
* Media Feed tests for the gallery plugin
|
||||
*
|
||||
* @group plugin_gallery
|
||||
* @group plugins
|
||||
* @group internet
|
||||
*/
|
||||
class FeedGalleryTest extends DokuWikiTest
|
||||
{
|
||||
protected $pluginsEnabled = ['gallery'];
|
||||
|
||||
public function testGetImages()
|
||||
{
|
||||
$url = 'https://www.flickr.com/services/feeds/photoset.gne?nsid=22019303@N00&set=72177720310667219&lang=en-us&format=atom';
|
||||
$gallery = new FeedGallery($url, new Options());
|
||||
$images = $gallery->getImages();
|
||||
$this->assertIsArray($images);
|
||||
$this->assertCount(3, $images);
|
||||
}
|
||||
}
|
86
_test/GeneralTest.php
Normal file
86
_test/GeneralTest.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\gallery\test;
|
||||
|
||||
use DokuWikiTest;
|
||||
|
||||
/**
|
||||
* General tests for the gallery plugin
|
||||
*
|
||||
* @group plugin_gallery
|
||||
* @group plugins
|
||||
*/
|
||||
class GeneralTest extends DokuWikiTest
|
||||
{
|
||||
|
||||
/**
|
||||
* Simple test to make sure the plugin.info.txt is in correct format
|
||||
*/
|
||||
public function testPluginInfo(): void
|
||||
{
|
||||
$file = __DIR__ . '/../plugin.info.txt';
|
||||
$this->assertFileExists($file);
|
||||
|
||||
$info = confToHash($file);
|
||||
|
||||
$this->assertArrayHasKey('base', $info);
|
||||
$this->assertArrayHasKey('author', $info);
|
||||
$this->assertArrayHasKey('email', $info);
|
||||
$this->assertArrayHasKey('date', $info);
|
||||
$this->assertArrayHasKey('name', $info);
|
||||
$this->assertArrayHasKey('desc', $info);
|
||||
$this->assertArrayHasKey('url', $info);
|
||||
|
||||
$this->assertEquals('gallery', $info['base']);
|
||||
$this->assertRegExp('/^https?:\/\//', $info['url']);
|
||||
$this->assertTrue(mail_isvalid($info['email']));
|
||||
$this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']);
|
||||
$this->assertTrue(false !== strtotime($info['date']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to ensure that every conf['...'] entry in conf/default.php has a corresponding meta['...'] entry in
|
||||
* conf/metadata.php.
|
||||
*/
|
||||
public function testPluginConf(): void
|
||||
{
|
||||
$conf_file = __DIR__ . '/../conf/default.php';
|
||||
$meta_file = __DIR__ . '/../conf/metadata.php';
|
||||
|
||||
if (!file_exists($conf_file) && !file_exists($meta_file)) {
|
||||
self::markTestSkipped('No config files exist -> skipping test');
|
||||
}
|
||||
|
||||
if (file_exists($conf_file)) {
|
||||
include($conf_file);
|
||||
}
|
||||
if (file_exists($meta_file)) {
|
||||
include($meta_file);
|
||||
}
|
||||
|
||||
$this->assertEquals(
|
||||
gettype($conf),
|
||||
gettype($meta),
|
||||
'Both ' . DOKU_PLUGIN . 'gallery/conf/default.php and ' . DOKU_PLUGIN . 'gallery/conf/metadata.php have to exist and contain the same keys.'
|
||||
);
|
||||
|
||||
if ($conf !== null && $meta !== null) {
|
||||
foreach ($conf as $key => $value) {
|
||||
$this->assertArrayHasKey(
|
||||
$key,
|
||||
$meta,
|
||||
'Key $meta[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'gallery/conf/metadata.php'
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($meta as $key => $value) {
|
||||
$this->assertArrayHasKey(
|
||||
$key,
|
||||
$conf,
|
||||
'Key $conf[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'gallery/conf/default.php'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
43
_test/NamespaceGalleryTest.php
Normal file
43
_test/NamespaceGalleryTest.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\gallery\test;
|
||||
|
||||
use dokuwiki\plugin\gallery\classes\NamespaceGallery;
|
||||
use dokuwiki\plugin\gallery\classes\Options;
|
||||
use DokuWikiTest;
|
||||
|
||||
/**
|
||||
* Namespace Gallery tests for the gallery plugin
|
||||
*
|
||||
* @group plugin_gallery
|
||||
* @group plugins
|
||||
*/
|
||||
class NamespaceGalleryTest extends DokuWikiTest
|
||||
{
|
||||
protected $pluginsEnabled = ['gallery'];
|
||||
|
||||
/**
|
||||
* Copy demo images to the media directory
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
global $conf;
|
||||
\TestUtils::rcopy($conf['mediadir'], __DIR__ . '/data/media/gallery');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check that the images are returned correctly
|
||||
*/
|
||||
public function testGetImages()
|
||||
{
|
||||
$gallery = new NamespaceGallery('gallery', new Options());
|
||||
|
||||
$images = $gallery->getImages();
|
||||
$this->assertIsArray($images);
|
||||
$this->assertCount(3, $images);
|
||||
}
|
||||
}
|
BIN
_test/data/media/gallery/img_0153.jpg
Normal file
BIN
_test/data/media/gallery/img_0153.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 MiB |
BIN
_test/data/media/gallery/img_0373.jpg
Normal file
BIN
_test/data/media/gallery/img_0373.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 MiB |
BIN
_test/data/media/gallery/img_1378.jpg
Normal file
BIN
_test/data/media/gallery/img_1378.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 MiB |
84
classes/AbstractGallery.php
Normal file
84
classes/AbstractGallery.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\gallery\classes;
|
||||
|
||||
abstract class AbstractGallery
|
||||
{
|
||||
|
||||
|
||||
/** @var Image[] */
|
||||
protected $images = [];
|
||||
/** @var Options */
|
||||
protected Options $options;
|
||||
|
||||
/**
|
||||
* Initialize the Gallery
|
||||
*
|
||||
* @param string $src The source from where to get the images
|
||||
* @param Options $options Gallery configuration
|
||||
*/
|
||||
public function __construct($src, Options $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple heuristic if something is an image
|
||||
*
|
||||
* @param string $src
|
||||
* @return bool
|
||||
*/
|
||||
public function hasImageExtension($src)
|
||||
{
|
||||
return (bool)preg_match(Image::IMG_REGEX, $src);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the images of this gallery
|
||||
*
|
||||
* The result will be sorted, reversed and limited according to the options
|
||||
*
|
||||
* @return Image[]
|
||||
*/
|
||||
public function getImages()
|
||||
{
|
||||
$images = $this->images; // create a copy of the array
|
||||
|
||||
switch ($this->options->sort) {
|
||||
case Options::SORT_FILE:
|
||||
usort($images, function ($a, $b) {
|
||||
return strcmp($a->getFilename(), $b->getFilename());
|
||||
});
|
||||
break;
|
||||
case Options::SORT_CTIME:
|
||||
usort($images, function ($a, $b) {
|
||||
return $a->getCreated() - $b->getCreated();
|
||||
});
|
||||
break;
|
||||
case Options::SORT_MTIME:
|
||||
usort($images, function ($a, $b) {
|
||||
return $a->getModified() - $b->getModified();
|
||||
});
|
||||
break;
|
||||
case Options::SORT_TITLE:
|
||||
usort($images, function ($a, $b) {
|
||||
return strcmp($a->getTitle(), $b->getTitle());
|
||||
});
|
||||
break;
|
||||
case Options::SORT_RANDOM:
|
||||
shuffle($images);
|
||||
break;
|
||||
}
|
||||
if ($this->options->reverse) {
|
||||
$images = array_reverse($images);
|
||||
}
|
||||
if ($this->options->offset) {
|
||||
$images = array_slice($images, $this->options->offset);
|
||||
}
|
||||
if ($this->options->limit) {
|
||||
$images = array_slice($images, 0, $this->options->limit);
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
}
|
90
classes/FeedGallery.php
Normal file
90
classes/FeedGallery.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\gallery\classes;
|
||||
|
||||
use FeedParser;
|
||||
|
||||
class FeedGallery extends AbstractGallery
|
||||
{
|
||||
protected $feedHost;
|
||||
protected $feedPath;
|
||||
|
||||
/** @inheritdoc */
|
||||
public function __construct($url, Options $options)
|
||||
{
|
||||
parent::__construct($url, $options);
|
||||
$this->initBaseUrl($url);
|
||||
$this->parseFeed($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given feed and adds all images to the gallery
|
||||
*
|
||||
* @param string $url
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function parseFeed($url) {
|
||||
$feed = new FeedParser();
|
||||
$feed->set_feed_url($url);
|
||||
$ok = $feed->init();
|
||||
if (!$ok) throw new \Exception($feed->error());
|
||||
|
||||
foreach ($feed->get_items() as $item) {
|
||||
$enclosure = $item->get_enclosure();
|
||||
if (!$enclosure) continue;
|
||||
|
||||
// skip non-image enclosures
|
||||
if ($enclosure->get_type() && substr($enclosure->get_type(), 0, 5) != 'image') {
|
||||
continue;
|
||||
} elseif (!$this->hasImageExtension($enclosure->get_link())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$enclosureLink = $this->makeAbsoluteUrl($enclosure->get_link());
|
||||
$detailLink = $this->makeAbsoluteUrl($item->get_link());
|
||||
|
||||
$image = new Image($enclosureLink);
|
||||
$image->setDetaillink($detailLink);
|
||||
$image->setTitle(htmlspecialchars_decode($enclosure->get_title() ?? '', ENT_COMPAT));
|
||||
$image->setDescription(strip_tags(htmlspecialchars_decode($enclosure->get_description() ?? '', ENT_COMPAT)));
|
||||
$image->setCreated($item->get_date('U'));
|
||||
$image->setModified($item->get_date('U'));
|
||||
$image->setWidth($enclosure->get_width());
|
||||
$image->setHeight($enclosure->get_height());
|
||||
|
||||
$this->images[] = $image;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given URL absolute using feed's URL as base
|
||||
*
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
protected function makeAbsoluteUrl($url)
|
||||
{
|
||||
|
||||
if (!preg_match('/^https?:\/\//i', $url)) {
|
||||
if ($url[0] == '/') {
|
||||
$url = $this->feedHost . $url;
|
||||
} else {
|
||||
$url = $this->feedHost . $this->feedPath . $url;
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize base url to use for broken feeds with non-absolute links
|
||||
* @param string $url The feed URL
|
||||
* @return void
|
||||
*/
|
||||
protected function initBaseUrl($url)
|
||||
{
|
||||
$main = parse_url($url);
|
||||
$this->feedHost = $main['scheme'] . '://' . $main['host'] . (!empty($main['port']) ? ':' . $main['port'] : '');
|
||||
$this->feedPath = dirname($main['path']) . '/';
|
||||
}
|
||||
}
|
225
classes/Formatter.php
Normal file
225
classes/Formatter.php
Normal file
@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\gallery\classes;
|
||||
|
||||
|
||||
class Formatter
|
||||
{
|
||||
protected Options $options;
|
||||
|
||||
/**
|
||||
* @param Options $options
|
||||
*/
|
||||
public function __construct(Options $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
// region Main Formatters
|
||||
|
||||
/**
|
||||
* @param AbstractGallery $gallery
|
||||
* @return string
|
||||
*/
|
||||
public function format(AbstractGallery $gallery)
|
||||
{
|
||||
$html = '<div class="plugin-gallery" id="gallery__' . $this->options->galleryID . '">';
|
||||
|
||||
$images = $gallery->getImages();
|
||||
$pages = $this->paginate($images);
|
||||
foreach ($pages as $page => $images) {
|
||||
$html .= $this->formatPage($images, $page);
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of pages for the given images
|
||||
*
|
||||
* @param Image[] $images
|
||||
* @return Image[][]
|
||||
*/
|
||||
protected function paginate($images)
|
||||
{
|
||||
if ($this->options->paginate) {
|
||||
$pages = array_chunk($images, $this->options->paginate);
|
||||
} else {
|
||||
$pages = [$images];
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given images into a gallery page
|
||||
*
|
||||
* @param Image[] $images
|
||||
* @param int $page The page number
|
||||
* @return string
|
||||
*/
|
||||
protected function formatPage($images, int $page)
|
||||
{
|
||||
$html = '<div class="gallery_page" id="gallery__' . $this->options->galleryID . '_' . $page . '">';
|
||||
foreach ($images as $image) {
|
||||
$html .= $this->formatImage($image);
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
protected function formatImage(Image $image)
|
||||
{
|
||||
global $ID;
|
||||
|
||||
// thumbnail image properties
|
||||
list($w, $h) = $this->getThumbnailSize($image);
|
||||
$img = [];
|
||||
$img['width'] = $w;
|
||||
$img['height'] = $h;
|
||||
$img['src'] = ml($image->getSrc(), ['w' => $w, 'h' => $h], true, '&');
|
||||
$img['alt'] = $image->getFilename();
|
||||
$img['loading'] = 'lazy';
|
||||
|
||||
// link properties
|
||||
$a = [];
|
||||
$a['href'] = $this->getDetailLink($image);
|
||||
$a['title'] = $image->getTitle();
|
||||
$a['data-caption'] = $image->getDescription();
|
||||
if ($this->options->lightbox) {
|
||||
$a['class'] = "lightbox JSnocheck";
|
||||
$a['rel'] = 'lightbox[gal-' . substr(md5($ID), 4) . ']'; //unique ID for the gallery
|
||||
$a['data-url'] = $this->getLightboxLink($image);
|
||||
}
|
||||
|
||||
// figure properties
|
||||
$fig = [];
|
||||
$fig['class'] = 'gallery-image';
|
||||
$fig['style'] = 'width: ' . $w . 'px;';
|
||||
|
||||
# differentiate between the URL for the lightbox and the URL for details
|
||||
# using a data-attribute
|
||||
# needs slight adjustment in the swipebox script
|
||||
# fall back to href when no data-attribute is set
|
||||
# lightbox url should have width/height limit -> adjust from old defaults to 1600x1200?
|
||||
# use detail URLs for thumbnail, title and filename
|
||||
# when direct is set it should link to full size image
|
||||
|
||||
|
||||
$html = '<figure ' . buildAttributes($fig, true) . '>';
|
||||
$html .= '<a ' . buildAttributes($a, true) . '>';
|
||||
$html .= '<img ' . buildAttributes($img, true) . ' />';
|
||||
$html .= '</a>';
|
||||
|
||||
if ($this->options->showtitle || $this->options->showname) {
|
||||
$html .= '<figcaption>';
|
||||
if ($this->options->showname) {
|
||||
$a = [
|
||||
'href' => $this->getDetailLink($image),
|
||||
'class' => 'gallery-filename',
|
||||
'title' => $image->getFilename(),
|
||||
];
|
||||
$html .= '<a ' . buildAttributes($a) . '>' . hsc($image->getFilename()) . '</a>';
|
||||
}
|
||||
if ($this->options->showtitle) {
|
||||
$a = [
|
||||
'href' => $this->getDetailLink($image),
|
||||
'class' => 'gallery-title',
|
||||
'title' => $image->getTitle(),
|
||||
];
|
||||
$html .= '<a ' . buildAttributes($a) . '>' . hsc($image->getTitle()) . '</a>';
|
||||
}
|
||||
$html .= '</figcaption>';
|
||||
}
|
||||
|
||||
$html .= '</figure>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Utilities
|
||||
|
||||
protected function getDetailLink(Image $image)
|
||||
{
|
||||
if ($image->getDetaillink()) {
|
||||
// external image
|
||||
return $image->getDetaillink();
|
||||
} else {
|
||||
return ml($image->getSrc(), '', $this->options->direct, '&');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the direct link to the image but limit it to a certain size
|
||||
*
|
||||
* @param Image $image
|
||||
* @return string
|
||||
*/
|
||||
protected function getLightboxLink(Image $image)
|
||||
{
|
||||
// use original image if no size is available
|
||||
if (!$image->getWidth() || !$image->getHeight()) {
|
||||
return ml($image->getSrc(), '', true, '&');
|
||||
}
|
||||
|
||||
// fit into bounding box
|
||||
list($width, $height) = $this->fitBoundingBox(
|
||||
$image->getWidth(),
|
||||
$image->getHeight(),
|
||||
$this->options->lightboxWidth,
|
||||
$this->options->lightboxHeight
|
||||
);
|
||||
|
||||
// no upscaling
|
||||
if ($width > $image->getWidth() || $height > $image->getHeight()) {
|
||||
return ml($image->getSrc(), '', true, '&');
|
||||
}
|
||||
|
||||
return ml($image->getSrc(), ['w' => $width, 'h' => $height], true, '&');
|
||||
}
|
||||
|
||||
/** Calculate the thumbnail size */
|
||||
protected function getThumbnailSize(Image $image)
|
||||
{
|
||||
$crop = $this->options->crop;
|
||||
if (!$image->getWidth() || !$image->getHeight()) {
|
||||
$crop = true;
|
||||
}
|
||||
if (!$crop) {
|
||||
list($thumbWidth, $thumbHeight) = $this->fitBoundingBox(
|
||||
$image->getWidth(),
|
||||
$image->getHeight(),
|
||||
$this->options->thumbnailWidth,
|
||||
$this->options->thumbnailHeight
|
||||
);
|
||||
} else {
|
||||
$thumbWidth = $this->options->thumbnailWidth;
|
||||
$thumbHeight = $this->options->thumbnailHeight;
|
||||
}
|
||||
return [$thumbWidth, $thumbHeight];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the size of a thumbnail to fit into a bounding box
|
||||
*
|
||||
* @param int $imgWidth
|
||||
* @param int $imgHeight
|
||||
* @param int $bBoxWidth
|
||||
* @param int $bBoxHeight
|
||||
* @return int[]
|
||||
*/
|
||||
protected function fitBoundingBox($imgWidth, $imgHeight, $bBoxWidth, $bBoxHeight)
|
||||
{
|
||||
$scale = min($bBoxWidth / $imgWidth, $bBoxHeight / $imgHeight);
|
||||
|
||||
$width = round($imgWidth * $scale);
|
||||
$height = round($imgHeight * $scale);
|
||||
|
||||
return [$width, $height];
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
191
classes/Image.php
Normal file
191
classes/Image.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\gallery\classes;
|
||||
|
||||
use dokuwiki\Utf8\PhpString;
|
||||
|
||||
class Image
|
||||
{
|
||||
const IMG_REGEX = '/\.(jpe?g|gif|png)$/i'; // FIXME add more formats
|
||||
|
||||
protected $isExternal = false;
|
||||
protected $src;
|
||||
protected $filename;
|
||||
protected $localfile;
|
||||
protected $title;
|
||||
protected $description;
|
||||
protected $width;
|
||||
protected $height;
|
||||
protected $created = 0;
|
||||
protected $modified = 0;
|
||||
protected $detaillink;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $src local ID or external URL to image
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($src)
|
||||
{
|
||||
$this->src = $src;
|
||||
if (preg_match('/^https:\/\//i', $src)) {
|
||||
$this->isExternal = true;
|
||||
$path = parse_url($src, PHP_URL_PATH);
|
||||
$this->filename = basename($path);
|
||||
} else {
|
||||
$this->localfile = mediaFN($src);
|
||||
if (!file_exists($this->localfile)) throw new \Exception('File not found: ' . $this->localfile);
|
||||
$this->filename = basename($this->localfile);
|
||||
$this->modified = filemtime($this->localfile);
|
||||
|
||||
$jpegMeta = new \JpegMeta($this->localfile);
|
||||
$this->title = $jpegMeta->getField('Simple.Title');
|
||||
$this->description = $jpegMeta->getField('Iptc.Caption');
|
||||
$this->created = $jpegMeta->getField('Date.EarliestTime');
|
||||
$this->width = $jpegMeta->getField('File.Width');
|
||||
$this->height = $jpegMeta->getField('File.Height');
|
||||
}
|
||||
}
|
||||
|
||||
public function isExternal()
|
||||
{
|
||||
return $this->isExternal;
|
||||
}
|
||||
|
||||
public function getSrc()
|
||||
{
|
||||
return $this->src;
|
||||
}
|
||||
|
||||
public function getFilename()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function setFilename(string $filename)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
}
|
||||
|
||||
public function getLocalfile()
|
||||
{
|
||||
return $this->localfile;
|
||||
}
|
||||
|
||||
public function setLocalfile(string $localfile)
|
||||
{
|
||||
$this->localfile = $localfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
if (empty($this->title) || $this->title == $this->filename) {
|
||||
$title = str_replace('_', ' ', $this->filename);
|
||||
$title = preg_replace(self::IMG_REGEX, '', $title);
|
||||
$title = PhpString::ucwords($title);
|
||||
return $title;
|
||||
}
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return trim(str_replace("\n", ' ', $this->description));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $description
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $width
|
||||
*/
|
||||
public function setWidth($width)
|
||||
{
|
||||
$this->width = $width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $height
|
||||
*/
|
||||
public function setHeight($height)
|
||||
{
|
||||
$this->height = $height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCreated()
|
||||
{
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $created
|
||||
*/
|
||||
public function setCreated($created)
|
||||
{
|
||||
$this->created = $created;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getModified()
|
||||
{
|
||||
return $this->modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $modified
|
||||
*/
|
||||
public function setModified($modified)
|
||||
{
|
||||
$this->modified = $modified;
|
||||
}
|
||||
|
||||
public function getDetaillink()
|
||||
{
|
||||
return $this->detaillink;
|
||||
}
|
||||
|
||||
public function setDetaillink($detaillink)
|
||||
{
|
||||
$this->detaillink = $detaillink;
|
||||
}
|
||||
}
|
73
classes/NamespaceGallery.php
Normal file
73
classes/NamespaceGallery.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\gallery\classes;
|
||||
|
||||
class NamespaceGallery extends AbstractGallery
|
||||
{
|
||||
|
||||
/** @inheritdoc */
|
||||
public function __construct($ns, $options)
|
||||
{
|
||||
parent::__construct($ns, $options);
|
||||
$this->searchNamespace($ns, $options->recursive, $options->filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the images
|
||||
*
|
||||
* @param string $ns
|
||||
* @param bool $recursive search recursively?
|
||||
* @param string $filter regular expresion to filter image IDs against (without namespace)
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function searchNamespace($ns, $recursive, $filter)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (media_exists($ns) && !is_dir(mediaFN($ns))) {
|
||||
// this is a single file, not a namespace
|
||||
if ($this->hasImageExtension($ns)) {
|
||||
$this->images[] = new Image($ns);
|
||||
}
|
||||
} else {
|
||||
search(
|
||||
$this->images,
|
||||
$conf['mediadir'],
|
||||
[$this, 'searchCallback'],
|
||||
[
|
||||
'depth' => $recursive ? 0 : 1,
|
||||
'filter' => $filter
|
||||
],
|
||||
utf8_encodeFN(str_replace(':', '/', $ns))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for search() to find images
|
||||
*/
|
||||
public function searchCallback(&$data, $base, $file, $type, $lvl, $opts)
|
||||
{
|
||||
if ($type == 'd') {
|
||||
if (empty($opts['depth'])) return true; // recurse forever
|
||||
$depth = substr_count($file, '/'); // we can't use level because we start deeper
|
||||
if ($depth >= $opts['depth']) return false; // depth reached
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = pathID($file, true);
|
||||
|
||||
// skip non-valid files
|
||||
if ($id != cleanID($id)) return false;
|
||||
//check ACL for namespace (we have no ACL for mediafiles)
|
||||
if (auth_quickaclcheck(getNS($id) . ':*') < AUTH_READ) return false;
|
||||
// skip non-images
|
||||
if (!$this->hasImageExtension($file)) return false;
|
||||
// skip filtered images
|
||||
if ($opts['filter'] && !preg_match($opts['filter'], noNS($id))) return false;
|
||||
|
||||
// still here, add to result
|
||||
$data[] = new Image($id);
|
||||
return false;
|
||||
}
|
||||
}
|
109
classes/Options.php
Normal file
109
classes/Options.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\gallery\classes;
|
||||
|
||||
class Options
|
||||
{
|
||||
|
||||
const SORT_FILE = 'file';
|
||||
const SORT_CTIME = 'date';
|
||||
const SORT_MTIME = 'mod';
|
||||
const SORT_TITLE = 'title';
|
||||
const SORT_RANDOM = 'random';
|
||||
|
||||
const ALIGN_NONE = 0;
|
||||
const ALIGN_LEFT = 1;
|
||||
const ALIGN_RIGHT = 2;
|
||||
const ALIGN_CENTER = 3;
|
||||
|
||||
// defaults
|
||||
public string $galleryID = '';
|
||||
public int $thumbnailWidth = 120;
|
||||
public int $thumbnailHeight = 120;
|
||||
public int $lightboxWidth = 1600;
|
||||
public int $lightboxHeight = 1200;
|
||||
public int $columns = 0;
|
||||
public string $filter = '';
|
||||
public bool $lightbox = false;
|
||||
public bool $direct = false;
|
||||
public bool $showname = false;
|
||||
public bool $showtitle = false;
|
||||
public bool $reverse = false;
|
||||
public bool $cache = true;
|
||||
public bool $crop = false;
|
||||
public bool $recursive = true;
|
||||
public $sort = self::SORT_FILE;
|
||||
public int $limit = 0;
|
||||
public int $offset = 0;
|
||||
public int $paginate = 0;
|
||||
public int $align = self::ALIGN_NONE;
|
||||
|
||||
/**
|
||||
* Options constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// load options from config
|
||||
$plugin = plugin_load('syntax', 'gallery');
|
||||
$this->thumbnailWidth = $plugin->getConf('thumbnail_width');
|
||||
$this->thumbnailHeight = $plugin->getConf('thumbnail_height');
|
||||
$this->lightboxWidth = $plugin->getConf('image_width');
|
||||
$this->lightboxHeight = $plugin->getConf('image_height');
|
||||
$this->columns = $plugin->getConf('cols');
|
||||
$this->sort = $plugin->getConf('sort');
|
||||
$this->parseParameters($plugin->getConf('options'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple option strings parser
|
||||
*
|
||||
* @param string $params
|
||||
* @return void
|
||||
*/
|
||||
public function parseParameters($params)
|
||||
{
|
||||
$params = preg_replace('/[,&?]+/', ' ', $params);
|
||||
$params = explode(' ', $params);
|
||||
foreach ($params as $param) {
|
||||
if ($param === '') continue;
|
||||
if ($param == 'titlesort') {
|
||||
$this->sort = self::SORT_TITLE;
|
||||
} elseif ($param == 'datesort') {
|
||||
$this->sort = self::SORT_CTIME;
|
||||
} elseif ($param == 'modsort') {
|
||||
$this->sort = self::SORT_MTIME;
|
||||
} elseif (preg_match('/^=(\d+)$/', $param, $match)) {
|
||||
$this->limit = (int)$match[1];
|
||||
} elseif (preg_match('/^\+(\d+)$/', $param, $match)) {
|
||||
$this->offset = (int)$match[1];
|
||||
} elseif (is_numeric($param)) {
|
||||
$this->columns = (int)$param;
|
||||
} elseif (preg_match('/^~(\d+)$/', $param, $match)) {
|
||||
$this->paginate = (int)$match[1];
|
||||
} elseif (preg_match('/^(\d+)([xX])(\d+)$/', $param, $match)) {
|
||||
if ($match[2] == 'X') {
|
||||
$this->lightboxWidth = (int)$match[1];
|
||||
$this->lightboxHeight = (int)$match[3];
|
||||
} else {
|
||||
$this->thumbnailWidth = (int)$match[1];
|
||||
$this->thumbnailHeight = (int)$match[3];
|
||||
}
|
||||
} elseif (strpos($param, '*') !== false) {
|
||||
$param = preg_quote($param, '/');
|
||||
$param = '/^' . str_replace('\\*', '.*?', $param) . '$/';
|
||||
$this->filter = $param;
|
||||
} else {
|
||||
if (substr($param, 0, 2) == 'no') {
|
||||
$opt = substr($param, 2);
|
||||
$set = false;
|
||||
} else {
|
||||
$opt = $param;
|
||||
$set = true;
|
||||
}
|
||||
if (!property_exists($this, $opt) || !is_bool($this->$opt)) continue;
|
||||
$this->$opt = $set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -7,8 +7,8 @@
|
||||
|
||||
$conf['thumbnail_width'] = 120;
|
||||
$conf['thumbnail_height'] = 120;
|
||||
$conf['image_width'] = 800;
|
||||
$conf['image_height'] = 600;
|
||||
$conf['image_width'] = 1600;
|
||||
$conf['image_height'] = 1200;
|
||||
$conf['cols'] = 5;
|
||||
|
||||
$conf['sort'] = 'file';
|
||||
|
@ -5,12 +5,23 @@
|
||||
* @author Dmitry Baikov <dsbaikov@gmail.com>
|
||||
*/
|
||||
|
||||
use dokuwiki\plugin\gallery\classes\Options;
|
||||
|
||||
$meta['thumbnail_width'] = array('numeric');
|
||||
$meta['thumbnail_height'] = array('numeric');
|
||||
$meta['image_width'] = array('numeric');
|
||||
$meta['image_height'] = array('numeric');
|
||||
$meta['cols'] = array('numeric');
|
||||
|
||||
$meta['sort'] = array('multichoice', '_choices' => array('file', 'mod', 'date', 'title'));
|
||||
$meta['options'] = array('multicheckbox', '_choices' => array('cache', 'crop', 'direct', 'lightbox', 'random', 'reverse', 'showname', 'showtitle'));
|
||||
$meta['sort'] = array(
|
||||
'multichoice',
|
||||
'_choices' => array(
|
||||
Options::SORT_FILE,
|
||||
Options::SORT_CTIME,
|
||||
Options::SORT_MTIME,
|
||||
Options::SORT_TITLE,
|
||||
Options::SORT_RANDOM,
|
||||
)
|
||||
);
|
||||
$meta['options'] = array('multicheckbox', '_choices' => array('cache', 'crop', 'direct', 'lightbox', 'reverse', 'showname', 'showtitle'));
|
||||
|
||||
|
661
syntax.php
661
syntax.php
@ -1,6 +1,10 @@
|
||||
<?php
|
||||
|
||||
use dokuwiki\File\PageResolver;
|
||||
use dokuwiki\plugin\gallery\classes\FeedGallery;
|
||||
use dokuwiki\plugin\gallery\classes\Formatter;
|
||||
use dokuwiki\plugin\gallery\classes\NamespaceGallery;
|
||||
use dokuwiki\plugin\gallery\classes\Options;
|
||||
|
||||
/**
|
||||
* Embed an image gallery
|
||||
@ -43,638 +47,73 @@ class syntax_plugin_gallery extends DokuWiki_Syntax_Plugin
|
||||
global $ID;
|
||||
$match = substr($match, 10, -2); //strip markup from start and end
|
||||
|
||||
$data = array();
|
||||
$options = new Options();
|
||||
|
||||
$data['galid'] = substr(md5($match), 0, 4);
|
||||
// unique gallery ID
|
||||
$options->galleryID = substr(md5($match), 0, 4);
|
||||
|
||||
// alignment
|
||||
$data['align'] = 0;
|
||||
if (substr($match, 0, 1) == ' ') $data['align'] += 1;
|
||||
if (substr($match, -1, 1) == ' ') $data['align'] += 2;
|
||||
if (substr($match, 0, 1) == ' ') $options->align += Options::ALIGN_LEFT;
|
||||
if (substr($match, -1, 1) == ' ') $options->align += Options::ALIGN_RIGHT;
|
||||
|
||||
// extract params
|
||||
list($ns, $params) = explode('?', $match, 2);
|
||||
$ns = trim($ns);
|
||||
// extract src and params
|
||||
list($src, $params) = sexplode('?', $match, 2);
|
||||
$src = trim($src);
|
||||
|
||||
// namespace (including resolving relatives)
|
||||
if (!preg_match('/^https?:\/\//i', $ns)) {
|
||||
// resolve relative namespace
|
||||
if (!preg_match('/^https?:\/\//i', $src)) {
|
||||
$pageResolver = new PageResolver($ID);
|
||||
$data['ns'] = $pageResolver->resolveId($ns);
|
||||
} else {
|
||||
$data['ns'] = $ns;
|
||||
$src = $pageResolver->resolveId($src);
|
||||
}
|
||||
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$this->getDataFromParams($this->getConf('options') . ',' . $params)
|
||||
);
|
||||
// parse parameters
|
||||
$options->parseParameters($params);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract options from the provided parameter string
|
||||
*
|
||||
* @param string $params
|
||||
*
|
||||
* @return array associative array of the options defined in the string
|
||||
*/
|
||||
public function getDataFromParams($params)
|
||||
{
|
||||
// set the defaults
|
||||
$data = [];
|
||||
$data['tw'] = $this->getConf('thumbnail_width');
|
||||
$data['th'] = $this->getConf('thumbnail_height');
|
||||
$data['iw'] = $this->getConf('image_width');
|
||||
$data['ih'] = $this->getConf('image_height');
|
||||
$data['cols'] = $this->getConf('cols');
|
||||
$data['filter'] = '';
|
||||
$data['lightbox'] = false;
|
||||
$data['direct'] = false;
|
||||
$data['showname'] = false;
|
||||
$data['showtitle'] = false;
|
||||
$data['reverse'] = false;
|
||||
$data['random'] = false;
|
||||
$data['cache'] = true;
|
||||
$data['crop'] = false;
|
||||
$data['recursive'] = true;
|
||||
$data['sort'] = $this->getConf('sort');
|
||||
$data['limit'] = 0;
|
||||
$data['offset'] = 0;
|
||||
$data['paginate'] = 0;
|
||||
|
||||
// parse additional options
|
||||
$params = preg_replace('/[,&?]+/', ' ', $params);
|
||||
$params = explode(' ', $params);
|
||||
foreach ($params as $param) {
|
||||
if ($param === '') continue;
|
||||
if ($param == 'titlesort') {
|
||||
$data['sort'] = 'title';
|
||||
} elseif ($param == 'datesort') {
|
||||
$data['sort'] = 'date';
|
||||
} elseif ($param == 'modsort') {
|
||||
$data['sort'] = 'mod';
|
||||
} elseif (preg_match('/^=(\d+)$/', $param, $match)) {
|
||||
$data['limit'] = $match[1];
|
||||
} elseif (preg_match('/^\+(\d+)$/', $param, $match)) {
|
||||
$data['offset'] = $match[1];
|
||||
} elseif (is_numeric($param)) {
|
||||
$data['cols'] = (int)$param;
|
||||
} elseif (preg_match('/^~(\d+)$/', $param, $match)) {
|
||||
$data['paginate'] = $match[1];
|
||||
} elseif (preg_match('/^(\d+)([xX])(\d+)$/', $param, $match)) {
|
||||
if ($match[2] == 'X') {
|
||||
$data['iw'] = $match[1];
|
||||
$data['ih'] = $match[3];
|
||||
} else {
|
||||
$data['tw'] = $match[1];
|
||||
$data['th'] = $match[3];
|
||||
}
|
||||
} elseif (strpos($param, '*') !== false) {
|
||||
$param = preg_quote($param, '/');
|
||||
$param = '/^' . str_replace('\\*', '.*?', $param) . '$/';
|
||||
$data['filter'] = $param;
|
||||
} else {
|
||||
if (substr($param, 0, 2) == 'no') {
|
||||
$data[substr($param, 2)] = false;
|
||||
} else {
|
||||
$data[$param] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// implicit direct linking?
|
||||
if ($data['lightbox']) $data['direct'] = true;
|
||||
|
||||
return $data;
|
||||
return [
|
||||
$src, $options
|
||||
];
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function render($mode, Doku_Renderer $R, $data)
|
||||
{
|
||||
global $ID;
|
||||
|
||||
[$src, $options] = $data;
|
||||
|
||||
if (preg_match('/^https?:\/\//i', $src)) {
|
||||
$gallery = new FeedGallery($src, $options);
|
||||
} else {
|
||||
$gallery = new NamespaceGallery($src, $options);
|
||||
}
|
||||
|
||||
|
||||
if ($mode == 'xhtml') {
|
||||
$R->info['cache'] &= $data['cache'];
|
||||
$R->doc .= $this->formatGallery($data);
|
||||
$R->info['cache'] = $options->cache;
|
||||
$formatter = new Formatter($options);
|
||||
$R->doc .= $formatter->format($gallery);
|
||||
|
||||
// FIXME next steps:
|
||||
// * implement minimal standard renderer for all renderers (just inline thumbnails with links)
|
||||
// * maybe implement PDF renderer separately from XHTML
|
||||
// * adjust lightbox script
|
||||
// * redo CSS
|
||||
// * add more unit tests
|
||||
|
||||
|
||||
return true;
|
||||
} elseif ($mode == 'metadata') {
|
||||
$rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_CACHE);
|
||||
$img = $rel['firstimage'];
|
||||
if (empty($img)) {
|
||||
$files = $this->findimages($data);
|
||||
if (count($files)) $R->internalmedia($files[0]['id']);
|
||||
// render the first image of the gallery, to ensure it will be used as first image if needed
|
||||
$images = $gallery->getImages();
|
||||
if (count($images)) {
|
||||
if ($images[0]->isExternal()) {
|
||||
$R->externalmedia($images[0]->getSrc());
|
||||
} else {
|
||||
$R->internalmedia($images[0]->getSrc());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads images from a MediaRSS or ATOM feed
|
||||
*/
|
||||
protected function loadRSS($url)
|
||||
{
|
||||
$feed = new FeedParser();
|
||||
$feed->set_feed_url($url);
|
||||
$feed->init();
|
||||
$files = array();
|
||||
|
||||
// base url to use for broken feeds with non-absolute links
|
||||
$main = parse_url($url);
|
||||
$host = $main['scheme'] . '://' .
|
||||
$main['host'] .
|
||||
(($main['port']) ? ':' . $main['port'] : '');
|
||||
$path = dirname($main['path']) . '/';
|
||||
|
||||
foreach ($feed->get_items() as $item) {
|
||||
if ($enclosure = $item->get_enclosure()) {
|
||||
// skip non-image enclosures
|
||||
if ($enclosure->get_type()) {
|
||||
if (substr($enclosure->get_type(), 0, 5) != 'image') continue;
|
||||
} else {
|
||||
if (!preg_match('/\.(jpe?g|png|gif)(\?|$)/i',
|
||||
$enclosure->get_link())) continue;
|
||||
}
|
||||
|
||||
// non absolute links
|
||||
$ilink = $enclosure->get_link();
|
||||
if (!preg_match('/^https?:\/\//i', $ilink)) {
|
||||
if ($ilink[0] == '/') {
|
||||
$ilink = $host . $ilink;
|
||||
} else {
|
||||
$ilink = $host . $path . $ilink;
|
||||
}
|
||||
}
|
||||
$link = $item->link;
|
||||
if (!preg_match('/^https?:\/\//i', $link)) {
|
||||
if ($link[0] == '/') {
|
||||
$link = $host . $link;
|
||||
} else {
|
||||
$link = $host . $path . $link;
|
||||
}
|
||||
}
|
||||
|
||||
$files[] = array(
|
||||
'id' => $ilink,
|
||||
'isimg' => true,
|
||||
'file' => basename($ilink),
|
||||
// decode to avoid later double encoding
|
||||
'title' => htmlspecialchars_decode($enclosure->get_title(), ENT_COMPAT),
|
||||
'desc' => strip_tags(htmlspecialchars_decode($enclosure->get_description(), ENT_COMPAT)),
|
||||
'width' => $enclosure->get_width(),
|
||||
'height' => $enclosure->get_height(),
|
||||
'mtime' => $item->get_date('U'),
|
||||
'ctime' => $item->get_date('U'),
|
||||
'detail' => $link,
|
||||
);
|
||||
}
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather all photos matching the given criteria
|
||||
*/
|
||||
protected function findimages(&$data)
|
||||
{
|
||||
global $conf;
|
||||
$files = array();
|
||||
|
||||
// http URLs are supposed to be media RSS feeds
|
||||
if (preg_match('/^https?:\/\//i', $data['ns'])) {
|
||||
$files = $this->loadRSS($data['ns']);
|
||||
$data['_single'] = false;
|
||||
} else {
|
||||
$dir = utf8_encodeFN(str_replace(':', '/', $data['ns']));
|
||||
// all possible images for the given namespace (or a single image)
|
||||
if (is_file($conf['mediadir'] . '/' . $dir)) {
|
||||
$files[] = array(
|
||||
'id' => $data['ns'],
|
||||
'isimg' => preg_match('/\.(jpe?g|gif|png)$/', $dir),
|
||||
'file' => basename($dir),
|
||||
'mtime' => filemtime($conf['mediadir'] . '/' . $dir),
|
||||
'meta' => new JpegMeta($conf['mediadir'] . '/' . $dir)
|
||||
);
|
||||
$data['_single'] = true;
|
||||
} else {
|
||||
$depth = $data['recursive'] ? 0 : 1;
|
||||
search($files,
|
||||
$conf['mediadir'],
|
||||
'search_media',
|
||||
array('depth' => $depth),
|
||||
$dir);
|
||||
$data['_single'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// done, yet?
|
||||
$len = count($files);
|
||||
if (!$len) return $files;
|
||||
if (isset($data['single']) && $data['single']) return $files;
|
||||
|
||||
// filter images
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
if (!$files[$i]['isimg']) {
|
||||
unset($files[$i]); // this is faster, because RE was done before
|
||||
} elseif ($data['filter']) {
|
||||
if (!preg_match($data['filter'], noNS($files[$i]['id']))) unset($files[$i]);
|
||||
}
|
||||
}
|
||||
if ($len < 1) return $files;
|
||||
|
||||
// random?
|
||||
if ($data['random']) {
|
||||
shuffle($files);
|
||||
} else {
|
||||
// sort?
|
||||
if ($data['sort'] == 'date') {
|
||||
usort($files, array($this, 'datesort'));
|
||||
} elseif ($data['sort'] == 'mod') {
|
||||
usort($files, array($this, 'modsort'));
|
||||
} elseif ($data['sort'] == 'title') {
|
||||
usort($files, array($this, 'titlesort'));
|
||||
}
|
||||
|
||||
// reverse?
|
||||
if ($data['reverse']) $files = array_reverse($files);
|
||||
}
|
||||
|
||||
// limits and offsets?
|
||||
if ($data['offset']) $files = array_slice($files, $data['offset']);
|
||||
if ($data['limit']) $files = array_slice($files, 0, $data['limit']);
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* usort callback to sort by file lastmodified time
|
||||
*/
|
||||
protected function modsort($a, $b)
|
||||
{
|
||||
if ($a['mtime'] < $b['mtime']) return -1;
|
||||
if ($a['mtime'] > $b['mtime']) return 1;
|
||||
return strcmp($a['file'], $b['file']);
|
||||
}
|
||||
|
||||
/**
|
||||
* usort callback to sort by EXIF date
|
||||
*/
|
||||
protected function datesort($a, $b)
|
||||
{
|
||||
$da = $this->readMeta($a, 'cdate');
|
||||
$db = $this->readMeta($b, 'cdate');
|
||||
if ($da < $db) return -1;
|
||||
if ($da > $db) return 1;
|
||||
return strcmp($a['file'], $b['file']);
|
||||
}
|
||||
|
||||
/**
|
||||
* usort callback to sort by EXIF title
|
||||
*/
|
||||
protected function titlesort($a, $b)
|
||||
{
|
||||
$ta = $this->readMeta($a, 'title');
|
||||
$tb = $this->readMeta($b, 'title');
|
||||
return strcmp($ta, $tb);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does the gallery formatting
|
||||
*/
|
||||
protected function formatGallery($data)
|
||||
{
|
||||
$ret = '';
|
||||
|
||||
$files = $this->findimages($data);
|
||||
|
||||
//anything found?
|
||||
if (!count($files)) {
|
||||
$ret .= '<div class="nothing">' . $this->getLang('nothingfound') . '</div>';
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// prepare alignment
|
||||
$align = '';
|
||||
$xalign = '';
|
||||
if ($data['align'] == 1) {
|
||||
$align = ' gallery_right';
|
||||
$xalign = ' align="right"';
|
||||
}
|
||||
if ($data['align'] == 2) {
|
||||
$align = ' gallery_left';
|
||||
$xalign = ' align="left"';
|
||||
}
|
||||
if ($data['align'] == 3) {
|
||||
$align = ' gallery_center';
|
||||
$xalign = ' align="center"';
|
||||
}
|
||||
if (!$data['_single']) {
|
||||
if (!$align) $align = ' gallery_center'; // center galleries on default
|
||||
if (!$xalign) $xalign = ' align="center"';
|
||||
}
|
||||
|
||||
$page = 0;
|
||||
|
||||
// build gallery
|
||||
if ($data['_single']) {
|
||||
$ret .= $this->formatThumbnail($files[0], $data);
|
||||
$ret .= $this->formatName($files[0], $data);
|
||||
$ret .= $this->formatTitle($files[0], $data);
|
||||
} elseif ($data['cols'] > 0) { // format as table
|
||||
$close_pg = false;
|
||||
|
||||
$i = 0;
|
||||
foreach ($files as $img) {
|
||||
|
||||
// new page?
|
||||
if ($data['paginate'] && ($i % $data['paginate'] == 0)) {
|
||||
$ret .= '<div class="gallery_page gallery__' . $data['galid'] . '" id="gallery__' . $data['galid'] . '_' . (++$page) . '">';
|
||||
$close_pg = true;
|
||||
}
|
||||
|
||||
// new table?
|
||||
if ($i == 0 || ($data['paginate'] && ($i % $data['paginate'] == 0))) {
|
||||
$ret .= '<table>';
|
||||
|
||||
}
|
||||
|
||||
// new row?
|
||||
if ($i % $data['cols'] == 0) {
|
||||
$ret .= '<tr>';
|
||||
}
|
||||
|
||||
// an image cell
|
||||
$ret .= '<td>';
|
||||
$ret .= $this->formatThumbnail($img, $data);
|
||||
$ret .= $this->formatName($img, $data);
|
||||
$ret .= $this->formatTitle($img, $data);
|
||||
$ret .= '</td>';
|
||||
$i++;
|
||||
|
||||
// done with this row? close it
|
||||
$close_tr = true;
|
||||
if ($i % $data['cols'] == 0) {
|
||||
$ret .= '</tr>';
|
||||
$close_tr = false;
|
||||
}
|
||||
|
||||
// close current page and table
|
||||
if ($data['paginate'] && ($i % $data['paginate'] == 0)) {
|
||||
if ($close_tr) {
|
||||
// add remaining empty cells
|
||||
while ($i % $data['cols']) {
|
||||
$ret .= '<td></td>';
|
||||
$i++;
|
||||
}
|
||||
$ret .= '</tr>';
|
||||
}
|
||||
$ret .= '</table>';
|
||||
$ret .= '</div>';
|
||||
$close_pg = false;
|
||||
$i = 0; // reset counter to ensure next page is properly started
|
||||
}
|
||||
|
||||
} // foreach
|
||||
|
||||
if ($close_tr) {
|
||||
// add remaining empty cells
|
||||
while ($i % $data['cols']) {
|
||||
$ret .= '<td></td>';
|
||||
$i++;
|
||||
}
|
||||
$ret .= '</tr>';
|
||||
}
|
||||
|
||||
if (!$data['paginate']) {
|
||||
$ret .= '</table>';
|
||||
} elseif ($close_pg) {
|
||||
$ret .= '</table>';
|
||||
$ret .= '</div>';
|
||||
}
|
||||
} else { // format as div sequence
|
||||
$i = 0;
|
||||
$close_pg = false;
|
||||
foreach ($files as $img) {
|
||||
|
||||
if ($data['paginate'] && ($i % $data['paginate'] == 0)) {
|
||||
$ret .= '<div class="gallery_page gallery__' . $data['galid'] . '" id="gallery__' . $data['galid'] . '_' . (++$page) . '">';
|
||||
$close_pg = true;
|
||||
}
|
||||
|
||||
$ret .= '<div>';
|
||||
$ret .= $this->formatThumbnail($img, $data);
|
||||
$ret .= $this->formatName($img, $data);
|
||||
$ret .= $this->formatTitle($img, $data);
|
||||
$ret .= '</div> ';
|
||||
|
||||
$i++;
|
||||
|
||||
if ($data['paginate'] && ($i % $data['paginate'] == 0)) {
|
||||
$ret .= '</div>';
|
||||
$close_pg = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($close_pg) $ret .= '</div>';
|
||||
|
||||
$ret .= '<br style="clear:both" />';
|
||||
}
|
||||
|
||||
// pagination links
|
||||
$pgret = '';
|
||||
if ($page) {
|
||||
$pgret .= '<div class="gallery_pages"><span>' . $this->getLang('pages') . ' </span>';
|
||||
for ($j = 1; $j <= $page; $j++) {
|
||||
$pgret .= '<a href="#gallery__' . $data['galid'] . '_' . $j . '" class="gallery_pgsel button">' . $j . '</a> ';
|
||||
}
|
||||
$pgret .= '</div>';
|
||||
}
|
||||
|
||||
return '<div class="gallery' . $align . '"' . $xalign . '>' . $pgret . $ret . '<div class="clearer"></div></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how a thumbnail should look like
|
||||
*/
|
||||
protected function formatThumbnail(&$img, $data)
|
||||
{
|
||||
global $ID;
|
||||
|
||||
// calculate thumbnail size
|
||||
if (!$data['crop']) {
|
||||
$w = (int)$this->readMeta($img, 'width');
|
||||
$h = (int)$this->readMeta($img, 'height');
|
||||
if ($w && $h) {
|
||||
$dim = array();
|
||||
if ($w > $data['tw'] || $h > $data['th']) {
|
||||
$ratio = $this->calculateRatio($img, $data['tw'], $data['th']);
|
||||
$w = floor($w * $ratio);
|
||||
$h = floor($h * $ratio);
|
||||
$dim = array('w' => $w, 'h' => $h);
|
||||
}
|
||||
} else {
|
||||
$data['crop'] = true; // no size info -> always crop
|
||||
}
|
||||
}
|
||||
if ($data['crop']) {
|
||||
$w = $data['tw'];
|
||||
$h = $data['th'];
|
||||
$dim = array('w' => $w, 'h' => $h);
|
||||
}
|
||||
|
||||
//prepare img attributes
|
||||
$i = array();
|
||||
$i['width'] = $w;
|
||||
$i['height'] = $h;
|
||||
$i['border'] = 0;
|
||||
$i['alt'] = $this->readMeta($img, 'title');
|
||||
$i['class'] = 'tn';
|
||||
$iatt = buildAttributes($i);
|
||||
$src = ml($img['id'], $dim);
|
||||
|
||||
// prepare lightbox dimensions
|
||||
$w_lightbox = (int)$this->readMeta($img, 'width');
|
||||
$h_lightbox = (int)$this->readMeta($img, 'height');
|
||||
$dim_lightbox = array();
|
||||
if ($w_lightbox > $data['iw'] || $h_lightbox > $data['ih']) {
|
||||
$ratio = $this->calculateRatio($img, $data['iw'], $data['ih']);
|
||||
$w_lightbox = floor($w_lightbox * $ratio);
|
||||
$h_lightbox = floor($h_lightbox * $ratio);
|
||||
$dim_lightbox = array('w' => $w_lightbox, 'h' => $h_lightbox);
|
||||
}
|
||||
|
||||
//prepare link attributes
|
||||
$a = array();
|
||||
$a['title'] = $this->readMeta($img, 'title');
|
||||
$a['data-caption'] = trim(str_replace("\n", ' ', $this->readMeta($img, 'desc')));
|
||||
if (!$a['data-caption']) unset($a['data-caption']);
|
||||
if ($data['lightbox']) {
|
||||
$href = ml($img['id'], $dim_lightbox);
|
||||
$a['class'] = "lightbox JSnocheck";
|
||||
$a['rel'] = 'lightbox[gal-' . substr(md5($ID), 4) . ']'; //unique ID for the gallery
|
||||
} elseif ($img['detail'] && !$data['direct']) {
|
||||
$href = $img['detail'];
|
||||
} else {
|
||||
$href = ml($img['id'], array('id' => $ID), $data['direct']);
|
||||
}
|
||||
$aatt = buildAttributes($a);
|
||||
|
||||
// prepare output
|
||||
$ret = '';
|
||||
$ret .= '<a href="' . $href . '" ' . $aatt . '>';
|
||||
$ret .= '<img src="' . $src . '" ' . $iatt . ' />';
|
||||
$ret .= '</a>';
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines how a filename + link should look
|
||||
*/
|
||||
protected function formatName($img, $data)
|
||||
{
|
||||
global $ID;
|
||||
|
||||
if (!$data['showname']) {
|
||||
return '';
|
||||
}
|
||||
|
||||
//prepare link
|
||||
$lnk = ml($img['id'], array('id' => $ID), false);
|
||||
|
||||
// prepare output
|
||||
$ret = '';
|
||||
$ret .= '<br /><a href="' . $lnk . '">';
|
||||
$ret .= hsc($img['file']);
|
||||
$ret .= '</a>';
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how title + link should look
|
||||
*/
|
||||
protected function formatTitle($img, $data)
|
||||
{
|
||||
global $ID;
|
||||
|
||||
if (!$data['showtitle']) {
|
||||
return '';
|
||||
}
|
||||
|
||||
//prepare link
|
||||
$lnk = ml($img['id'], array('id' => $ID), false);
|
||||
|
||||
// prepare output
|
||||
$ret = '';
|
||||
$ret .= '<br /><a href="' . $lnk . '">';
|
||||
$ret .= hsc($this->readMeta($img, 'title'));
|
||||
$ret .= '</a>';
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the metadata of an item
|
||||
*
|
||||
* Automatically checks if a JPEGMeta object is available or if all data is
|
||||
* supplied in array
|
||||
*/
|
||||
protected function readMeta(&$img, $opt)
|
||||
{
|
||||
if ($img['meta']) {
|
||||
// map JPEGMeta calls to opt names
|
||||
|
||||
switch ($opt) {
|
||||
case 'title':
|
||||
return $img['meta']->getField('Simple.Title');
|
||||
case 'desc':
|
||||
return $img['meta']->getField('Iptc.Caption');
|
||||
case 'cdate':
|
||||
return $img['meta']->getField('Date.EarliestTime');
|
||||
case 'width':
|
||||
return $img['meta']->getField('File.Width');
|
||||
case 'height':
|
||||
return $img['meta']->getField('File.Height');
|
||||
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
} else {
|
||||
// just return the array field
|
||||
return $img[$opt];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the multiplier needed to resize the image to the given
|
||||
* dimensions
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function calculateRatio(&$img, $maxwidth, $maxheight = 0)
|
||||
{
|
||||
if (!$maxheight) $maxheight = $maxwidth;
|
||||
|
||||
$w = $this->readMeta($img, 'width');
|
||||
$h = $this->readMeta($img, 'height');
|
||||
|
||||
$ratio = 1;
|
||||
if ($w >= $h) {
|
||||
if ($w >= $maxwidth) {
|
||||
$ratio = $maxwidth / $w;
|
||||
} elseif ($h > $maxheight) {
|
||||
$ratio = $maxheight / $h;
|
||||
}
|
||||
} else {
|
||||
if ($h >= $maxheight) {
|
||||
$ratio = $maxheight / $h;
|
||||
} elseif ($w > $maxwidth) {
|
||||
$ratio = $maxwidth / $w;
|
||||
}
|
||||
}
|
||||
return $ratio;
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user